From 983300035301ea17201b6cdbabdb4941e5e0c3de Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 12:05:18 +0100 Subject: [PATCH 01/19] Switched conf file to JSON. --- conf.c | 70 ++++++++++++++++++++++------------------------------- turnip.c | 2 +- turnip.json | 11 +++++++++ 3 files changed, 41 insertions(+), 42 deletions(-) create mode 100644 turnip.json diff --git a/conf.c b/conf.c index 2c0c6c8..3bb26d8 100644 --- a/conf.c +++ b/conf.c @@ -3,65 +3,53 @@ #include #include +#include #include "conf.h" -static char * -skipspaces(char *p) { - - while(isspace(*p)) p++; - - return p; -} - struct conf * conf_read(const char *filename) { + json_t *j, *jtmp; + json_error_t error; struct conf *conf; - FILE *f = fopen(filename, "r"); - - if(!f) { - return NULL; - } - + /* defaults */ conf = calloc(1, sizeof(struct conf)); + conf->redis_host = strdup("127.0.0.1"); conf->redis_port = 6379; + conf->http_host = strdup("0.0.0.0"); conf->http_port = 7379; - while(!feof(f)) { - char buffer[100], *ret; - memset(buffer, 0, sizeof(buffer)); - if(!(ret = fgets(buffer, sizeof(buffer)-1, f))) { - break; - } - if(*ret == '#') { /* comments */ - continue; - } + j = json_load_file(filename, 0, &error); + if(!j) { + fprintf(stderr, "Error: %s (line %d)\n", error.text, error.line); + return conf; + } - if(*ret != 0) { - ret[strlen(ret)-1] = 0; /* remove new line */ - } + jtmp = json_object_get(j, "redis_host"); + if(jtmp && json_typeof(jtmp) == JSON_STRING) { + free(conf->redis_host); + conf->redis_host = strdup(json_string_value(jtmp)); + } - if(strncmp(ret, "redis_host", 10) == 0) { - conf->redis_host = strdup(skipspaces(ret + 11)); - } else if(strncmp(ret, "redis_port", 10) == 0) { - conf->redis_port = (short)atoi(skipspaces(ret + 10)); - } else if(strncmp(ret, "http_host", 10) == 0) { - conf->http_host = strdup(skipspaces(ret + 11)); - } else if(strncmp(ret, "http_port", 9) == 0) { - conf->http_port = (short)atoi(skipspaces(ret + 10)); - } + jtmp = json_object_get(j, "redis_port"); + if(jtmp && json_typeof(jtmp) == JSON_INTEGER) { + conf->redis_port = json_integer_value(jtmp); } - fclose(f); - /* default values */ - if(!conf->redis_host) { - conf->redis_host = strdup("127.0.0.1"); + jtmp = json_object_get(j, "http_host"); + if(jtmp && json_typeof(jtmp) == JSON_STRING) { + free(conf->http_host); + conf->http_host = strdup(json_string_value(jtmp)); } - if(!conf->http_host) { - conf->http_host = strdup("0.0.0.0"); + + jtmp = json_object_get(j, "http_port"); + if(jtmp && json_typeof(jtmp) == JSON_INTEGER) { + conf->http_port = json_integer_value(jtmp); } + json_decref(j); + return conf; } diff --git a/turnip.c b/turnip.c index 55c6f12..adf967a 100644 --- a/turnip.c +++ b/turnip.c @@ -50,7 +50,7 @@ main(int argc, char *argv[]) { s->base = event_base_new(); struct evhttp *http = evhttp_new(s->base); - s->cfg = conf_read("turnip.conf"); + s->cfg = conf_read("turnip.json"); /* ignore sigpipe */ #ifdef SIGPIPE diff --git a/turnip.json b/turnip.json new file mode 100644 index 0000000..1227414 --- /dev/null +++ b/turnip.json @@ -0,0 +1,11 @@ +{ + "redis_host": "127.0.0.1", + "redis_port": 6379, + + "http_host": "0.0.0.0", + "http_port": 7379, + + "disable": { + "255.255.255.255/32": ["DEBUG", "FLUSHDB", "FLUSHALL"] + } +} From c7f855ff393c63756581f133142e33475381f8cd Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 12:17:35 +0100 Subject: [PATCH 02/19] Refactoring in JSON conf loader. --- conf.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/conf.c b/conf.c index 3bb26d8..6c7a79a 100644 --- a/conf.c +++ b/conf.c @@ -12,6 +12,7 @@ conf_read(const char *filename) { json_t *j, *jtmp; json_error_t error; struct conf *conf; + void *kv; /* defaults */ conf = calloc(1, sizeof(struct conf)); @@ -26,26 +27,21 @@ conf_read(const char *filename) { return conf; } - jtmp = json_object_get(j, "redis_host"); - if(jtmp && json_typeof(jtmp) == JSON_STRING) { - free(conf->redis_host); - conf->redis_host = strdup(json_string_value(jtmp)); - } - - jtmp = json_object_get(j, "redis_port"); - if(jtmp && json_typeof(jtmp) == JSON_INTEGER) { - conf->redis_port = json_integer_value(jtmp); - } - - jtmp = json_object_get(j, "http_host"); - if(jtmp && json_typeof(jtmp) == JSON_STRING) { - free(conf->http_host); - conf->http_host = strdup(json_string_value(jtmp)); - } - - jtmp = json_object_get(j, "http_port"); - if(jtmp && json_typeof(jtmp) == JSON_INTEGER) { - conf->http_port = json_integer_value(jtmp); + for(kv = json_object_iter(j); kv; kv = json_object_iter_next(j, kv)) { + jtmp = json_object_iter_value(kv); + if(strcmp(json_object_iter_key(kv), "redis_host") == 0 && json_typeof(jtmp) == JSON_STRING) { + free(conf->redis_host); + conf->redis_host = strdup(json_string_value(jtmp)); + } else if(strcmp(json_object_iter_key(kv), "redis_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) { + conf->redis_port = json_integer_value(jtmp); + } else if(strcmp(json_object_iter_key(kv), "http_host") == 0 && json_typeof(jtmp) == JSON_STRING) { + free(conf->http_host); + conf->http_host = strdup(json_string_value(jtmp)); + } else if(strcmp(json_object_iter_key(kv), "http_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) { + conf->http_port = json_integer_value(jtmp); + } else if(strcmp(json_object_iter_key(kv), "disable") == 0 && json_typeof(jtmp) == JSON_OBJECT) { + /* TODO */ + } } json_decref(j); From 1209922c0311123cbf8d4c4125d58f0e1b8cd793 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 14:09:56 +0100 Subject: [PATCH 03/19] Parsed and stored disabled commands. --- conf.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- conf.h | 14 ++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index 6c7a79a..dfaabfe 100644 --- a/conf.c +++ b/conf.c @@ -2,14 +2,18 @@ #include #include #include +#include #include #include "conf.h" +static struct disabled_command * +conf_disable_commands(json_t *jtab); + struct conf * conf_read(const char *filename) { - json_t *j, *jtmp; + json_t *j; json_error_t error; struct conf *conf; void *kv; @@ -28,7 +32,8 @@ conf_read(const char *filename) { } for(kv = json_object_iter(j); kv; kv = json_object_iter_next(j, kv)) { - jtmp = json_object_iter_value(kv); + json_t *jtmp = json_object_iter_value(kv); + if(strcmp(json_object_iter_key(kv), "redis_host") == 0 && json_typeof(jtmp) == JSON_STRING) { free(conf->redis_host); conf->redis_host = strdup(json_string_value(jtmp)); @@ -40,7 +45,7 @@ conf_read(const char *filename) { } else if(strcmp(json_object_iter_key(kv), "http_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) { conf->http_port = json_integer_value(jtmp); } else if(strcmp(json_object_iter_key(kv), "disable") == 0 && json_typeof(jtmp) == JSON_OBJECT) { - /* TODO */ + conf->disabled = conf_disable_commands(jtmp); } } @@ -49,6 +54,75 @@ conf_read(const char *filename) { return conf; } + +struct disabled_command * +conf_disable_commands(json_t *jtab) { + + struct disabled_command *root = NULL; + + void *kv; + for(kv = json_object_iter(jtab); kv; kv = json_object_iter_next(jtab, kv)) { + + unsigned int i, cur, n; + char *p, *ip; + const char *s; + in_addr_t mask_ip; + short mask_bits = 0; + + struct disabled_command *dc; + json_t *val = json_object_iter_value(kv); + + if(json_typeof(val) != JSON_ARRAY) { + continue; /* TODO: report error? */ + } + + /* parse key in format "ip/mask" */ + s = json_object_iter_key(kv); + p = strchr(s, ':'); + if(!p) { + ip = strdup(s); + } else { + ip = calloc(p - s + 1, 1); + memcpy(ip, s, p - s); + mask_bits = atoi(p+1); + } + mask_ip = inet_addr(ip); + + + /* count strings in the array */ + n = 0; + for(i = 0; i < json_array_size(val); ++i) { + json_t *jelem = json_array_get(val, i); + if(json_typeof(jelem) == JSON_STRING) { + n++; + } + } + + /* allocate block */ + dc = calloc(1, sizeof(struct disabled_command)); + dc->commands = calloc(n, sizeof(char*)); + dc->mask_ip = mask_ip; + dc->mask_bits = mask_bits; + dc->next = root; + root = dc; + + /* add all disabled commands */ + for(i = 0, cur = 0; i < json_array_size(val); ++i) { + json_t *jelem = json_array_get(val, i); + if(json_typeof(jelem) == JSON_STRING) { + s = json_string_value(jelem); + size_t sz = strlen(s); + + dc->commands[cur] = calloc(1 + sz, 1); + memcpy(dc->commands[cur], s, sz); + cur++; + } + } + } + + return root; +} + void conf_free(struct conf *conf) { diff --git a/conf.h b/conf.h index e914b09..b38c6c1 100644 --- a/conf.h +++ b/conf.h @@ -1,6 +1,18 @@ #ifndef CONF_H #define CONF_H +#include +#include + +struct disabled_command { + + in_addr_t mask_ip; + short mask_bits; + + char **commands; + + struct disabled_command *next; +}; struct conf { @@ -9,6 +21,8 @@ struct conf { char *http_host; short http_port; + + struct disabled_command *disabled; }; struct conf * From de5c2839e4d12856fff99e390483caf20d4233b7 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 15:43:32 +0100 Subject: [PATCH 04/19] Added IP range restriction. --- README.markdown | 4 ++-- cmd.c | 45 +++++++++++++++++++++++++++++++++++++++++++-- cmd.h | 2 +- conf.c | 13 +++++++------ conf.h | 5 +++-- turnip.c | 11 +++++++++-- turnip.conf | 5 ----- turnip.json | 2 +- 8 files changed, 66 insertions(+), 21 deletions(-) delete mode 100644 turnip.conf diff --git a/README.markdown b/README.markdown index d523137..68b8e4c 100644 --- a/README.markdown +++ b/README.markdown @@ -21,6 +21,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Raw Redis 2.0 protocol output with `?format=raw` * HTTP 1.1 pipelining (45 kqps on a desktop Linux machine.) * Connects to Redis using a TCP or UNIX socket. +* Restricted commands by IP range (CIDR subnet + mask), returning 403 errors. # Ideas, TODO... * Add meta-data info per key (MIME type in a second key, for instance). @@ -30,9 +31,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Add logging. * Enrich config file: * Provide timeout (this needs to be added to hiredis first.) - * Restrict commands by IP range * Get config file path from command line. -* Change config file to JSON format? That would be convenient. * Send your ideas using the github tracker or on twitter [@yowgi](http://twitter.com/yowgi). # HTTP error codes @@ -41,6 +40,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Could also be used: * Timeout on the redis side: 503 Service Unavailable * Missing key: 404 Not Found + * 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: diff --git a/cmd.c b/cmd.c index 0c3053a..b9e09b0 100644 --- a/cmd.c +++ b/cmd.c @@ -1,5 +1,6 @@ #include "cmd.h" #include "server.h" +#include "conf.h" #include "formats/json.h" #include "formats/raw.h" @@ -7,6 +8,8 @@ #include #include #include +#include +#include struct cmd * cmd_new(struct evhttp_request *rq, int count) { @@ -32,7 +35,39 @@ cmd_free(struct cmd *c) { free(c); } -void +int +cmd_authorized(struct conf *cfg, struct evhttp_request *rq, const char *verb, size_t verb_len) { + + struct disabled_command *dc; + unsigned int i; + + char *client_ip; + u_short client_port; + in_addr_t client_addr; + + /* find client's address */ + evhttp_connection_get_peer(rq->evcon, &client_ip, &client_port); + client_addr = ntohl(inet_addr(client_ip)); + + for(dc = cfg->disabled; dc; dc = dc->next) { + /* CIDR test */ + + if((client_addr & dc->mask) != (dc->subnet & dc->mask)) { + continue; + } + + /* matched an ip */ + for(i = 0; i < dc->count; ++i) { + if(strncasecmp(dc->commands[i], verb, verb_len) == 0) { + return 0; + } + } + } + + return 1; +} + +int cmd_run(struct server *s, struct evhttp_request *rq, const char *uri, size_t uri_len) { @@ -71,9 +106,14 @@ cmd_run(struct server *s, struct evhttp_request *rq, cmd->argv[0] = uri; cmd->argv_len[0] = cmd_len; + /* check that the client is able to run this command */ + if(!cmd_authorized(s->cfg, rq, cmd->argv[0], cmd->argv_len[0])) { + return -1; + } + if(!slash) { redisAsyncCommandArgv(s->ac, fun, cmd, 1, cmd->argv, cmd->argv_len); - return; + return 0; } p = slash + 1; while(p < uri + uri_len) { @@ -97,6 +137,7 @@ cmd_run(struct server *s, struct evhttp_request *rq, } redisAsyncCommandArgv(s->ac, fun, cmd, param_count, cmd->argv, cmd->argv_len); + return 0; } diff --git a/cmd.h b/cmd.h index 74af65c..bc586fd 100644 --- a/cmd.h +++ b/cmd.h @@ -28,7 +28,7 @@ cmd_new(struct evhttp_request *rq, int count); void cmd_free(struct cmd *c); -void +int cmd_run(struct server *s, struct evhttp_request *rq, const char *uri, size_t uri_len); diff --git a/conf.c b/conf.c index dfaabfe..bd8dde8 100644 --- a/conf.c +++ b/conf.c @@ -66,7 +66,7 @@ conf_disable_commands(json_t *jtab) { unsigned int i, cur, n; char *p, *ip; const char *s; - in_addr_t mask_ip; + in_addr_t mask, subnet; short mask_bits = 0; struct disabled_command *dc; @@ -78,7 +78,7 @@ conf_disable_commands(json_t *jtab) { /* parse key in format "ip/mask" */ s = json_object_iter_key(kv); - p = strchr(s, ':'); + p = strchr(s, '/'); if(!p) { ip = strdup(s); } else { @@ -86,8 +86,8 @@ conf_disable_commands(json_t *jtab) { memcpy(ip, s, p - s); mask_bits = atoi(p+1); } - mask_ip = inet_addr(ip); - + mask = (mask_bits == 0 ? 0 : (0xffffffff << (32 - mask_bits))); + subnet = ntohl(inet_addr(ip)) & mask; /* count strings in the array */ n = 0; @@ -101,8 +101,9 @@ conf_disable_commands(json_t *jtab) { /* allocate block */ dc = calloc(1, sizeof(struct disabled_command)); dc->commands = calloc(n, sizeof(char*)); - dc->mask_ip = mask_ip; - dc->mask_bits = mask_bits; + dc->subnet = subnet; + dc->mask = mask; + dc->count = n; dc->next = root; root = dc; diff --git a/conf.h b/conf.h index b38c6c1..cde415f 100644 --- a/conf.h +++ b/conf.h @@ -6,9 +6,10 @@ struct disabled_command { - in_addr_t mask_ip; - short mask_bits; + in_addr_t subnet; + in_addr_t mask; + unsigned int count; char **commands; struct disabled_command *next; diff --git a/turnip.c b/turnip.c index adf967a..097c81c 100644 --- a/turnip.c +++ b/turnip.c @@ -18,19 +18,22 @@ on_request(struct evhttp_request *rq, void *ctx) { const char *uri = evhttp_request_uri(rq); struct server *s = ctx; + int ret; if(!s->ac) { /* redis is unavailable */ evhttp_send_reply(rq, 503, "Service Unavailable", NULL); return; } + /* check that the command can be executed */ + switch(rq->type) { case EVHTTP_REQ_GET: - cmd_run(s, rq, 1+uri, strlen(uri)-1); + ret = cmd_run(s, rq, 1+uri, strlen(uri)-1); break; case EVHTTP_REQ_POST: - cmd_run(s, rq, + ret = cmd_run(s, rq, (const char*)EVBUFFER_DATA(rq->input_buffer), EVBUFFER_LENGTH(rq->input_buffer)); break; @@ -39,6 +42,10 @@ on_request(struct evhttp_request *rq, void *ctx) { evhttp_send_reply(rq, 405, "Method Not Allowed", NULL); return; } + + if(ret < 0) { + evhttp_send_reply(rq, 403, "Forbidden", NULL); + } } int diff --git a/turnip.conf b/turnip.conf deleted file mode 100644 index b2d37cb..0000000 --- a/turnip.conf +++ /dev/null @@ -1,5 +0,0 @@ -redis_host 127.0.0.1 -redis_port 6379 - -http_host 0.0.0.0 -http_port 7379 diff --git a/turnip.json b/turnip.json index 1227414..5df0381 100644 --- a/turnip.json +++ b/turnip.json @@ -6,6 +6,6 @@ "http_port": 7379, "disable": { - "255.255.255.255/32": ["DEBUG", "FLUSHDB", "FLUSHALL"] + "0.0.0.0/0": ["DEBUG", "FLUSHDB", "FLUSHALL"] } } From fbcee78d8c65b23a77fb4a0428ea045c8cc10c92 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 15:47:11 +0100 Subject: [PATCH 05/19] Updated benchmark. --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 68b8e4c..1c14b04 100644 --- a/README.markdown +++ b/README.markdown @@ -19,7 +19,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * GET and POST are supported. * JSON output by default, optional JSONP parameter. * Raw Redis 2.0 protocol output with `?format=raw` -* HTTP 1.1 pipelining (45 kqps on a desktop Linux machine.) +* 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), returning 403 errors. From f33ecf711de75bef43491e576b356fefda2efe34 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 16:25:09 +0100 Subject: [PATCH 06/19] Disabled MULTI/EXEC/WATCH/DISCARD. --- README.markdown | 6 +++--- cmd.c | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index 1c14b04..35fb346 100644 --- a/README.markdown +++ b/README.markdown @@ -25,9 +25,9 @@ curl -d "GET/hello" http://127.0.0.1:7379/ # Ideas, TODO... * Add meta-data info per key (MIME type in a second key, for instance). -* Support PUT, DELETE, HEAD? -* Support pub/sub. -* Disable MULTI/EXEC/DISCARD/WATCH. +* Support PUT, DELETE, HEAD? How? For which commands? +* Support pub/sub (waiting for HiRedis ticket \#17 in order to add this.) +* MULTI/EXEC/DISCARD/WATCH are disabled at the moment; find a way to use them. * Add logging. * Enrich config file: * Provide timeout (this needs to be added to hiredis first.) diff --git a/cmd.c b/cmd.c index b9e09b0..a32944a 100644 --- a/cmd.c +++ b/cmd.c @@ -38,6 +38,8 @@ cmd_free(struct cmd *c) { int cmd_authorized(struct conf *cfg, struct evhttp_request *rq, const char *verb, size_t verb_len) { + char *always_off[] = {"MULTI", "EXEC", "WATCH", "DISCARD", "SUBSCRIBE", "PSUBSCRIBE"}; + struct disabled_command *dc; unsigned int i; @@ -45,6 +47,14 @@ cmd_authorized(struct conf *cfg, struct evhttp_request *rq, const char *verb, si u_short client_port; in_addr_t client_addr; + /* some commands are always disabled, regardless of the config file. */ + for(i = 0; i < sizeof(always_off) / sizeof(always_off[0]); ++i) { + if(strncasecmp(always_off[i], verb, verb_len) == 0) { + return 0; + } + } + + /* find client's address */ evhttp_connection_get_peer(rq->evcon, &client_ip, &client_port); client_addr = ntohl(inet_addr(client_ip)); From 760a61822b92047503f31e13de85467e45b36afe Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 16:58:46 +0100 Subject: [PATCH 07/19] Conf file from CLI, and more ideas. --- README.markdown | 4 ++-- turnip.c | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.markdown b/README.markdown index 35fb346..300d755 100644 --- a/README.markdown +++ b/README.markdown @@ -25,13 +25,13 @@ curl -d "GET/hello" http://127.0.0.1:7379/ # Ideas, TODO... * Add meta-data info per key (MIME type in a second key, for instance). -* Support PUT, DELETE, HEAD? How? For which commands? +* Support PUT, DELETE, HEAD, OPTIONS? How? For which commands? * Support pub/sub (waiting for HiRedis ticket \#17 in order to add this.) * MULTI/EXEC/DISCARD/WATCH are disabled at the moment; find a way to use them. * Add logging. * Enrich config file: * Provide timeout (this needs to be added to hiredis first.) -* Get config file path from command line. +* Multi-server support, using consistent hashing. * Send your ideas using the github tracker or on twitter [@yowgi](http://twitter.com/yowgi). # HTTP error codes diff --git a/turnip.c b/turnip.c index 097c81c..56e9842 100644 --- a/turnip.c +++ b/turnip.c @@ -50,14 +50,16 @@ on_request(struct evhttp_request *rq, void *ctx) { int main(int argc, char *argv[]) { - (void)argc; - (void)argv; struct server *s = calloc(1, sizeof(struct server)); s->base = event_base_new(); struct evhttp *http = evhttp_new(s->base); - - s->cfg = conf_read("turnip.json"); + + if(argc > 1) { + s->cfg = conf_read(argv[1]); + } else { + s->cfg = conf_read("turnip.json"); + } /* ignore sigpipe */ #ifdef SIGPIPE From 1cace807a1e4c96f93d184a138bfe1051f4ecdc9 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 17:21:07 +0100 Subject: [PATCH 08/19] Lint. --- conf.c | 19 ++++++++++--------- conf.h | 1 - 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/conf.c b/conf.c index bd8dde8..919b0ed 100644 --- a/conf.c +++ b/conf.c @@ -38,12 +38,12 @@ conf_read(const char *filename) { free(conf->redis_host); conf->redis_host = strdup(json_string_value(jtmp)); } else if(strcmp(json_object_iter_key(kv), "redis_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) { - conf->redis_port = json_integer_value(jtmp); + conf->redis_port = (short)json_integer_value(jtmp); } else if(strcmp(json_object_iter_key(kv), "http_host") == 0 && json_typeof(jtmp) == JSON_STRING) { free(conf->http_host); conf->http_host = strdup(json_string_value(jtmp)); } else if(strcmp(json_object_iter_key(kv), "http_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) { - conf->http_port = json_integer_value(jtmp); + conf->http_port = (short)json_integer_value(jtmp); } else if(strcmp(json_object_iter_key(kv), "disable") == 0 && json_typeof(jtmp) == JSON_OBJECT) { conf->disabled = conf_disable_commands(jtmp); } @@ -67,7 +67,7 @@ conf_disable_commands(json_t *jtab) { char *p, *ip; const char *s; in_addr_t mask, subnet; - short mask_bits = 0; + unsigned short mask_bits = 0; struct disabled_command *dc; json_t *val = json_object_iter_value(kv); @@ -82,9 +82,9 @@ conf_disable_commands(json_t *jtab) { if(!p) { ip = strdup(s); } else { - ip = calloc(p - s + 1, 1); - memcpy(ip, s, p - s); - mask_bits = atoi(p+1); + ip = calloc((size_t)(p - s + 1), 1); + memcpy(ip, s, (size_t)(p - s)); + mask_bits = (unsigned short)atoi(p+1); } mask = (mask_bits == 0 ? 0 : (0xffffffff << (32 - mask_bits))); subnet = ntohl(inet_addr(ip)) & mask; @@ -92,7 +92,7 @@ conf_disable_commands(json_t *jtab) { /* count strings in the array */ n = 0; for(i = 0; i < json_array_size(val); ++i) { - json_t *jelem = json_array_get(val, i); + json_t *jelem = json_array_get(val, (size_t)i); if(json_typeof(jelem) == JSON_STRING) { n++; } @@ -100,7 +100,7 @@ conf_disable_commands(json_t *jtab) { /* allocate block */ dc = calloc(1, sizeof(struct disabled_command)); - dc->commands = calloc(n, sizeof(char*)); + dc->commands = calloc((size_t)n, sizeof(char*)); dc->subnet = subnet; dc->mask = mask; dc->count = n; @@ -111,8 +111,9 @@ conf_disable_commands(json_t *jtab) { for(i = 0, cur = 0; i < json_array_size(val); ++i) { json_t *jelem = json_array_get(val, i); if(json_typeof(jelem) == JSON_STRING) { + size_t sz; s = json_string_value(jelem); - size_t sz = strlen(s); + sz = strlen(s); dc->commands[cur] = calloc(1 + sz, 1); memcpy(dc->commands[cur], s, sz); diff --git a/conf.h b/conf.h index cde415f..7da8340 100644 --- a/conf.h +++ b/conf.h @@ -1,7 +1,6 @@ #ifndef CONF_H #define CONF_H -#include #include struct disabled_command { From 63394166b595177244f7d7055cf0d8b2f48eee50 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 17:32:21 +0100 Subject: [PATCH 09/19] Fixed leak. --- formats/json.c | 1 + formats/raw.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/formats/json.c b/formats/json.c index 90ff00b..1ea25a3 100644 --- a/formats/json.c +++ b/formats/json.c @@ -42,6 +42,7 @@ json_reply(redisAsyncContext *c, void *r, void *privdata) { evbuffer_free(body); json_decref(j); freeReplyObject(r); + evhttp_clear_headers(&cmd->uri_params); cmd_free(cmd); free(json_reply); } diff --git a/formats/raw.c b/formats/raw.c index 37b9379..7fe2056 100644 --- a/formats/raw.c +++ b/formats/raw.c @@ -20,6 +20,8 @@ raw_reply(redisAsyncContext *c, void *r, void *privdata) { char *raw_out; size_t sz; + evhttp_clear_headers(&cmd->uri_params); + if (reply == NULL) { evhttp_send_reply(cmd->rq, 404, "Not Found", NULL); return; @@ -124,7 +126,7 @@ raw_wrap(const redisReply *r, size_t *sz) { *sz = 1 + integer_length(r->len) + 1 + r->len + 1; p = ret = malloc(*sz); p += sprintf(p, "$%d\n", r->len); - memcpy(p, r->str, *sz - 1); + memcpy(p, r->str, *sz - 1 - (p-ret)); memcpy(ret + *sz - 1, "\n", 1); return ret; From f4e28936b52758ae7c9cd7e1a1c29945bb2148f3 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Mon, 27 Dec 2010 18:31:21 +0100 Subject: [PATCH 10/19] Added support for Redis AUTH command. --- README.markdown | 1 + conf.c | 4 ++++ conf.h | 1 + formats/raw.c | 2 +- server.c | 5 ++++- turnip.json | 1 + 6 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 300d755..3622cda 100644 --- a/README.markdown +++ b/README.markdown @@ -22,6 +22,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * 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), returning 403 errors. +* Possible Redis authentication in the config file. # Ideas, TODO... * Add meta-data info per key (MIME type in a second key, for instance). diff --git a/conf.c b/conf.c index 919b0ed..cb9cfef 100644 --- a/conf.c +++ b/conf.c @@ -39,6 +39,8 @@ conf_read(const char *filename) { conf->redis_host = strdup(json_string_value(jtmp)); } else if(strcmp(json_object_iter_key(kv), "redis_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) { conf->redis_port = (short)json_integer_value(jtmp); + } else if(strcmp(json_object_iter_key(kv), "redis_auth") == 0 && json_typeof(jtmp) == JSON_STRING) { + conf->redis_auth = strdup(json_string_value(jtmp)); } else if(strcmp(json_object_iter_key(kv), "http_host") == 0 && json_typeof(jtmp) == JSON_STRING) { free(conf->http_host); conf->http_host = strdup(json_string_value(jtmp)); @@ -129,6 +131,8 @@ void conf_free(struct conf *conf) { free(conf->redis_host); + free(conf->redis_auth); + free(conf->http_host); free(conf); diff --git a/conf.h b/conf.h index 7da8340..917d8a2 100644 --- a/conf.h +++ b/conf.h @@ -18,6 +18,7 @@ struct conf { char *redis_host; short redis_port; + char *redis_auth; char *http_host; short http_port; diff --git a/formats/raw.c b/formats/raw.c index 7fe2056..8bd0914 100644 --- a/formats/raw.c +++ b/formats/raw.c @@ -13,12 +13,12 @@ raw_wrap(const redisReply *r, size_t *sz); void raw_reply(redisAsyncContext *c, void *r, void *privdata) { - (void)c; struct evbuffer *body; redisReply *reply = r; struct cmd *cmd = privdata; char *raw_out; size_t sz; + (void)c; evhttp_clear_headers(&cmd->uri_params); diff --git a/server.c b/server.c index 6d4e90d..e19aa6b 100644 --- a/server.c +++ b/server.c @@ -50,10 +50,13 @@ on_timer_reconnect(int fd, short event, void *ctx) { printf("Error: %s\n", s->ac->errstr); } - redisLibeventAttach(s->ac, s->base); redisAsyncSetConnectCallback(s->ac, connectCallback); redisAsyncSetDisconnectCallback(s->ac, disconnectCallback); + + if (s->cfg->redis_auth) { /* authenticate. */ + redisAsyncCommand(s->ac, NULL, NULL, "AUTH %s", s->cfg->redis_auth); + } } void diff --git a/turnip.json b/turnip.json index 5df0381..3174b4b 100644 --- a/turnip.json +++ b/turnip.json @@ -1,6 +1,7 @@ { "redis_host": "127.0.0.1", "redis_port": 6379, + "redis_auth": null, "http_host": "0.0.0.0", "http_port": 7379, From 1ea4a62f3db192dd04b7c61318d9673c51be8b10 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Tue, 28 Dec 2010 17:24:15 +0100 Subject: [PATCH 11/19] Ideas. --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 3622cda..02924a2 100644 --- a/README.markdown +++ b/README.markdown @@ -29,6 +29,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Support PUT, DELETE, HEAD, OPTIONS? How? For which commands? * Support pub/sub (waiting for HiRedis ticket \#17 in order to add this.) * MULTI/EXEC/DISCARD/WATCH are disabled at the moment; find a way to use them. +* Drop privileges. * Add logging. * Enrich config file: * Provide timeout (this needs to be added to hiredis first.) From f25e7bfb8711a8182dd58f63fefb2ab00cb823e2 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Tue, 28 Dec 2010 17:43:26 +0100 Subject: [PATCH 12/19] Rename --- Makefile | 4 ++-- README.markdown | 2 +- turnip.c => dishy.c | 4 ++-- turnip.json => dishy.json | 0 server.c | 4 ++-- server.h | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) rename turnip.c => dishy.c (96%) rename turnip.json => dishy.json (100%) diff --git a/Makefile b/Makefile index 9703e30..2c9b055 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -OUT=turnip +OUT=dishy HIREDIS_OBJ=hiredis/hiredis.o hiredis/sds.o hiredis/net.o hiredis/async.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 -OBJS=turnip.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) +OBJS=dishy.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) CFLAGS=-O3 -Wall -Wextra -I. -Ijansson/src LDFLAGS=-levent diff --git a/README.markdown b/README.markdown index 02924a2..b7606ce 100644 --- a/README.markdown +++ b/README.markdown @@ -4,7 +4,7 @@ A very simple prototype providing an HTTP interface to Redis. It uses [hiredis](
 make clean all
