Added RAW output.

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

@ -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

@ -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,15 +34,18 @@ 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
# 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
* Unknown verb: 405 Method Not Allowed
* 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:**
<pre>
@ -66,8 +69,38 @@ $ curl http://127.0.0.1:7379/TYPE/y
$ 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"]})
</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>

39
cmd.c

@ -1,5 +1,8 @@
#include "cmd.h"
#include "json.h"
#include "server.h"
#include "formats/json.h"
#include "formats/raw.h"
#include <stdlib.h>
#include <string.h>
@ -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;
}

@ -8,6 +8,9 @@
#include <event.h>
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

@ -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) {
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;

Loading…
Cancel
Save