about summary refs log tree commit homepage
path: root/http_get.c
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2012-01-18 15:01:25 -0800
committerEric Wong <normalperson@yhbt.net>2012-01-18 16:12:46 -0800
commit553d01f3b12e99ba7ada372d5523b1cf629bac88 (patch)
treee0f226978fe50036d056bd683379934ba082d7cc /http_get.c
parent8982da6e68ea3de50a47bff0c13bab1f519908a6 (diff)
downloadcmogstored-553d01f3b12e99ba7ada372d5523b1cf629bac88.tar.gz
Features supported:

* HTTP/1.1 persistent connections + pipelining
* HTTP/1.0 keepalive
* special-case for path=/ to keep "mogadm check" happy
Diffstat (limited to 'http_get.c')
-rw-r--r--http_get.c130
1 files changed, 130 insertions, 0 deletions
diff --git a/http_get.c b/http_get.c
new file mode 100644
index 0000000..20ae8f0
--- /dev/null
+++ b/http_get.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
+ * License: GPLv3 or later (see COPYING for details)
+ */
+#include "cmogstored.h"
+#include "http.h"
+#include <sys/sendfile.h>
+
+void mog_http_get_open(struct mog_http *http, char *buf, bool head_only)
+{
+        struct stat sb;
+        struct iovec iov;
+        int rc;
+        char *path = mog_http_path(http, buf);
+
+        if (!path) goto forbidden; /* path traversal attack */
+        assert(http->forward == NULL && "already have http->forward");
+        assert(path[0] == '/' && "bad path");
+
+        if (path[1] == '\0') { /* keep "mogadm check" happy */
+                sb.st_size = 0;
+        } else if (head_only) {
+                if (mog_stat(http->svc, path, &sb) < 0) goto err;
+                if (!S_ISREG(sb.st_mode)) goto forbidden;
+        } else {
+                struct mog_file *file;
+
+                http->forward = mog_file_open_read(http->svc, path);
+                if (http->forward == NULL)
+                        goto err;
+
+                file = &http->forward->as.file;
+                file->path = NULL; /* ugh... */
+                if (fstat(http->forward->fd, &sb) < 0) {
+                        PRESERVE_ERRNO( mog_file_close(http->forward) );
+                        http->forward = NULL;
+                        goto err;
+                }
+                if (!S_ISREG(sb.st_mode)) {
+                        mog_file_close(http->forward);
+                        http->forward = NULL;
+                        goto forbidden;
+                }
+                file->fsize = sb.st_size;
+        }
+
+        /* single buffer so we can use MSG_MORE later... */
+        iov.iov_base = mog_fsbuf_get(&iov.iov_len);
+        rc = snprintf(iov.iov_base, iov.iov_len,
+                        "HTTP/1.1 200 OK\r\n"
+                        "Status: 200 OK\r\n"
+                        "Content-Length: %lld\r\n"
+                        "Content-Type: application/octet-stream\r\n"
+                        "Connection: %s\r\n"
+                        "\r\n",
+                        (long long)sb.st_size,
+                        http->persistent ? "keep-alive" : "close");
+        assert(rc > 0 && "we suck at snprintf");
+        iov.iov_len = rc;
+        assert(http->wbuf == NULL && "tried to write to a busy client");
+
+        http->wbuf = mog_trywritev(mog_fd_of(http)->fd, &iov, 1);
+        return;
+
+err:
+        switch (errno) {
+        case EACCES:
+forbidden:
+                mog_http_resp(http, "403 Forbidden", true);
+                return;
+        case ENOENT:
+                mog_http_resp(http, "404 Not Found", true);
+                return;
+        }
+        PRESERVE_ERRNO(do {
+                mog_http_resp(http, "500 Internal Server Error", true);
+        } while(0));
+}
+
+enum mog_next mog_http_get_in_progress(struct mog_fd *mfd)
+{
+        struct mog_http *http = &mfd->as.http;
+        struct mog_fd *file_mfd;
+        struct mog_file *file;
+        ssize_t w;
+        size_t count;
+
+        /*
+         * most readahead is 128K, so we try to send half of that to give
+         * the kernel time to do readahead
+         */
+        static const size_t max_sendfile = 64 * 1024; /* TODO: make tunable */
+
+        assert(http->wbuf == NULL && "can't serve file with http->wbuf");
+        assert(http->forward && http->forward != MOG_IOSTAT && "bad forward");
+        file_mfd = http->forward;
+        file = &file_mfd->as.file;
+
+        assert(file->fsize >= 0 && "fsize is negative");
+        count = file->fsize > max_sendfile ? max_sendfile : file->fsize;
+        if (count == 0) goto done;
+retry:
+        w = sendfile(mfd->fd, file_mfd->fd, &file->foff, count);
+        if (w > 0) {
+                if (file->foff == file->fsize) goto done;
+                return MOG_NEXT_ACTIVE;
+        } else if (w < 0) {
+                switch (errno) {
+                case_EAGAIN: return MOG_NEXT_WAIT_WR;
+                case EINTR: goto retry;
+                }
+                http->persistent = 0;
+        } else { /* w == 0 */
+                /*
+                 * if we can't fulfill the value set by our Content-Length:
+                 * header, we must kill the TCP connection
+                 */
+                http->persistent = 0;
+                syslog(LOG_ERR,
+                       "sendfile()-d 0 bytes at offset=%lld; file truncated?",
+                       (long long)file->foff);
+        }
+done:
+        mog_file_close(http->forward);
+        if (http->persistent) {
+                mog_http_reset_parser(http);
+                return MOG_NEXT_ACTIVE;
+        }
+        return MOG_NEXT_CLOSE;
+}