From f931f18971780dcefad244137906a4a6905a17f9 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Sat, 1 Jan 2011 15:12:29 +0100 Subject: [PATCH 01/12] Added support for Flash clients. --- README.markdown | 1 + server.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/README.markdown b/README.markdown index 37cc4ec..5aa492e 100644 --- a/README.markdown +++ b/README.markdown @@ -36,6 +36,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Provide timeout (this needs to be added to hiredis first.) * Multi-server support, using consistent hashing. * Send your ideas using the github tracker, on twitter [@yowgi](http://twitter.com/yowgi) or by mail to n.favrefelix@gmail.com. +* Add WebSocket support, allow cross-origin XHR. # HTTP error codes * Unknown HTTP verb: 405 Method Not Allowed diff --git a/server.c b/server.c index a285eca..152de71 100644 --- a/server.c +++ b/server.c @@ -94,6 +94,26 @@ server_copy(const struct server *s) { return ret; } +/* Adobe flash cross-domain request */ +void +on_flash_request(struct evhttp_request *rq, void *ctx) { + + (void)ctx; + + char out[] = "\n" +"\n" +"\n" + "\n" +"\n"; + + struct evbuffer *body = evbuffer_new(); + evbuffer_add(body, out, sizeof(out) - 1); + + evhttp_add_header(rq->output_headers, "Content-Type", "application/xml"); + evhttp_send_reply(rq, 200, "OK", body); + evbuffer_free(body); +} + void on_request(struct evhttp_request *rq, void *ctx) { @@ -140,6 +160,7 @@ server_start(struct server *s) { /* start http server */ evhttp_bind_socket(s->http, s->cfg->http_host, s->cfg->http_port); + evhttp_set_cb(s->http, "/crossdomain.xml", on_flash_request, s); evhttp_set_gencb(s->http, on_request, s); /* drop privileges */ From 1ad059d7b9992b910b16549571d6d481153d0ab5 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 14:02:24 +0100 Subject: [PATCH 02/12] Started adding support for a custom content-type in a second key. --- Makefile | 2 +- README.markdown | 2 +- cmd.c | 6 ++++++ formats/custom-type.c | 44 +++++++++++++++++++++++++++++++++++++++++++ formats/custom-type.h | 12 ++++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 formats/custom-type.c create mode 100644 formats/custom-type.h diff --git a/Makefile b/Makefile index 0101958..617acbf 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ OUT=webdis HIREDIS_OBJ=hiredis/hiredis.o hiredis/sds.o hiredis/net.o hiredis/async.o hiredis/dict.o JANSSON_OBJ=jansson/src/dump.o jansson/src/error.o jansson/src/hashtable.o jansson/src/load.o jansson/src/strbuffer.o jansson/src/utf.o jansson/src/value.o jansson/src/variadic.o -FORMAT_OBJS=formats/json.o formats/raw.o formats/common.o +FORMAT_OBJS=formats/json.o formats/raw.o formats/common.o formats/custom-type.o OBJS=webdis.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) libb64/cencode.o acl.o CFLAGS=-O3 -Wall -Wextra -I. -Ijansson/src LDFLAGS=-levent diff --git a/README.markdown b/README.markdown index 5aa492e..be27fe0 100644 --- a/README.markdown +++ b/README.markdown @@ -25,9 +25,9 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Possible Redis authentication in the config file. * Pub/Sub using `Transfer-Encoding: chunked`, works with JSONP as well. Webdis can be used as a Comet server. * Drop privileges on startup. +* MIME type in a second key with `?format=custom&typeKey=` # Ideas, TODO... -* Add meta-data info per key (MIME type in a second key, for instance). * Support PUT, DELETE, HEAD, OPTIONS? How? For which commands? * MULTI/EXEC/DISCARD/WATCH are disabled at the moment; find a way to use them. * Add logs. diff --git a/cmd.c b/cmd.c index 51c11e5..be96c95 100644 --- a/cmd.c +++ b/cmd.c @@ -5,6 +5,7 @@ #include "formats/json.h" #include "formats/raw.h" +#include "formats/custom-type.h" #include #include @@ -134,6 +135,9 @@ cmd_run(struct server *s, struct evhttp_request *rq, cur_param++; } + /* MGET if */ + // if(cmd->arg_len[0] == 3 && strncasecmp(cmd->argv[0], "GET", 3) == 0 && ) { + redisAsyncCommandArgv(s->ac, fun, cmd, param_count, cmd->argv, cmd->argv_len); return 0; } @@ -152,6 +156,8 @@ get_formatting_function(struct evkeyvalq *params) { return raw_reply; } else if(strcmp(kv->value, "json") == 0) { return json_reply; + } else if(strcmp(kv->value, "custom") == 0) { + return custom_type_reply; } break; } diff --git a/formats/custom-type.c b/formats/custom-type.c new file mode 100644 index 0000000..7b79420 --- /dev/null +++ b/formats/custom-type.c @@ -0,0 +1,44 @@ +#include "custom-type.h" +#include "cmd.h" +#include "common.h" + +#include +#include +#include + +void +custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { + + redisReply *reply = r; + struct cmd *cmd = privdata; + char *ct; + (void)c; + + evhttp_clear_headers(&cmd->uri_params); + + if (reply == NULL) { + evhttp_send_reply(cmd->rq, 404, "Not Found", NULL); + return; + } + + /* we expect array(string, string) */ + if(reply->type != REDIS_REPLY_ARRAY || reply->elements != 2) { + evhttp_send_reply(cmd->rq, 400, "Bad request", NULL); + return; + } + + if(reply->element[0]->type != REDIS_REPLY_STRING) { + evhttp_send_reply(cmd->rq, 400, "Bad request", NULL); + return; + } + + if(reply->element[1]->type != REDIS_REPLY_STRING) { + ct = reply->element[1]->str; + } else { + ct = "binary/octet-stream"; + } + + /* send reply */ + format_send_reply(cmd, reply->element[0]->str, reply->element[0]->len, ct); +} + diff --git a/formats/custom-type.h b/formats/custom-type.h new file mode 100644 index 0000000..cb5b7be --- /dev/null +++ b/formats/custom-type.h @@ -0,0 +1,12 @@ +#ifndef CUSTOM_TYPE_H +#define CUSTOM_TYPE_H + +#include +#include + +struct cmd; + +void +custom_type_reply(redisAsyncContext *c, void *r, void *privdata); + +#endif From 87567e2a9a4adc4bf473e4bc36b936eb9eeee1b5 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 14:04:19 +0100 Subject: [PATCH 03/12] Need custom content-type in a key. --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 5aa492e..515d081 100644 --- a/README.markdown +++ b/README.markdown @@ -37,6 +37,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Multi-server support, using consistent hashing. * Send your ideas using the github tracker, on twitter [@yowgi](http://twitter.com/yowgi) or by mail to n.favrefelix@gmail.com. * Add WebSocket support, allow cross-origin XHR. +* Support URL-encoded parameters, the current implementation is pretty limited (no `/` support, for instance). # HTTP error codes * Unknown HTTP verb: 405 Method Not Allowed From 4448b0fac0fbda2517fa6541679e26a1db0555d1 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 14:27:50 +0100 Subject: [PATCH 04/12] Proper decoding of URL parameters. --- README.markdown | 2 +- cmd.c | 42 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/README.markdown b/README.markdown index 515d081..d291847 100644 --- a/README.markdown +++ b/README.markdown @@ -25,6 +25,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Possible Redis authentication in the config file. * Pub/Sub using `Transfer-Encoding: chunked`, works with JSONP as well. Webdis can be used as a Comet server. * Drop privileges on startup. +* URL-encoded parameters for binary data or slashes. For instance, `%2f` is decoded as `/` but not used as a command separator. # Ideas, TODO... * Add meta-data info per key (MIME type in a second key, for instance). @@ -37,7 +38,6 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Multi-server support, using consistent hashing. * Send your ideas using the github tracker, on twitter [@yowgi](http://twitter.com/yowgi) or by mail to n.favrefelix@gmail.com. * Add WebSocket support, allow cross-origin XHR. -* Support URL-encoded parameters, the current implementation is pretty limited (no `/` support, for instance). # HTTP error codes * Unknown HTTP verb: 405 Method Not Allowed diff --git a/cmd.c b/cmd.c index 51c11e5..3c9a0db 100644 --- a/cmd.c +++ b/cmd.c @@ -9,6 +9,7 @@ #include #include #include +#include struct cmd * cmd_new(struct evhttp_request *rq, int count) { @@ -53,6 +54,35 @@ void on_http_disconnect(struct evhttp_connection *evcon, void *ctx) { free(ps); } +/* taken from libevent */ +static char * +decode_uri(const char *uri, size_t length, size_t *out_len, int always_decode_plus) { + char c; + size_t i, j; + int in_query = always_decode_plus; + + char *ret = malloc(length); + + for (i = j = 0; i < length; i++) { + c = uri[i]; + if (c == '?') { + in_query = 1; + } else if (c == '+' && in_query) { + c = ' '; + } else if (c == '%' && isxdigit((unsigned char)uri[i+1]) && + isxdigit((unsigned char)uri[i+2])) { + char tmp[] = { uri[i+1], uri[i+2], '\0' }; + c = (char)strtol(tmp, NULL, 16); + i += 2; + } + ret[j++] = c; + } + *out_len = (size_t)j; + + return ret; +} + + int cmd_run(struct server *s, struct evhttp_request *rq, const char *uri, size_t uri_len) { @@ -61,7 +91,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, char *slash = strchr(uri, '/'); const char *p; int cmd_len; - int param_count = 0, cur_param = 1; + int param_count = 0, cur_param = 1, i; struct cmd *cmd; formatting_fun fun; @@ -128,18 +158,18 @@ cmd_run(struct server *s, struct evhttp_request *rq, } /* record argument */ - cmd->argv[cur_param] = arg; - cmd->argv_len[cur_param] = arg_len; - + cmd->argv[cur_param] = decode_uri(arg, arg_len, &cmd->argv_len[cur_param], 1); cur_param++; } redisAsyncCommandArgv(s->ac, fun, cmd, param_count, cmd->argv, cmd->argv_len); + for(i = 1; i < cur_param; ++i) { + free((char*)cmd->argv[i]); + } + return 0; } - - formatting_fun get_formatting_function(struct evkeyvalq *params) { From 1ea7cd0720e0d35b28936c266b2b528ae5436e3c Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 15:14:15 +0100 Subject: [PATCH 05/12] Special GET formatter for key + content-type key. --- README.markdown | 2 +- cmd.c | 46 +++++++++++++++++++++++++++---------------- cmd.h | 9 +++++++-- formats/common.c | 2 +- formats/custom-type.c | 18 ++++++++++++++++- formats/custom-type.h | 3 +++ 6 files changed, 58 insertions(+), 22 deletions(-) diff --git a/README.markdown b/README.markdown index 7289b74..be4f894 100644 --- a/README.markdown +++ b/README.markdown @@ -25,7 +25,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Possible Redis authentication in the config file. * Pub/Sub using `Transfer-Encoding: chunked`, works with JSONP as well. Webdis can be used as a Comet server. * Drop privileges on startup. -* MIME type in a second key with `?format=custom&typeKey=` +* For `GET` commands, MIME type in a second key with `?format=custom&typeKey=` or custom MIME type with `format=custom&type=something/here`. * URL-encoded parameters for binary data or slashes. For instance, `%2f` is decoded as `/` but not used as a command separator. # Ideas, TODO... diff --git a/cmd.c b/cmd.c index e835bd4..8bb37ce 100644 --- a/cmd.c +++ b/cmd.c @@ -20,8 +20,8 @@ cmd_new(struct evhttp_request *rq, int count) { c->rq = rq; c->count = count; - c->argv = calloc(count, sizeof(char*)); - c->argv_len = calloc(count, sizeof(size_t)); + c->argv = calloc(1+count, sizeof(char*)); + c->argv_len = calloc(1+count, sizeof(size_t)); return c; } @@ -33,6 +33,9 @@ cmd_free(struct cmd *c) { free(c->argv); free(c->argv_len); + free(c->mime); + free(c->mimeKey); + free(c); } @@ -95,7 +98,9 @@ cmd_run(struct server *s, struct evhttp_request *rq, int param_count = 0, cur_param = 1, i; struct cmd *cmd; - formatting_fun fun; + + formatting_fun f_format; + transform_fun f_transform = NULL; /* count arguments */ if(qmark) { @@ -117,7 +122,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, evhttp_parse_query(uri, &cmd->uri_params); /* get output formatting function */ - fun = get_formatting_function(&cmd->uri_params); + get_functions(cmd, &f_format, &f_transform); /* there is always a first parameter, it's the command name */ cmd->argv[0] = uri; @@ -141,7 +146,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, } if(!slash) { - redisAsyncCommandArgv(s->ac, fun, cmd, 1, cmd->argv, cmd->argv_len); + redisAsyncCommandArgv(s->ac, f_format, cmd, 1, cmd->argv, cmd->argv_len); return 0; } p = slash + 1; @@ -163,10 +168,11 @@ cmd_run(struct server *s, struct evhttp_request *rq, cur_param++; } - /* MGET if */ - // if(cmd->arg_len[0] == 3 && strncasecmp(cmd->argv[0], "GET", 3) == 0 && ) { + /* transform command if we need to. */ + if(f_transform) f_transform(cmd); + + redisAsyncCommandArgv(s->ac, f_format, cmd, cmd->count, cmd->argv, cmd->argv_len); - redisAsyncCommandArgv(s->ac, fun, cmd, param_count, cmd->argv, cmd->argv_len); for(i = 1; i < cur_param; ++i) { free((char*)cmd->argv[i]); } @@ -174,26 +180,32 @@ cmd_run(struct server *s, struct evhttp_request *rq, return 0; } -formatting_fun -get_formatting_function(struct evkeyvalq *params) { +void +get_functions(struct cmd *cmd, formatting_fun *f_format, transform_fun *f_transform) { struct evkeyval *kv; + /* defaults */ + *f_format = json_reply; + *f_transform = NULL; + /* check for JSONP */ - TAILQ_FOREACH(kv, params, next) { + TAILQ_FOREACH(kv, &cmd->uri_params, next) { if(strcmp(kv->key, "format") == 0) { if(strcmp(kv->value, "raw") == 0) { - return raw_reply; + *f_format = raw_reply; } else if(strcmp(kv->value, "json") == 0) { - return json_reply; - } else if(strcmp(kv->value, "custom") == 0) { - return custom_type_reply; + *f_format = json_reply; } break; + } else if(strcmp(kv->key, "typeKey") == 0) { /* MIME type in key. */ + cmd->mimeKey = strdup(kv->value); + *f_transform = custom_type_process_cmd; + *f_format = custom_type_reply; + } else if(strcmp(kv->key, "type") == 0) { /* MIME type in parameter */ + cmd->mime = strdup(kv->value); } } - - return json_reply; } int diff --git a/cmd.h b/cmd.h index e2e1857..fde50ab 100644 --- a/cmd.h +++ b/cmd.h @@ -9,8 +9,10 @@ struct evhttp_request; struct server; +struct cmd; typedef void (*formatting_fun)(redisAsyncContext *, void *, void *); +typedef void (*transform_fun)(struct cmd *); struct cmd { @@ -22,6 +24,9 @@ struct cmd { struct evkeyvalq uri_params; int started_responding; + + char *mime; + char *mimeKey; }; struct pubsub_client { @@ -39,8 +44,8 @@ int cmd_run(struct server *s, struct evhttp_request *rq, const char *uri, size_t uri_len); -formatting_fun -get_formatting_function(struct evkeyvalq *params); +void +get_functions(struct cmd *cmd, formatting_fun *f_format, transform_fun *f_transform); int cmd_is_subscribe(struct cmd *cmd); diff --git a/formats/common.c b/formats/common.c index 277354f..e87a136 100644 --- a/formats/common.c +++ b/formats/common.c @@ -12,7 +12,7 @@ format_send_reply(struct cmd *cmd, const char *p, size_t sz, const char *content /* send reply */ body = evbuffer_new(); evbuffer_add(body, p, sz); - evhttp_add_header(cmd->rq->output_headers, "Content-Type", content_type); + evhttp_add_header(cmd->rq->output_headers, "Content-Type", cmd->mime?cmd->mime:content_type); if(cmd_is_subscribe(cmd)) { free_cmd = 0; diff --git a/formats/custom-type.c b/formats/custom-type.c index 7b79420..54d4182 100644 --- a/formats/custom-type.c +++ b/formats/custom-type.c @@ -32,7 +32,7 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { return; } - if(reply->element[1]->type != REDIS_REPLY_STRING) { + if(reply->element[1]->type == REDIS_REPLY_STRING) { ct = reply->element[1]->str; } else { ct = "binary/octet-stream"; @@ -42,3 +42,19 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { format_send_reply(cmd, reply->element[0]->str, reply->element[0]->len, ct); } +void +custom_type_process_cmd(struct cmd *cmd) { + /* MGET if mode is “custom” */ + if(cmd->argv_len[0] == 3 && strncasecmp(cmd->argv[0], "GET", 3) == 0 && cmd->mimeKey) { + + cmd->count++; /* space for content-type key */ + + /* replace command with MGET */ + cmd->argv[0] = "MGET"; + cmd->argv_len[0] = 4; + + /* add mime key after the key. */ + cmd->argv[2] = cmd->mimeKey; + cmd->argv_len[2] = strlen(cmd->mimeKey); + } +} diff --git a/formats/custom-type.h b/formats/custom-type.h index cb5b7be..959e0be 100644 --- a/formats/custom-type.h +++ b/formats/custom-type.h @@ -9,4 +9,7 @@ struct cmd; void custom_type_reply(redisAsyncContext *c, void *r, void *privdata); +void +custom_type_process_cmd(struct cmd *cmd); + #endif From 5ca45a54d936a56e36e4563bd76cb0ff28d05a38 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 20:45:53 +0100 Subject: [PATCH 06/12] Working custom handler. --- Makefile | 2 +- README.markdown | 4 +++- cmd.c | 9 +++++---- formats/custom-type.c | 26 ++++++++++++++++++++++---- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 617acbf..69d2706 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ HIREDIS_OBJ=hiredis/hiredis.o hiredis/sds.o hiredis/net.o hiredis/async.o hiredi JANSSON_OBJ=jansson/src/dump.o jansson/src/error.o jansson/src/hashtable.o jansson/src/load.o jansson/src/strbuffer.o jansson/src/utf.o jansson/src/value.o jansson/src/variadic.o FORMAT_OBJS=formats/json.o formats/raw.o formats/common.o formats/custom-type.o OBJS=webdis.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) libb64/cencode.o acl.o -CFLAGS=-O3 -Wall -Wextra -I. -Ijansson/src +CFLAGS=-O0 -ggdb -Wall -Wextra -I. -Ijansson/src LDFLAGS=-levent all: $(OUT) Makefile diff --git a/README.markdown b/README.markdown index be4f894..73a1434 100644 --- a/README.markdown +++ b/README.markdown @@ -25,7 +25,9 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Possible Redis authentication in the config file. * Pub/Sub using `Transfer-Encoding: chunked`, works with JSONP as well. Webdis can be used as a Comet server. * Drop privileges on startup. -* For `GET` commands, MIME type in a second key with `?format=custom&typeKey=` or custom MIME type with `format=custom&type=something/here`. +* For `GET` commands: + * MIME type in a second key with `/GET/k?typeKey=type-k`. This will transform the `GET` request into `MGET` and fetch both `k` and `type-k`. If `type-k` is a string, it will be used as Content-Type in the response. If the key doesn't exist or isn't a string, `binary/octet-stream` is used instead. + * Custom MIME type with `?type=text/plain` (or any other MIME type). * URL-encoded parameters for binary data or slashes. For instance, `%2f` is decoded as `/` but not used as a command separator. # Ideas, TODO... diff --git a/cmd.c b/cmd.c index 8bb37ce..a1c9ab5 100644 --- a/cmd.c +++ b/cmd.c @@ -155,12 +155,12 @@ cmd_run(struct server *s, struct evhttp_request *rq, const char *arg = p; int arg_len; char *next = strchr(arg, '/'); - if(next) { /* found a slash */ + if(!next || next > uri + uri_len) { /* last argument */ + p = uri + uri_len; + arg_len = p - arg; + } else { /* found a slash */ arg_len = next - arg; p = next + 1; - } else { /* last argument */ - arg_len = uri + uri_len - arg; - p = uri + uri_len; } /* record argument */ @@ -204,6 +204,7 @@ get_functions(struct cmd *cmd, formatting_fun *f_format, transform_fun *f_transf *f_format = custom_type_reply; } else if(strcmp(kv->key, "type") == 0) { /* MIME type in parameter */ cmd->mime = strdup(kv->value); + *f_format = custom_type_reply; } } } diff --git a/formats/custom-type.c b/formats/custom-type.c index 54d4182..b78e167 100644 --- a/formats/custom-type.c +++ b/formats/custom-type.c @@ -21,15 +21,25 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { return; } + if(cmd->mime) { /* use the given content-type */ + if(reply->type != REDIS_REPLY_STRING) { + goto fail; + } + format_send_reply(cmd, reply->str, reply->len, cmd->mime); + return; + } + + if(!cmd->mimeKey) { /* how did we get here? */ + goto fail; + } + /* we expect array(string, string) */ if(reply->type != REDIS_REPLY_ARRAY || reply->elements != 2) { - evhttp_send_reply(cmd->rq, 400, "Bad request", NULL); - return; + goto fail; } if(reply->element[0]->type != REDIS_REPLY_STRING) { - evhttp_send_reply(cmd->rq, 400, "Bad request", NULL); - return; + goto fail; } if(reply->element[1]->type == REDIS_REPLY_STRING) { @@ -40,8 +50,16 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { /* send reply */ format_send_reply(cmd, reply->element[0]->str, reply->element[0]->len, ct); + + cmd_free(cmd); + return; + +fail: + evhttp_send_reply(cmd->rq, 400, "Bad request", NULL); + cmd_free(cmd); } +/* This will change a GET command into MGET if a key is provided to get the response MIME-type from. */ void custom_type_process_cmd(struct cmd *cmd) { /* MGET if mode is “custom” */ From ccee5e698f0bb5524e5bcb50770aa522896565f3 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 21:32:28 +0100 Subject: [PATCH 07/12] Bugfix, restored Makefile. --- Makefile | 2 +- formats/custom-type.c | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 69d2706..617acbf 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ HIREDIS_OBJ=hiredis/hiredis.o hiredis/sds.o hiredis/net.o hiredis/async.o hiredi JANSSON_OBJ=jansson/src/dump.o jansson/src/error.o jansson/src/hashtable.o jansson/src/load.o jansson/src/strbuffer.o jansson/src/utf.o jansson/src/value.o jansson/src/variadic.o FORMAT_OBJS=formats/json.o formats/raw.o formats/common.o formats/custom-type.o OBJS=webdis.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) libb64/cencode.o acl.o -CFLAGS=-O0 -ggdb -Wall -Wextra -I. -Ijansson/src +CFLAGS=-O3 -Wall -Wextra -I. -Ijansson/src LDFLAGS=-levent all: $(OUT) Makefile diff --git a/formats/custom-type.c b/formats/custom-type.c index b78e167..b1b56f5 100644 --- a/formats/custom-type.c +++ b/formats/custom-type.c @@ -50,8 +50,6 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { /* send reply */ format_send_reply(cmd, reply->element[0]->str, reply->element[0]->len, ct); - - cmd_free(cmd); return; fail: From 07d7c138053a554057e90b927a56714a88b0a24e Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 21:36:15 +0100 Subject: [PATCH 08/12] Refactoring. --- formats/custom-type.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/formats/custom-type.c b/formats/custom-type.c index b1b56f5..abc6bc3 100644 --- a/formats/custom-type.c +++ b/formats/custom-type.c @@ -6,6 +6,12 @@ #include #include +static void +custom_400(struct cmd *cmd) { + evhttp_send_reply(cmd->rq, 400, "Bad request", NULL); + cmd_free(cmd); +} + void custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { @@ -23,25 +29,20 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { if(cmd->mime) { /* use the given content-type */ if(reply->type != REDIS_REPLY_STRING) { - goto fail; + custom_400(cmd); + return; } format_send_reply(cmd, reply->str, reply->len, cmd->mime); return; } - if(!cmd->mimeKey) { /* how did we get here? */ - goto fail; - } - /* we expect array(string, string) */ - if(reply->type != REDIS_REPLY_ARRAY || reply->elements != 2) { - goto fail; - } - - if(reply->element[0]->type != REDIS_REPLY_STRING) { - goto fail; + if(!cmd->mimeKey || reply->type != REDIS_REPLY_ARRAY || reply->elements != 2 || reply->element[0]->type != REDIS_REPLY_STRING) { + custom_400(cmd); + return; } + /* case of MGET, we need to have a string for content-type in element[1] */ if(reply->element[1]->type == REDIS_REPLY_STRING) { ct = reply->element[1]->str; } else { @@ -51,10 +52,6 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { /* send reply */ format_send_reply(cmd, reply->element[0]->str, reply->element[0]->len, ct); return; - -fail: - evhttp_send_reply(cmd->rq, 400, "Bad request", NULL); - cmd_free(cmd); } /* This will change a GET command into MGET if a key is provided to get the response MIME-type from. */ From a3aa1a9225c3c6766e44c9e20602d97ff4ad1d9c Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 21:40:59 +0100 Subject: [PATCH 09/12] Bugfix in RAW mode, more code doc. --- cmd.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cmd.c b/cmd.c index a1c9ab5..e450ce3 100644 --- a/cmd.c +++ b/cmd.c @@ -92,7 +92,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, const char *uri, size_t uri_len) { char *qmark = strchr(uri, '?'); - char *slash = strchr(uri, '/'); + char *slash; const char *p; int cmd_len; int param_count = 0, cur_param = 1, i; @@ -112,6 +112,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, cmd = cmd_new(rq, param_count); + slash = memchr(uri, '/', uri_len); if(slash) { cmd_len = slash - uri; } else { @@ -145,6 +146,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, evhttp_connection_set_closecb(rq->evcon, on_http_disconnect, ps); } + /* no args (e.g. INFO command) */ if(!slash) { redisAsyncCommandArgv(s->ac, f_format, cmd, 1, cmd->argv, cmd->argv_len); return 0; @@ -171,6 +173,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, /* transform command if we need to. */ if(f_transform) f_transform(cmd); + /* push command to Redis. */ redisAsyncCommandArgv(s->ac, f_format, cmd, cmd->count, cmd->argv, cmd->argv_len); for(i = 1; i < cur_param; ++i) { @@ -180,6 +183,10 @@ cmd_run(struct server *s, struct evhttp_request *rq, return 0; } +/** + * Return 2 functions, one to format the reply and + * one to transform the command before processing it. + */ void get_functions(struct cmd *cmd, formatting_fun *f_format, transform_fun *f_transform) { @@ -189,20 +196,20 @@ get_functions(struct cmd *cmd, formatting_fun *f_format, transform_fun *f_transf *f_format = json_reply; *f_transform = NULL; - /* check for JSONP */ + /* loop over the query string */ TAILQ_FOREACH(kv, &cmd->uri_params, next) { - if(strcmp(kv->key, "format") == 0) { + if(strcmp(kv->key, "format") == 0) { /* output format */ if(strcmp(kv->value, "raw") == 0) { *f_format = raw_reply; } else if(strcmp(kv->value, "json") == 0) { *f_format = json_reply; } break; - } else if(strcmp(kv->key, "typeKey") == 0) { /* MIME type in key. */ + } else if(strcmp(kv->key, "typeKey") == 0) { /* MIME type in a key. */ cmd->mimeKey = strdup(kv->value); *f_transform = custom_type_process_cmd; *f_format = custom_type_reply; - } else if(strcmp(kv->key, "type") == 0) { /* MIME type in parameter */ + } else if(strcmp(kv->key, "type") == 0) { /* MIME type directly in parameter */ cmd->mime = strdup(kv->value); *f_format = custom_type_reply; } From 1abb4149f571ff3a64cd574bc80da80bc99d257e Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 21:59:47 +0100 Subject: [PATCH 10/12] Final touch to content-type feature. --- README.markdown | 38 ++++++++++++++++++++++++++++++++++++++ cmd.c | 6 +++--- formats/custom-type.c | 7 +++++-- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/README.markdown b/README.markdown index 73a1434..bd2b044 100644 --- a/README.markdown +++ b/README.markdown @@ -147,3 +147,41 @@ $ curl http://127.0.0.1:7379/MAKE-ME-COFFEE?format=raw -ERR unknown command 'MAKE-ME-COFFEE' + +# Custom content-type +Webdis can serve `GET` requests with a custom content-type. There are two ways of doing this; the content-type can be in a key that is fetched with the content, or given as a query string parameter. + +**Content-Type in parameter:** +
+curl -v "http://127.0.0.1:7379/GET/hello.html?type=text/html"
+[...]
+< HTTP/1.1 200 OK
+< Content-Type: text/html
+< Date: Mon, 03 Jan 2011 20:43:36 GMT
+< Content-Length: 137
+<
+<!DOCTYPE html>
+<html>
+...
+</html>
+
+ +**Content-Type in a separate key:** +
+curl "http://127.0.0.1:7379/SET/hello.type/text%2fhtml"
+{"SET":[true,"OK"]}
+
+curl "http://127.0.0.1:7379/GET/hello.type"
+{"GET":"text/html"}
+
+curl -v "http://127.0.0.1:7379/GET/hello.html?typeKey=hello.type"
+< HTTP/1.1 200 OK
+< Content-Type: text/html
+< Date: Mon, 03 Jan 2011 20:56:43 GMT
+< Content-Length: 137
+<
+<!DOCTYPE html>
+<html>
+...
+</html>
+
diff --git a/cmd.c b/cmd.c index e450ce3..742cadd 100644 --- a/cmd.c +++ b/cmd.c @@ -20,8 +20,8 @@ cmd_new(struct evhttp_request *rq, int count) { c->rq = rq; c->count = count; - c->argv = calloc(1+count, sizeof(char*)); - c->argv_len = calloc(1+count, sizeof(size_t)); + c->argv = calloc(count, sizeof(char*)); + c->argv_len = calloc(count, sizeof(size_t)); return c; } @@ -176,7 +176,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, /* push command to Redis. */ redisAsyncCommandArgv(s->ac, f_format, cmd, cmd->count, cmd->argv, cmd->argv_len); - for(i = 1; i < cur_param; ++i) { + for(i = 1; i < cmd->count; ++i) { free((char*)cmd->argv[i]); } diff --git a/formats/custom-type.c b/formats/custom-type.c index abc6bc3..39baff9 100644 --- a/formats/custom-type.c +++ b/formats/custom-type.c @@ -58,16 +58,19 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { void custom_type_process_cmd(struct cmd *cmd) { /* MGET if mode is “custom” */ - if(cmd->argv_len[0] == 3 && strncasecmp(cmd->argv[0], "GET", 3) == 0 && cmd->mimeKey) { + if(cmd->count == 2 && cmd->argv_len[0] == 3 && + strncasecmp(cmd->argv[0], "GET", 3) == 0 && cmd->mimeKey) { cmd->count++; /* space for content-type key */ + cmd->argv = realloc(cmd->argv, cmd->count * sizeof(char*)); + cmd->argv_len = realloc(cmd->argv_len, cmd->count * sizeof(size_t)); /* replace command with MGET */ cmd->argv[0] = "MGET"; cmd->argv_len[0] = 4; /* add mime key after the key. */ - cmd->argv[2] = cmd->mimeKey; + cmd->argv[2] = strdup(cmd->mimeKey); cmd->argv_len[2] = strlen(cmd->mimeKey); } } From aaf49205e005d485579f026e409d08abb39d7df9 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 3 Jan 2011 22:03:31 +0100 Subject: [PATCH 11/12] Markdown fixes. --- README.markdown | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.markdown b/README.markdown index bd2b044..d5f38cd 100644 --- a/README.markdown +++ b/README.markdown @@ -152,21 +152,24 @@ $ curl http://127.0.0.1:7379/MAKE-ME-COFFEE?format=raw Webdis can serve `GET` requests with a custom content-type. There are two ways of doing this; the content-type can be in a key that is fetched with the content, or given as a query string parameter. **Content-Type in parameter:** +
 curl -v "http://127.0.0.1:7379/GET/hello.html?type=text/html"
 [...]
-< HTTP/1.1 200 OK
-< Content-Type: text/html
-< Date: Mon, 03 Jan 2011 20:43:36 GMT
-< Content-Length: 137
-<
+< HTTP/1.1 200 OK
+< Content-Type: text/html
+< Date: Mon, 03 Jan 2011 20:43:36 GMT
+< Content-Length: 137
+<
 <!DOCTYPE html>
 <html>
 ...
 </html>
+
 
**Content-Type in a separate key:** +
 curl "http://127.0.0.1:7379/SET/hello.type/text%2fhtml"
 {"SET":[true,"OK"]}
@@ -175,11 +178,11 @@ curl "http://127.0.0.1:7379/GET/hello.type"
 {"GET":"text/html"}
 
 curl -v "http://127.0.0.1:7379/GET/hello.html?typeKey=hello.type"
-< HTTP/1.1 200 OK
-< Content-Type: text/html
-< Date: Mon, 03 Jan 2011 20:56:43 GMT
-< Content-Length: 137
-<
+< HTTP/1.1 200 OK
+< Content-Type: text/html
+< Date: Mon, 03 Jan 2011 20:56:43 GMT
+< Content-Length: 137
+<
 <!DOCTYPE html>
 <html>
 ...

From d8298c355662c701727ae270897923bdbfa58aac Mon Sep 17 00:00:00 2001
From: Nicolas Favre-Felix 
Date: Mon, 3 Jan 2011 22:04:02 +0100
Subject: [PATCH 12/12] Markdown fixes.

---
 README.markdown | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.markdown b/README.markdown
index d5f38cd..2ec6eed 100644
--- a/README.markdown
+++ b/README.markdown
@@ -165,7 +165,6 @@ curl -v "http://127.0.0.1:7379/GET/hello.html?type=text/html"
 <html>
 ...
 </html>
-
 
**Content-Type in a separate key:** @@ -178,6 +177,7 @@ curl "http://127.0.0.1:7379/GET/hello.type" {"GET":"text/html"} curl -v "http://127.0.0.1:7379/GET/hello.html?typeKey=hello.type" +[...] < HTTP/1.1 200 OK < Content-Type: text/html < Date: Mon, 03 Jan 2011 20:56:43 GMT