# HG changeset patch # User John Tsiombikas # Date 1429260308 -10800 # Node ID 5ec50ca0d0710bdf90f3a67879db146954a91d9c # Parent 4f191dbfac7ecf1e5bb0c58c7520cb77c60f9f98 separated the server code diff -r 4f191dbfac7e -r 5ec50ca0d071 src/http.c --- a/src/http.c Fri Apr 17 01:57:25 2015 +0300 +++ b/src/http.c Fri Apr 17 11:45:08 2015 +0300 @@ -5,9 +5,10 @@ #include #include #include "http.h" +#include "logger.h" -const char *http_method_str[] = { +static const char *http_method_str[] = { "", "OPTIONS", "GET", @@ -22,13 +23,13 @@ /* HTTP 1xx message strings */ -const char *http_msg1xx[] = { +static const char *http_msg1xx[] = { "Continue", /* 100 */ "Switching Protocols" /* 101 */ }; /* HTTP 2xx message strings */ -const char *http_msg2xx[] = { +static const char *http_msg2xx[] = { "OK", /* 200 */ "Created", /* 201 */ "Accepted", /* 202 */ @@ -39,7 +40,7 @@ }; /* HTTP 3xx message strings */ -const char *http_msg3xx[] = { +static const char *http_msg3xx[] = { "Multiple Choices", /* 300 */ "Moved Permanently", /* 301 */ "Found", /* 302 */ @@ -51,7 +52,7 @@ }; /* HTTP 4xx error strings */ -const char *http_msg4xx[] = { +static const char *http_msg4xx[] = { "Bad Request", /* 400 */ "Unauthorized", /* 401 */ "What the Fuck?", /* 402 */ @@ -73,7 +74,7 @@ }; /* HTTP 5xx error strings */ -const char *http_msg5xx[] = { +static const char *http_msg5xx[] = { "Internal Server Error", /* 500 */ "Not Implemented", /* 501 */ "Bad Gateway", /* 502 */ @@ -86,7 +87,7 @@ static enum http_method parse_method(const char *s); -int http_parse_header(struct http_req_header *hdr, const char *buf, int bufsz) +int http_parse_request(struct http_req_header *hdr, const char *buf, int bufsz) { int i, nlines = 0; char *rqline = 0; @@ -167,23 +168,23 @@ return HTTP_HDR_OK; } -void http_print_header(struct http_req_header *hdr) +void http_log_request(struct http_req_header *hdr) { int i; - printf("HTTP request header\n"); - printf(" method: %s\n", http_method_str[hdr->method]); - printf(" uri: %s\n", hdr->uri); - printf(" version: %d.%d\n", hdr->ver_major, hdr->ver_minor); - printf(" fields (%d):\n", hdr->num_hdrfields); + logmsg("HTTP request header\n"); + logmsg(" method: %s\n", http_method_str[hdr->method]); + logmsg(" uri: %s\n", hdr->uri); + logmsg(" version: %d.%d\n", hdr->ver_major, hdr->ver_minor); + logmsg(" fields (%d):\n", hdr->num_hdrfields); for(i=0; inum_hdrfields; i++) { - printf(" %s\n", hdr->hdrfields[i]); + logmsg(" %s\n", hdr->hdrfields[i]); } - putchar('\n'); + logmsg("\n"); } -void http_destroy_header(struct http_req_header *hdr) +void http_destroy_request(struct http_req_header *hdr) { int i; @@ -211,7 +212,7 @@ { int sz; va_list ap; - char *field, *newarr, tmp; + char *field, **newarr, tmp; va_start(ap, fmt); sz = vsnprintf(&tmp, 0, fmt, ap); @@ -229,7 +230,9 @@ free(field); return -1; } - resp->fields[resp->num_fields++] = newarr; + resp->fields = newarr; + + resp->fields[resp->num_fields++] = field; return 0; } diff -r 4f191dbfac7e -r 5ec50ca0d071 src/http.h --- a/src/http.h Fri Apr 17 01:57:25 2015 +0300 +++ b/src/http.h Fri Apr 17 11:45:08 2015 +0300 @@ -36,9 +36,9 @@ #define HTTP_HDR_NOMEM -2 #define HTTP_HDR_PARTIAL -3 -int http_parse_header(struct http_req_header *hdr, const char *buf, int bufsz); -void http_print_header(struct http_req_header *hdr); -void http_destroy_header(struct http_req_header *hdr); +int http_parse_request(struct http_req_header *hdr, const char *buf, int bufsz); +void http_log_request(struct http_req_header *hdr); +void http_destroy_request(struct http_req_header *hdr); int http_init_resp(struct http_resp_header *resp); int http_add_resp_field(struct http_resp_header *resp, const char *fmt, ...); diff -r 4f191dbfac7e -r 5ec50ca0d071 src/logger.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/logger.c Fri Apr 17 11:45:08 2015 +0300 @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include "logger.h" + +static FILE *logfile; + +int set_log_file(const char *fname) +{ + FILE *fp; + + if(!(fp = fopen(fname, "w"))) { + fprintf(stderr, "failed to open logfile: %s: %s\n", fname, strerror(errno)); + return -1; + } + setvbuf(fp, 0, _IONBF, 0); + logfile = fp; + return 0; +} + +void logmsg(const char *fmt, ...) +{ + va_list ap; + + if(!logfile) { + logfile = stderr; + } + + va_start(ap, fmt); + vfprintf(logfile, fmt, ap); + va_end(ap); +} diff -r 4f191dbfac7e -r 5ec50ca0d071 src/logger.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/logger.h Fri Apr 17 11:45:08 2015 +0300 @@ -0,0 +1,8 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +int set_log_file(const char *fname); + +void logmsg(const char *fname, ...); + +#endif /* LOGGER_H_ */ diff -r 4f191dbfac7e -r 5ec50ca0d071 src/main.c --- a/src/main.c Fri Apr 17 01:57:25 2015 +0300 +++ b/src/main.c Fri Apr 17 11:45:08 2015 +0300 @@ -1,58 +1,18 @@ #include #include #include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "http.h" -#include "mime.h" +#include "tinyweb.h" -/* HTTP version */ -#define HTTP_VER_MAJOR 1 -#define HTTP_VER_MINOR 1 -#define HTTP_VER_STR "1.1" - -/* maximum request length: 64mb */ -#define MAX_REQ_LENGTH (65536 * 1024) - -struct client { - int s; - char *rcvbuf; - int bufsz; - struct client *next; -}; - -int start_server(void); -int accept_conn(int lis); -void close_conn(struct client *c); -int handle_client(struct client *c); -int do_get(struct client *c, const char *uri, int with_body); -void respond_error(struct client *c, int errcode); +int parse_args(int argc, char **argv); void sighandler(int s); -int parse_args(int argc, char **argv); - -static int lis; -static int port = 8080; -static struct client *clist; - -static const char *indexfiles[] = { - "index.cgi", - "index.html", - "index.htm", - 0 -}; int main(int argc, char **argv) { + int *sockets, num_sockets, sockets_arr_size = 0; + if(parse_args(argc, argv) == -1) { return 1; } @@ -61,279 +21,47 @@ signal(SIGTERM, sighandler); signal(SIGQUIT, sighandler); - if((lis = start_server()) == -1) { - return 1; - } + tw_start(); for(;;) { - struct client *c, dummy; - int maxfd = lis; + int i; fd_set rdset; + num_sockets = tw_get_sockets(0); + if(num_sockets > sockets_arr_size) { + int newsz = sockets_arr_size ? sockets_arr_size * 2 : 16; + int *newarr = realloc(sockets, newsz * sizeof *sockets); + if(!newarr) { + fprintf(stderr, "failed to allocate sockets array\n"); + tw_stop(); + return 1; + } + sockets = newarr; + sockets_arr_size = newsz; + } + tw_get_sockets(sockets); + FD_ZERO(&rdset); - FD_SET(lis, &rdset); - - c = clist; - while(c) { - if(c->s > maxfd) { - maxfd = c->s; - } - FD_SET(c->s, &rdset); - c = c->next; + for(i=0; is, &rdset)) { - handle_client(c); - } - c = c->next; - } - - if(FD_ISSET(lis, &rdset)) { - accept_conn(lis); - } - - dummy.next = clist; - c = &dummy; - - while(c->next) { - struct client *n = c->next; - - if(n->s == -1) { - /* marked for removal */ - c->next = n->next; - free(n); - } else { - c = c->next; + for(i=0; is = s; - c->rcvbuf = 0; - c->bufsz = 0; - c->next = clist; - clist = c; - return 0; -} - -void close_conn(struct client *c) -{ - close(c->s); - c->s = -1; /* mark it for removal */ - free(c->rcvbuf); -} - -int handle_client(struct client *c) -{ - struct http_req_header hdr; - static char buf[2048]; - int rdsz, status; - - while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) { - char *newbuf; - int newsz = c->bufsz + rdsz; - if(newsz > MAX_REQ_LENGTH) { - respond_error(c, 413); - return -1; - } - - if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) { - fprintf(stderr, "failed to allocate %d byte buffer\n", newsz); - respond_error(c, 503); - return -1; - } - - memcpy(newbuf + c->bufsz, buf, rdsz); - newbuf[newsz] = 0; - - c->rcvbuf = newbuf; - c->bufsz = newsz; - } - - if((status = http_parse_header(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) { - http_print_header(&hdr); - switch(status) { - case HTTP_HDR_INVALID: - respond_error(c, 400); - return -1; - - case HTTP_HDR_NOMEM: - respond_error(c, 503); - return -1; - - case HTTP_HDR_PARTIAL: - return 0; /* partial header, continue reading */ - } - } - http_print_header(&hdr); - - /* we only support GET and HEAD at this point, so freak out on anything else */ - switch(hdr.method) { - case HTTP_GET: - do_get(c, hdr.uri, 1); - break; - - case HTTP_HEAD: - do_get(c, hdr.uri, 0); - break; - - default: - respond_error(c, 501); - return -1; - } - - close_conn(c); - return 0; -} - -int do_get(struct client *c, const char *uri, int with_body) -{ - const char *ptr; - struct http_resp_header resp; - - if((ptr = strstr(uri, "://"))) { - /* skip the host part */ - if(!(uri = strchr(ptr + 3, '/'))) { - respond_error(c, 404); - return -1; - } - ++uri; - } - - if(*uri) { - struct stat st; - char *path = 0; - char *buf; - const char *type; - int fd, size; - - if(stat(uri, &st) == -1) { - respond_error(c, 404); - return -1; - } - - if(S_ISDIR(st.st_mode)) { - int i; - path = alloca(strlen(uri) + 64); - - for(i=0; indexfiles[i]; i++) { - sprintf(path, "%s/%s", uri, indexfiles[i]); - if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) { - break; - } - } - - if(indexfiles[i] == 0) { - respond_error(c, 404); - return -1; - } - } else { - path = (char*)uri; - } - - if((fd = open(path, O_RDONLY)) == -1) { - respond_error(c, 403); - return -1; - } - - /* construct response header */ - http_init_resp(&resp); - http_add_resp_field(&resp, "Content-Length: %d", st.st_size); - if((type = mime_type(path))) { - http_add_resp_field(&resp, "Content-Type: %s", type); - } - - size = http_serialize_resp(&resp, 0); - buf = alloca(size); - http_serialize_resp(&resp, buf); - send(c->s, buf, size, 0); - - if(with_body) { - char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if(cont == (void*)-1) { - respond_error(c, 503); - close(fd); - return -1; - } - } - - close(fd); - } - return 0; -} - -void respond_error(struct client *c, int errcode) -{ - char buf[512]; - - sprintf(buf, HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode)); - - send(c->s, buf, strlen(buf), 0); - close_conn(c); -} - void sighandler(int s) { if(s == SIGINT || s == SIGTERM || s == SIGQUIT) { - close(lis); - while(clist) { - struct client *c = clist; - clist = clist->next; - close_conn(c); - free(c); - } - clist = 0; - + tw_stop(); printf("bye!\n"); exit(0); } @@ -356,9 +84,13 @@ if(argv[i][0] == '-' && argv[i][2] == 0) { switch(argv[i][1]) { case 'p': - if((port = atoi(argv[++i])) == 0) { - fprintf(stderr, "-p must be followed by a valid port number\n"); - return -1; + { + int port = atoi(argv[++i]); + if(!port) { + fprintf(stderr, "-p must be followed by a valid port number\n"); + return -1; + } + tw_set_port(port); } break; diff -r 4f191dbfac7e -r 5ec50ca0d071 src/mime.c --- a/src/mime.c Fri Apr 17 01:57:25 2015 +0300 +++ b/src/mime.c Fri Apr 17 11:45:08 2015 +0300 @@ -33,7 +33,7 @@ if(types) return 0; - if(rb_init(types, RB_KEY_STRING) == -1) { + if((types = rb_create(RB_KEY_STRING))) { return -1; } rb_set_delete_func(types, del_func, 0); diff -r 4f191dbfac7e -r 5ec50ca0d071 src/tinyweb.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/tinyweb.c Fri Apr 17 11:45:08 2015 +0300 @@ -0,0 +1,388 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tinyweb.h" +#include "http.h" +#include "mime.h" +#include "logger.h" + +/* HTTP version */ +#define HTTP_VER_MAJOR 1 +#define HTTP_VER_MINOR 1 +#define HTTP_VER_STR "1.1" + +/* maximum request length: 64mb */ +#define MAX_REQ_LENGTH (65536 * 1024) + +struct client { + int s; + char *rcvbuf; + int bufsz; + struct client *next; +}; + +static int accept_conn(int lis); +static void close_conn(struct client *c); +static int handle_client(struct client *c); +static int do_get(struct client *c, const char *uri, int with_body); +static void respond_error(struct client *c, int errcode); + +static int lis = -1; +static int maxfd; +static int port = 8080; +static struct client *clist; +static int num_clients; + +static const char *indexfiles[] = { + "index.cgi", + "index.html", + "index.htm", + 0 +}; + +void tw_set_port(int p) +{ + port = p; +} + +int tw_set_root(const char *path) +{ + return chdir(path); +} + +int tw_set_logfile(const char *fname) +{ + return set_log_file(fname); +} + +int tw_start(void) +{ + int s; + struct sockaddr_in sa; + + logmsg("starting server ...\n"); + + if(lis != -1) { + logmsg("can't start tinyweb server: already running!\n"); + return -1; + } + + if((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) { + logmsg("failed to create listening socket: %s\n", strerror(errno)); + return -1; + } + fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); + + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = INADDR_ANY; + sa.sin_port = htons(port); + + if(bind(s, (struct sockaddr*)&sa, sizeof sa) == -1) { + logmsg("failed to bind socket to port %d: %s\n", port, strerror(errno)); + return -1; + } + listen(s, 16); + + lis = s; + return s; +} + +int tw_stop(void) +{ + if(lis == -1) { + return -1; + } + + logmsg("stopping server...\n"); + + close(lis); + lis = -1; + + while(clist) { + struct client *c = clist; + clist = clist->next; + close_conn(c); + free(c); + } + clist = 0; + + return 0; +} + +int tw_get_sockets(int *socks) +{ + struct client *c, dummy; + + if(!socks) { + /* just return the count */ + return num_clients + 1; /* +1 for the listening socket */ + } + + /* first cleanup the clients marked for removal */ + dummy.next = clist; + c = &dummy; + + while(c->next) { + struct client *n = c->next; + + if(n->s == -1) { + /* marked for removal */ + c->next = n->next; + free(n); + --num_clients; + } else { + c = c->next; + } + } + clist = dummy.next; + + /* go through the client list and populate the array */ + maxfd = lis; + *socks++ = lis; + + c = clist; + while(c) { + *socks++ = c->s; + if(c->s > maxfd) { + maxfd = c->s; + } + c = c->next; + } + return num_clients + 1; /* +1 for the listening socket */ +} + +int tw_get_maxfd(void) +{ + return maxfd; +} + +int tw_handle_socket(int s) +{ + struct client *c; + + if(s == lis) { + return accept_conn(s); + } + + /* find which client corresponds to this socket */ + c = clist; + while(c) { + if(c->s == s) { + return handle_client(c); + } + c = c->next; + } + + logmsg("socket %d doesn't correspond to any client\n"); + return -1; +} + +static int accept_conn(int lis) +{ + int s; + struct client *c; + struct sockaddr_in addr; + socklen_t addr_sz = sizeof addr; + + if((s = accept(lis, (struct sockaddr*)&addr, &addr_sz)) == -1) { + logmsg("failed to accept incoming connection: %s\n", strerror(errno)); + return -1; + } + fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); + + if(!(c = malloc(sizeof *c))) { + logmsg("failed to allocate memory while accepting connection: %s\n", strerror(errno)); + return -1; + } + c->s = s; + c->rcvbuf = 0; + c->bufsz = 0; + c->next = clist; + clist = c; + return 0; +} + +static void close_conn(struct client *c) +{ + close(c->s); + c->s = -1; /* mark it for removal */ + free(c->rcvbuf); + c->rcvbuf = 0; +} + +static int handle_client(struct client *c) +{ + struct http_req_header hdr; + static char buf[2048]; + int rdsz, status; + + while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) { + char *newbuf; + int newsz = c->bufsz + rdsz; + if(newsz > MAX_REQ_LENGTH) { + respond_error(c, 413); + return -1; + } + + if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) { + logmsg("failed to allocate %d byte buffer\n", newsz); + respond_error(c, 503); + return -1; + } + + memcpy(newbuf + c->bufsz, buf, rdsz); + newbuf[newsz] = 0; + + c->rcvbuf = newbuf; + c->bufsz = newsz; + } + + if((status = http_parse_request(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) { + http_log_request(&hdr); + switch(status) { + case HTTP_HDR_INVALID: + respond_error(c, 400); + return -1; + + case HTTP_HDR_NOMEM: + respond_error(c, 503); + return -1; + + case HTTP_HDR_PARTIAL: + return 0; /* partial header, continue reading */ + } + } + http_log_request(&hdr); + + /* we only support GET and HEAD at this point, so freak out on anything else */ + switch(hdr.method) { + case HTTP_GET: + if(do_get(c, hdr.uri, 1) == -1) { + return -1; + } + break; + + case HTTP_HEAD: + if(do_get(c, hdr.uri, 0) == -1) { + return -1; + } + break; + + default: + respond_error(c, 501); + return -1; + } + + close_conn(c); + return 0; +} + +static int do_get(struct client *c, const char *uri, int with_body) +{ + const char *ptr; + struct http_resp_header resp; + + if((ptr = strstr(uri, "://"))) { + uri = ptr + 3; + } + + /* skip the host part and the first slash if it exists */ + if((ptr = strchr(uri, '/'))) { + uri = ptr + 1; + } + + if(*uri) { + struct stat st; + char *path = 0; + char *rsphdr; + const char *type; + int fd, rspsize; + + if(stat(uri, &st) == -1) { + respond_error(c, 404); + return -1; + } + + if(S_ISDIR(st.st_mode)) { + int i; + path = alloca(strlen(uri) + 64); + + for(i=0; indexfiles[i]; i++) { + sprintf(path, "%s/%s", uri, indexfiles[i]); + if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) { + break; + } + } + + if(indexfiles[i] == 0) { + respond_error(c, 404); + return -1; + } + } else { + path = (char*)uri; + } + + if((fd = open(path, O_RDONLY)) == -1) { + respond_error(c, 403); + return -1; + } + + /* construct response header */ + http_init_resp(&resp); + http_add_resp_field(&resp, "Content-Length: %d", st.st_size); + if((type = mime_type(path))) { + http_add_resp_field(&resp, "Content-Type: %s", type); + } + + rspsize = http_serialize_resp(&resp, 0); + rsphdr = alloca(rspsize); + http_serialize_resp(&resp, rsphdr); + + if(with_body) { + int cont_left = st.st_size; + char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if(cont == (void*)-1) { + respond_error(c, 503); + close(fd); + return -1; + } + ptr = cont; + + send(c->s, rsphdr, rspsize, 0); + while(cont_left > 0) { + int sz = cont_left < 4096 ? cont_left : 4096; + send(c->s, ptr, sz, 0); + ptr += sz; + } + + munmap(cont, st.st_size); + } else { + send(c->s, rsphdr, rspsize, 0); + } + + close(fd); + } + return 0; +} + +static void respond_error(struct client *c, int errcode) +{ + char buf[512]; + + sprintf(buf, "HTTP/" HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode)); + + send(c->s, buf, strlen(buf), 0); + close_conn(c); +} + diff -r 4f191dbfac7e -r 5ec50ca0d071 src/tinyweb.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/tinyweb.h Fri Apr 17 11:45:08 2015 +0300 @@ -0,0 +1,16 @@ +#ifndef TINYWEB_H_ +#define TINYWEB_H_ + +void tw_set_port(int port); +int tw_set_root(const char *path); +int tw_set_logfile(const char *fname); + +int tw_start(void); +int tw_stop(void); + +int tw_get_sockets(int *socks); +int tw_get_maxfd(void); +int tw_handle_socket(int s); + + +#endif /* TINYWEB_H_ */