BINS = hiredis-example hiredis-test
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
-OPTIMIZATION?=-O2
+OPTIMIZATION?=-O3
ifeq ($(uname_S),SunOS)
- CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6
- CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
+ CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6 $(ARCH) $(PROF)
+ CCLINK?=-ldl -lnsl -lsocket -lm -lpthread
+ LDFLAGS?=-L. -Wl,-R,.
DYLIBNAME?=libhiredis.so
- DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
+ DYLIB_MAKE_CMD?=$(CC) -G -o ${DYLIBNAME} ${OBJ}
STLIBNAME?=libhiredis.a
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
else ifeq ($(uname_S),Darwin)
- CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
- CCLINK?= -lm -pthread
- OBJARCH?= -arch i386 -arch x86_64
+ CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
+ CCLINK?=-lm -pthread
+ LDFLAGS?=-L. -Wl,-rpath,.
+ OBJARCH?=-arch i386 -arch x86_64
DYLIBNAME?=libhiredis.dylib
DYLIB_MAKE_CMD?=libtool -dynamic -o ${DYLIBNAME} -lm ${DEBUG} - ${OBJ}
STLIBNAME?=libhiredis.a
STLIB_MAKE_CMD?=libtool -static -o ${STLIBNAME} - ${OBJ}
else
- CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
- CCLINK?= -lm -pthread
+ CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
+ CCLINK?=-lm -pthread
+ LDFLAGS?=-L. -Wl,-rpath,.
DYLIBNAME?=libhiredis.so
DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
STLIBNAME?=libhiredis.a
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
endif
-CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
-DEBUG?= -g -ggdb
+CCOPT= $(CFLAGS) $(CCLINK)
+DEBUG?= -g -ggdb
PREFIX?= /usr/local
INSTALL_INC= $(PREFIX)/include/hiredis
# Deps (use make dep to generate this)
net.o: net.c fmacros.h net.h
async.o: async.c async.h hiredis.h sds.h util.h
-example-libev.o: example-libev.c hiredis.h async.h adapters/libev.h
-example-libevent.o: example-libevent.c hiredis.h async.h adapters/libevent.h
example.o: example.c hiredis.h
hiredis.o: hiredis.c hiredis.h net.h sds.h util.h
sds.o: sds.c sds.h
static: ${STLIBNAME}
# Binaries:
-hiredis-example-libevent: example-libevent.o ${DYLIBNAME}
- $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -levent -Wl,-rpath,. example-libevent.c
+hiredis-example-libevent: example-libevent.c adapters/libevent.h ${DYLIBNAME}
+ $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -levent example-libevent.c
+
+hiredis-example-libev: example-libev.c adapters/libev.h ${DYLIBNAME}
+ $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -lev example-libev.c
-hiredis-example-libev: example-libev.o ${DYLIBNAME}
- $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -lev -Wl,-rpath,. example-libev.c
+ifndef AE_DIR
+hiredis-example-ae:
+ @echo "Please specify AE_DIR (e.g. <redis repository>/src)"
+ @false
+else
+hiredis-example-ae: example-ae.c adapters/ae.h ${DYLIBNAME}
+ $(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) $(LDFLAGS) -lhiredis example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o
+endif
hiredis-%: %.o ${DYLIBNAME}
- $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -Wl,-rpath,. $<
+ $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis $<
test: hiredis-test
./hiredis-test
### Connecting
-The function `redisConnect` is used to create a so-called `redisContext`. The context is where
-Hiredis holds state for a connection. The `redisContext` struct has an `error` field that is
-non-NULL when the connection is in an error state. It contains a string with a textual
-representation of the error. After trying to connect to Redis using `redisConnect` you should
-check the `error` field to see if establishing the connection was successful:
+The function `redisConnect` is used to create a so-called `redisContext`. The
+context is where Hiredis holds state for a connection. The `redisContext`
+struct has an integer `err` field that is non-zero when an the connection is in
+an error state. The field `errstr` will contain a string with a description of
+the error. More information on errors can be found in the **Errors** section.
+After trying to connect to Redis using `redisConnect` you should
+check the `err` field to see if establishing the connection was successful:
redisContext *c = redisConnect("127.0.0.1", 6379);
- if (c->error != NULL) {
- printf("Error: %s\n", c->error);
- // handle error
+ if (c->err) {
+ printf("Error: %s\n", c->errstr);
+ // handle error
}
### Sending commands
### Using replies
The return value of `redisCommand` holds a reply when the command was
-successfully executed. When the return value is `NULL`, the `error` field
-in the context can be used to find out what was the cause of failure.
+successfully executed. When an error occurs, the return value is `NULL` and
+the `err` field in the context will be set (see section on **Errors**).
Once an error is returned the context cannot be reused and you should set up
a new connection.
After calling either function one or more times, `redisGetReply` can be used to receive the
subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
the latter means an error occurred while reading a reply. Just as with the other commands,
-the `error` field in the context can be used to find out what the cause of this error is.
+the `err` field in the context can be used to find out what the cause of this error is.
The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
a single call to `write(2)`):
reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply);
while(redisGetReply(context,&reply) == REDIS_OK) {
- // consume message
- freeReplyObject(reply);
+ // consume message
+ freeReplyObject(reply);
}
+### Errors
+
+When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
+returned. The `err` field inside the context will be non-zero and set to one of the
+following constants:
+
+* **`REDIS_ERR_IO`**:
+ There was an I/O error while creating the connection, trying to write
+ to the socket or read from the socket. If you included `errno.h` in your
+ application, you can use the global `errno` variable to find out what is
+ wrong.
+
+* **`REDIS_ERR_EOF`**:
+ The server closed the connection which resulted in an empty read.
+
+* **`REDIS_ERR_PROTOCOL`**:
+ There was an error while parsing the protocol.
+
+* **`REDIS_ERR_OTHER`**:
+ Any other error. Currently, it is only used when a specified hostname to connect
+ to cannot be resolved.
+
+In every case, the `errstr` field in the context will be set to hold a string representation
+of the error.
+
## Asynchronous API
Hiredis comes with an asynchronous API that works easily with any event library.
### Connecting
The function `redisAsyncConnect` can be used to establish a non-blocking connection to
-Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `error` field
+Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
should be checked after creation to see if there were errors creating the connection.
Because the connection that will be created is non-blocking, the kernel is not able to
instantly return if the specified host and port is able to accept a connection.
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
- if (c->error != NULL) {
- printf("Error: %s\n", c->error);
- // handle error
+ if (c->err) {
+ printf("Error: %s\n", c->errstr);
+ // handle error
}
The asynchronous context can hold a disconnect callback function that is called when the
void(const redisAsyncContext *c, int status);
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
-user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `error`
+user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
field in the context can be accessed to find out the cause of the error.
The context object is always free'd after the disconnect callback fired. When a reconnect is needed,
--- /dev/null
+#include <sys/types.h>
+#include <ae.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisAeEvents {
+ redisAsyncContext *context;
+ aeEventLoop *loop;
+ int fd;
+ int reading, writing;
+} redisAeEvents;
+
+void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
+ ((void)el); ((void)fd); ((void)mask);
+
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ redisAsyncHandleRead(e->context);
+}
+
+void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
+ ((void)el); ((void)fd); ((void)mask);
+
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ redisAsyncHandleWrite(e->context);
+}
+
+void redisAeAddRead(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (!e->reading) {
+ e->reading = 1;
+ aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);
+ }
+}
+
+void redisAeDelRead(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (e->reading) {
+ e->reading = 0;
+ aeDeleteFileEvent(loop,e->fd,AE_READABLE);
+ }
+}
+
+void redisAeAddWrite(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (!e->writing) {
+ e->writing = 1;
+ aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
+ }
+}
+
+void redisAeDelWrite(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (e->writing) {
+ e->writing = 0;
+ aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);
+ }
+}
+
+void redisAeCleanup(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ redisAeDelRead(privdata);
+ redisAeDelWrite(privdata);
+ free(e);
+}
+
+int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisAeEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->_adapter_data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = (redisAeEvents*)malloc(sizeof(*e));
+ e->context = ac;
+ e->loop = loop;
+ e->fd = c->fd;
+ e->reading = e->writing = 0;
+
+ /* Register functions to start/stop listening for events */
+ ac->evAddRead = redisAeAddRead;
+ ac->evDelRead = redisAeDelRead;
+ ac->evAddWrite = redisAeAddWrite;
+ ac->evDelWrite = redisAeDelWrite;
+ ac->evCleanup = redisAeCleanup;
+ ac->_adapter_data = e;
+
+ return REDIS_OK;
+}
+
ev_io rev, wev;
} redisLibevEvents;
-void redisLibevReadEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
- ((void)loop); ((void)revents);
- redisLibevEvents *e = watcher->data;
+void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+ ((void)loop);
+#endif
+ ((void)revents);
+
+ redisLibevEvents *e = (redisLibevEvents*)watcher->data;
redisAsyncHandleRead(e->context);
}
-void redisLibevWriteEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
- ((void)loop); ((void)revents);
- redisLibevEvents *e = watcher->data;
+void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+ ((void)loop);
+#endif
+ ((void)revents);
+
+ redisLibevEvents *e = (redisLibevEvents*)watcher->data;
redisAsyncHandleWrite(e->context);
}
void redisLibevAddRead(void *privdata) {
- redisLibevEvents *e = privdata;
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ struct ev_loop *loop = e->loop;
+ ((void)loop);
if (!e->reading) {
e->reading = 1;
- ev_io_start(e->loop,&e->rev);
+ ev_io_start(EV_A_ &e->rev);
}
}
void redisLibevDelRead(void *privdata) {
- redisLibevEvents *e = privdata;
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ struct ev_loop *loop = e->loop;
+ ((void)loop);
if (e->reading) {
e->reading = 0;
- ev_io_stop(e->loop,&e->rev);
+ ev_io_stop(EV_A_ &e->rev);
}
}
void redisLibevAddWrite(void *privdata) {
- redisLibevEvents *e = privdata;
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ struct ev_loop *loop = e->loop;
+ ((void)loop);
if (!e->writing) {
e->writing = 1;
- ev_io_start(e->loop,&e->wev);
+ ev_io_start(EV_A_ &e->wev);
}
}
void redisLibevDelWrite(void *privdata) {
- redisLibevEvents *e = privdata;
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ struct ev_loop *loop = e->loop;
+ ((void)loop);
if (e->writing) {
e->writing = 0;
- ev_io_stop(e->loop,&e->wev);
+ ev_io_stop(EV_A_ &e->wev);
}
}
void redisLibevCleanup(void *privdata) {
- redisLibevEvents *e = privdata;
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
redisLibevDelRead(privdata);
redisLibevDelWrite(privdata);
free(e);
}
-int redisLibevAttach(redisAsyncContext *ac, struct ev_loop *loop) {
+int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisLibevEvents *e;
/* Nothing should be attached when something is already attached */
- if (ac->data != NULL)
+ if (ac->_adapter_data != NULL)
return REDIS_ERR;
/* Create container for context and r/w events */
- e = malloc(sizeof(*e));
+ e = (redisLibevEvents*)malloc(sizeof(*e));
e->context = ac;
+#if EV_MULTIPLICITY
e->loop = loop;
+#else
+ e->loop = NULL;
+#endif
e->reading = e->writing = 0;
e->rev.data = e;
e->wev.data = e;
ac->evAddWrite = redisLibevAddWrite;
ac->evDelWrite = redisLibevDelWrite;
ac->evCleanup = redisLibevCleanup;
- ac->data = e;
+ ac->_adapter_data = e;
/* Initialize read/write events */
ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
return REDIS_OK;
}
+
void redisLibeventReadEvent(int fd, short event, void *arg) {
((void)fd); ((void)event);
- redisLibeventEvents *e = arg;
+ redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleRead(e->context);
}
void redisLibeventWriteEvent(int fd, short event, void *arg) {
((void)fd); ((void)event);
- redisLibeventEvents *e = arg;
+ redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleWrite(e->context);
}
void redisLibeventAddRead(void *privdata) {
- redisLibeventEvents *e = privdata;
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_add(&e->rev,NULL);
}
void redisLibeventDelRead(void *privdata) {
- redisLibeventEvents *e = privdata;
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(&e->rev);
}
void redisLibeventAddWrite(void *privdata) {
- redisLibeventEvents *e = privdata;
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_add(&e->wev,NULL);
}
void redisLibeventDelWrite(void *privdata) {
- redisLibeventEvents *e = privdata;
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(&e->wev);
}
void redisLibeventCleanup(void *privdata) {
- redisLibeventEvents *e = privdata;
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(&e->rev);
event_del(&e->wev);
free(e);
redisLibeventEvents *e;
/* Nothing should be attached when something is already attached */
- if (ac->data != NULL)
+ if (ac->_adapter_data != NULL)
return REDIS_ERR;
/* Create container for context and r/w events */
- e = malloc(sizeof(*e));
+ e = (redisLibeventEvents*)malloc(sizeof(*e));
e->context = ac;
/* Register functions to start/stop listening for events */
ac->evAddWrite = redisLibeventAddWrite;
ac->evDelWrite = redisLibeventDelWrite;
ac->evCleanup = redisLibeventCleanup;
- ac->data = e;
+ ac->_adapter_data = e;
/* Initialize and install read/write events */
event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
/*
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
- /* Set all bytes in the async part of the context to 0 */
- memset(ac+sizeof(redisContext),0,sizeof(redisAsyncContext)-sizeof(redisContext));
+ c = &(ac->c);
+
+ /* The regular connect functions will always set the flag REDIS_CONNECTED.
+ * For the async API, we want to wait until the first write event is
+ * received up before setting this flag, so reset it here. */
+ c->flags &= ~REDIS_CONNECTED;
+
+ ac->err = 0;
+ ac->errstr = NULL;
+ ac->data = NULL;
+ ac->_adapter_data = NULL;
+
+ ac->evAddRead = NULL;
+ ac->evDelRead = NULL;
+ ac->evAddWrite = NULL;
+ ac->evDelWrite = NULL;
+ ac->evCleanup = NULL;
+
+ ac->onConnect = NULL;
+ ac->onDisconnect = NULL;
+
+ ac->replies.head = NULL;
+ ac->replies.tail = NULL;
return ac;
}
return redisSetReplyObjectFunctions(c,fn);
}
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
+ if (ac->onConnect == NULL) {
+ ac->onConnect = fn;
+ return REDIS_OK;
+ }
+ return REDIS_ERR;
+}
+
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
if (ac->onDisconnect == NULL) {
ac->onDisconnect = fn;
}
/* Signal event lib to clean up */
- if (ac->evCleanup) ac->evCleanup(ac->data);
+ if (ac->evCleanup) ac->evCleanup(ac->_adapter_data);
/* Execute callback with proper status */
if (ac->onDisconnect) ac->onDisconnect(ac,status);
__redisAsyncDisconnect(ac);
} else {
/* Always re-schedule reads */
- if (ac->evAddRead) ac->evAddRead(ac->data);
+ if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
redisProcessCallbacks(ac);
}
}
} else {
/* Continue writing when not done, stop writing otherwise */
if (!done) {
- if (ac->evAddWrite) ac->evAddWrite(ac->data);
+ if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
} else {
- if (ac->evDelWrite) ac->evDelWrite(ac->data);
+ if (ac->evDelWrite) ac->evDelWrite(ac->_adapter_data);
}
- /* Always schedule reads when something was written */
- if (ac->evAddRead) ac->evAddRead(ac->data);
+ /* Always schedule reads after writes */
+ if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
+
+ /* Fire onConnect when this is the first write event. */
+ if (!(c->flags & REDIS_CONNECTED)) {
+ c->flags |= REDIS_CONNECTED;
+ if (ac->onConnect) ac->onConnect(ac);
+ }
}
}
__redisPushCallback(&ac->replies,&cb);
/* Always schedule a write when the write buffer is non-empty */
- if (ac->evAddWrite) ac->evAddWrite(ac->data);
+ if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
return REDIS_OK;
}
#define __HIREDIS_ASYNC_H
#include "hiredis.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
/* Reply callback prototype and container */
redisCallback *head, *tail;
} redisCallbackList;
-/* Disconnect callback prototype */
+/* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
+typedef void (redisConnectCallback)(const struct redisAsyncContext*);
/* Context for an async connection to Redis */
typedef struct redisAsyncContext {
int err;
char *errstr;
+ /* Not used by hiredis */
+ void *data;
+
+ /* Used by the different event lib adapters to store their private data */
+ void *_adapter_data;
+
/* Called when the library expects to start reading/writing.
* The supplied functions should be idempotent. */
void (*evAddRead)(void *privdata);
void (*evAddWrite)(void *privdata);
void (*evDelWrite)(void *privdata);
void (*evCleanup)(void *privdata);
- void *data;
/* Called when either the connection is terminated due to an error or per
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
redisDisconnectCallback *onDisconnect;
+ /* Called when the first write event was received. */
+ redisConnectCallback *onConnect;
+
/* Reply callbacks */
redisCallbackList replies;
} redisAsyncContext;
/* Functions that proxy to hiredis */
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn);
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
void redisAsyncDisconnect(redisAsyncContext *ac);
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
+#ifdef __cplusplus
+}
+#endif
+
#endif
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include "hiredis.h"
+#include "async.h"
+#include "adapters/ae.h"
+
+/* Put event loop in the global scope, so it can be explicitly stopped */
+static aeEventLoop *loop;
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c) {
+ ((void)c);
+ printf("connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ }
+ printf("disconnected...\n");
+ aeStop(loop);
+}
+
+int main (int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ loop = aeCreateEventLoop();
+ redisAeAttach(loop, c);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+ aeMain(loop);
+ return 0;
+}
+
redisAsyncDisconnect(c);
}
+void connectCallback(const redisAsyncContext *c) {
+ ((void)c);
+ printf("connected...\n");
+}
+
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
}
+ printf("disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
- struct ev_loop *loop = ev_default_loop(0);
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
return 1;
}
- redisLibevAttach(c,loop);
+ redisLibevAttach(EV_DEFAULT_ c);
+ redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
- ev_loop(loop, 0);
+ ev_loop(EV_DEFAULT_ 0);
return 0;
}
redisAsyncDisconnect(c);
}
+void connectCallback(const redisAsyncContext *c) {
+ ((void)c);
+ printf("connected...\n");
+}
+
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
}
+ printf("disconnected...\n");
}
int main (int argc, char **argv) {
}
redisLibeventAttach(c,base);
+ redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
/* PING server */
reply = redisCommand(c,"PING");
- printf("PONG: %s\n", reply->str);
+ printf("PING: %s\n", reply->str);
freeReplyObject(reply);
/* Set a key */
/*
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
#include <unistd.h>
#include <assert.h>
#include <errno.h>
+#include <ctype.h>
#include "hiredis.h"
#include "net.h"
void *reply; /* holds temporary reply */
sds buf; /* read buffer */
- unsigned int pos; /* buffer cursor */
+ size_t pos; /* buffer cursor */
+ size_t len; /* buffer length */
redisReadTask rstack[3]; /* stack of read tasks */
int ridx; /* index of stack */
+ void *privdata; /* user-settable arbitrary field */
} redisReader;
static redisReply *createReplyObject(int type);
/* Create a reply object */
static redisReply *createReplyObject(int type) {
- redisReply *r = calloc(sizeof(*r),1);
+ redisReply *r = malloc(sizeof(*r));
if (!r) redisOOM();
r->type = type;
if (r->element[j]) freeReplyObject(r->element[j]);
free(r->element);
break;
- default:
- if (r->str != NULL)
- free(r->str);
+ case REDIS_REPLY_ERROR:
+ case REDIS_REPLY_STATUS:
+ case REDIS_REPLY_STRING:
+ free(r->str);
break;
}
free(r);
r->len = len;
if (task->parent) {
- redisReply *parent = task->parent;
+ redisReply *parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
if ((r->element = calloc(sizeof(redisReply*),elements)) == NULL)
redisOOM();
if (task->parent) {
- redisReply *parent = task->parent;
+ redisReply *parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
redisReply *r = createReplyObject(REDIS_REPLY_INTEGER);
r->integer = value;
if (task->parent) {
- redisReply *parent = task->parent;
+ redisReply *parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
static void *createNilObject(const redisReadTask *task) {
redisReply *r = createReplyObject(REDIS_REPLY_NIL);
if (task->parent) {
- redisReply *parent = task->parent;
+ redisReply *parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
static char *readBytes(redisReader *r, unsigned int bytes) {
char *p;
- if (sdslen(r->buf)-r->pos >= bytes) {
+ if (r->len-r->pos >= bytes) {
p = r->buf+r->pos;
r->pos += bytes;
return p;
return NULL;
}
-static char *seekNewline(char *s) {
- /* Find pointer to \r\n without strstr */
- while (s != NULL) {
- s = strchr(s,'\r');
- if (s != NULL) {
- if (s[1] == '\n')
- break;
- else
- s++;
+/* Find pointer to \r\n. */
+static char *seekNewline(char *s, size_t len) {
+ int pos = 0;
+ int _len = len-1;
+
+ /* Position should be < len-1 because the character at "pos" should be
+ * followed by a \n. Note that strchr cannot be used because it doesn't
+ * allow to search a limited length and the buffer that is being searched
+ * might not have a trailing NULL character. */
+ while (pos < _len) {
+ while(pos < _len && s[pos] != '\r') pos++;
+ if (s[pos] != '\r') {
+ /* Not found. */
+ return NULL;
} else {
- break;
+ if (s[pos+1] == '\n') {
+ /* Found. */
+ return s+pos;
+ } else {
+ /* Continue searching. */
+ pos++;
+ }
}
}
- return s;
+ return NULL;
+}
+
+/* Read a long long value starting at *s, under the assumption that it will be
+ * terminated by \r\n. Ambiguously returns -1 for unexpected input. */
+static long long readLongLong(char *s) {
+ long long v = 0;
+ int dec, mult = 1;
+ char c;
+
+ if (*s == '-') {
+ mult = -1;
+ s++;
+ } else if (*s == '+') {
+ mult = 1;
+ s++;
+ }
+
+ while ((c = *(s++)) != '\r') {
+ dec = c - '0';
+ if (dec >= 0 && dec < 10) {
+ v *= 10;
+ v += dec;
+ } else {
+ /* Should not happen... */
+ return -1;
+ }
+ }
+
+ return mult*v;
}
static char *readLine(redisReader *r, int *_len) {
int len;
p = r->buf+r->pos;
- s = seekNewline(p);
+ s = seekNewline(p,(r->len-r->pos));
if (s != NULL) {
len = s-(r->buf+r->pos);
r->pos += len+2; /* skip \r\n */
if ((p = readLine(r,&len)) != NULL) {
if (r->fn) {
if (cur->type == REDIS_REPLY_INTEGER) {
- obj = r->fn->createInteger(cur,strtoll(p,NULL,10));
+ obj = r->fn->createInteger(cur,readLongLong(p));
} else {
obj = r->fn->createString(cur,p,len);
}
obj = (void*)(size_t)(cur->type);
}
- /* If there is no root yet, register this object as root. */
- if (r->reply == NULL)
- r->reply = obj;
+ /* Set reply if this is the root object. */
+ if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return 0;
}
char *p, *s;
long len;
unsigned long bytelen;
+ int success = 0;
p = r->buf+r->pos;
- s = seekNewline(p);
+ s = seekNewline(p,r->len-r->pos);
if (s != NULL) {
p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
- len = strtol(p,NULL,10);
+ len = readLongLong(p);
if (len < 0) {
/* The nil object can always be created. */
obj = r->fn ? r->fn->createNil(cur) :
(void*)REDIS_REPLY_NIL;
+ success = 1;
} else {
/* Only continue when the buffer contains the entire bulk item. */
bytelen += len+2; /* include \r\n */
- if (r->pos+bytelen <= sdslen(r->buf)) {
+ if (r->pos+bytelen <= r->len) {
obj = r->fn ? r->fn->createString(cur,s+2,len) :
(void*)REDIS_REPLY_STRING;
+ success = 1;
}
}
/* Proceed when obj was created. */
- if (obj != NULL) {
+ if (success) {
r->pos += bytelen;
- if (r->reply == NULL)
- r->reply = obj;
+
+ /* Set reply if this is the root object. */
+ if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return 0;
}
void *obj;
char *p;
long elements;
+ int root = 0;
+
+ /* Set error for nested multi bulks with depth > 1 */
+ if (r->ridx == 2) {
+ redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
+ "No support for nested multi bulk replies with depth > 1"));
+ return -1;
+ }
if ((p = readLine(r,NULL)) != NULL) {
- elements = strtol(p,NULL,10);
+ elements = readLongLong(p);
+ root = (r->ridx == 0);
+
if (elements == -1) {
obj = r->fn ? r->fn->createNil(cur) :
(void*)REDIS_REPLY_NIL;
/* Modify task stack when there are more than 0 elements. */
if (elements > 0) {
cur->elements = elements;
+ cur->obj = obj;
r->ridx++;
r->rstack[r->ridx].type = -1;
r->rstack[r->ridx].elements = -1;
- r->rstack[r->ridx].parent = obj;
r->rstack[r->ridx].idx = 0;
+ r->rstack[r->ridx].obj = NULL;
+ r->rstack[r->ridx].parent = cur;
+ r->rstack[r->ridx].privdata = r->privdata;
} else {
moveToNextTask(r);
}
}
- /* Object was created, so we can always continue. */
- if (r->reply == NULL)
- r->reply = obj;
+ /* Set reply if this is the root object. */
+ if (root) r->reply = obj;
return 0;
}
return -1;
default:
byte = sdscatrepr(sdsempty(),p,1);
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
- "protocol error, got %s as reply type byte", byte));
+ "Protocol error, got %s as reply type byte", byte));
sdsfree(byte);
return -1;
}
case REDIS_REPLY_ARRAY:
return processMultiBulkItem(r);
default:
- redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
- "unknown item type '%d'", cur->type));
+ assert(NULL);
return -1;
}
}
return REDIS_ERR;
}
+/* Set the private data field that is used in the read tasks. This argument can
+ * be used to curry arbitrary data to the custom reply object functions. */
+int redisReplyReaderSetPrivdata(void *reader, void *privdata) {
+ redisReader *r = reader;
+ if (r->reply == NULL) {
+ r->privdata = privdata;
+ return REDIS_OK;
+ }
+ return REDIS_ERR;
+}
+
/* External libraries wrapping hiredis might need access to the temporary
* variable while the reply is built up. When the reader contains an
* object in between receiving some bytes to parse, this object might
redisReader *r = reader;
/* Copy the provided buffer. */
- if (buf != NULL && len >= 1)
+ if (buf != NULL && len >= 1) {
r->buf = sdscatlen(r->buf,buf,len);
+ r->len = sdslen(r->buf);
+ }
}
int redisReplyReaderGetReply(void *reader, void **reply) {
if (reply != NULL) *reply = NULL;
/* When the buffer is empty, there will never be a reply. */
- if (sdslen(r->buf) == 0)
+ if (r->len == 0)
return REDIS_OK;
/* Set first item to process when the stack is empty. */
if (r->ridx == -1) {
r->rstack[0].type = -1;
r->rstack[0].elements = -1;
- r->rstack[0].parent = NULL;
r->rstack[0].idx = -1;
+ r->rstack[0].obj = NULL;
+ r->rstack[0].parent = NULL;
+ r->rstack[0].privdata = r->privdata;
r->ridx = 0;
}
/* Discard the consumed part of the buffer. */
if (r->pos > 0) {
- if (r->pos == sdslen(r->buf)) {
+ if (r->pos == r->len) {
/* sdsrange has a quirck on this edge case. */
sdsfree(r->buf);
r->buf = sdsempty();
} else {
- r->buf = sdsrange(r->buf,r->pos,sdslen(r->buf));
+ r->buf = sdsrange(r->buf,r->pos,r->len);
}
r->pos = 0;
+ r->len = sdslen(r->buf);
}
/* Emit a reply when there is one. */
r->reply = NULL;
/* Destroy the buffer when it is empty and is quite large. */
- if (sdslen(r->buf) == 0 && sdsavail(r->buf) > 16*1024) {
+ if (r->len == 0 && sdsavail(r->buf) > 16*1024) {
sdsfree(r->buf);
r->buf = sdsempty();
r->pos = 0;
char *cmd = NULL; /* final command */
int pos; /* position in final command */
sds current; /* current argument */
+ int interpolated = 0; /* did we do interpolation on an argument? */
char **argv = NULL;
int argc = 0, j;
int totlen = 0;
if (sdslen(current) != 0) {
addArgument(current, &argv, &argc, &totlen);
current = sdsempty();
+ interpolated = 0;
}
} else {
current = sdscatlen(current,c,1);
switch(c[1]) {
case 's':
arg = va_arg(ap,char*);
- current = sdscat(current,arg);
+ size = strlen(arg);
+ if (size > 0)
+ current = sdscatlen(current,arg,size);
+ interpolated = 1;
break;
case 'b':
arg = va_arg(ap,char*);
size = va_arg(ap,size_t);
- current = sdscatlen(current,arg,size);
+ if (size > 0)
+ current = sdscatlen(current,arg,size);
+ interpolated = 1;
break;
case '%':
- cmd = sdscat(cmd,"%");
+ current = sdscat(current,"%");
break;
+ default:
+ /* Try to detect printf format */
+ {
+ char _format[16];
+ const char *_p = c+1;
+ size_t _l = 0;
+ va_list _cpy;
+
+ /* Flags */
+ if (*_p != '\0' && *_p == '#') _p++;
+ if (*_p != '\0' && *_p == '0') _p++;
+ if (*_p != '\0' && *_p == '-') _p++;
+ if (*_p != '\0' && *_p == ' ') _p++;
+ if (*_p != '\0' && *_p == '+') _p++;
+
+ /* Field width */
+ while (*_p != '\0' && isdigit(*_p)) _p++;
+
+ /* Precision */
+ if (*_p == '.') {
+ _p++;
+ while (*_p != '\0' && isdigit(*_p)) _p++;
+ }
+
+ /* Modifiers */
+ if (*_p != '\0') {
+ if (*_p == 'h' || *_p == 'l') {
+ /* Allow a single repetition for these modifiers */
+ if (_p[0] == _p[1]) _p++;
+ _p++;
+ }
+ }
+
+ /* Conversion specifier */
+ if (*_p != '\0' && strchr("diouxXeEfFgGaA",*_p) != NULL) {
+ _l = (_p+1)-c;
+ if (_l < sizeof(_format)-2) {
+ memcpy(_format,c,_l);
+ _format[_l] = '\0';
+ va_copy(_cpy,ap);
+ current = sdscatvprintf(current,_format,_cpy);
+ interpolated = 1;
+ va_end(_cpy);
+
+ /* Update current position (note: outer blocks
+ * increment c twice so compensate here) */
+ c = _p-1;
+ }
+ }
+
+ /* Consume and discard vararg */
+ va_arg(ap,void);
+ }
}
c++;
}
}
/* Add the last argument if needed */
- if (sdslen(current) != 0) {
+ if (interpolated || sdslen(current) != 0) {
addArgument(current, &argv, &argc, &totlen);
} else {
sdsfree(current);
static redisContext *redisContextInit() {
redisContext *c = calloc(sizeof(redisContext),1);
- c->fd = -1; /* quick fix for a bug that should be addressed differently */
c->err = 0;
c->errstr = NULL;
c->obuf = sdsempty();
redisContext *redisConnect(const char *ip, int port) {
redisContext *c = redisContextInit();
c->flags |= REDIS_BLOCK;
- c->flags |= REDIS_CONNECTED;
redisContextConnectTcp(c,ip,port);
return c;
}
redisContext *redisConnectNonBlock(const char *ip, int port) {
redisContext *c = redisContextInit();
c->flags &= ~REDIS_BLOCK;
- c->flags |= REDIS_CONNECTED;
redisContextConnectTcp(c,ip,port);
return c;
}
redisContext *redisConnectUnix(const char *path) {
redisContext *c = redisContextInit();
c->flags |= REDIS_BLOCK;
- c->flags |= REDIS_CONNECTED;
redisContextConnectUnix(c,path);
return c;
}
redisContext *redisConnectUnixNonBlock(const char *path) {
redisContext *c = redisContextInit();
c->flags &= ~REDIS_BLOCK;
- c->flags |= REDIS_CONNECTED;
redisContextConnectUnix(c,path);
return c;
}
/*
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
#define HIREDIS_MAJOR 0
#define HIREDIS_MINOR 9
-#define HIREDIS_PATCH 0
+#define HIREDIS_PATCH 2
#define REDIS_ERR -1
#define REDIS_OK 0
* should be terminated once all replies have been read. */
#define REDIS_DISCONNECTING 0x4
-#define REDIS_REPLY_ERROR 0
#define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
+#define REDIS_REPLY_ERROR 6
+
+#ifdef __cplusplus
+extern "C" {
+#endif
/* This is the reply object returned by redisCommand() */
typedef struct redisReply {
typedef struct redisReadTask {
int type;
int elements; /* number of elements in multibulk container */
- void *parent; /* optional pointer to parent object */
int idx; /* index in parent (array) object */
+ void *obj; /* holds user-generated value for a read task */
+ struct redisReadTask *parent; /* parent task */
+ void *privdata; /* user-settable arbitrary field */
} redisReadTask;
typedef struct redisReplyObjectFunctions {
void freeReplyObject(void *reply);
void *redisReplyReaderCreate();
int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn);
+int redisReplyReaderSetPrivdata(void *reader, void *privdata);
void *redisReplyReaderGetObject(void *reader);
char *redisReplyReaderGetError(void *reader);
void redisReplyReaderFree(void *ptr);
void *redisCommand(redisContext *c, const char *format, ...);
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+#ifdef __cplusplus
+}
+#endif
+
#endif
/* Extracted from anet.c to work properly with Hiredis error reporting.
*
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
- * All rights reserved.
+ * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
+ * All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
#include <stdarg.h>
#include <stdio.h>
-#include "hiredis.h"
+#include "net.h"
#include "sds.h"
/* Forward declaration */
he = gethostbyname(addr);
if (he == NULL) {
__redisSetError(c,REDIS_ERR_OTHER,
- sdscatprintf(sdsempty(),"can't resolve: %s",addr));
+ sdscatprintf(sdsempty(),"Can't resolve: %s",addr));
close(s);
return REDIS_ERR;
}
}
c->fd = s;
+ c->flags |= REDIS_CONNECTED;
return REDIS_OK;
}
}
c->fd = s;
+ c->flags |= REDIS_CONNECTED;
return REDIS_OK;
}
#ifndef __NET_H
#define __NET_H
+#include "hiredis.h"
+
+#if defined(__sun)
+#define AF_LOCAL AF_UNIX
+#endif
+
int redisContextConnectTcp(redisContext *c, const char *addr, int port);
int redisContextConnectUnix(redisContext *c, const char *path);
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd);
+ test("Format command with %%s and an empty string: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+ free(cmd);
+
test("Format command with %%b string interpolation: ");
len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd);
+ test("Format command with %%b and an empty string: ");
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+ free(cmd);
+
+ test("Format command with literal %%: ");
+ len = redisFormatCommand(&cmd,"SET %% %%");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(1+2)+4+(1+2));
+ free(cmd);
+
+ test("Format command with printf-delegation (long long): ");
+ len = redisFormatCommand(&cmd,"key:%08lld",1234ll);
+ test_cond(strncmp(cmd,"*1\r\n$12\r\nkey:00001234\r\n",len) == 0 &&
+ len == 4+5+(12+2));
+ free(cmd);
+
+ test("Format command with printf-delegation (float): ");
+ len = redisFormatCommand(&cmd,"v:%06.1f",12.34f);
+ test_cond(strncmp(cmd,"*1\r\n$8\r\nv:0012.3\r\n",len) == 0 &&
+ len == 4+4+(8+2));
+ free(cmd);
+
+ test("Format command with printf-delegation and extra interpolation: ");
+ len = redisFormatCommand(&cmd,"key:%d %b",1234,"foo",3);
+ test_cond(strncmp(cmd,"*2\r\n$8\r\nkey:1234\r\n$3\r\nfoo\r\n",len) == 0 &&
+ len == 4+4+(8+2)+4+(3+2));
+ free(cmd);
+
+ test("Format command with wrong printf format and extra interpolation: ");
+ len = redisFormatCommand(&cmd,"key:%08p %b",1234,"foo",3);
+ test_cond(strncmp(cmd,"*2\r\n$6\r\nkey:8p\r\n$3\r\nfoo\r\n",len) == 0 &&
+ len == 4+4+(6+2)+4+(3+2));
+ free(cmd);
+
const char *argv[3];
argv[0] = "SET";
- argv[1] = "foo";
+ argv[1] = "foo\0xxx";
argv[2] = "bar";
- size_t lens[3] = { 3, 3, 3 };
+ size_t lens[3] = { 3, 7, 3 };
int argc = 3;
test("Format command by passing argc/argv without lengths: ");
test("Format command by passing argc/argv with lengths: ");
len = redisFormatCommandArgv(&cmd,argc,argv,lens);
- test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
- len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(7+2)+4+(3+2));
free(cmd);
}
static void test_blocking_connection() {
redisContext *c;
redisReply *reply;
+ int major, minor;
- __connect(&c);
- test("Returns I/O error when the connection is lost: ");
- reply = redisCommand(c,"QUIT");
- test_cond(strcasecmp(reply->str,"OK") == 0 && redisCommand(c,"PING") == NULL);
-
- /* Two conditions may happen, depending on the type of connection.
- * When connected via TCP, the socket will not yet be aware of the closed
- * connection and the write(2) call will succeed, but the read(2) will
- * result in an EOF. When connected via Unix sockets, the socket will be
- * immediately aware that it was closed and fail on the write(2) call. */
- if (use_unix) {
- fprintf(stderr,"Error: %s\n", c->errstr);
- assert(c->err == REDIS_ERR_IO &&
- strcmp(c->errstr,"Broken pipe") == 0);
- } else {
- fprintf(stderr,"Error: %s\n", c->errstr);
- assert(c->err == REDIS_ERR_EOF &&
- strcmp(c->errstr,"Server closed the connection") == 0);
- }
- freeReplyObject(reply);
+ test("Returns error when host cannot be resolved: ");
+ c = redisConnect((char*)"idontexist.local", 6379);
+ test_cond(c->err == REDIS_ERR_OTHER &&
+ strcmp(c->errstr,"Can't resolve: idontexist.local") == 0);
redisFree(c);
- __connect(&c); /* reconnect */
+ test("Returns error when the port is not open: ");
+ c = redisConnect((char*)"localhost", 56380);
+ test_cond(c->err == REDIS_ERR_IO &&
+ strcmp(c->errstr,"Connection refused") == 0);
+ redisFree(c);
+
+ __connect(&c);
test("Is able to deliver commands: ");
reply = redisCommand(c,"PING");
test_cond(reply->type == REDIS_REPLY_STATUS &&
/* Make sure the DB is emtpy */
reply = redisCommand(c,"DBSIZE");
- if (reply->type != REDIS_REPLY_INTEGER ||
- reply->integer != 0) {
- printf("Sorry DB 9 is not empty, test can not continue\n");
+ if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) {
+ printf("Database #9 is not empty, test can not continue\n");
exit(1);
- } else {
- printf("DB 9 is empty... test can continue\n");
}
freeReplyObject(reply);
reply->element[1]->type == REDIS_REPLY_STATUS &&
strcasecmp(reply->element[1]->str,"pong") == 0);
freeReplyObject(reply);
+
+ {
+ /* Find out Redis version to determine the path for the next test */
+ const char *field = "redis_version:";
+ char *p, *eptr;
+
+ reply = redisCommand(c,"INFO");
+ p = strstr(reply->str,field);
+ major = strtol(p+strlen(field),&eptr,10);
+ p = eptr+1; /* char next to the first "." */
+ minor = strtol(p,&eptr,10);
+ freeReplyObject(reply);
+ }
+
+ test("Returns I/O error when the connection is lost: ");
+ reply = redisCommand(c,"QUIT");
+ if (major >= 2 && minor > 0) {
+ /* > 2.0 returns OK on QUIT and read() should be issued once more
+ * to know the descriptor is at EOF. */
+ test_cond(strcasecmp(reply->str,"OK") == 0 &&
+ redisGetReply(c,(void**)&reply) == REDIS_ERR);
+ freeReplyObject(reply);
+ } else {
+ test_cond(reply == NULL);
+ }
+
+ /* On 2.0, QUIT will cause the connection to be closed immediately and
+ * the read(2) for the reply on QUIT will set the error to EOF.
+ * On >2.0, QUIT will return with OK and another read(2) needed to be
+ * issued to find out the socket was closed by the server. In both
+ * conditions, the error will be set to EOF. */
+ assert(c->err == REDIS_ERR_EOF &&
+ strcmp(c->errstr,"Server closed the connection") == 0);
+
+ /* Clean up context and reconnect again */
+ redisFree(c);
+ __connect(&c);
}
static void test_reply_reader() {
ret = redisReplyReaderGetReply(reader,NULL);
err = redisReplyReaderGetError(reader);
test_cond(ret == REDIS_ERR &&
- strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
+ strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
redisReplyReaderFree(reader);
/* when the reply already contains multiple items, they must be free'd
ret = redisReplyReaderGetReply(reader,NULL);
err = redisReplyReaderGetError(reader);
test_cond(ret == REDIS_ERR &&
- strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
+ strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
+ redisReplyReaderFree(reader);
+
+ test("Set error on nested multi bulks with depth > 1: ");
+ reader = redisReplyReaderCreate();
+ redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
+ redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
+ redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
+ ret = redisReplyReaderGetReply(reader,NULL);
+ err = redisReplyReaderGetError(reader);
+ test_cond(ret == REDIS_ERR &&
+ strncasecmp(err,"No support for",14) == 0);
redisReplyReaderFree(reader);
test("Works with NULL functions for reply: ");