From 7c152db3c17783bc50dd85e5e87ce7aa1520423d Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Tue, 4 Jan 2011 20:31:49 +0100 Subject: [PATCH 01/12] Could MD5 be affecting performance that much? --- formats/common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/formats/common.c b/formats/common.c index 006efef..46c60ae 100644 --- a/formats/common.c +++ b/formats/common.c @@ -5,6 +5,7 @@ #include #include +/* TODO: replace this with a faster hash function */ char *etag_new(const char *p, size_t sz) { md5_byte_t buf[16]; From 8c8954d25a3e60d5a515695b15dcb8fd257c57d2 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Tue, 4 Jan 2011 20:45:46 +0100 Subject: [PATCH 02/12] Removed debug info. --- server.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/server.c b/server.c index 152de71..aa2a2a0 100644 --- a/server.c +++ b/server.c @@ -122,7 +122,6 @@ on_request(struct evhttp_request *rq, void *ctx) { int ret; if(!s->ac) { /* redis is unavailable */ - printf("503\n"); evhttp_send_reply(rq, 503, "Service Unavailable", NULL); return; } @@ -140,7 +139,6 @@ on_request(struct evhttp_request *rq, void *ctx) { break; default: - printf("405\n"); evhttp_send_reply(rq, 405, "Method Not Allowed", NULL); return; } From 469e51608b122f7dc3f7421fb75d7c7384b1499e Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Tue, 4 Jan 2011 21:32:03 +0100 Subject: [PATCH 03/12] Removed content-type in another key, added suffixes instead. --- cmd.c | 69 ++++++++++++++++++++++--------------------- cmd.h | 15 ++++++---- formats/custom-type.c | 40 +++++++++---------------- formats/custom-type.h | 3 -- formats/raw.c | 2 +- 5 files changed, 61 insertions(+), 68 deletions(-) diff --git a/cmd.c b/cmd.c index 01cc479..f769dbf 100644 --- a/cmd.c +++ b/cmd.c @@ -33,9 +33,6 @@ cmd_free(struct cmd *c) { free(c->argv); free(c->argv_len); - free(c->mime); - free(c->mimeKey); - free(c); } @@ -100,7 +97,6 @@ cmd_run(struct server *s, struct evhttp_request *rq, struct cmd *cmd; formatting_fun f_format; - transform_fun f_transform = NULL; /* count arguments */ if(qmark) { @@ -112,6 +108,13 @@ cmd_run(struct server *s, struct evhttp_request *rq, cmd = cmd_new(rq, param_count); + /* parse URI parameters */ + evhttp_parse_query(uri, &cmd->uri_params); + + /* get output formatting function */ + uri_len = cmd_read_params(cmd, uri, uri_len, &f_format); + + /* check if we only have one command or more. */ slash = memchr(uri, '/', uri_len); if(slash) { cmd_len = slash - uri; @@ -119,12 +122,6 @@ cmd_run(struct server *s, struct evhttp_request *rq, cmd_len = uri_len; } - /* parse URI parameters */ - evhttp_parse_query(uri, &cmd->uri_params); - - /* get output formatting function */ - cmd_read_params(cmd, &f_format, &f_transform); - /* there is always a first parameter, it's the command name */ cmd->argv[0] = uri; cmd->argv_len[0] = cmd_len; @@ -170,9 +167,6 @@ cmd_run(struct server *s, struct evhttp_request *rq, cur_param++; } - /* 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); @@ -187,33 +181,42 @@ cmd_run(struct server *s, struct evhttp_request *rq, * Return 2 functions, one to format the reply and * one to transform the command before processing it. */ -void -cmd_read_params(struct cmd *cmd, formatting_fun *f_format, transform_fun *f_transform) { +int +cmd_read_params(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun *f_format) { + + const char *ext; + int ext_len = -1; + unsigned int i; - struct evkeyval *kv; + struct reply_format funs[] = { + {.s = "json", .sz = 4, .f = json_reply, .ct = "application/json"}, + {.s = "raw", .sz = 3, .f = raw_reply, .ct = "binary/octet-stream"}, + {.s = "txt", .sz = 3, .f = custom_type_reply, .ct = "text/plain"}, + {.s = "html", .sz = 4, .f = custom_type_reply, .ct = "text/html"}, + {.s = "png", .sz = 3, .f = custom_type_reply, .ct = "image/png"}, + }; /* defaults */ *f_format = json_reply; - *f_transform = NULL; - - /* loop over the query string */ - TAILQ_FOREACH(kv, &cmd->uri_params, next) { - 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; - } + + /* find extension */ + for(ext = uri + uri_len - 1; ext != uri && *ext != '/'; --ext) { + if(*ext == '.') { + ext++; + ext_len = uri + uri_len - ext; break; - } 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 directly in parameter */ - cmd->mime = strdup(kv->value); - *f_format = custom_type_reply; } } + if(!ext_len) return uri_len; + + /* find function for the given extension */ + for(i = 0; i < sizeof(funs)/sizeof(funs[0]); ++i) { + if(ext_len == (int)funs[i].sz && strncmp(ext, funs[i].s, ext_len) == 0) { + *f_format = funs[i].f; + cmd->mime = funs[i].ct; + } + } + return uri_len - ext_len - 1; } int diff --git a/cmd.h b/cmd.h index 3d8aab7..cd94b2a 100644 --- a/cmd.h +++ b/cmd.h @@ -12,7 +12,6 @@ struct server; struct cmd; typedef void (*formatting_fun)(redisAsyncContext *, void *, void *); -typedef void (*transform_fun)(struct cmd *); struct cmd { @@ -26,8 +25,7 @@ struct cmd { int started_responding; /* HTTP data */ - char *mime; - char *mimeKey; + const char *mime; char *if_none_match; }; @@ -37,6 +35,13 @@ struct pubsub_client { struct evhttp_request *rq; }; +struct reply_format { + const char *s; + size_t sz; + formatting_fun f; + const char *ct; +}; + struct cmd * cmd_new(struct evhttp_request *rq, int count); @@ -47,8 +52,8 @@ int cmd_run(struct server *s, struct evhttp_request *rq, const char *uri, size_t uri_len); -void -cmd_read_params(struct cmd *cmd, formatting_fun *f_format, transform_fun *f_transform); +int +cmd_read_params(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun *f_format); int cmd_is_subscribe(struct cmd *cmd); diff --git a/formats/custom-type.c b/formats/custom-type.c index 39baff9..c378440 100644 --- a/formats/custom-type.c +++ b/formats/custom-type.c @@ -28,16 +28,24 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { } if(cmd->mime) { /* use the given content-type */ - if(reply->type != REDIS_REPLY_STRING) { - custom_400(cmd); - return; + switch(reply->type) { + + case REDIS_REPLY_NIL: + format_send_reply(cmd, "", 0, cmd->mime); + return; + + case REDIS_REPLY_STRING: + format_send_reply(cmd, reply->str, reply->len, cmd->mime); + return; + + default: + custom_400(cmd); + return; } - format_send_reply(cmd, reply->str, reply->len, cmd->mime); - return; } /* we expect array(string, string) */ - if(!cmd->mimeKey || reply->type != REDIS_REPLY_ARRAY || reply->elements != 2 || reply->element[0]->type != REDIS_REPLY_STRING) { + if(reply->type != REDIS_REPLY_ARRAY || reply->elements != 2 || reply->element[0]->type != REDIS_REPLY_STRING) { custom_400(cmd); return; } @@ -54,23 +62,3 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { return; } -/* 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” */ - 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] = strdup(cmd->mimeKey); - cmd->argv_len[2] = strlen(cmd->mimeKey); - } -} diff --git a/formats/custom-type.h b/formats/custom-type.h index 959e0be..cb5b7be 100644 --- a/formats/custom-type.h +++ b/formats/custom-type.h @@ -9,7 +9,4 @@ struct cmd; void custom_type_reply(redisAsyncContext *c, void *r, void *privdata); -void -custom_type_process_cmd(struct cmd *cmd); - #endif diff --git a/formats/raw.c b/formats/raw.c index 33ce8a6..b6fdad7 100644 --- a/formats/raw.c +++ b/formats/raw.c @@ -28,7 +28,7 @@ raw_reply(redisAsyncContext *c, void *r, void *privdata) { raw_out = raw_wrap(r, &sz); /* send reply */ - format_send_reply(cmd, raw_out, sz, "binary/octet-stream"); + format_send_reply(cmd, raw_out, sz, cmd->mime?cmd->mime:"binary/octet-stream"); /* cleanup */ free(raw_out); From 75cbbc2064bd33990a9fd03ee00cf9995b4a19c3 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Tue, 4 Jan 2011 21:46:39 +0100 Subject: [PATCH 04/12] Reworded Content-Type description. --- README.markdown | 51 +++++++++++++++---------------------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/README.markdown b/README.markdown index 7917186..30167c2 100644 --- a/README.markdown +++ b/README.markdown @@ -17,17 +17,15 @@ curl -d "GET/hello" http://127.0.0.1:7379/ # Features * GET and POST are supported. -* JSON output by default, optional JSONP parameter. -* Raw Redis 2.0 protocol output with `?format=raw` +* JSON output by default, optional JSONP parameter (`?jsonp=myFunction`). +* Raw Redis 2.0 protocol output with `.raw` suffix * HTTP 1.1 pipelining (50,000 http requests per second on a desktop Linux machine.) * Connects to Redis using a TCP or UNIX socket. * Restricted commands by IP range (CIDR subnet + mask) or HTTP Basic Auth, returning 403 errors. * 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 `/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). +* Custom Content-Type using a pre-defined file extension. * URL-encoded parameters for binary data or slashes. For instance, `%2f` is decoded as `/` but not used as a command separator. # Ideas, TODO... @@ -119,20 +117,20 @@ myCustomFunction({"TYPE":[true,"string"]}) # RAW output -This is the raw output of Redis; enable it with `?format=raw`. +This is the raw output of Redis; enable it with the `.raw` suffix.
 
 // string
