From 7cfb80d4ce8ea945f5f0b516819d56b9152c6803 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 24 Jan 2011 23:13:37 +0100 Subject: [PATCH] Add client.{c,h}. Needs a lot more refactoring. --- Makefile | 2 +- acl.c | 1 + client.c | 246 ++++++++++++++++++++++++++++++++++++++++++ client.h | 91 ++++++++++++++++ cmd.c | 1 + formats/common.c | 1 + formats/custom-type.c | 1 + formats/json.c | 1 + http.c | 236 ++-------------------------------------- http.h | 79 +------------- server.c | 2 + 11 files changed, 354 insertions(+), 307 deletions(-) create mode 100644 client.c create mode 100644 client.h diff --git a/Makefile b/Makefile index abd639b..7abf92f 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ JANSSON_OBJ=jansson/src/dump.o jansson/src/error.o jansson/src/hashtable.o janss FORMAT_OBJS=formats/json.o formats/raw.o formats/common.o formats/custom-type.o HTTP_PARSER_OBJS=http-parser/http_parser.o DEPS=$(FORMAT_OBJS) $(HIREDIS_OBJ) $(JANSSON_OBJ) $(HTTP_PARSER_OBJS) -OBJS=webdis.o conf.o cmd.o slog.o server.o libb64/cencode.o acl.o md5/md5.o http.o $(DEPS) +OBJS=webdis.o conf.o cmd.o slog.o server.o libb64/cencode.o acl.o md5/md5.o http.o client.o $(DEPS) CFLAGS=-O0 -ggdb -Wall -Wextra -I. -Ijansson/src -Ihttp-parser LDFLAGS=-levent diff --git a/acl.c b/acl.c index 6eb7ef5..4703fa5 100644 --- a/acl.c +++ b/acl.c @@ -2,6 +2,7 @@ #include "cmd.h" #include "conf.h" #include "http.h" +#include "client.h" #include #include diff --git a/client.c b/client.c new file mode 100644 index 0000000..48819a4 --- /dev/null +++ b/client.c @@ -0,0 +1,246 @@ +#include "client.h" +#include "server.h" +#include "cmd.h" +#include "http.h" + +#include +#include +#include +#include + +#include "hiredis/async.h" + +struct http_client * +http_client_new(int fd, struct server *s) { + + struct http_client *c = calloc(1, sizeof(struct http_client)); + c->fd = fd; + c->s = s; + + c->settings.on_path = http_on_path; + c->settings.on_body = http_on_body; + c->settings.on_message_complete = http_on_complete; + c->settings.on_header_field = http_on_header_name; + c->settings.on_header_value = http_on_header_value; + c->settings.on_query_string = http_on_query_string; + + http_parser_init(&c->parser, HTTP_REQUEST); + c->parser.data = c; + + return c; +} + + +void +http_client_read(int fd, short event, void *ctx) { + + struct http_client *c = ctx; + char buffer[64*1024]; + int ret, nparsed; + + (void)fd; + (void)event; + + ret = read(c->fd, buffer, sizeof(buffer)); + if(ret <= 0) { /* broken connection, bye */ + http_client_free(c); + return; + } + + /* TODO: http parse. */ + nparsed = http_parser_execute(&c->parser, &c->settings, buffer, ret); + if(c->parser.upgrade) { + /* TODO */ + } else if(nparsed != ret) { /* invalid */ + http_client_free(c); + return; + } + + if(!c->executing) { + http_client_serve(c); + } +} + +static void +http_client_cleanup(struct http_client *c) { + + if(c->sub) { + return; /* we need to keep those. */ + } + + free(c->path.s); + memset(&c->path, 0, sizeof(str_t)); + + free(c->body.s); + memset(&c->body, 0, sizeof(str_t)); + + free(c->input_headers.connection.s); + memset(&c->input_headers.connection, 0, sizeof(str_t)); + + free(c->input_headers.if_none_match.s); + memset(&c->input_headers.if_none_match, 0, sizeof(str_t)); + + free(c->input_headers.authorization.s); + memset(&c->input_headers.authorization, 0, sizeof(str_t)); + + free(c->output_headers.content_type.s); + memset(&c->output_headers.content_type, 0, sizeof(str_t)); + + free(c->output_headers.etag.s); + memset(&c->output_headers.etag, 0, sizeof(str_t)); + + free(c->qs_type.s); + memset(&c->qs_type, 0, sizeof(str_t)); + + free(c->qs_jsonp.s); + memset(&c->qs_jsonp, 0, sizeof(str_t)); + + memset(&c->verb, 0, sizeof(c->verb)); + + c->executing = 0; +} + +void +http_client_free(struct http_client *c) { + + event_del(&c->ev); + close(c->fd); + + if(c->sub) { + /* clean up redis object */ + redisAsyncFree(c->sub->s->ac); + + /* clean up command object */ + if(c->sub->cmd) { + cmd_free(c->sub->cmd); + } + free(c->sub); + c->sub = NULL; + } + + http_client_cleanup(c); + free(c); +} + +int +http_client_keep_alive(struct http_client *c) { + + /* check disconnection */ + int disconnect = 0; + + if(c->parser.http_major == 0) { + disconnect = 1; /* No version given. */ + } else if(c->parser.http_major == 1 && c->parser.http_minor == 0) { + disconnect = 1; /* HTTP 1.0: disconnect by default */ + } + if(c->input_headers.connection.s) { + if(strncasecmp(c->input_headers.connection.s, "Keep-Alive", 10) == 0) { + disconnect = 0; + } else if(strncasecmp(c->input_headers.connection.s, "Close", 5) == 0) { + disconnect = 1; + } + } + return disconnect ? 0 : 1; +} + +void +http_client_reset(struct http_client *c) { + + if(!http_client_keep_alive(c) && !c->sub) { + http_client_free(c); + return; + } + + http_client_cleanup(c); + http_parser_init(&c->parser, HTTP_REQUEST); +} + +/** + * Add read event callback + */ +void +http_client_serve(struct http_client *c) { + + event_set(&c->ev, c->fd, EV_READ, http_client_read, c); + event_base_set(c->s->base, &c->ev); + event_add(&c->ev, NULL); +} + +/**** Parser callbacks ****/ + +/** + * Called when the path has been found. This is before any `?query-string'. + */ +int +http_on_path(http_parser *p, const char *at, size_t length) { + + struct http_client *c = p->data; + + c->path.s = calloc(length+1, 1); + memcpy(c->path.s, at, length); + c->path.sz = length; + + /* save HTTP verb as well */ + c->verb = (enum http_method)p->method; + + return 0; +} + +/** + * Called when the whole body has been read. + */ +int +http_on_body(http_parser *p, const char *at, size_t length) { + struct http_client *c = p->data; + + c->body.s = calloc(length+1, 1); + memcpy(c->body.s, at, length); + c->body.sz = length; + + return 0; +} + +/** + * Called when the query string has been completely read. + */ +int +http_on_query_string(http_parser *parser, const char *at, size_t length) { + + struct http_client *c = parser->data; + const char *p = at; + + while(p < at + length) { + + const char *key = p, *val; + int key_len, val_len; + char *eq = memchr(key, '=', length - (p-at)); + if(!eq || eq > at + length) { /* last argument */ + break; + } else { /* found an '=' */ + char *and; + val = eq + 1; + key_len = eq - key; + p = eq + 1; + + and = memchr(p, '&', length - (p-at)); + if(!and || and > at + length) { + val_len = at + length - p; /* last arg */ + } else { + val_len = and - val; /* cur arg */ + p = and + 1; + } + + if(key_len == 4 && strncmp(key, "type", 4) == 0) { + http_set_header(&c->qs_type, val, val_len); + } else if(key_len == 5 && strncmp(key, "jsonp", 5) == 0) { + http_set_header(&c->qs_jsonp, val, val_len); + } + + if(!and) { + break; + } + } + } + return 0; +} + diff --git a/client.h b/client.h new file mode 100644 index 0000000..5b5ff17 --- /dev/null +++ b/client.h @@ -0,0 +1,91 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include "http.h" + +struct server; + +struct http_client { + + /* socket and server reference */ + int fd; + in_addr_t addr; + struct event ev; + struct server *s; + int executing; + + /* http parser */ + http_parser_settings settings; + http_parser parser; + + /* decoded http */ + enum http_method verb; + str_t path; + str_t body; + + /* input headers from client */ + struct { + str_t connection; + str_t if_none_match; + str_t authorization; + } input_headers; + + /* response headers */ + struct input_headers { + str_t content_type; + str_t etag; + } output_headers; + + /* query string */ + str_t qs_type; + str_t qs_jsonp; + + /* pub/sub */ + struct subscription *sub; + + /* private, used in HTTP parser */ + str_t last_header_name; +}; + + +struct http_client * +http_client_new(int fd, struct server *s); + +void +http_client_serve(struct http_client *c); + +void +http_client_free(struct http_client *c); + +void +http_client_reset(struct http_client *c); + +int +http_on_path(http_parser*, const char *at, size_t length); + +int +http_on_path(http_parser*, const char *at, size_t length); + +int +http_on_body(http_parser*, const char *at, size_t length); + +int +http_on_header_name(http_parser*, const char *at, size_t length); + +int +http_on_header_value(http_parser*, const char *at, size_t length); + +int +http_on_complete(http_parser*); + +int +http_on_query_string(http_parser*, const char *at, size_t length); + +void +http_send_reply(struct http_client *c, short code, const char *msg, + const char *body, size_t body_len); + +int +http_client_keep_alive(struct http_client *c); + +#endif diff --git a/cmd.c b/cmd.c index 66e4568..92c1ccc 100644 --- a/cmd.c +++ b/cmd.c @@ -3,6 +3,7 @@ #include "conf.h" #include "acl.h" #include "http.h" +#include "client.h" #include "formats/json.h" #include "formats/raw.h" diff --git a/formats/common.c b/formats/common.c index 387d2ad..5f61916 100644 --- a/formats/common.c +++ b/formats/common.c @@ -1,6 +1,7 @@ #include "common.h" #include "cmd.h" #include "http.h" +#include "client.h" #include "md5/md5.h" #include diff --git a/formats/custom-type.c b/formats/custom-type.c index 2f8b17b..26eb13d 100644 --- a/formats/custom-type.c +++ b/formats/custom-type.c @@ -2,6 +2,7 @@ #include "cmd.h" #include "common.h" #include "http.h" +#include "client.h" #include #include diff --git a/formats/json.c b/formats/json.c index 0f20cf1..9f200b2 100644 --- a/formats/json.c +++ b/formats/json.c @@ -2,6 +2,7 @@ #include "common.h" #include "cmd.h" #include "http.h" +#include "client.h" #include #include diff --git a/http.c b/http.c index abafccc..9625cd0 100644 --- a/http.c +++ b/http.c @@ -2,194 +2,12 @@ #include "server.h" #include "slog.h" #include "cmd.h" +#include "client.h" #include #include #include -struct http_client * -http_client_new(int fd, struct server *s) { - - struct http_client *c = calloc(1, sizeof(struct http_client)); - c->fd = fd; - c->s = s; - - c->settings.on_path = http_on_path; - c->settings.on_body = http_on_body; - c->settings.on_message_complete = http_on_complete; - c->settings.on_header_field = http_on_header_name; - c->settings.on_header_value = http_on_header_value; - c->settings.on_query_string = http_on_query_string; - - http_parser_init(&c->parser, HTTP_REQUEST); - c->parser.data = c; - - return c; -} - - -void -http_client_read(int fd, short event, void *ctx) { - - struct http_client *c = ctx; - char buffer[64*1024]; - int ret, nparsed; - - (void)fd; - (void)event; - - ret = read(c->fd, buffer, sizeof(buffer)); - if(ret <= 0) { /* broken connection, bye */ - http_client_free(c); - return; - } - - /* TODO: http parse. */ - nparsed = http_parser_execute(&c->parser, &c->settings, buffer, ret); - if(c->parser.upgrade) { - /* TODO */ - } else if(nparsed != ret) { /* invalid */ - http_client_free(c); - return; - } - - if(!c->executing) { - http_client_serve(c); - } -} - -static void -http_client_cleanup(struct http_client *c) { - - if(c->sub) { - return; /* we need to keep those. */ - } - - free(c->path.s); - memset(&c->path, 0, sizeof(str_t)); - - free(c->body.s); - memset(&c->body, 0, sizeof(str_t)); - - free(c->input_headers.connection.s); - memset(&c->input_headers.connection, 0, sizeof(str_t)); - - free(c->input_headers.if_none_match.s); - memset(&c->input_headers.if_none_match, 0, sizeof(str_t)); - - free(c->input_headers.authorization.s); - memset(&c->input_headers.authorization, 0, sizeof(str_t)); - - free(c->output_headers.content_type.s); - memset(&c->output_headers.content_type, 0, sizeof(str_t)); - - free(c->output_headers.etag.s); - memset(&c->output_headers.etag, 0, sizeof(str_t)); - - free(c->qs_type.s); - memset(&c->qs_type, 0, sizeof(str_t)); - - free(c->qs_jsonp.s); - memset(&c->qs_jsonp, 0, sizeof(str_t)); - - memset(&c->verb, 0, sizeof(c->verb)); - - c->executing = 0; -} - - -void -http_client_free(struct http_client *c) { - - event_del(&c->ev); - close(c->fd); - - if(c->sub) { - /* clean up redis object */ - redisAsyncFree(c->sub->s->ac); - - /* clean up command object */ - if(c->sub->cmd) { - cmd_free(c->sub->cmd); - } - free(c->sub); - c->sub = NULL; - } - - http_client_cleanup(c); - free(c); -} - -static int -http_client_keep_alive(struct http_client *c) { - - /* check disconnection */ - int disconnect = 0; - - if(c->parser.http_major == 0) { - disconnect = 1; /* No version given. */ - } else if(c->parser.http_major == 1 && c->parser.http_minor == 0) { - disconnect = 1; /* HTTP 1.0: disconnect by default */ - } - if(c->input_headers.connection.s) { - if(strncasecmp(c->input_headers.connection.s, "Keep-Alive", 10) == 0) { - disconnect = 0; - } else if(strncasecmp(c->input_headers.connection.s, "Close", 5) == 0) { - disconnect = 1; - } - } - return disconnect ? 0 : 1; -} - -void -http_client_reset(struct http_client *c) { - - if(!http_client_keep_alive(c) && !c->sub) { - http_client_free(c); - return; - } - - http_client_cleanup(c); - http_parser_init(&c->parser, HTTP_REQUEST); -} - -/** - * Add read event callback - */ -void -http_client_serve(struct http_client *c) { - - event_set(&c->ev, c->fd, EV_READ, http_client_read, c); - event_base_set(c->s->base, &c->ev); - event_add(&c->ev, NULL); -} - -int -http_on_path(http_parser *p, const char *at, size_t length) { - - struct http_client *c = p->data; - - c->path.s = calloc(length+1, 1); - memcpy(c->path.s, at, length); - c->path.sz = length; - - /* save HTTP verb as well */ - c->verb = (enum http_method)p->method; - - return 0; -} - -int -http_on_body(http_parser *p, const char *at, size_t length) { - struct http_client *c = p->data; - - c->body.s = calloc(length+1, 1); - memcpy(c->body.s, at, length); - c->body.sz = length; - - return 0; -} - /* Adobe flash cross-domain request */ static int http_crossdomain(struct http_client *c) { @@ -211,7 +29,6 @@ http_crossdomain(struct http_client *c) { return 0; } - /* reply to OPTIONS HTTP verb */ static int http_options(struct http_client *c) { @@ -276,6 +93,8 @@ http_on_complete(http_parser *p) { return ret; } +/* HTTP Replies */ + void http_send_error(struct http_client *c, short code, const char *msg) { @@ -310,7 +129,7 @@ http_send_reply(struct http_client *c, short code, const char *msg, http_response_set_body(&resp, body, body_len); /* flush response in the socket */ - if(http_response_write(&resp, c->fd)) { + if(http_response_write(&resp, c->fd)) { /* failure */ http_client_free(c); } else { if(c->sub) { /* don't free the client, but monitor fd. */ @@ -344,6 +163,7 @@ http_send_reply_chunk(struct http_client *c, const char *p, size_t sz) { write(c->fd, "\r\n", 2); } +/* send nil chunk to mark the end of a stream. */ void http_send_reply_end(struct http_client *c) { @@ -401,50 +221,6 @@ http_on_header_value(http_parser *p, const char *at, size_t length) { return 0; } -/** - * Called when the query string is found - */ -int -http_on_query_string(http_parser *parser, const char *at, size_t length) { - - struct http_client *c = parser->data; - const char *p = at; - - while(p < at + length) { - - const char *key = p, *val; - int key_len, val_len; - char *eq = memchr(key, '=', length - (p-at)); - if(!eq || eq > at + length) { /* last argument */ - break; - } else { /* found an '=' */ - char *and; - val = eq + 1; - key_len = eq - key; - p = eq + 1; - - and = memchr(p, '&', length - (p-at)); - if(!and || and > at + length) { - val_len = at + length - p; /* last arg */ - } else { - val_len = and - val; /* cur arg */ - p = and + 1; - } - - if(key_len == 4 && strncmp(key, "type", 4) == 0) { - http_set_header(&c->qs_type, val, val_len); - } else if(key_len == 5 && strncmp(key, "jsonp", 5) == 0) { - http_set_header(&c->qs_jsonp, val, val_len); - } - - if(!and) { - break; - } - } - } - return 0; -} - /* HTTP Response */ void @@ -478,11 +254,13 @@ http_response_set_header(struct http_response *r, const char *k, const char *v) size_t klen = strlen(k); if(strncmp(r->headers[i].s, k, klen) == 0 && r->headers[i].s[klen] == ':') { pos = i; + /* free old value before replacing it. */ free(r->headers[i].s); break; } } + /* extend array */ if(pos == r->header_count) { r->headers = realloc(r->headers, sizeof(str_t)*(r->header_count + 1)); r->header_count++; diff --git a/http.h b/http.h index 976537b..9821c85 100644 --- a/http.h +++ b/http.h @@ -5,53 +5,13 @@ #include #include "http-parser/http_parser.h" +struct http_client; /* FIXME: this shouldn't be here. */ + typedef struct { char *s; size_t sz; } str_t; -struct http_client { - - /* socket and server reference */ - int fd; - in_addr_t addr; - struct event ev; - struct server *s; - int executing; - - /* http parser */ - http_parser_settings settings; - http_parser parser; - - /* decoded http */ - enum http_method verb; - str_t path; - str_t body; - - /* input headers from client */ - struct { - str_t connection; - str_t if_none_match; - str_t authorization; - } input_headers; - - /* response headers */ - struct input_headers { - str_t content_type; - str_t etag; - } output_headers; - - /* query string */ - str_t qs_type; - str_t qs_jsonp; - - /* pub/sub */ - struct subscription *sub; - - /* private, used in HTTP parser */ - str_t last_header_name; -}; - struct http_response { short code; const char *msg; @@ -63,44 +23,9 @@ struct http_response { size_t body_len; }; -struct http_client * -http_client_new(int fd, struct server *s); - -void -http_client_serve(struct http_client *c); - -void -http_client_free(struct http_client *c); - -int -http_on_path(http_parser*, const char *at, size_t length); - -int -http_on_path(http_parser*, const char *at, size_t length); - -int -http_on_body(http_parser*, const char *at, size_t length); - -int -http_on_header_name(http_parser*, const char *at, size_t length); - -int -http_on_header_value(http_parser*, const char *at, size_t length); - -int -http_on_complete(http_parser*); - -int -http_on_query_string(http_parser*, const char *at, size_t length); - - void http_set_header(str_t *h, const char *p, size_t sz); -void -http_send_reply(struct http_client *c, short code, const char *msg, - const char *body, size_t body_len); - /* Transfer-encoding: chunked */ void http_send_reply_start(struct http_client *c, short code, const char *msg); diff --git a/server.c b/server.c index 091b97c..6de454c 100644 --- a/server.c +++ b/server.c @@ -3,6 +3,7 @@ #include "cmd.h" #include "slog.h" #include "http.h" +#include "client.h" #include #include @@ -173,6 +174,7 @@ on_possible_accept(int fd, short event, void *ctx) { http_client_serve(c); } + void server_start(struct server *s) {