-./turnip &
+./dishy &
 curl http://127.0.0.1:7379/SET/hello/world
 → {"SET":[true,"OK"]}
 curl http://127.0.0.1:7379/GET/hello
diff --git a/turnip.c b/dishy.c
similarity index 96%
rename from turnip.c
rename to dishy.c
index 56e9842..c60f08e 100644
--- a/turnip.c
+++ b/dishy.c
@@ -58,7 +58,7 @@ main(int argc, char *argv[]) {
 	if(argc > 1) {
 		s->cfg = conf_read(argv[1]);
 	} else {
-		s->cfg = conf_read("turnip.json");
+		s->cfg = conf_read("dishy.json");
 	}
 
 	/* ignore sigpipe */
@@ -71,7 +71,7 @@ main(int argc, char *argv[]) {
 	evhttp_set_gencb(http, on_request, s);
 
 	/* attach hiredis to libevent base */
-	turnip_connect(s);
+	dishy_connect(s);
 
 	/* loop */
 	event_base_dispatch(s->base);
diff --git a/turnip.json b/dishy.json
similarity index 100%
rename from turnip.json
rename to dishy.json
diff --git a/server.c b/server.c
index e19aa6b..c143e08 100644
--- a/server.c
+++ b/server.c
@@ -21,7 +21,7 @@ disconnectCallback(const redisAsyncContext *c, int status) {
 	printf("disconnected, schedule reconnect.\n");
 	s->ac = NULL;
 
-	turnip_connect(s);
+	dishy_connect(s);
 }
 
 static void
@@ -60,7 +60,7 @@ on_timer_reconnect(int fd, short event, void *ctx) {
 }
 
 void
-turnip_connect(struct server *s) {
+dishy_connect(struct server *s) {
 	/* schedule reconnect */
 	evtimer_set(&s->ev_reconnect, on_timer_reconnect, s);
 	event_base_set(s->base, &s->ev_reconnect);
diff --git a/server.h b/server.h
index 031f9fd..f8b8162 100644
--- a/server.h
+++ b/server.h
@@ -1,5 +1,5 @@
-#ifndef TURNIP_H
-#define TURNIP_H
+#ifndef DISHY_H
+#define DISHY_H
 
 #include 
 #include 
@@ -17,7 +17,7 @@ struct server {
 };
 
 void
-turnip_connect(struct server *s);
+dishy_connect(struct server *s);
 
 #endif
 

From f1649f442be3f3eccb0d5c47192e9e46ba873176 Mon Sep 17 00:00:00 2001
From: Nicolas Favre-Felix 
Date: Tue, 28 Dec 2010 17:44:40 +0100
Subject: [PATCH 13/19] Added missing file.

---
 jansson/src/jansson_config.h | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
 create mode 100644 jansson/src/jansson_config.h

diff --git a/jansson/src/jansson_config.h b/jansson/src/jansson_config.h
new file mode 100644
index 0000000..ff0f43f
--- /dev/null
+++ b/jansson/src/jansson_config.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010 Petri Lehtinen 
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ *
+ * This file specifies a part of the site-specific configuration for
+ * Jansson, namely those things that affect the public API in
+ * jansson.h.
+ *
+ * The configure script copies this file to jansson_config.h and
+ * replaces @var@ substitutions by values that fit your system. If you
+ * cannot run the configure script, you can do the value substitution
+ * by hand.
+ */
+
+#ifndef JANSSON_CONFIG_H
+#define JANSSON_CONFIG_H
+
+/* If your compiler supports the inline keyword in C, JSON_INLINE is
+   defined to `inline', otherwise empty. In C++, the inline is always
+   supported. */
+#ifdef __cplusplus
+#define JSON_INLINE inline
+#else
+#define JSON_INLINE inline
+#endif
+
+/* If your compiler supports the `long long` type,
+   JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
+#define JSON_INTEGER_IS_LONG_LONG 1
+
+#endif

From bd15e0c938bfbe13c8b6ca8b0e8874d062d4e2de Mon Sep 17 00:00:00 2001
From: Nicolas Favre-Felix 
Date: Tue, 28 Dec 2010 18:02:27 +0100
Subject: [PATCH 14/19] Faster first connection.

---
 server.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/server.c b/server.c
index c143e08..2935286 100644
--- a/server.c
+++ b/server.c
@@ -21,6 +21,9 @@ disconnectCallback(const redisAsyncContext *c, int status) {
 	printf("disconnected, schedule reconnect.\n");
 	s->ac = NULL;
 
+	/* wait 10 msec and reconnect */
+	s->tv_reconnect.tv_sec = 0;
+	s->tv_reconnect.tv_usec = 100000;
 	dishy_connect(s);
 }
 
@@ -64,8 +67,6 @@ dishy_connect(struct server *s) {
 	/* schedule reconnect */
 	evtimer_set(&s->ev_reconnect, on_timer_reconnect, s);
 	event_base_set(s->base, &s->ev_reconnect);
-	s->tv_reconnect.tv_sec = 1;
-	s->tv_reconnect.tv_usec = 0;
 	evtimer_add(&s->ev_reconnect, &s->tv_reconnect);
 }
 

From da21b27c67d36973e00bd57b31139245d822f14b Mon Sep 17 00:00:00 2001
From: Nicolas Favre-Felix 
Date: Tue, 28 Dec 2010 18:06:38 +0100
Subject: [PATCH 15/19] Removed debug messages.

---
 server.c | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/server.c b/server.c
index 2935286..1fcd1e6 100644
--- a/server.c
+++ b/server.c
@@ -9,16 +9,14 @@
 static void
 connectCallback(const redisAsyncContext *c) {
 	((void)c);
-	printf("connected...\n");
 }
 
 static void
 disconnectCallback(const redisAsyncContext *c, int status) {
 	struct server *s = c->data;
 	if (status != REDIS_OK) {
-		printf("Error: %s\n", c->errstr);
+		fprintf(stderr, "Error: %s\n", c->errstr);
 	}
-	printf("disconnected, schedule reconnect.\n");
 	s->ac = NULL;
 
 	/* wait 10 msec and reconnect */
@@ -36,10 +34,9 @@ on_timer_reconnect(int fd, short event, void *ctx) {
 
 	if(s->ac) {
 		redisLibeventCleanup(s->ac->_adapter_data);
+		redisFree((redisContext*)s->ac);
 	}
 
-	/* TODO: free AC. */
-
 	if(s->cfg->redis_host[0] == '/') { /* unix socket */
 		s->ac = redisAsyncConnectUnix(s->cfg->redis_host);
 	} else {
@@ -49,8 +46,7 @@ on_timer_reconnect(int fd, short event, void *ctx) {
 	s->ac->data = s;
 
 	if(s->ac->err) {
-		/* Let *c leak for now... */
-		printf("Error: %s\n", s->ac->errstr);
+		fprintf(stderr, "Error: %s\n", s->ac->errstr);
 	}
 
 	redisLibeventAttach(s->ac, s->base);

From a42f287685af56239ca27d20aa9bd6c670a389fb Mon Sep 17 00:00:00 2001
From: Nicolas Favre-Felix 
Date: Tue, 28 Dec 2010 20:44:22 +0100
Subject: [PATCH 16/19] Name change.

---
 Makefile                  | 4 ++--
 README.markdown           | 2 +-
 server.c                  | 4 ++--
 server.h                  | 6 +++---
 dishy.c => webdis.c       | 4 ++--
 dishy.json => webdis.json | 0
 6 files changed, 10 insertions(+), 10 deletions(-)
 rename dishy.c => webdis.c (96%)
 rename dishy.json => webdis.json (100%)

diff --git a/Makefile b/Makefile
index 2c9b055..760bf31 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
-OUT=dishy
+OUT=webdis
 HIREDIS_OBJ=hiredis/hiredis.o hiredis/sds.o hiredis/net.o hiredis/async.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
-OBJS=dishy.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ)
+OBJS=webdis.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ)
 CFLAGS=-O3 -Wall -Wextra -I. -Ijansson/src
 LDFLAGS=-levent
 
diff --git a/README.markdown b/README.markdown
index b7606ce..6820c8e 100644
--- a/README.markdown
+++ b/README.markdown
@@ -4,7 +4,7 @@ A very simple prototype providing an HTTP interface to Redis. It uses [hiredis](
 
 
 make clean all
-./dishy &
+./webdis &
 curl http://127.0.0.1:7379/SET/hello/world
 → {"SET":[true,"OK"]}
 curl http://127.0.0.1:7379/GET/hello
diff --git a/server.c b/server.c
index 1fcd1e6..f7d1a8d 100644
--- a/server.c
+++ b/server.c
@@ -22,7 +22,7 @@ disconnectCallback(const redisAsyncContext *c, int status) {
 	/* wait 10 msec and reconnect */
 	s->tv_reconnect.tv_sec = 0;
 	s->tv_reconnect.tv_usec = 100000;
-	dishy_connect(s);
+	webdis_connect(s);
 }
 
 static void
@@ -59,7 +59,7 @@ on_timer_reconnect(int fd, short event, void *ctx) {
 }
 
 void
-dishy_connect(struct server *s) {
+webdis_connect(struct server *s) {
 	/* schedule reconnect */
 	evtimer_set(&s->ev_reconnect, on_timer_reconnect, s);
 	event_base_set(s->base, &s->ev_reconnect);
diff --git a/server.h b/server.h
index f8b8162..ffd91b1 100644
--- a/server.h
+++ b/server.h
@@ -1,5 +1,5 @@
-#ifndef DISHY_H
-#define DISHY_H
+#ifndef SERVER_H
+#define SERVER_H
 
 #include 
 #include 
@@ -17,7 +17,7 @@ struct server {
 };
 
 void
-dishy_connect(struct server *s);
+webdis_connect(struct server *s);
 
 #endif
 
diff --git a/dishy.c b/webdis.c
similarity index 96%
rename from dishy.c
rename to webdis.c
index c60f08e..edb28ed 100644
--- a/dishy.c
+++ b/webdis.c
@@ -58,7 +58,7 @@ main(int argc, char *argv[]) {
 	if(argc > 1) {
 		s->cfg = conf_read(argv[1]);
 	} else {
-		s->cfg = conf_read("dishy.json");
+		s->cfg = conf_read("webdis.json");
 	}
 
 	/* ignore sigpipe */
@@ -71,7 +71,7 @@ main(int argc, char *argv[]) {
 	evhttp_set_gencb(http, on_request, s);
 
 	/* attach hiredis to libevent base */
-	dishy_connect(s);
+	webdis_connect(s);
 
 	/* loop */
 	event_base_dispatch(s->base);
diff --git a/dishy.json b/webdis.json
similarity index 100%
rename from dishy.json
rename to webdis.json

From b7182ab85e3acf6975857e0eb8352632efd8d981 Mon Sep 17 00:00:00 2001
From: Nicolas Favre-Felix 
Date: Tue, 28 Dec 2010 21:12:46 +0100
Subject: [PATCH 17/19] Added contact and reworded ideas.

---
 README.markdown | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/README.markdown b/README.markdown
index 6820c8e..ecb1dae 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,6 +1,6 @@
 # About
 
-A very simple prototype providing an HTTP interface to Redis. It uses [hiredis](https://github.com/antirez/hiredis) and [jansson](https://github.com/akheron/jansson).
+A very simple web server providing an HTTP interface to Redis. It uses [hiredis](https://github.com/antirez/hiredis), [jansson](https://github.com/akheron/jansson) and libevent.
 
 
 make clean all
@@ -29,18 +29,18 @@ curl -d "GET/hello" http://127.0.0.1:7379/
 * Support PUT, DELETE, HEAD, OPTIONS? How? For which commands?
 * Support pub/sub (waiting for HiRedis ticket \#17 in order to add this.)
 * MULTI/EXEC/DISCARD/WATCH are disabled at the moment; find a way to use them.
-* Drop privileges.
-* Add logging.
+* Drop privileges on startup.
+* Add logs.
 * 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 or on twitter [@yowgi](http://twitter.com/yowgi).
+* 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
 * Redis is unreachable: 503 Service Unavailable
 * Could also be used:
-	* Timeout on the redis side: 503 Service Unavailable
+	* Timeout on the redis side: 503 Service Unavailable (this isn't supported by HiRedis yet).
 	* Missing key: 404 Not Found
 	* Unauthorized command (disabled in config file): 403 Forbidden
 

From 208a6d692691a02268157c2800550ef3e0fb9a9a Mon Sep 17 00:00:00 2001
From: Nicolas Favre-Felix 
Date: Tue, 28 Dec 2010 21:44:27 +0100
Subject: [PATCH 18/19] Added missing license file.

---
 COPYING | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 COPYING

diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..a9ae619
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,24 @@
+Copyright (c) 2010-2011, Nicolas Favre-Felix
+
+All rights reserved.
+ 
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+ 
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+ 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ 

From ab9f037f8968c4193de6bea86e9f736d4a25b05d Mon Sep 17 00:00:00 2001
From: Nicolas Favre-Felix 
Date: Wed, 29 Dec 2010 10:52:46 +0100
Subject: [PATCH 19/19] New idea.

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

diff --git a/README.markdown b/README.markdown
index ecb1dae..b741c24 100644
--- a/README.markdown
+++ b/README.markdown
@@ -31,6 +31,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/
 * MULTI/EXEC/DISCARD/WATCH are disabled at the moment; find a way to use them.
 * Drop privileges on startup.
 * Add logs.
+* Support POST of raw Redis protocol data, and execute the whole thing. This could be useful for MULTI/EXEC transactions.
 * Enrich config file:
 	* Provide timeout (this needs to be added to hiredis first.)
 * Multi-server support, using consistent hashing.