Support Redis wire protocol over HTML5 WebSockets.

master
Nicolas Favre-Felix 14 years ago
parent 9f92ecfd12
commit b3f2b2f306

@ -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
</pre>
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**:
<pre>
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();
</pre>
This produces the following output:
<pre>
JSON socket connected!
JSON received: {"SET":[true,"OK"]}
JSON received: {"GET":"world"}
</pre>

@ -1,6 +1,8 @@
#include "raw.h"
#include "common.h"
#include "http.h"
#include "client.h"
#include "cmd.h"
#include <string.h>
#include <hiredis/hiredis.h>
@ -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) {

@ -5,9 +5,12 @@
#include <hiredis/async.h>
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

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head><title>WebSocket example</title></head>
<body>
<script type="text/javascript">
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);
};
}
function testRAW() {
var rawSocket = new WebSocket("ws://127.0.0.1:7379/.raw");
rawSocket.onopen = function() {
console.log("raw socket connected!");
rawSocket.send("*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n");
rawSocket.send("*2\r\n$3\r\nGET\r\n$5\r\nhello\r\n");
};
rawSocket.onmessage = function(messageEvent) {
console.log("Raw received:", messageEvent.data);
};
}
addEventListener("DOMContentLoaded", function(){
testJSON();
testRAW();
}, null);
</script>
</body>
</html>

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
@ -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;
}
}

Loading…
Cancel
Save