-$ curl http://127.0.0.1:7379/GET/z?format=raw
+$ curl http://127.0.0.1:7379/GET/z.raw
 $5
 hello
 
 // number
-curl http://127.0.0.1:7379/INCR/a?format=raw
+curl http://127.0.0.1:7379/INCR/a.raw
 :2
 
 // list
-$ curl http://127.0.0.1:7379/LRANGE/x/0/-1?format=raw
+$ curl http://127.0.0.1:7379/LRANGE/x/0/-1?.raw
 *2
 $3
 abc
@@ -140,22 +138,24 @@ $3
 def
 
 // status
-$ curl http://127.0.0.1:7379/TYPE/y?format=raw
+$ curl http://127.0.0.1:7379/TYPE/y.raw
 +zset
 
 // error, which is basically a status
-$ curl http://127.0.0.1:7379/MAKE-ME-COFFEE?format=raw
+$ curl http://127.0.0.1:7379/MAKE-ME-COFFEE.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:** +Several content-types are available: + * `.json` for `application/json` (this is the default Content-Type). + * `.txt` for `text/plain` + * `.html` for `text/html` + * `.png` for `image/png`
-curl -v "http://127.0.0.1:7379/GET/hello.html?type=text/html"
+curl -v "http://127.0.0.1:7379/GET/hello.html"	# the key is “hello” here, not “hello.html”
 [...]
 < HTTP/1.1 200 OK
 < Content-Type: text/html
