Basically, modules contain command handlers—these are C functions with the following signature:
int MyCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
As can be seen from the signature, the function returns an integer, either OK or ERR. Usually, returning OK (even if returning an error to the user) is fine.
The command handler accepts a RedisModuleCtx* object. This object is opaque to the module developer, but internally it contains the calling client’s state, and even internal memory management, which we’ll get to later. Next it receives argv and argc, which are basically the arguments the user has passed to the command being called. The first argument is the name of the call itself, and the rest are simply parsed arguments from the Redis protocol. Notice that they are received as RedisModuleString objects, which again, are opaque. They can be converted to normal C strings with zero copy if manipulation is needed.
To activate the module’s commands, the standard entry point for a module is a function called int RedisModule_OnLoad(RedisModuleCtx *ctx). This function tells Redis which commands are in the module and maps them to their handler.
In this short tutorial we’ll focus on a very simple example of a module that implements a new Redis command: HGETSET <key> <element> <new value>. HGETSET is a combination of HGET and HSET that allows you to retrieve the current value in a HASH object and set a new value in its place, atomically. This is pretty basic, and could also be done with a simple transaction or a LUA script, but HGETSET has the advantage of being really simple.
int HGetSetCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { return REDISMODULE_OK; }
Again, this currently does nothing, it just returns the OK code. So let’s give it some substance.
Remember, our command is HGETSET <key> <element> <new value>, meaning it will always have four arguments in argv. So let’s make sure this is indeed what happens:
/* HGETSET <key> <element> <new value> */ int HGetSetCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { if (argc != 4) { return RedisModule_WrongArity(ctx); } Return REDISMODULE_OK; }
RedisModule_WrongArity will return a standard error to the client in the form of:
(error) ERR wrong number of arguments for ‘get’ command.
One of the great features of the Redis Modules API is automatic resource and memory management. While the module author can allocate and free memory independently, calling RedisModule_AutoMemory allows you to automate the creation of Redis resources and allocate Redis strings, keys and responses during the handler’s lifecycle.
RedisModule_AutoMemory(ctx);
Now we’ll run the first of two Redis calls, HGET. We pass argv[1] and argv[2], the key and element, as the arguments. We use the generic RedisModule_Call command, which simply allows the module developer to call any existing Redis commands, much like a LUA script:
RedisModuleCallReply *rep = RedisModule_Call(ctx, “HGET”, “ss”, argv[1], argv[2]); // And let’s make sure it’s not an error if (RedisModule_CallReplyType(rep) == REDISMODULE_REPLY_ERROR) { return RedisModule_ReplyWithCallReply(ctx, srep); }
Notice that RedisModule_Call’s third argument, “ss,” denotes how Redis should treat the passed variadic arguments to the function. “ss” means “two RedisModuleString objects.” Other specifiers are “c” for a c-string, “d” for double, “l” for long, and “b” for a c-buffer (a string followed by its length).
Now let’s perform the second Redis call, HSET:
RedisModuleCallReply *srep = RedisModule_Call(ctx, “HSET”, “sss”, argv[1], argv[2], argv[3]); if (RedisModule_CallReplyType(srep) == REDISMODULE_REPLY_ERROR) { return RedisModule_ReplyWithCallReply(ctx, srep); }
Using HSET is similar to the HGET command, except that we pass three arguments to it.
In this simple case, we just need to return the result of HGET, or the value before we changed it. This is done using a simple function, RedisModule_ReplyWithCallReply, which forwards the reply object to the client:
RedisModule_ReplyWithCallReply(ctx, rep); return REDISMODULE_OK; }
And that’s it! Our command handler is ready; we just need to register our module and command handler properly.
The entry point for all Redis Modules is a function called RedisModule_OnLoad, which the developer has to implement. It registers and initializes the module, and registers its commands with Redis so that they can be called. Initializing our module works like so:
int RedisModule_OnLoad(RedisModuleCtx *ctx) { // Register the module itself – it’s called ‘example’ and has an API version of 1 if (RedisModule_Init(ctx, “example”, 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) { return REDISMODULE_ERR; } // register our command – it is a write command, with one key at argv[1] if (RedisModule_CreateCommand(ctx, “HGETSET”, HGetSetCommand, “write”, 1, 1, 1) == REDISMODULE_ERR) { return REDISMODULE_ERR; } return REDISMODULE_OK; }
And that’s about it! Our module is done.
All that’s left is to compile our module. I won’t go into the specifics of creating a makefile for it, but what you need to know is that Redis Modules require no special linking. Once you’ve included the redismodule.h file in your module’s files and implemented the entry point function, that’s all Redis needs to load your module. Any other linking is up to you. The commands needed to compile our basic module with gcc are:
On Linux: $ gcc -fPIC -std=gnu99 -c -o module.o module.c $ ld -o module.so module.o -shared -Bsymbolic -lc On OSX: $ gcc -dynamic -fno-common -std=gnu99 -c -o module.o module.c $ ld -o module.so module.o -bundle -undefined dynamic_lookup -lc
Once you’ve built your module, you need to load it. Assuming you’ve downloaded Redis from its latest stable build (which supports Modules), you just run it with the loadmodule command line argument:
redis-server –loadmodule /path/to/module.so
Redis is now running and has loaded our module. We can simply connect with redis-cli and run our commands!
The full source code detailed here can be found as part of RedisModuleSDK, which includes a Module project template, makefile and a utility library (with functions automating some of the more boring stuff around writing modules that are not included in the original API). You do not have to use it, but feel free to. Our module is done.