Merge branch 'master' into reconnect

master
Nicolas Favre-Felix 14 years ago
commit fe8d0ac252

@ -6,23 +6,31 @@ A very simple prototype providing an HTTP interface to Redis. It uses [hiredis](
make clean all make clean all
./turnip & ./turnip &
curl http://127.0.0.1:7379/SET/hello/world curl http://127.0.0.1:7379/SET/hello/world
→ {"SET":[true,"OK"]}
curl http://127.0.0.1:7379/GET/hello curl http://127.0.0.1:7379/GET/hello
“world” {"GET":"world"}
curl -d "GET/hello" http://127.0.0.1:7379/ curl -d "GET/hello" http://127.0.0.1:7379/
“world” {"GET":"world"}
</pre> </pre>
# Features
* GET and POST are supported.
* JSON output, optional JSONP parameter.
* HTTP 1.1 pipelining (45 kqps on a desktop Linux machine.)
* 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).
* Find a way to format multi-bulk data * 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?
* Add JSONP callbacks * Support pub/sub.
* Add support for Redis UNIX socket * Disable MULTI/EXEC/DISCARD/WATCH.
* Enrich config file * Add logging.
* Provide timeout * Enrich config file:
* Provide timeout (this needs to be added to hiredis first.)
* 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).
@ -57,4 +65,9 @@ $ curl http://127.0.0.1:7379/TYPE/y
// 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:
$ curl "http://127.0.0.1:7379/TYPE/y?jsonp=myCustomFunction"
myCustomFunction({"TYPE":[true,"string"]})
</pre> </pre>

10
cmd.c

