Added RAW output.

master
Nicolas Favre-Felix 14 years ago
parent 282d900b51
commit e2f2b365ad

@ -1,7 +1,8 @@
OUT=turnip OUT=turnip
HIREDIS_OBJ=hiredis/hiredis.o hiredis/sds.o hiredis/net.o hiredis/async.o 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 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 CFLAGS=-O0 -ggdb -Wall -Wextra -I. -Ijansson/src
LDFLAGS=-levent LDFLAGS=-levent

@ -18,13 +18,13 @@ curl -d "GET/hello" http://127.0.0.1:7379/
# Features # Features
* GET and POST are supported. * GET and POST are supported.
* JSON output, optional JSONP parameter. * 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.) * HTTP 1.1 pipelining (45 kqps on a desktop Linux machine.)
* Connects to Redis using a TCP or UNIX socket. * Connects to Redis using a TCP or UNIX socket.
# Ideas # Ideas
* Add meta-data info per key (MIME type in a second key, for instance). * 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 PUT, DELETE, HEAD?
* Support pub/sub. * Support pub/sub.
* Disable MULTI/EXEC/DISCARD/WATCH. * Disable MULTI/EXEC/DISCARD/WATCH.
@ -34,40 +34,73 @@ curl -d "GET/hello" http://127.0.0.1:7379/
* Restrict commands by IP range * Restrict commands by IP range
* Send your ideas using the github tracker or on twitter [@yowgi](http://twitter.com/yowgi). * 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) # HTTP error codes
* Missing key: 404 Not Found * Unknown HTTP verb: 405 Method Not Allowed
* Timeout on the redis side: 503 Service Unavailable * Disconnected from redis: 503 Service Unavailable
* Unknown verb: 405 Method Not Allowed * Could also be used:
* Timeout on the redis side: 503 Service Unavailable
* Missing key: 404 Not Found
# JSON output # JSON output
The URI `/COMMAND/arg0/arg1/.../argN` returns a JSON object with the command as a key and the result as a value. 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:** **Examples:**
<pre> <pre>
// string // string
$ curl http://127.0.0.1:7379/GET/y $ curl http://127.0.0.1:7379/GET/y
{"GET":"41"} {"GET":"41"}
// number // number
$ curl http://127.0.0.1:7379/INCR/y $ curl http://127.0.0.1:7379/INCR/y
{"INCR":42} {"INCR":42}
// list // 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"]} {"LRANGE":["abc","def"]}
// status // status
$ curl http://127.0.0.1:7379/TYPE/y $ curl http://127.0.0.1:7379/TYPE/y
{"TYPE":[true,"string"]} {"TYPE":[true,"string"]}
// error, which is basically a status // 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'"]} {"MAKE-ME-COFFEE":[false,"ERR unknown command 'MAKE-ME-COFFEE'"]}
// JSONP callback: // JSONP callback:
$ curl "http://127.0.0.1:7379/TYPE/y?jsonp=myCustomFunction" $ curl "http://127.0.0.1:7379/TYPE/y?jsonp=myCustomFunction"
myCustomFunction({"TYPE":[true,"string"]}) myCustomFunction({"TYPE":[true,"string"]})
</pre>
# RAW output
This is the raw Redis output.
<pre>
// 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'
</pre> </pre>

39
cmd.c

@ -1,5 +1,8 @@
#include "cmd.h" #include "cmd.h"
#include "json.h" #include "server.h"
#include "formats/json.h"
#include "formats/raw.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -30,17 +33,17 @@ cmd_free(struct cmd *c) {
} }
void 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) { const char *uri, size_t uri_len) {
char *qmark = strchr(uri, '?'); char *qmark = strchr(uri, '?');
char *slash = strchr(uri, '/'); char *slash = strchr(uri, '/');
const char *p;
int cmd_len; int cmd_len;
int param_count = 0, cur_param = 1; int param_count = 0, cur_param = 1;
struct cmd *cmd; struct cmd *cmd;
formatting_fun fun;
const char *p;
/* count arguments */ /* count arguments */
if(qmark) { if(qmark) {
@ -61,12 +64,15 @@ cmd_run(redisAsyncContext *c, struct evhttp_request *rq,
/* parse URI parameters */ /* parse URI parameters */
evhttp_parse_query(uri, &cmd->uri_params); 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 */ /* there is always a first parameter, it's the command name */
cmd->argv[0] = uri; cmd->argv[0] = uri;
cmd->argv_len[0] = cmd_len; cmd->argv_len[0] = cmd_len;
if(!slash) { 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; return;
} }
p = slash + 1; p = slash + 1;
@ -90,6 +96,27 @@ cmd_run(redisAsyncContext *c, struct evhttp_request *rq,
cur_param++; 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;
}

@ -8,6 +8,9 @@
#include <event.h> #include <event.h>
struct evhttp_request; struct evhttp_request;
struct server;
typedef void (*formatting_fun)(redisAsyncContext *, void *, void *);
struct cmd { struct cmd {
@ -26,7 +29,10 @@ void
cmd_free(struct cmd *c); cmd_free(struct cmd *c);
void 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); const char *uri, size_t uri_len);
formatting_fun
get_formatting_funtion(struct evkeyvalq *params);
#endif #endif

@ -0,0 +1,146 @@
#include "raw.h"
#include "cmd.h"
#include <string.h>
#include <hiredis/hiredis.h>
#include <hiredis/async.h>
#include <event.h>
#include <evhttp.h>
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;
}
}

@ -0,0 +1,13 @@
#ifndef RAW_H
#define RAW_H
#include <hiredis/hiredis.h>
#include <hiredis/async.h>
struct cmd;
void
raw_reply(redisAsyncContext *c, void *r, void *privdata);
#endif

@ -26,11 +26,11 @@ on_request(struct evhttp_request *rq, void *ctx) {
switch(rq->type) { switch(rq->type) {
case EVHTTP_REQ_GET: case EVHTTP_REQ_GET:
cmd_run(s->ac, rq, 1+uri, strlen(uri)-1); cmd_run(s, rq, 1+uri, strlen(uri)-1);
break; break;
case EVHTTP_REQ_POST: case EVHTTP_REQ_POST:
cmd_run(s->ac, rq, cmd_run(s, rq,
(const char*)EVBUFFER_DATA(rq->input_buffer), (const char*)EVBUFFER_DATA(rq->input_buffer),
EVBUFFER_LENGTH(rq->input_buffer)); EVBUFFER_LENGTH(rq->input_buffer));
break; break;

Loading…
Cancel
Save