@@ -168,24 +168,3 @@ curl -v "http://127.0.0.1:7379/GET/hello.html?type=text/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>
-
From 6e3c4240ee5e79f9302f884baa83b165b4429e53 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Tue, 4 Jan 2011 21:54:43 +0100 Subject: [PATCH 05/12] Cleanup. --- cmd.c | 21 ++++++++++++++------- cmd.h | 9 +-------- formats/custom-type.c | 36 ++++++------------------------------ formats/raw.c | 2 +- 4 files changed, 22 insertions(+), 46 deletions(-) diff --git a/cmd.c b/cmd.c index f769dbf..24199a0 100644 --- a/cmd.c +++ b/cmd.c @@ -112,7 +112,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, evhttp_parse_query(uri, &cmd->uri_params); /* get output formatting function */ - uri_len = cmd_read_params(cmd, uri, uri_len, &f_format); + uri_len = cmd_select_format(cmd, uri, uri_len, &f_format); /* check if we only have one command or more. */ slash = memchr(uri, '/', uri_len); @@ -135,9 +135,9 @@ cmd_run(struct server *s, struct evhttp_request *rq, /* check if we have to split the connection */ if(cmd_is_subscribe(cmd)) { struct pubsub_client *ps; + ps = calloc(1, sizeof(struct pubsub_client)); ps->s = s = server_copy(s); - ps->rq = rq; evhttp_connection_set_closecb(rq->evcon, on_http_disconnect, ps); @@ -177,17 +177,24 @@ 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. + * Select Content-Type and processing function. */ int -cmd_read_params(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun *f_format) { +cmd_select_format(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun *f_format) { const char *ext; int ext_len = -1; unsigned int i; + /* those are the available reply formats */ + struct reply_format { + const char *s; + size_t sz; + formatting_fun f; + const char *ct; + }; struct reply_format funs[] = { {.s = "json", .sz = 4, .f = json_reply, .ct = "application/json"}, {.s = "raw", .sz = 3, .f = raw_reply, .ct = "binary/octet-stream"}, @@ -196,7 +203,7 @@ cmd_read_params(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun {.s = "png", .sz = 3, .f = custom_type_reply, .ct = "image/png"}, }; - /* defaults */ + /* default */ *f_format = json_reply; /* find extension */ @@ -207,7 +214,7 @@ cmd_read_params(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun break; } } - if(!ext_len) return uri_len; + if(!ext_len) return uri_len; /* nothing found */ /* find function for the given extension */ for(i = 0; i < sizeof(funs)/sizeof(funs[0]); ++i) { diff --git a/cmd.h b/cmd.h index cd94b2a..a477336 100644 --- a/cmd.h +++ b/cmd.h @@ -35,13 +35,6 @@ struct pubsub_client { struct evhttp_request *rq; }; -struct reply_format { - const char *s; - size_t sz; - formatting_fun f; - const char *ct; -}; - struct cmd * cmd_new(struct evhttp_request *rq, int count); @@ -53,7 +46,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, const char *uri, size_t uri_len); int -cmd_read_params(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun *f_format); +cmd_select_format(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun *f_format); int cmd_is_subscribe(struct cmd *cmd); diff --git a/formats/custom-type.c b/formats/custom-type.c index c378440..a58ba7f 100644 --- a/formats/custom-type.c +++ b/formats/custom-type.c @@ -6,59 +6,35 @@ #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) { redisReply *reply = r; struct cmd *cmd = privdata; - char *ct; (void)c; evhttp_clear_headers(&cmd->uri_params); - if (reply == NULL) { + if(reply == NULL) { evhttp_send_reply(cmd->rq, 404, "Not Found", NULL); return; } - if(cmd->mime) { /* use the given content-type */ + if(cmd->mime) { /* use the given content-type, but only for strings */ switch(reply->type) { - case REDIS_REPLY_NIL: + case REDIS_REPLY_NIL: /* or nil values */ format_send_reply(cmd, "", 0, cmd->mime); return; case REDIS_REPLY_STRING: format_send_reply(cmd, reply->str, reply->len, cmd->mime); return; - - default: - custom_400(cmd); - return; } } - /* we expect array(string, string) */ - if(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 { - ct = "binary/octet-stream"; - } - - /* send reply */ - format_send_reply(cmd, reply->element[0]->str, reply->element[0]->len, ct); - return; + /* couldn't make sense of what the client wanted. */ + evhttp_send_reply(cmd->rq, 400, "Bad request", NULL); + cmd_free(cmd); } diff --git a/formats/raw.c b/formats/raw.c index b6fdad7..33ce8a6 100644 --- a/formats/raw.c +++ b/formats/raw.c @@ -28,7 +28,7 @@ raw_reply(redisAsyncContext *c, void *r, void *privdata) { raw_out = raw_wrap(r, &sz); /* send reply */ - format_send_reply(cmd, raw_out, sz, cmd->mime?cmd->mime:"binary/octet-stream"); + format_send_reply(cmd, raw_out, sz, "binary/octet-stream"); /* cleanup */ free(raw_out); From 5e8fe94c3e60a4e3d16e999856966dd633557ecc Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Tue, 4 Jan 2011 22:35:04 +0100 Subject: [PATCH 06/12] Refactoring. --- README.markdown | 2 +- formats/custom-type.c | 2 -- formats/raw.c | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/README.markdown b/README.markdown index 30167c2..b9df252 100644 --- a/README.markdown +++ b/README.markdown @@ -36,8 +36,8 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Enrich config file: * 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. +* Send your ideas using the github tracker, on twitter [@yowgi](http://twitter.com/yowgi) or by mail to n.favrefelix@gmail.com. # HTTP error codes * Unknown HTTP verb: 405 Method Not Allowed diff --git a/formats/custom-type.c b/formats/custom-type.c index a58ba7f..b437ec1 100644 --- a/formats/custom-type.c +++ b/formats/custom-type.c @@ -13,8 +13,6 @@ custom_type_reply(redisAsyncContext *c, void *r, void *privdata) { struct cmd *cmd = privdata; (void)c; - evhttp_clear_headers(&cmd->uri_params); - if(reply == NULL) { evhttp_send_reply(cmd->rq, 404, "Not Found", NULL); return; diff --git a/formats/raw.c b/formats/raw.c index 33ce8a6..1a90318 100644 --- a/formats/raw.c +++ b/formats/raw.c @@ -18,8 +18,6 @@ raw_reply(redisAsyncContext *c, void *r, void *privdata) { size_t sz; (void)c; - evhttp_clear_headers(&cmd->uri_params); - if (reply == NULL) { evhttp_send_reply(cmd->rq, 404, "Not Found", NULL); return; From 95945578b89759cc96f8e6bb68f0cebc84202c05 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Wed, 5 Jan 2011 15:34:07 +0100 Subject: [PATCH 07/12] Ideas --- README.markdown | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index b9df252..964fa31 100644 --- a/README.markdown +++ b/README.markdown @@ -36,7 +36,9 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Enrich config file: * Provide timeout (this needs to be added to hiredis first.) * Multi-server support, using consistent hashing. -* Add WebSocket support, allow cross-origin XHR. +* Add WebSocket support (with which protocol?) +* Allow cross-origin XHR. +* Allow file upload with PUT? Saving a file in Redis using the `SET` command should be easy to do with cURL. * Send your ideas using the github tracker, on twitter [@yowgi](http://twitter.com/yowgi) or by mail to n.favrefelix@gmail.com. # HTTP error codes @@ -54,6 +56,8 @@ The URI `/COMMAND/arg0/arg1/.../argN` executes the command on Redis and returns * `GET /COMMAND/arg0/.../argN` * `POST /` with `COMMAND/arg0/.../argN` in the HTTP body. +Special characters: `/` and `.` have special meanings, `/` separates arguments and `.` adds a file extension to change the Content-Type. They can be replaced by `%2f` and `%2e`, respectively. + # ACL Access control is configured in `webdis.json`. Each configuration tries to match a client profile according to two criterias: @@ -66,6 +70,7 @@ Examples: { "disabled": ["DEBUG", "FLUSHDB", "FLUSHALL"], }, + { "http_basic_auth": "user:password", "disabled": ["DEBUG", "FLUSHDB", "FLUSHALL"], From 102f9fcd12230387396e91b7ef42b898b8afb398 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Wed, 5 Jan 2011 16:08:23 +0100 Subject: [PATCH 08/12] Started adding Accept support. --- cmd.c | 17 +++++++++++++++-- cmd.h | 3 ++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cmd.c b/cmd.c index 24199a0..fa42bc9 100644 --- a/cmd.c +++ b/cmd.c @@ -33,6 +33,8 @@ cmd_free(struct cmd *c) { free(c->argv); free(c->argv_len); + if(c->mime_free) free(c->mime); + free(c); } @@ -184,7 +186,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, int cmd_select_format(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun *f_format) { - const char *ext; + const char *ext, *accept_ct; int ext_len = -1; unsigned int i; @@ -206,6 +208,13 @@ cmd_select_format(struct cmd *cmd, const char *uri, size_t uri_len, formatting_f /* default */ *f_format = json_reply; + /* if there is an Accept header, use it */ + if((accept_ct = evhttp_find_header(cmd->rq->input_headers, "Accept"))) { + cmd->mime = strdup(accept_ct); + cmd->mime_free = 1; + // printf("Accept: [%s]\n", cmd->mime); + } + /* find extension */ for(ext = uri + uri_len - 1; ext != uri && *ext != '/'; --ext) { if(*ext == '.') { @@ -219,8 +228,12 @@ cmd_select_format(struct cmd *cmd, const char *uri, size_t uri_len, formatting_f /* find function for the given extension */ for(i = 0; i < sizeof(funs)/sizeof(funs[0]); ++i) { if(ext_len == (int)funs[i].sz && strncmp(ext, funs[i].s, ext_len) == 0) { + + if(cmd->mime_free) free(cmd->mime); + cmd->mime = (char*)funs[i].ct; + cmd->mime_free = 0; + *f_format = funs[i].f; - cmd->mime = funs[i].ct; } } return uri_len - ext_len - 1; diff --git a/cmd.h b/cmd.h index a477336..563f6cc 100644 --- a/cmd.h +++ b/cmd.h @@ -25,7 +25,8 @@ struct cmd { int started_responding; /* HTTP data */ - const char *mime; + char *mime; + int mime_free; char *if_none_match; }; From 71bc9e39f0e91a0381d5e3dc2ee1f1f05844561a Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Wed, 5 Jan 2011 18:14:15 +0100 Subject: [PATCH 09/12] Added a few content types, added support for ?type. --- README.markdown | 6 +++++- cmd.c | 29 ++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/README.markdown b/README.markdown index 964fa31..3f4cf6c 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. -* Custom Content-Type using a pre-defined file extension. +* Custom Content-Type using a pre-defined file extension, or with `?type=some/thing`. * URL-encoded parameters for binary data or slashes. For instance, `%2f` is decoded as `/` but not used as a command separator. # Ideas, TODO... @@ -157,7 +157,11 @@ Several content-types are available: * `.json` for `application/json` (this is the default Content-Type). * `.txt` for `text/plain` * `.html` for `text/html` + * `xhtml` for `application/xhtml+xml` + * `xml` for `text/xml` * `.png` for `image/png` + * `jpg` or `jpeg` for `image/jpeg` + * Any other with the `?type=anything/youwant` query string.
 curl -v "http://127.0.0.1:7379/GET/hello.html"	# the key is “hello” here, not “hello.html”
diff --git a/cmd.c b/cmd.c
index fa42bc9..596d4a4 100644
--- a/cmd.c
+++ b/cmd.c
@@ -172,7 +172,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 < cmd->count; ++i) {
+	for(i = 1; i < cur_param; ++i) {
 		free((char*)cmd->argv[i]);
 	}
 
@@ -186,7 +186,8 @@ cmd_run(struct server *s, struct evhttp_request *rq,
 int
 cmd_select_format(struct cmd *cmd, const char *uri, size_t uri_len, formatting_fun *f_format) {
 
-	const char *ext, *accept_ct;
+	struct evkeyval *kv;
+	const char *ext;
 	int ext_len = -1;
 	unsigned int i;
 
@@ -202,19 +203,17 @@ cmd_select_format(struct cmd *cmd, const char *uri, size_t uri_len, formatting_f
 		{.s = "raw", .sz = 3, .f = raw_reply, .ct = "binary/octet-stream"},
 		{.s = "txt", .sz = 3, .f = custom_type_reply, .ct = "text/plain"},
 		{.s = "html", .sz = 4, .f = custom_type_reply, .ct = "text/html"},
+		{.s = "xhtml", .sz = 5, .f = custom_type_reply, .ct = "application/xhtml+xml"},
+		{.s = "xml", .sz = 3, .f = custom_type_reply, .ct = "text/xml"},
+
 		{.s = "png", .sz = 3, .f = custom_type_reply, .ct = "image/png"},
+		{.s = "jpg", .sz = 3, .f = custom_type_reply, .ct = "image/jpeg"},
+		{.s = "jpeg", .sz = 4, .f = custom_type_reply, .ct = "image/jpeg"},
 	};
 
 	/* default */
 	*f_format = json_reply;
 
-	/* if there is an Accept header, use it */
-	if((accept_ct = evhttp_find_header(cmd->rq->input_headers, "Accept"))) {
-		cmd->mime = strdup(accept_ct);
-		cmd->mime_free = 1;
-		// printf("Accept: [%s]\n", cmd->mime);
-	}
-
 	/* find extension */
 	for(ext = uri + uri_len - 1; ext != uri && *ext != '/'; --ext) {
 		if(*ext == '.') {
@@ -236,6 +235,18 @@ cmd_select_format(struct cmd *cmd, const char *uri, size_t uri_len, formatting_f
 			*f_format = funs[i].f;
 		}
 	}
+
+	/* the user can force it with ?type=some/thing */
+	TAILQ_FOREACH(kv, &cmd->uri_params, next) {
+		if(strcmp(kv->key, "type") == 0) {
+
+			*f_format = custom_type_reply;
+			cmd->mime = strdup(kv->value);
+			cmd->mime_free = 1;
+
+			break;
+		}
+	}
 	return uri_len - ext_len - 1;
 }
 

From 62903789417fd8508abd18b2eb34dc2317a2ad54 Mon Sep 17 00:00:00 2001
From: Nicolas Favre-Felix 
Date: Wed, 5 Jan 2011 18:22:49 +0100
Subject: [PATCH 10/12] More doc, and markdown fixes.

---
 README.markdown | 45 ++++++++++++++++++++++++++++++---------------
 1 file changed, 30 insertions(+), 15 deletions(-)

diff --git a/README.markdown b/README.markdown
index 3f4cf6c..15acce5 100644
--- a/README.markdown
+++ b/README.markdown
@@ -51,12 +51,14 @@ curl -d "GET/hello" http://127.0.0.1:7379/
 	* Unauthorized command (disabled in config file): 403 Forbidden.
 
 # Command format
-The URI `/COMMAND/arg0/arg1/.../argN` executes the command on Redis and returns the response to the client. GET and POST are supported:
+The URI `/COMMAND/arg0/arg1/.../argN.ext` executes the command on Redis and returns the response to the client. GET and POST are supported:
 
-* `GET /COMMAND/arg0/.../argN`
+* `GET /COMMAND/arg0/.../argN.ext`
 * `POST /` with `COMMAND/arg0/.../argN` in the HTTP body.
 
-Special characters: `/` and `.` have special meanings, `/` separates arguments and `.` adds a file extension to change the Content-Type. They can be replaced by `%2f` and `%2e`, respectively.
+`.ext` is an optional extension; it is not read as part of the last argument but only represents the output format. Several formats are available (see below).
+
+Special characters: `/` and `.` have special meanings, `/` separates arguments and `.` changes the Content-Type. They can be replaced by `%2f` and `%2e`, respectively.
 
 # ACL
 Access control is configured in `webdis.json`. Each configuration tries to match a client profile according to two criterias:
@@ -118,7 +120,6 @@ $ curl http://127.0.0.1:7379/MAKE-ME-COFFEE
 // JSONP callback:
 $ curl  "http://127.0.0.1:7379/TYPE/y?jsonp=myCustomFunction"
 myCustomFunction({"TYPE":[true,"string"]})
-
 
# RAW output @@ -149,22 +150,21 @@ $ curl http://127.0.0.1:7379/TYPE/y.raw // error, which is basically a status $ curl http://127.0.0.1:7379/MAKE-ME-COFFEE.raw -ERR unknown command 'MAKE-ME-COFFEE' - # Custom content-type Several content-types are available: - * `.json` for `application/json` (this is the default Content-Type). - * `.txt` for `text/plain` - * `.html` for `text/html` - * `xhtml` for `application/xhtml+xml` - * `xml` for `text/xml` - * `.png` for `image/png` - * `jpg` or `jpeg` for `image/jpeg` - * Any other with the `?type=anything/youwant` query string. +* `.json` for `application/json` (this is the default Content-Type). +* `.txt` for `text/plain` +* `.html` for `text/html` +* `xhtml` for `application/xhtml+xml` +* `xml` for `text/xml` +* `.png` for `image/png` +* `jpg` or `jpeg` for `image/jpeg` +* Any other with the `?type=anything/youwant` query string.
-curl -v "http://127.0.0.1:7379/GET/hello.html"	# the key is “hello” here, not “hello.html”
+curl -v "http://127.0.0.1:7379/GET/hello.html"
 [...]
 < HTTP/1.1 200 OK
 < Content-Type: text/html
@@ -173,7 +173,22 @@ curl -v "http://127.0.0.1:7379/GET/hello.html"	# the key is “hello” here, no
 <
 <!DOCTYPE html>
 <html>
-...
+[...]
 </html>
+
+curl -v "http://127.0.0.1:7379/GET/hello.txt"
+[...]
+< HTTP/1.1 200 OK
+< Content-Type: text/plain
+< Date: Mon, 03 Jan 2011 20:43:36 GMT
+< Content-Length: 137
+[...]
+
+curl -v "http://127.0.0.1:7379/GET/big-file?type=application/pdf"
+[...]
+< HTTP/1.1 200 OK
+< Content-Type: application/pdf
+< Date: Mon, 03 Jan 2011 20:45:12 GMT
+[...]
 
From 20d2aa8dd9cf27758028c94e40e386c903d2c05e Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Wed, 5 Jan 2011 18:23:26 +0100 Subject: [PATCH 11/12] Typo --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 15acce5..5cb43a0 100644 --- a/README.markdown +++ b/README.markdown @@ -136,7 +136,7 @@ curl http://127.0.0.1:7379/INCR/a.raw :2 // list -$ curl http://127.0.0.1:7379/LRANGE/x/0/-1?.raw +$ curl http://127.0.0.1:7379/LRANGE/x/0/-1.raw *2 $3 abc From 910e741fe798e72e4f9967e2c692e0d0d8fcb1f0 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Wed, 5 Jan 2011 18:24:03 +0100 Subject: [PATCH 12/12] Mardown fixes --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 5cb43a0..8e325be 100644 --- a/README.markdown +++ b/README.markdown @@ -154,6 +154,7 @@ $ curl http://127.0.0.1:7379/MAKE-ME-COFFEE.raw # Custom content-type Several content-types are available: + * `.json` for `application/json` (this is the default Content-Type). * `.txt` for `text/plain` * `.html` for `text/html`