From e2f2b365ad4cb7c50418a174234ad12e91ac4845 Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Sun, 26 Dec 2010 15:59:35 +0100 Subject: [PATCH] Added RAW output. --- Makefile | 3 +- README.markdown | 55 ++++++++++++--- cmd.c | 39 +++++++++-- cmd.h | 8 ++- json.c => formats/json.c | 0 json.h => formats/json.h | 0 formats/raw.c | 146 +++++++++++++++++++++++++++++++++++++++ formats/raw.h | 13 ++++ turnip.c | 4 +- 9 files changed, 247 insertions(+), 21 deletions(-) rename json.c => formats/json.c (100%) rename json.h => formats/json.h (100%) create mode 100644 formats/raw.c create mode 100644 formats/raw.h diff --git a/Makefile b/Makefile index a44ea40..3408b08 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ OUT=turnip 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 -OBJS=turnip.o conf.o json.o cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) +FORMAT_OBJS=formats/json.o formats/raw.o +OBJS=turnip.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) CFLAGS=-O0 -ggdb -Wall -Wextra -I. -Ijansson/src LDFLAGS=-levent diff --git a/README.markdown b/README.markdown index aa3b9f2..28cc259 100644 --- a/README.markdown +++ b/README.markdown @@ -18,13 +18,13 @@ curl -d "GET/hello" http://127.0.0.1:7379/ # Features * GET and POST are supported. * JSON output, optional JSONP parameter. +* 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. # Ideas * Add meta-data info per key (MIME type in a second key, for instance). -* Add a “raw” output format, and find a way to format multi-bulk data in that format. * Support PUT, DELETE, HEAD? * Support pub/sub. * Disable MULTI/EXEC/DISCARD/WATCH. @@ -34,40 +34,73 @@ curl -d "GET/hello" http://127.0.0.1:7379/ * Restrict commands by IP range * Send your ideas using the github tracker or on twitter [@yowgi](http://twitter.com/yowgi). -# HTTP error codes that could be used (although that's not the case at the moment) -* Missing key: 404 Not Found -* Timeout on the redis side: 503 Service Unavailable -* Unknown verb: 405 Method Not Allowed +# HTTP error codes +* Unknown HTTP verb: 405 Method Not Allowed +* Disconnected from redis: 503 Service Unavailable +* Could also be used: + * Timeout on the redis side: 503 Service Unavailable + * Missing key: 404 Not Found # JSON output The URI `/COMMAND/arg0/arg1/.../argN` returns a JSON object with the command as a key and the result as a value. +JSON is the default output format. **Examples:**
 // string
-$ curl  http://127.0.0.1:7379/GET/y
+$ curl http://127.0.0.1:7379/GET/y
 {"GET":"41"}
 
 // number
-$ curl  http://127.0.0.1:7379/INCR/y
+$ curl http://127.0.0.1:7379/INCR/y
 {"INCR":42}
 
 // list
-$ curl  http://127.0.0.1:7379/LRANGE/x/0/1
+$ curl http://127.0.0.1:7379/LRANGE/x/0/1
 {"LRANGE":["abc","def"]}
 
 // status
-$ curl  http://127.0.0.1:7379/TYPE/y
+$ curl http://127.0.0.1:7379/TYPE/y
 {"TYPE":[true,"string"]}
 
 // error, which is basically a status
-$ curl  http://127.0.0.1:7379/MAKE-ME-COFFEE
+$ curl http://127.0.0.1:7379/MAKE-ME-COFFEE
 {"MAKE-ME-COFFEE":[false,"ERR unknown command 'MAKE-ME-COFFEE'"]}
 
-
 // JSONP callback:
 $ curl  "http://127.0.0.1:7379/TYPE/y?jsonp=myCustomFunction"
 myCustomFunction({"TYPE":[true,"string"]})
