summaryrefslogtreecommitdiff
path: root/hiredis.c
diff options
context:
space:
mode:
Diffstat (limited to 'hiredis.c')
-rw-r--r--hiredis.c269
1 files changed, 269 insertions, 0 deletions
diff --git a/hiredis.c b/hiredis.c
new file mode 100644
index 0000000..0147a4c
--- /dev/null
+++ b/hiredis.c
@@ -0,0 +1,269 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "hiredis.h"
+#include "anet.h"
+#include "sds.h"
+
+static redisReply *redisReadReply(int fd);
+static redisReply *createReplyObject(int type, sds reply);
+
+/* We simply abort on out of memory */
+static void redisOOM(void) {
+ fprintf(stderr,"Out of memory in hiredis.c");
+ exit(1);
+}
+
+/* Connect to a Redis instance. On success NULL is returned and *fd is set
+ * to the socket file descriptor. On error a redisReply object is returned
+ * with reply->type set to REDIS_REPLY_ERROR and reply->string containing
+ * the error message. This replyObject must be freed with redisFreeReply(). */
+redisReply *redisConnect(int *fd, char *ip, int port) {
+ char err[ANET_ERR_LEN];
+
+ *fd = anetTcpConnect(err,ip,port);
+ if (*fd == ANET_ERR)
+ return createReplyObject(REDIS_REPLY_ERROR,sdsnew(err));
+ anetTcpNoDelay(NULL,*fd);
+ return NULL;
+}
+
+/* Create a reply object */
+static redisReply *createReplyObject(int type, sds reply) {
+ redisReply *r = malloc(sizeof(*r));
+
+ if (!r) redisOOM();
+ r->type = type;
+ r->reply = reply;
+ return r;
+}
+
+/* Free a reply object */
+void freeReplyObject(redisReply *r) {
+ size_t j;
+
+ switch(r->type) {
+ case REDIS_REPLY_INTEGER:
+ break; /* Nothing to free */
+ case REDIS_REPLY_ARRAY:
+ for (j = 0; j < r->elements; j++)
+ freeReplyObject(r->element[j]);
+ free(r->element);
+ break;
+ default:
+ sdsfree(r->reply);
+ break;
+ }
+ free(r);
+}
+
+static redisReply *redisIOError(void) {
+ return createReplyObject(REDIS_REPLY_ERROR,sdsnew("I/O error"));
+}
+
+/* In a real high performance C client this should be bufferized */
+static sds redisReadLine(int fd) {
+ sds line = sdsempty();
+
+ while(1) {
+ char c;
+ ssize_t ret;
+
+ ret = read(fd,&c,1);
+ if (ret == -1) {
+ sdsfree(line);
+ return NULL;
+ } else if ((ret == 0) || (c == '\n')) {
+ break;
+ } else {
+ line = sdscatlen(line,&c,1);
+ }
+ }
+ return sdstrim(line,"\r\n");
+}
+
+static redisReply *redisReadSingleLineReply(int fd, int type) {
+ sds buf = redisReadLine(fd);
+
+ if (buf == NULL) return redisIOError();
+ return createReplyObject(type,buf);
+}
+
+static redisReply *redisReadIntegerReply(int fd) {
+ sds buf = redisReadLine(fd);
+ redisReply *r = malloc(sizeof(*r));
+
+ if (r == NULL) redisOOM();
+ if (buf == NULL) return redisIOError();
+ r->type = REDIS_REPLY_INTEGER;
+ r->integer = strtoll(buf,NULL,10);
+ return r;
+}
+
+static redisReply *redisReadBulkReply(int fd) {
+ sds replylen = redisReadLine(fd);
+ sds buf;
+ char crlf[2];
+ int bulklen;
+
+ if (replylen == NULL) return redisIOError();
+ bulklen = atoi(replylen);
+ sdsfree(replylen);
+ if (bulklen == -1)
+ return createReplyObject(REDIS_REPLY_NIL,sdsempty());
+
+ buf = sdsnewlen(NULL,bulklen);
+ anetRead(fd,buf,bulklen);
+ anetRead(fd,crlf,2);
+ return createReplyObject(REDIS_REPLY_STRING,buf);
+}
+
+static redisReply *redisReadMultiBulkReply(int fd) {
+ sds replylen = redisReadLine(fd);
+ long elements, j;
+ redisReply *r;
+
+ if (replylen == NULL) return redisIOError();
+ elements = strtol(replylen,NULL,10);
+ sdsfree(replylen);
+
+ if (elements == -1)
+ return createReplyObject(REDIS_REPLY_NIL,sdsempty());
+
+ if ((r = malloc(sizeof(*r))) == NULL) redisOOM();
+ r->type = REDIS_REPLY_ARRAY;
+ r->elements = elements;
+ if ((r->element = malloc(sizeof(*r)*elements)) == NULL) redisOOM();
+ for (j = 0; j < elements; j++)
+ r->element[j] = redisReadReply(fd);
+ return r;
+}
+
+static redisReply *redisReadReply(int fd) {
+ char type;
+
+ if (anetRead(fd,&type,1) <= 0) return redisIOError();
+ switch(type) {
+ case '-':
+ return redisReadSingleLineReply(fd,REDIS_REPLY_ERROR);
+ case '+':
+ return redisReadSingleLineReply(fd,REDIS_REPLY_STRING);
+ case ':':
+ return redisReadIntegerReply(fd);
+ case '$':
+ return redisReadBulkReply(fd);
+ case '*':
+ return redisReadMultiBulkReply(fd);
+ default:
+ printf("protocol error, got '%c' as reply type byte\n", type);
+ exit(1);
+ }
+}
+
+/* Helper function for redisCommand(). It's used to append the next argument
+ * to the argument vector. */
+static void addArgument(sds a, char ***argv, int *argc) {
+ (*argc)++;
+ if ((*argv = realloc(*argv, sizeof(char*)*(*argc))) == NULL) redisOOM();
+ (*argv)[(*argc)-1] = a;
+}
+
+/* Execute a command. This function is printf alike:
+ *
+ * %s represents a C nul terminated string you want to interpolate
+ * %b represents a binary safe string
+ *
+ * When using %b you need to provide both the pointer to the string
+ * and the length in bytes. Examples:
+ *
+ * redisCommand("GET %s", mykey);
+ * redisCommand("SET %s %b", mykey, somevalue, somevalue_len);
+ *
+ * RETURN VALUE:
+ *
+ * The returned value is a redisReply object that must be freed using the
+ * redisFreeReply() function.
+ *
+ * given a redisReply "reply" you can test if there was an error in this way:
+ *
+ * if (reply->type == REDIS_REPLY_ERROR) {
+ * printf("Error in request: %s\n", reply->reply);
+ * }
+ *
+ * The replied string itself is in reply->reply if the reply type is
+ * a REDIS_REPLY_STRING. If the reply is a multi bulk reply then
+ * reply->type is REDIS_REPLY_ARRAY and you can access all the elements
+ * in this way:
+ *
+ * for (i = 0; i < reply->elements; i++)
+ * printf("%d: %s\n", i, reply->element[i]);
+ *
+ * Finally when type is REDIS_REPLY_INTEGER the long long integer is
+ * stored at reply->integer.
+ */
+redisReply *redisCommand(int fd, char *format, ...) {
+ va_list ap;
+ size_t size;
+ char *arg, *c = format;
+ sds cmd = sdsempty(); /* whole command buffer */
+ sds current = sdsempty(); /* current argument */
+ char **argv = NULL;
+ int argc = 0, j;
+
+ /* Build the command string accordingly to protocol */
+ va_start(ap,format);
+ while(*c != '\0') {
+ if (*c != '%' || c[1] == '\0') {
+ if (*c == ' ') {
+ if (sdslen(current) != 0) {
+ addArgument(current, &argv, &argc);
+ current = sdsempty();
+ }
+ } else {
+ current = sdscatlen(current,c,1);
+ }
+ } else {
+ switch(c[1]) {
+ case 's':
+ arg = va_arg(ap,char*);
+ current = sdscat(current,arg);
+ break;
+ case 'b':
+ arg = va_arg(ap,char*);
+ size = va_arg(ap,size_t);
+ current = sdscatlen(current,arg,size);
+ break;
+ case '%':
+ cmd = sdscat(cmd,"%");
+ break;
+ }
+ c++;
+ }
+ c++;
+ }
+ va_end(ap);
+
+ /* Add the last argument if needed */
+ if (sdslen(current) != 0)
+ addArgument(current, &argv, &argc);
+ else
+ sdsfree(current);
+
+ /* Build the command at protocol level */
+ cmd = sdscatprintf(cmd,"*%d\r\n",argc);
+ for (j = 0; j < argc; j++) {
+ cmd = sdscatprintf(cmd,"$%zu\r\n",sdslen(argv[j]));
+ cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
+ cmd = sdscatlen(cmd,"\r\n",2);
+ sdsfree(argv[j]);
+ }
+ free(argv);
+
+ /* Send the command via socket */
+ anetWrite(fd,cmd,sdslen(cmd));
+ sdsfree(cmd);
+ return redisReadReply(fd);
+}