diff --git a/Makefile b/Makefile
index b481b5b..635ae37 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,13 @@
OUT=webdis
HIREDIS_OBJ=hiredis/hiredis.o hiredis/sds.o hiredis/net.o hiredis/async.o hiredis/dict.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
+<<<<<<< HEAD
FORMAT_OBJS=formats/json.o formats/raw.o formats/common.o
OBJS=webdis.o conf.o $(FORMAT_OBJS) cmd.o slog.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) libb64/cencode.o acl.o
+=======
+FORMAT_OBJS=formats/json.o formats/raw.o formats/common.o formats/custom-type.o
+OBJS=webdis.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) libb64/cencode.o acl.o
+>>>>>>> d8298c355662c701727ae270897923bdbfa58aac
CFLAGS=-O3 -Wall -Wextra -I. -Ijansson/src
LDFLAGS=-levent
diff --git a/README.markdown b/README.markdown
index 37cc4ec..2ec6eed 100644
--- a/README.markdown
+++ b/README.markdown
@@ -25,9 +25,12 @@ curl -d "GET/hello" http://127.0.0.1:7379/
* Possible Redis authentication in the config file.
* Pub/Sub using `Transfer-Encoding: chunked`, works with JSONP as well. Webdis can be used as a Comet server.
* Drop privileges on startup.
+* For `GET` commands:
+ * MIME type in a second key with `/GET/k?typeKey=type-k`. This will transform the `GET` request into `MGET` and fetch both `k` and `type-k`. If `type-k` is a string, it will be used as Content-Type in the response. If the key doesn't exist or isn't a string, `binary/octet-stream` is used instead.
+ * Custom MIME type with `?type=text/plain` (or any other MIME type).
+* URL-encoded parameters for binary data or slashes. For instance, `%2f` is decoded as `/` but not used as a command separator.
# Ideas, TODO...
-* Add meta-data info per key (MIME type in a second key, for instance).
* Support PUT, DELETE, HEAD, OPTIONS? How? For which commands?
* MULTI/EXEC/DISCARD/WATCH are disabled at the moment; find a way to use them.
* Add logs.
@@ -36,6 +39,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/
* Provide timeout (this needs to be added to hiredis first.)
* Multi-server support, using consistent hashing.
* Send your ideas using the github tracker, on twitter [@yowgi](http://twitter.com/yowgi) or by mail to n.favrefelix@gmail.com.
+* Add WebSocket support, allow cross-origin XHR.
# HTTP error codes
* Unknown HTTP verb: 405 Method Not Allowed
@@ -143,3 +147,44 @@ $ curl http://127.0.0.1:7379/MAKE-ME-COFFEE?format=raw
-ERR unknown command 'MAKE-ME-COFFEE'
+
+# Custom content-type
+Webdis can serve `GET` requests with a custom content-type. There are two ways of doing this; the content-type can be in a key that is fetched with the content, or given as a query string parameter.
+
+**Content-Type in parameter:**
+
+
+curl -v "http://127.0.0.1:7379/GET/hello.html?type=text/html"
+[...]
+< HTTP/1.1 200 OK
+< Content-Type: text/html
+< Date: Mon, 03 Jan 2011 20:43:36 GMT
+< Content-Length: 137
+<
+<!DOCTYPE html>
+<html>
+...
+</html>
+
+
+**Content-Type in a separate key:**
+
+
+curl "http://127.0.0.1:7379/SET/hello.type/text%2fhtml"
+{"SET":[true,"OK"]}
+
+curl "http://127.0.0.1:7379/GET/hello.type"
+{"GET":"text/html"}
+
+curl -v "http://127.0.0.1:7379/GET/hello.html?typeKey=hello.type"
+[...]
+< HTTP/1.1 200 OK
+< Content-Type: text/html
+< Date: Mon, 03 Jan 2011 20:56:43 GMT
+< Content-Length: 137
+<
+<!DOCTYPE html>
+<html>
+...
+</html>
+
diff --git a/cmd.c b/cmd.c
index 51c11e5..742cadd 100644
--- a/cmd.c
+++ b/cmd.c
@@ -5,10 +5,12 @@
#include "formats/json.h"
#include "formats/raw.h"
+#include "formats/custom-type.h"
#include
#include
#include
+#include
struct cmd *
cmd_new(struct evhttp_request *rq, int count) {
@@ -31,6 +33,9 @@ cmd_free(struct cmd *c) {
free(c->argv);
free(c->argv_len);
+ free(c->mime);
+ free(c->mimeKey);
+
free(c);
}
@@ -53,18 +58,49 @@ void on_http_disconnect(struct evhttp_connection *evcon, void *ctx) {
free(ps);
}
+/* taken from libevent */
+static char *
+decode_uri(const char *uri, size_t length, size_t *out_len, int always_decode_plus) {
+ char c;
+ size_t i, j;
+ int in_query = always_decode_plus;
+
+ char *ret = malloc(length);
+
+ for (i = j = 0; i < length; i++) {
+ c = uri[i];
+ if (c == '?') {
+ in_query = 1;
+ } else if (c == '+' && in_query) {
+ c = ' ';
+ } else if (c == '%' && isxdigit((unsigned char)uri[i+1]) &&
+ isxdigit((unsigned char)uri[i+2])) {
+ char tmp[] = { uri[i+1], uri[i+2], '\0' };
+ c = (char)strtol(tmp, NULL, 16);
+ i += 2;
+ }
+ ret[j++] = c;
+ }
+ *out_len = (size_t)j;
+
+ return ret;
+}
+
+
int
cmd_run(struct server *s, struct evhttp_request *rq,
const char *uri, size_t uri_len) {
char *qmark = strchr(uri, '?');
- char *slash = strchr(uri, '/');
+ char *slash;
const char *p;
int cmd_len;
- int param_count = 0, cur_param = 1;
+ int param_count = 0, cur_param = 1, i;
struct cmd *cmd;
- formatting_fun fun;
+
+ formatting_fun f_format;
+ transform_fun f_transform = NULL;
/* count arguments */
if(qmark) {
@@ -76,6 +112,7 @@ cmd_run(struct server *s, struct evhttp_request *rq,
cmd = cmd_new(rq, param_count);
+ slash = memchr(uri, '/', uri_len);
if(slash) {
cmd_len = slash - uri;
} else {
@@ -86,7 +123,7 @@ cmd_run(struct server *s, struct evhttp_request *rq,
evhttp_parse_query(uri, &cmd->uri_params);
/* get output formatting function */
- fun = get_formatting_function(&cmd->uri_params);
+ get_functions(cmd, &f_format, &f_transform);
/* there is always a first parameter, it's the command name */
cmd->argv[0] = uri;
@@ -109,8 +146,9 @@ cmd_run(struct server *s, struct evhttp_request *rq,
evhttp_connection_set_closecb(rq->evcon, on_http_disconnect, ps);
}
+ /* no args (e.g. INFO command) */
if(!slash) {
- redisAsyncCommandArgv(s->ac, fun, cmd, 1, cmd->argv, cmd->argv_len);
+ redisAsyncCommandArgv(s->ac, f_format, cmd, 1, cmd->argv, cmd->argv_len);
return 0;
}
p = slash + 1;
@@ -119,45 +157,63 @@ cmd_run(struct server *s, struct evhttp_request *rq,
const char *arg = p;
int arg_len;
char *next = strchr(arg, '/');
- if(next) { /* found a slash */
+ if(!next || next > uri + uri_len) { /* last argument */
+ p = uri + uri_len;
+ arg_len = p - arg;
+ } else { /* found a slash */
arg_len = next - arg;
p = next + 1;
- } else { /* last argument */
- arg_len = uri + uri_len - arg;
- p = uri + uri_len;
}
/* record argument */
- cmd->argv[cur_param] = arg;
- cmd->argv_len[cur_param] = arg_len;
-
+ cmd->argv[cur_param] = decode_uri(arg, arg_len, &cmd->argv_len[cur_param], 1);
cur_param++;
}
- redisAsyncCommandArgv(s->ac, fun, cmd, param_count, cmd->argv, cmd->argv_len);
- return 0;
-}
+ /* transform command if we need to. */
+ if(f_transform) f_transform(cmd);
+ /* push command to Redis. */
+ redisAsyncCommandArgv(s->ac, f_format, cmd, cmd->count, cmd->argv, cmd->argv_len);
+ for(i = 1; i < cmd->count; ++i) {
+ free((char*)cmd->argv[i]);
+ }
-formatting_fun
-get_formatting_function(struct evkeyvalq *params) {
+ return 0;
+}
+
+/**
+ * Return 2 functions, one to format the reply and
+ * one to transform the command before processing it.
+ */
+void
+get_functions(struct cmd *cmd, formatting_fun *f_format, transform_fun *f_transform) {
struct evkeyval *kv;
- /* check for JSONP */
- TAILQ_FOREACH(kv, params, next) {
- if(strcmp(kv->key, "format") == 0) {
+ /* defaults */
+ *f_format = json_reply;
+ *f_transform = NULL;
+
+ /* loop over the query string */
+ TAILQ_FOREACH(kv, &cmd->uri_params, next) {
+ if(strcmp(kv->key, "format") == 0) { /* output format */
if(strcmp(kv->value, "raw") == 0) {
- return raw_reply;
+ *f_format = raw_reply;
} else if(strcmp(kv->value, "json") == 0) {
- return json_reply;
+ *f_format = json_reply;
}
break;
+ } else if(strcmp(kv->key, "typeKey") == 0) { /* MIME type in a key. */
+ cmd->mimeKey = strdup(kv->value);
+ *f_transform = custom_type_process_cmd;
+ *f_format = custom_type_reply;
+ } else if(strcmp(kv->key, "type") == 0) { /* MIME type directly in parameter */
+ cmd->mime = strdup(kv->value);
+ *f_format = custom_type_reply;
}
}
-
- return json_reply;
}
int
diff --git a/cmd.h b/cmd.h
index e2e1857..fde50ab 100644
--- a/cmd.h
+++ b/cmd.h
@@ -9,8 +9,10 @@
struct evhttp_request;
struct server;
+struct cmd;
typedef void (*formatting_fun)(redisAsyncContext *, void *, void *);
+typedef void (*transform_fun)(struct cmd *);
struct cmd {
@@ -22,6 +24,9 @@ struct cmd {
struct evkeyvalq uri_params;
int started_responding;
+
+ char *mime;
+ char *mimeKey;
};
struct pubsub_client {
@@ -39,8 +44,8 @@ int
cmd_run(struct server *s, struct evhttp_request *rq,
const char *uri, size_t uri_len);
-formatting_fun
-get_formatting_function(struct evkeyvalq *params);
+void
+get_functions(struct cmd *cmd, formatting_fun *f_format, transform_fun *f_transform);
int
cmd_is_subscribe(struct cmd *cmd);
diff --git a/formats/common.c b/formats/common.c
index 277354f..e87a136 100644
--- a/formats/common.c
+++ b/formats/common.c
@@ -12,7 +12,7 @@ format_send_reply(struct cmd *cmd, const char *p, size_t sz, const char *content
/* send reply */
body = evbuffer_new();
evbuffer_add(body, p, sz);
- evhttp_add_header(cmd->rq->output_headers, "Content-Type", content_type);
+ evhttp_add_header(cmd->rq->output_headers, "Content-Type", cmd->mime?cmd->mime:content_type);
if(cmd_is_subscribe(cmd)) {
free_cmd = 0;
diff --git a/formats/custom-type.c b/formats/custom-type.c
new file mode 100644
index 0000000..39baff9
--- /dev/null
+++ b/formats/custom-type.c
@@ -0,0 +1,76 @@
+#include "custom-type.h"
+#include "cmd.h"
+#include "common.h"
+
+#include
+#include
+#include
+
+static void
+custom_400(struct cmd *cmd) {
+ evhttp_send_reply(cmd->rq, 400, "Bad request", NULL);
+ cmd_free(cmd);
+}
+
+void
+custom_type_reply(redisAsyncContext *c, void *r, void *privdata) {
+
+ redisReply *reply = r;
+ struct cmd *cmd = privdata;
+ char *ct;
+ (void)c;
+
+ evhttp_clear_headers(&cmd->uri_params);
+
+ if (reply == NULL) {
+ evhttp_send_reply(cmd->rq, 404, "Not Found", NULL);
+ return;
+ }
+
+ if(cmd->mime) { /* use the given content-type */
+ if(reply->type != REDIS_REPLY_STRING) {
+ custom_400(cmd);
+ return;
+ }
+ format_send_reply(cmd, reply->str, reply->len, cmd->mime);
+ return;
+ }
+
+ /* we expect array(string, string) */
+ if(!cmd->mimeKey || reply->type != REDIS_REPLY_ARRAY || reply->elements != 2 || reply->element[0]->type != REDIS_REPLY_STRING) {
+ custom_400(cmd);
+ return;
+ }
+
+ /* case of MGET, we need to have a string for content-type in element[1] */
+ if(reply->element[1]->type == REDIS_REPLY_STRING) {
+ ct = reply->element[1]->str;
+ } else {
+ ct = "binary/octet-stream";
+ }
+
+ /* send reply */
+ format_send_reply(cmd, reply->element[0]->str, reply->element[0]->len, ct);
+ return;
+}
+
+/* This will change a GET command into MGET if a key is provided to get the response MIME-type from. */
+void
+custom_type_process_cmd(struct cmd *cmd) {
+ /* MGET if mode is “custom” */
+ if(cmd->count == 2 && cmd->argv_len[0] == 3 &&
+ strncasecmp(cmd->argv[0], "GET", 3) == 0 && cmd->mimeKey) {
+
+ cmd->count++; /* space for content-type key */
+ cmd->argv = realloc(cmd->argv, cmd->count * sizeof(char*));
+ cmd->argv_len = realloc(cmd->argv_len, cmd->count * sizeof(size_t));
+
+ /* replace command with MGET */
+ cmd->argv[0] = "MGET";
+ cmd->argv_len[0] = 4;
+
+ /* add mime key after the key. */
+ cmd->argv[2] = strdup(cmd->mimeKey);
+ cmd->argv_len[2] = strlen(cmd->mimeKey);
+ }
+}
diff --git a/formats/custom-type.h b/formats/custom-type.h
new file mode 100644
index 0000000..959e0be
--- /dev/null
+++ b/formats/custom-type.h
@@ -0,0 +1,15 @@
+#ifndef CUSTOM_TYPE_H
+#define CUSTOM_TYPE_H
+
+#include
+#include
+
+struct cmd;
+
+void
+custom_type_reply(redisAsyncContext *c, void *r, void *privdata);
+
+void
+custom_type_process_cmd(struct cmd *cmd);
+
+#endif
diff --git a/server.c b/server.c
index 4370a1e..f574711 100644
--- a/server.c
+++ b/server.c
@@ -95,6 +95,26 @@ server_copy(const struct server *s) {
return ret;
}
+/* Adobe flash cross-domain request */
+void
+on_flash_request(struct evhttp_request *rq, void *ctx) {
+
+ (void)ctx;
+
+ char out[] = "\n"
+"\n"
+"\n"
+ "\n"
+"\n";
+
+ struct evbuffer *body = evbuffer_new();
+ evbuffer_add(body, out, sizeof(out) - 1);
+
+ evhttp_add_header(rq->output_headers, "Content-Type", "application/xml");
+ evhttp_send_reply(rq, 200, "OK", body);
+ evbuffer_free(body);
+}
+
void
on_request(struct evhttp_request *rq, void *ctx) {
const char *uri = evhttp_request_uri(rq);
@@ -146,6 +166,7 @@ server_start(struct server *s) {
/* start http server */
slog(s->cfg->logfile,1,"Starting HTTP Server");
evhttp_bind_socket(s->http, s->cfg->http_host, s->cfg->http_port);
+ evhttp_set_cb(s->http, "/crossdomain.xml", on_flash_request, s);
evhttp_set_gencb(s->http, on_request, s);
/* drop privileges */