diff --git a/README.markdown b/README.markdown
index 514abf3..6554179 100644
--- a/README.markdown
+++ b/README.markdown
@@ -26,6 +26,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/
* Raw Redis 2.0 protocol output with `.raw` suffix
* BSON support for compact responses and MongoDB compatibility.
* HTTP 1.1 pipelining (70,000 http requests per second on a desktop Linux machine.)
+* WebSocket support.
* Connects to Redis using a TCP or UNIX socket.
* Restricted commands by IP range (CIDR subnet + mask) or HTTP Basic Auth, returning 403 errors.
* Possible Redis authentication in the config file.
@@ -48,7 +49,6 @@ curl -d "GET/hello" http://127.0.0.1:7379/
* Multi-server support, using consistent hashing.
* Database selection in the URL? e.g. `/7/GET/key` to run the command on DB 7.
* SSL?
-* Add WebSocket support (with which protocol?).
* Send your ideas using the github tracker, on twitter [@yowgi](http://twitter.com/yowgi) or by mail to n.favrefelix@gmail.com.
# HTTP error codes
@@ -252,3 +252,34 @@ $ md5sum redis-logo.png out.png
The file was uploaded and re-downloaded properly: it has the same hash and the content-type was set properly thanks to the `.png` extension.
+
+# WebSockets
+Webdis supports WebSocket clients implementing [dixie-76](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76).
+Web Sockets are supported with the following formats, selected by the connection URL:
+
+* JSON (on `/` or `/.json`)
+* Raw Redis wire protocol (on `/.raw`)
+
+**Example**:
+
+function testJSON() {
+ var jsonSocket = new WebSocket("ws://127.0.0.1:7379/.json");
+ jsonSocket.onopen = function() {
+
+ console.log("JSON socket connected!");
+ jsonSocket.send(JSON.stringify(["SET", "hello", "world"]));
+ jsonSocket.send(JSON.stringify(["GET", "hello"]));
+ };
+ jsonSocket.onmessage = function(messageEvent) {
+ console.log("JSON received:", messageEvent.data);
+ };
+}
+testJSON();
+
+
+This produces the following output:
+
+JSON socket connected!
+JSON received: {"SET":[true,"OK"]}
+JSON received: {"GET":"world"}
+
diff --git a/formats/raw.c b/formats/raw.c
index 76565e5..8d44454 100644
--- a/formats/raw.c
+++ b/formats/raw.c
@@ -1,6 +1,8 @@
#include "raw.h"
#include "common.h"
#include "http.h"
+#include "client.h"
+#include "cmd.h"
#include
#include
@@ -48,6 +50,70 @@ integer_length(long long int i) {
return sz;
}
+/* extract Redis protocol string from WebSocket frame and fill struct cmd. */
+struct cmd *
+raw_ws_extract(struct http_client *c, const char *p, size_t sz) {
+
+ struct cmd *cmd = NULL;
+ void *reader = NULL;
+ redisReply *reply = NULL;
+ unsigned int i;
+ (void)c;
+
+ /* create protocol reader */
+ reader = redisReplyReaderCreate();
+
+ /* add data */
+ redisReplyReaderFeed(reader, (char*)p, sz);
+
+ /* parse data into reply object */
+ if(redisReplyReaderGetReply(reader, (void**)&reply) == REDIS_ERR) {
+ goto end;
+ }
+
+ /* add data from reply object to cmd struct */
+ if(reply->type != REDIS_REPLY_ARRAY) {
+ goto end;
+ }
+
+ /* create cmd object */
+ cmd = cmd_new(reply->elements);
+
+ for(i = 0; i < reply->elements; ++i) {
+ redisReply *ri = reply->element[i];
+
+ switch(ri->type) {
+ case REDIS_REPLY_STRING:
+ cmd->argv_len[i] = ri->len;
+ cmd->argv[i] = calloc(cmd->argv_len[i] + 1, 1);
+ memcpy(cmd->argv[i], ri->str, ri->len);
+ break;
+
+ case REDIS_REPLY_INTEGER:
+ cmd->argv_len[i] = integer_length(ri->integer);
+ cmd->argv[i] = calloc(cmd->argv_len[i] + 1, 1);
+ sprintf(cmd->argv[i], "%lld", ri->integer);
+ break;
+
+ default:
+ cmd_free(cmd);
+ cmd = NULL;
+ goto end;
+ }
+ }
+
+
+end:
+ /* free reader */
+ if(reader) redisReplyReaderFree(reader);
+
+ /* free reply */
+ if(reply) freeReplyObject(reply);
+
+ return cmd;
+}
+
+
static char *
raw_array(const redisReply *r, size_t *sz) {
diff --git a/formats/raw.h b/formats/raw.h
index f25607f..10321e3 100644
--- a/formats/raw.h
+++ b/formats/raw.h
@@ -5,9 +5,12 @@
#include
struct cmd;
+struct http_client;
void
raw_reply(redisAsyncContext *c, void *r, void *privdata);
+struct cmd *
+raw_ws_extract(struct http_client *c, const char *p, size_t sz);
#endif
diff --git a/tests/websocket.html b/tests/websocket.html
new file mode 100644
index 0000000..921f35a
--- /dev/null
+++ b/tests/websocket.html
@@ -0,0 +1,40 @@
+
+
+ WebSocket example
+
+
+
+
diff --git a/websocket.c b/websocket.c
index 7f5c7c4..36d3ef3 100644
--- a/websocket.c
+++ b/websocket.c
@@ -1,11 +1,14 @@
#include "md5/md5.h"
#include "websocket.h"
#include "client.h"
-#include "formats/json.h"
#include "cmd.h"
#include "worker.h"
#include "pool.h"
+/* message parsers */
+#include "formats/json.h"
+#include "formats/raw.h"
+
#include
#include
#include
@@ -148,23 +151,33 @@ static int
ws_execute(struct http_client *c, const char *frame, size_t frame_len) {
struct cmd*(*fun_extract)(struct http_client *, const char *, size_t) = NULL;
+ formatting_fun fun_reply = NULL;
- if(strncmp(c->path, "/.json", 6) == 0) {
+ if((c->path_sz == 1 && strncmp(c->path, "/", 1) == 0) ||
+ strncmp(c->path, "/.json", 6) == 0) {
fun_extract = json_ws_extract;
+ fun_reply = json_reply;
+ } else if(strncmp(c->path, "/.raw", 5) == 0) {
+ fun_extract = raw_ws_extract;
+ fun_reply = raw_reply;
}
if(fun_extract) {
+
+ /* Parse websocket frame into a cmd object. */
struct cmd *cmd = fun_extract(c, frame, frame_len);
+
if(cmd) {
/* copy client info into cmd. */
cmd_setup(cmd, c);
cmd->is_websocket = 1;
- /* get redis connection from pool */
+ /* get Redis connection from pool */
redisAsyncContext *ac = (redisAsyncContext*)pool_get_context(c->w->pool);
/* send it off */
- cmd_send(ac, json_reply, cmd);
+ cmd_send(ac, fun_reply, cmd);
+
return 0;
}
}