+
+
+ +# RAW output +This is the raw Redis output. +
+
+// string
+$ curl http://127.0.0.1:7379/GET/z?format=raw
+hello
+
+// number
+curl http://127.0.0.1:7379/INCR/a?format=raw
+:2
+
+// list
+$ curl http://127.0.0.1:7379/LRANGE/x/0/-1?format=raw
+*2
+$3
+abc
+$3
+def
+
+// status
+$ curl http://127.0.0.1:7379/TYPE/y?format=raw
++zset
+
+// error, which is basically a status
+$ curl http://127.0.0.1:7379/MAKE-ME-COFFEE?format=raw
+-ERR unknown command 'ABC'
+
 
diff --git a/cmd.c b/cmd.c index 6ff774c..0c3053a 100644 --- a/cmd.c +++ b/cmd.c @@ -1,5 +1,8 @@ #include "cmd.h" -#include "json.h" +#include "server.h" + +#include "formats/json.h" +#include "formats/raw.h" #include #include @@ -30,17 +33,17 @@ cmd_free(struct cmd *c) { } void -cmd_run(redisAsyncContext *c, struct evhttp_request *rq, +cmd_run(struct server *s, struct evhttp_request *rq, const char *uri, size_t uri_len) { char *qmark = strchr(uri, '?'); char *slash = strchr(uri, '/'); + const char *p; int cmd_len; int param_count = 0, cur_param = 1; struct cmd *cmd; - - const char *p; + formatting_fun fun; /* count arguments */ if(qmark) { @@ -61,12 +64,15 @@ cmd_run(redisAsyncContext *c, struct evhttp_request *rq, /* parse URI parameters */ evhttp_parse_query(uri, &cmd->uri_params); + /* get output formatting function */ + fun = get_formatting_funtion(&cmd->uri_params); + /* there is always a first parameter, it's the command name */ cmd->argv[0] = uri; cmd->argv_len[0] = cmd_len; if(!slash) { - redisAsyncCommandArgv(c, json_reply, cmd, 1, cmd->argv, cmd->argv_len); + redisAsyncCommandArgv(s->ac, fun, cmd, 1, cmd->argv, cmd->argv_len); return; } p = slash + 1; @@ -90,6 +96,27 @@ cmd_run(redisAsyncContext *c, struct evhttp_request *rq, cur_param++; } - redisAsyncCommandArgv(c, json_reply, cmd, param_count, cmd->argv, cmd->argv_len); + redisAsyncCommandArgv(s->ac, fun, cmd, param_count, cmd->argv, cmd->argv_len); } + + +formatting_fun +get_formatting_funtion(struct evkeyvalq *params) { + + struct evkeyval *kv; + + /* check for JSONP */ + TAILQ_FOREACH(kv, params, next) { + if(strcmp(kv->key, "format") == 0) { + if(strcmp(kv->value, "raw") == 0) { + return raw_reply; + } else if(strcmp(kv->value, "json") == 0) { + return json_reply; + } + break; + } + } + + return json_reply; +} diff --git a/cmd.h b/cmd.h index 3dd470d..74af65c 100644 --- a/cmd.h +++ b/cmd.h @@ -8,6 +8,9 @@ #include struct evhttp_request; +struct server; + +typedef void (*formatting_fun)(redisAsyncContext *, void *, void *); struct cmd { @@ -26,7 +29,10 @@ void cmd_free(struct cmd *c); void -cmd_run(redisAsyncContext *c, struct evhttp_request *rq, +cmd_run(struct server *s, struct evhttp_request *rq, const char *uri, size_t uri_len); +formatting_fun +get_formatting_funtion(struct evkeyvalq *params); + #endif diff --git a/json.c b/formats/json.c similarity index 100% rename from json.c rename to formats/json.c diff --git a/json.h b/formats/json.h similarity index 100% rename from json.h rename to formats/json.h diff --git a/formats/raw.c b/formats/raw.c new file mode 100644 index 0000000..d7d0033 --- /dev/null +++ b/formats/raw.c @@ -0,0 +1,146 @@ +#include "raw.h" +#include "cmd.h" + +#include +#include +#include +#include +#include + +static char * +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; + + if (reply == NULL) { + evhttp_send_reply(cmd->rq, 404, "Not Found", NULL); + return; + } + + raw_out = raw_wrap(r, &sz); + + /* send reply */ + body = evbuffer_new(); + evbuffer_add(body, raw_out, sz); + evhttp_add_header(cmd->rq->output_headers, "Content-Type", "text/plain"); + evhttp_send_reply(cmd->rq, 200, "OK", body); + + /* cleanup */ + evbuffer_free(body); + freeReplyObject(r); + cmd_free(cmd); + free(raw_out); +} + +static int +integer_length(long long int i) { + int sz = 0; + int ci = abs(i); + while (ci > 0) { + ci = (ci/10); + sz += 1; + } + if(i == 0) { /* log 0 doesn't make sense. */ + sz = 1; + } else if(i < 0) { /* allow for neg sign as well. */ + sz++; + } + return sz; +} + +static char * +raw_array(const redisReply *r, size_t *sz) { + + unsigned int i; + char *ret, *p; + + /* compute size */ + *sz = 0; + *sz += 1 + integer_length(r->elements) + 1; + for(i = 0; i < r->elements; ++i) { + redisReply *e = r->element[i]; + switch(e->type) { + case REDIS_REPLY_STRING: + *sz += 1 + integer_length(e->len) + 1 + + e->len + 1; + break; + case REDIS_REPLY_INTEGER: + *sz += 1 + integer_length(integer_length(e->integer)) + 1 + + integer_length(e->integer) + 1; + break; + + } + } + + /* allocate */ + p = ret = malloc(*sz); + p += sprintf(p, "*%zd\n", r->elements); + + /* copy */ + for(i = 0; i < r->elements; ++i) { + redisReply *e = r->element[i]; + switch(e->type) { + case REDIS_REPLY_STRING: + p += sprintf(p, "$%d\n", e->len); + memcpy(p, e->str, e->len); + p += e->len; + *p = '\n'; + p++; + break; + case REDIS_REPLY_INTEGER: + p += sprintf(p, "$%d\n%lld\n", + integer_length(e->integer), e->integer); + break; + } + } + + return ret; +} + +static char * +raw_wrap(const redisReply *r, size_t *sz) { + + char *ret; + + switch(r->type) { + case REDIS_REPLY_STATUS: + case REDIS_REPLY_ERROR: + *sz = 2 + r->len; + ret = malloc(*sz); + ret[0] = (r->type == REDIS_REPLY_STATUS?'+':'-'); + memcpy(ret+1, r->str, *sz-2); + memcpy(ret+*sz - 1, "\n", 1); + return ret; + + case REDIS_REPLY_STRING: + *sz = 1 + r->len; + ret = malloc(*sz); + memcpy(ret, r->str, *sz - 1); + memcpy(ret + *sz - 1, "\n", 1); + return ret; + + case REDIS_REPLY_INTEGER: + *sz = 2 + integer_length(r->integer); + ret = malloc(3+*sz); + sprintf(ret, ":%lld\n", r->integer); + return ret; + + case REDIS_REPLY_ARRAY: + return raw_array(r, sz); + + default: + *sz = 4; + ret = malloc(*sz); + memcpy(ret, "$-1\n", 4); + return ret; + } +} + diff --git a/formats/raw.h b/formats/raw.h new file mode 100644 index 0000000..f25607f --- /dev/null +++ b/formats/raw.h @@ -0,0 +1,13 @@ +#ifndef RAW_H +#define RAW_H + +#include +#include + +struct cmd; + +void +raw_reply(redisAsyncContext *c, void *r, void *privdata); + + +#endif diff --git a/turnip.c b/turnip.c index 50342ed..55c6f12 100644 --- a/turnip.c +++ b/turnip.c @@ -26,11 +26,11 @@ on_request(struct evhttp_request *rq, void *ctx) { switch(rq->type) { case EVHTTP_REQ_GET: - cmd_run(s->ac, rq, 1+uri, strlen(uri)-1); + cmd_run(s, rq, 1+uri, strlen(uri)-1); break; case EVHTTP_REQ_POST: - cmd_run(s->ac, rq, + cmd_run(s, rq, (const char*)EVBUFFER_DATA(rq->input_buffer), EVBUFFER_LENGTH(rq->input_buffer)); break;