diff options
author | Eric Wong <normalperson@yhbt.net> | 2012-01-18 15:01:25 -0800 |
---|---|---|
committer | Eric Wong <normalperson@yhbt.net> | 2012-01-18 16:12:46 -0800 |
commit | 553d01f3b12e99ba7ada372d5523b1cf629bac88 (patch) | |
tree | e0f226978fe50036d056bd683379934ba082d7cc /http_get.c | |
parent | 8982da6e68ea3de50a47bff0c13bab1f519908a6 (diff) | |
download | cmogstored-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.c | 130 |
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; +} |