@ -30,8 +30,10 @@ cmd_free(struct cmd *c) {
} }
void void
cmd_run(redisAsyncContext *c, struct evhttp_request *rq, const char *uri, size_t uri_len) { cmd_run(redisAsyncContext *c, struct evhttp_request *rq,
const char *uri, size_t uri_len) {
char *qmark = strchr(uri, '?');
char *slash = strchr(uri, '/'); char *slash = strchr(uri, '/');
int cmd_len; int cmd_len;
int param_count = 0, cur_param = 1; int param_count = 0, cur_param = 1;
@ -41,6 +43,9 @@ cmd_run(redisAsyncContext *c, struct evhttp_request *rq, const char *uri, size_t
const char *p; const char *p;
/* count arguments */ /* count arguments */
if(qmark) {
uri_len = qmark - uri;
}
for(p = uri; p && p < uri + uri_len; param_count++) { for(p = uri; p && p < uri + uri_len; param_count++) {
p = strchr(p+1, '/'); p = strchr(p+1, '/');
} }
@ -53,6 +58,9 @@ cmd_run(redisAsyncContext *c, struct evhttp_request *rq, const char *uri, size_t
cmd_len = uri_len; cmd_len = uri_len;
} }
/* parse URI parameters */
evhttp_parse_query(uri, &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;

@ -3,6 +3,9 @@
#include <stdlib.h> #include <stdlib.h>
#include <hiredis/async.h> #include <hiredis/async.h>
#include <sys/queue.h>
#include <evhttp.h>
#include <event.h>
struct evhttp_request; struct evhttp_request;
@ -12,6 +15,8 @@ struct cmd {
const char **argv; const char **argv;
size_t *argv_len; size_t *argv_len;
struct evhttp_request *rq; struct evhttp_request *rq;
struct evkeyvalq uri_params;
}; };
struct cmd * struct cmd *
@ -21,6 +26,7 @@ void
cmd_free(struct cmd *c); cmd_free(struct cmd *c);
void void
cmd_run(redisAsyncContext *c, struct evhttp_request *rq, const char *uri, size_t uri_len); cmd_run(redisAsyncContext *c, struct evhttp_request *rq,
const char *uri, size_t uri_len);
#endif #endif

@ -90,6 +90,7 @@ typedef struct redisAsyncContext {
/* Functions that proxy to hiredis */ /* Functions that proxy to hiredis */
redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn); int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);

@ -28,8 +28,9 @@ json_reply(redisAsyncContext *c, void *r, void *privdata) {
/* encode redis reply as JSON */ /* encode redis reply as JSON */
j = json_wrap_redis_reply(cmd, r); j = json_wrap_redis_reply(cmd, r);
/* get JSON as string */ /* get JSON as string, possibly with JSONP wrapper */
json_reply = json_dumps(j, JSON_COMPACT); json_reply = json_string_output(j, cmd);
/* send reply */ /* send reply */
body = evbuffer_new(); body = evbuffer_new();
@ -103,3 +104,32 @@ json_wrap_redis_reply(const struct cmd *cmd, const redisReply *r) {
return jroot; return jroot;
} }
char *
json_string_output(json_t *j, struct cmd *cmd) {
struct evkeyval *kv;
char *json_reply = json_dumps(j, JSON_COMPACT);
/* check for JSONP */
TAILQ_FOREACH(kv, &cmd->uri_params, next) {
if(strcmp(kv->key, "jsonp") == 0) {
size_t json_len = strlen(json_reply);
size_t val_len = strlen(kv->value);
size_t ret_len = val_len + 1 + json_len + 1;
char *ret = calloc(1 + ret_len, 1);
memcpy(ret, kv->value, val_len);
ret[val_len]='(';
memcpy(ret + val_len + 1, json_reply, json_len);
ret[val_len + 1 + json_len] = ')';
free(json_reply);
return ret;
}
}
return json_reply;
}

@ -10,5 +10,7 @@ struct cmd;
void void
json_reply(redisAsyncContext *c, void *r, void *privdata); json_reply(redisAsyncContext *c, void *r, void *privdata);
char *
json_string_output(json_t *j, struct cmd *cmd);
#endif #endif

@ -38,7 +38,17 @@ disconnectCallback(const redisAsyncContext *c, int status) {
static void static void
reconnect() { reconnect() {
__redis_context = redisAsyncConnect(cfg->redis_host, cfg->redis_port);
if(cfg->redis_host[0] == '/') { /* unix socket */
__redis_context = redisAsyncConnectUnix(cfg->redis_host);
} else {
__redis_context = redisAsyncConnect(cfg->redis_host, cfg->redis_port);
}
if(__redis_context->err) {
/* Let *c leak for now... */
printf("Error: %s\n", __redis_context->errstr);
}
redisLibeventAttach(__redis_context, __base); redisLibeventAttach(__redis_context, __base);
redisAsyncSetConnectCallback(__redis_context, connectCallback); redisAsyncSetConnectCallback(__redis_context, connectCallback);
@ -60,6 +70,7 @@ on_request(struct evhttp_request *rq, void *ctx) {
case EVHTTP_REQ_GET: case EVHTTP_REQ_GET:
cmd_run(__redis_context, rq, 1+uri, strlen(uri)-1); cmd_run(__redis_context, rq, 1+uri, strlen(uri)-1);
break; break;
case EVHTTP_REQ_POST: case EVHTTP_REQ_POST:
cmd_run(__redis_context, rq, cmd_run(__redis_context, rq,
(const char*)EVBUFFER_DATA(rq->input_buffer), (const char*)EVBUFFER_DATA(rq->input_buffer),
@ -67,7 +78,7 @@ on_request(struct evhttp_request *rq, void *ctx) {
break; break;
default: default:
evhttp_send_reply(rq, 500, "Unknown redis format", NULL); evhttp_send_reply(rq, 405, "Method Not Allowed", NULL);
return; return;
} }
} }
@ -82,6 +93,11 @@ main(int argc, char *argv[]) {
cfg = conf_read("turnip.conf"); cfg = conf_read("turnip.conf");
/* ignore sigpipe */
#ifdef SIGPIPE
signal(SIGPIPE, SIG_IGN);
#endif
/* start http server */ /* start http server */
evhttp_bind_socket(http, cfg->http_host, cfg->http_port); evhttp_bind_socket(http, cfg->http_host, cfg->http_port);
evhttp_set_gencb(http, on_request, NULL); evhttp_set_gencb(http, on_request, NULL);

Loading…
Cancel
Save