Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
5e8caca0
Unverified
Commit
5e8caca0
authored
Mar 22, 2019
by
Salvatore Sanfilippo
Committed by
GitHub
Mar 22, 2019
Browse files
Merge pull request #5944 from yossigo/command-filtering
Command Filtering API
parents
fcac3429
6c0a5fde
Changes
9
Hide whitespace changes
Inline
Side-by-side
src/module.c
View file @
5e8caca0
...
...
@@ -49,6 +49,8 @@ struct RedisModule {
list
*
types
;
/* Module data types. */
list
*
usedby
;
/* List of modules using APIs from this one. */
list
*
using
;
/* List of modules we use some APIs of. */
list
*
filters
;
/* List of filters the module has registered. */
int
in_call
;
/* RM_Call() nesting level */
};
typedef
struct
RedisModule
RedisModule
;
...
...
@@ -270,6 +272,25 @@ typedef struct RedisModuleDictIter {
raxIterator
ri
;
}
RedisModuleDictIter
;
typedef
struct
RedisModuleCommandFilterCtx
{
RedisModuleString
**
argv
;
int
argc
;
}
RedisModuleCommandFilterCtx
;
typedef
void
(
*
RedisModuleCommandFilterFunc
)
(
RedisModuleCommandFilterCtx
*
filter
);
typedef
struct
RedisModuleCommandFilter
{
/* The module that registered the filter */
RedisModule
*
module
;
/* Filter callback function */
RedisModuleCommandFilterFunc
callback
;
/* REDISMODULE_CMDFILTER_* flags */
int
flags
;
}
RedisModuleCommandFilter
;
/* Registered filters */
static
list
*
moduleCommandFilters
;
/* --------------------------------------------------------------------------
* Prototypes
* -------------------------------------------------------------------------- */
...
...
@@ -731,6 +752,7 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
module
->
types
=
listCreate
();
module
->
usedby
=
listCreate
();
module
->
using
=
listCreate
();
module
->
filters
=
listCreate
();
ctx
->
module
=
module
;
}
...
...
@@ -2724,12 +2746,6 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
RedisModuleCallReply
*
reply
=
NULL
;
int
replicate
=
0
;
/* Replicate this command? */
cmd
=
lookupCommandByCString
((
char
*
)
cmdname
);
if
(
!
cmd
)
{
errno
=
EINVAL
;
return
NULL
;
}
/* Create the client and dispatch the command. */
va_start
(
ap
,
fmt
);
c
=
createClient
(
-
1
);
...
...
@@ -2743,11 +2759,25 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
c
->
db
=
ctx
->
client
->
db
;
c
->
argv
=
argv
;
c
->
argc
=
argc
;
c
->
cmd
=
c
->
lastcmd
=
cmd
;
if
(
ctx
->
module
)
ctx
->
module
->
in_call
++
;
/* We handle the above format error only when the client is setup so that
* we can free it normally. */
if
(
argv
==
NULL
)
goto
cleanup
;
/* Call command filters */
moduleCallCommandFilters
(
c
);
/* Lookup command now, after filters had a chance to make modifications
* if necessary.
*/
cmd
=
lookupCommand
(
c
->
argv
[
0
]
->
ptr
);
if
(
!
cmd
)
{
errno
=
EINVAL
;
goto
cleanup
;
}
c
->
cmd
=
c
->
lastcmd
=
cmd
;
/* Basic arity checks. */
if
((
cmd
->
arity
>
0
&&
cmd
->
arity
!=
argc
)
||
(
argc
<
-
cmd
->
arity
))
{
errno
=
EINVAL
;
...
...
@@ -2797,6 +2827,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
autoMemoryAdd
(
ctx
,
REDISMODULE_AM_REPLY
,
reply
);
cleanup:
if
(
ctx
->
module
)
ctx
->
module
->
in_call
--
;
freeClient
(
c
);
return
reply
;
}
...
...
@@ -4778,6 +4809,214 @@ int moduleUnregisterUsedAPI(RedisModule *module) {
return
count
;
}
/* Unregister all filters registered by a module.
* This is called when a module is being unloaded.
*
* Returns the number of filters unregistered. */
int
moduleUnregisterFilters
(
RedisModule
*
module
)
{
listIter
li
;
listNode
*
ln
;
int
count
=
0
;
listRewind
(
module
->
filters
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
RedisModuleCommandFilter
*
filter
=
ln
->
value
;
listNode
*
ln
=
listSearchKey
(
moduleCommandFilters
,
filter
);
if
(
ln
)
{
listDelNode
(
moduleCommandFilters
,
ln
);
count
++
;
}
zfree
(
filter
);
}
return
count
;
}
/* --------------------------------------------------------------------------
* Module Command Filter API
* -------------------------------------------------------------------------- */
/* Register a new command filter function.
*
* Command filtering makes it possible for modules to extend Redis by plugging
* into the execution flow of all commands.
*
* A registered filter gets called before Redis executes *any* command. This
* includes both core Redis commands and commands registered by any module. The
* filter applies in all execution paths including:
*
* 1. Invocation by a client.
* 2. Invocation through `RedisModule_Call()` by any module.
* 3. Invocation through Lua 'redis.call()`.
* 4. Replication of a command from a master.
*
* The filter executes in a special filter context, which is different and more
* limited than a RedisModuleCtx. Because the filter affects any command, it
* must be implemented in a very efficient way to reduce the performance impact
* on Redis. All Redis Module API calls that require a valid context (such as
* `RedisModule_Call()`, `RedisModule_OpenKey()`, etc.) are not supported in a
* filter context.
*
* The `RedisModuleCommandFilterCtx` can be used to inspect or modify the
* executed command and its arguments. As the filter executes before Redis
* begins processing the command, any change will affect the way the command is
* processed. For example, a module can override Redis commands this way:
*
* 1. Register a `MODULE.SET` command which implements an extended version of
* the Redis `SET` command.
* 2. Register a command filter which detects invocation of `SET` on a specific
* pattern of keys. Once detected, the filter will replace the first
* argument from `SET` to `MODULE.SET`.
* 3. When filter execution is complete, Redis considers the new command name
* and therefore executes the module's own command.
*
* Note that in the above use case, if `MODULE.SET` itself uses
* `RedisModule_Call()` the filter will be applied on that call as well. If
* that is not desired, the `REDISMODULE_CMDFILTER_NOSELF` flag can be set when
* registering the filter.
*
* The `REDISMODULE_CMDFILTER_NOSELF` flag prevents execution flows that
* originate from the module's own `RM_Call()` from reaching the filter. This
* flag is effective for all execution flows, including nested ones, as long as
* the execution begins from the module's command context or a thread-safe
* context that is associated with a blocking command.
*
* Detached thread-safe contexts are *not* associated with the module and cannot
* be protected by this flag.
*
* If multiple filters are registered (by the same or different modules), they
* are executed in the order of registration.
*/
RedisModuleCommandFilter
*
RM_RegisterCommandFilter
(
RedisModuleCtx
*
ctx
,
RedisModuleCommandFilterFunc
callback
,
int
flags
)
{
RedisModuleCommandFilter
*
filter
=
zmalloc
(
sizeof
(
*
filter
));
filter
->
module
=
ctx
->
module
;
filter
->
callback
=
callback
;
filter
->
flags
=
flags
;
listAddNodeTail
(
moduleCommandFilters
,
filter
);
listAddNodeTail
(
ctx
->
module
->
filters
,
filter
);
return
filter
;
}
/* Unregister a command filter.
*/
int
RM_UnregisterCommandFilter
(
RedisModuleCtx
*
ctx
,
RedisModuleCommandFilter
*
filter
)
{
listNode
*
ln
;
/* A module can only remove its own filters */
if
(
filter
->
module
!=
ctx
->
module
)
return
REDISMODULE_ERR
;
ln
=
listSearchKey
(
moduleCommandFilters
,
filter
);
if
(
!
ln
)
return
REDISMODULE_ERR
;
listDelNode
(
moduleCommandFilters
,
ln
);
ln
=
listSearchKey
(
ctx
->
module
->
filters
,
filter
);
if
(
!
ln
)
return
REDISMODULE_ERR
;
/* Shouldn't happen */
listDelNode
(
ctx
->
module
->
filters
,
ln
);
return
REDISMODULE_OK
;
}
void
moduleCallCommandFilters
(
client
*
c
)
{
if
(
listLength
(
moduleCommandFilters
)
==
0
)
return
;
listIter
li
;
listNode
*
ln
;
listRewind
(
moduleCommandFilters
,
&
li
);
RedisModuleCommandFilterCtx
filter
=
{
.
argv
=
c
->
argv
,
.
argc
=
c
->
argc
};
while
((
ln
=
listNext
(
&
li
)))
{
RedisModuleCommandFilter
*
f
=
ln
->
value
;
/* Skip filter if REDISMODULE_CMDFILTER_NOSELF is set and module is
* currently processing a command.
*/
if
((
f
->
flags
&
REDISMODULE_CMDFILTER_NOSELF
)
&&
f
->
module
->
in_call
)
continue
;
/* Call filter */
f
->
callback
(
&
filter
);
}
c
->
argv
=
filter
.
argv
;
c
->
argc
=
filter
.
argc
;
}
/* Return the number of arguments a filtered command has. The number of
* arguments include the command itself.
*/
int
RM_CommandFilterArgsCount
(
RedisModuleCommandFilterCtx
*
fctx
)
{
return
fctx
->
argc
;
}
/* Return the specified command argument. The first argument (position 0) is
* the command itself, and the rest are user-provided args.
*/
const
RedisModuleString
*
RM_CommandFilterArgGet
(
RedisModuleCommandFilterCtx
*
fctx
,
int
pos
)
{
if
(
pos
<
0
||
pos
>=
fctx
->
argc
)
return
NULL
;
return
fctx
->
argv
[
pos
];
}
/* Modify the filtered command by inserting a new argument at the specified
* position. The specified RedisModuleString argument may be used by Redis
* after the filter context is destroyed, so it must not be auto-memory
* allocated, freed or used elsewhere.
*/
int
RM_CommandFilterArgInsert
(
RedisModuleCommandFilterCtx
*
fctx
,
int
pos
,
RedisModuleString
*
arg
)
{
int
i
;
if
(
pos
<
0
||
pos
>
fctx
->
argc
)
return
REDISMODULE_ERR
;
fctx
->
argv
=
zrealloc
(
fctx
->
argv
,
(
fctx
->
argc
+
1
)
*
sizeof
(
RedisModuleString
*
));
for
(
i
=
fctx
->
argc
;
i
>
pos
;
i
--
)
{
fctx
->
argv
[
i
]
=
fctx
->
argv
[
i
-
1
];
}
fctx
->
argv
[
pos
]
=
arg
;
fctx
->
argc
++
;
return
REDISMODULE_OK
;
}
/* Modify the filtered command by replacing an existing argument with a new one.
* The specified RedisModuleString argument may be used by Redis after the
* filter context is destroyed, so it must not be auto-memory allocated, freed
* or used elsewhere.
*/
int
RM_CommandFilterArgReplace
(
RedisModuleCommandFilterCtx
*
fctx
,
int
pos
,
RedisModuleString
*
arg
)
{
if
(
pos
<
0
||
pos
>=
fctx
->
argc
)
return
REDISMODULE_ERR
;
decrRefCount
(
fctx
->
argv
[
pos
]);
fctx
->
argv
[
pos
]
=
arg
;
return
REDISMODULE_OK
;
}
/* Modify the filtered command by deleting an argument at the specified
* position.
*/
int
RM_CommandFilterArgDelete
(
RedisModuleCommandFilterCtx
*
fctx
,
int
pos
)
{
int
i
;
if
(
pos
<
0
||
pos
>=
fctx
->
argc
)
return
REDISMODULE_ERR
;
decrRefCount
(
fctx
->
argv
[
pos
]);
for
(
i
=
pos
;
i
<
fctx
->
argc
-
1
;
i
++
)
{
fctx
->
argv
[
i
]
=
fctx
->
argv
[
i
+
1
];
}
fctx
->
argc
--
;
return
REDISMODULE_OK
;
}
/* --------------------------------------------------------------------------
* Modules API internals
* -------------------------------------------------------------------------- */
...
...
@@ -4824,6 +5063,9 @@ void moduleInitModulesSystem(void) {
moduleFreeContextReusedClient
->
flags
|=
CLIENT_MODULE
;
moduleFreeContextReusedClient
->
user
=
NULL
;
/* root user. */
/* Set up filter list */
moduleCommandFilters
=
listCreate
();
moduleRegisterCoreAPI
();
if
(
pipe
(
server
.
module_blocked_pipe
)
==
-
1
)
{
serverLog
(
LL_WARNING
,
...
...
@@ -4873,6 +5115,7 @@ void moduleLoadFromQueue(void) {
void
moduleFreeModuleStructure
(
struct
RedisModule
*
module
)
{
listRelease
(
module
->
types
);
listRelease
(
module
->
filters
);
sdsfree
(
module
->
name
);
zfree
(
module
);
}
...
...
@@ -4964,6 +5207,7 @@ int moduleUnload(sds name) {
moduleUnregisterCommands
(
module
);
moduleUnregisterSharedAPI
(
module
);
moduleUnregisterUsedAPI
(
module
);
moduleUnregisterFilters
(
module
);
/* Remove any notification subscribers this module might have */
moduleUnsubscribeNotifications
(
module
);
...
...
@@ -5228,4 +5472,11 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API
(
DictCompare
);
REGISTER_API
(
ExportSharedAPI
);
REGISTER_API
(
GetSharedAPI
);
REGISTER_API
(
RegisterCommandFilter
);
REGISTER_API
(
UnregisterCommandFilter
);
REGISTER_API
(
CommandFilterArgsCount
);
REGISTER_API
(
CommandFilterArgGet
);
REGISTER_API
(
CommandFilterArgInsert
);
REGISTER_API
(
CommandFilterArgReplace
);
REGISTER_API
(
CommandFilterArgDelete
);
}
src/modules/Makefile
View file @
5e8caca0
...
...
@@ -13,7 +13,7 @@ endif
.SUFFIXES
:
.c .so .xo .o
all
:
helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so
all
:
helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so
hellofilter.so
.c.xo
:
$(CC)
-I
.
$(CFLAGS)
$(SHOBJ_CFLAGS)
-fPIC
-c
$<
-o
$@
...
...
@@ -46,6 +46,10 @@ hellotimer.so: hellotimer.xo
hellodict.xo
:
../redismodule.h
hellodict.so
:
hellodict.xo
hellofilter.xo
:
../redismodule.h
hellofilter.so
:
hellofilter.xo
$(LD)
-o
$@
$<
$(SHOBJ_LDFLAGS)
$(LIBS)
-lc
testmodule.xo
:
../redismodule.h
...
...
src/modules/hellofilter.c
0 → 100644
View file @
5e8caca0
#define REDISMODULE_EXPERIMENTAL_API
#include "../redismodule.h"
#include <string.h>
static
RedisModuleString
*
log_key_name
;
static
const
char
log_command_name
[]
=
"hellofilter.log"
;
static
const
char
ping_command_name
[]
=
"hellofilter.ping"
;
static
const
char
unregister_command_name
[]
=
"hellofilter.unregister"
;
static
int
in_log_command
=
0
;
static
RedisModuleCommandFilter
*
filter
=
NULL
;
int
HelloFilter_UnregisterCommand
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
(
void
)
argc
;
(
void
)
argv
;
RedisModule_ReplyWithLongLong
(
ctx
,
RedisModule_UnregisterCommandFilter
(
ctx
,
filter
));
return
REDISMODULE_OK
;
}
int
HelloFilter_PingCommand
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
(
void
)
argc
;
(
void
)
argv
;
RedisModuleCallReply
*
reply
=
RedisModule_Call
(
ctx
,
"ping"
,
"c"
,
"@log"
);
if
(
reply
)
{
RedisModule_ReplyWithCallReply
(
ctx
,
reply
);
RedisModule_FreeCallReply
(
reply
);
}
else
{
RedisModule_ReplyWithSimpleString
(
ctx
,
"Unknown command or invalid arguments"
);
}
return
REDISMODULE_OK
;
}
int
HelloFilter_LogCommand
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
RedisModuleString
*
s
=
RedisModule_CreateString
(
ctx
,
""
,
0
);
int
i
;
for
(
i
=
1
;
i
<
argc
;
i
++
)
{
size_t
arglen
;
const
char
*
arg
=
RedisModule_StringPtrLen
(
argv
[
i
],
&
arglen
);
if
(
i
>
1
)
RedisModule_StringAppendBuffer
(
ctx
,
s
,
" "
,
1
);
RedisModule_StringAppendBuffer
(
ctx
,
s
,
arg
,
arglen
);
}
RedisModuleKey
*
log
=
RedisModule_OpenKey
(
ctx
,
log_key_name
,
REDISMODULE_WRITE
|
REDISMODULE_READ
);
RedisModule_ListPush
(
log
,
REDISMODULE_LIST_HEAD
,
s
);
RedisModule_CloseKey
(
log
);
RedisModule_FreeString
(
ctx
,
s
);
in_log_command
=
1
;
size_t
cmdlen
;
const
char
*
cmdname
=
RedisModule_StringPtrLen
(
argv
[
1
],
&
cmdlen
);
RedisModuleCallReply
*
reply
=
RedisModule_Call
(
ctx
,
cmdname
,
"v"
,
&
argv
[
2
],
argc
-
2
);
if
(
reply
)
{
RedisModule_ReplyWithCallReply
(
ctx
,
reply
);
RedisModule_FreeCallReply
(
reply
);
}
else
{
RedisModule_ReplyWithSimpleString
(
ctx
,
"Unknown command or invalid arguments"
);
}
in_log_command
=
0
;
return
REDISMODULE_OK
;
}
void
HelloFilter_CommandFilter
(
RedisModuleCommandFilterCtx
*
filter
)
{
if
(
in_log_command
)
return
;
/* don't process our own RM_Call() from HelloFilter_LogCommand() */
/* Fun manipulations:
* - Remove @delme
* - Replace @replaceme
* - Append @insertbefore or @insertafter
* - Prefix with Log command if @log encounterd
*/
int
log
=
0
;
int
pos
=
0
;
while
(
pos
<
RedisModule_CommandFilterArgsCount
(
filter
))
{
const
RedisModuleString
*
arg
=
RedisModule_CommandFilterArgGet
(
filter
,
pos
);
size_t
arg_len
;
const
char
*
arg_str
=
RedisModule_StringPtrLen
(
arg
,
&
arg_len
);
if
(
arg_len
==
6
&&
!
memcmp
(
arg_str
,
"@delme"
,
6
))
{
RedisModule_CommandFilterArgDelete
(
filter
,
pos
);
continue
;
}
if
(
arg_len
==
10
&&
!
memcmp
(
arg_str
,
"@replaceme"
,
10
))
{
RedisModule_CommandFilterArgReplace
(
filter
,
pos
,
RedisModule_CreateString
(
NULL
,
"--replaced--"
,
12
));
}
else
if
(
arg_len
==
13
&&
!
memcmp
(
arg_str
,
"@insertbefore"
,
13
))
{
RedisModule_CommandFilterArgInsert
(
filter
,
pos
,
RedisModule_CreateString
(
NULL
,
"--inserted-before--"
,
19
));
pos
++
;
}
else
if
(
arg_len
==
12
&&
!
memcmp
(
arg_str
,
"@insertafter"
,
12
))
{
RedisModule_CommandFilterArgInsert
(
filter
,
pos
+
1
,
RedisModule_CreateString
(
NULL
,
"--inserted-after--"
,
18
));
pos
++
;
}
else
if
(
arg_len
==
4
&&
!
memcmp
(
arg_str
,
"@log"
,
4
))
{
log
=
1
;
}
pos
++
;
}
if
(
log
)
RedisModule_CommandFilterArgInsert
(
filter
,
0
,
RedisModule_CreateString
(
NULL
,
log_command_name
,
sizeof
(
log_command_name
)
-
1
));
}
int
RedisModule_OnLoad
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
if
(
RedisModule_Init
(
ctx
,
"hellofilter"
,
1
,
REDISMODULE_APIVER_1
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
if
(
argc
!=
2
)
{
RedisModule_Log
(
ctx
,
"warning"
,
"Log key name not specified"
);
return
REDISMODULE_ERR
;
}
long
long
noself
=
0
;
log_key_name
=
RedisModule_CreateStringFromString
(
ctx
,
argv
[
0
]);
RedisModule_StringToLongLong
(
argv
[
1
],
&
noself
);
if
(
RedisModule_CreateCommand
(
ctx
,
log_command_name
,
HelloFilter_LogCommand
,
"write deny-oom"
,
1
,
1
,
1
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
if
(
RedisModule_CreateCommand
(
ctx
,
ping_command_name
,
HelloFilter_PingCommand
,
"deny-oom"
,
1
,
1
,
1
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
if
(
RedisModule_CreateCommand
(
ctx
,
unregister_command_name
,
HelloFilter_UnregisterCommand
,
"write deny-oom"
,
1
,
1
,
1
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
if
((
filter
=
RedisModule_RegisterCommandFilter
(
ctx
,
HelloFilter_CommandFilter
,
noself
?
REDISMODULE_CMDFILTER_NOSELF
:
0
))
==
NULL
)
return
REDISMODULE_ERR
;
return
REDISMODULE_OK
;
}
src/redismodule.h
View file @
5e8caca0
...
...
@@ -133,6 +133,11 @@
* of timers that are going to expire, sorted by expire time. */
typedef
uint64_t
RedisModuleTimerID
;
/* CommandFilter Flags */
/* Do filter RedisModule_Call() commands initiated by module itself. */
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
/* ------------------------- End of common defines ------------------------ */
#ifndef REDISMODULE_CORE
...
...
@@ -151,6 +156,8 @@ typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
typedef
struct
RedisModuleClusterInfo
RedisModuleClusterInfo
;
typedef
struct
RedisModuleDict
RedisModuleDict
;
typedef
struct
RedisModuleDictIter
RedisModuleDictIter
;
typedef
struct
RedisModuleCommandFilterCtx
RedisModuleCommandFilterCtx
;
typedef
struct
RedisModuleCommandFilter
RedisModuleCommandFilter
;
typedef
int
(
*
RedisModuleCmdFunc
)(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
);
typedef
void
(
*
RedisModuleDisconnectFunc
)(
RedisModuleCtx
*
ctx
,
RedisModuleBlockedClient
*
bc
);
...
...
@@ -163,6 +170,7 @@ typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value
typedef
void
(
*
RedisModuleTypeFreeFunc
)(
void
*
value
);
typedef
void
(
*
RedisModuleClusterMessageReceiver
)(
RedisModuleCtx
*
ctx
,
const
char
*
sender_id
,
uint8_t
type
,
const
unsigned
char
*
payload
,
uint32_t
len
);
typedef
void
(
*
RedisModuleTimerProc
)(
RedisModuleCtx
*
ctx
,
void
*
data
);
typedef
void
(
*
RedisModuleCommandFilterFunc
)
(
RedisModuleCommandFilterCtx
*
filter
);
#define REDISMODULE_TYPE_METHOD_VERSION 1
typedef
struct
RedisModuleTypeMethods
{
...
...
@@ -339,6 +347,13 @@ void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedC
void
REDISMODULE_API_FUNC
(
RedisModule_SetClusterFlags
)(
RedisModuleCtx
*
ctx
,
uint64_t
flags
);
int
REDISMODULE_API_FUNC
(
RedisModule_ExportSharedAPI
)(
RedisModuleCtx
*
ctx
,
const
char
*
apiname
,
void
*
func
);
void
*
REDISMODULE_API_FUNC
(
RedisModule_GetSharedAPI
)(
RedisModuleCtx
*
ctx
,
const
char
*
apiname
);
RedisModuleCommandFilter
*
REDISMODULE_API_FUNC
(
RedisModule_RegisterCommandFilter
)(
RedisModuleCtx
*
ctx
,
RedisModuleCommandFilterFunc
cb
,
int
flags
);
int
REDISMODULE_API_FUNC
(
RedisModule_UnregisterCommandFilter
)(
RedisModuleCtx
*
ctx
,
RedisModuleCommandFilter
*
filter
);
int
REDISMODULE_API_FUNC
(
RedisModule_CommandFilterArgsCount
)(
RedisModuleCommandFilterCtx
*
fctx
);
const
RedisModuleString
*
REDISMODULE_API_FUNC
(
RedisModule_CommandFilterArgGet
)(
RedisModuleCommandFilterCtx
*
fctx
,
int
pos
);
int
REDISMODULE_API_FUNC
(
RedisModule_CommandFilterArgInsert
)(
RedisModuleCommandFilterCtx
*
fctx
,
int
pos
,
RedisModuleString
*
arg
);
int
REDISMODULE_API_FUNC
(
RedisModule_CommandFilterArgReplace
)(
RedisModuleCommandFilterCtx
*
fctx
,
int
pos
,
RedisModuleString
*
arg
);
int
REDISMODULE_API_FUNC
(
RedisModule_CommandFilterArgDelete
)(
RedisModuleCommandFilterCtx
*
fctx
,
int
pos
);
#endif
/* This is included inline inside each Redis module. */
...
...
@@ -502,6 +517,13 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API
(
SetClusterFlags
);
REDISMODULE_GET_API
(
ExportSharedAPI
);
REDISMODULE_GET_API
(
GetSharedAPI
);
REDISMODULE_GET_API
(
RegisterCommandFilter
);
REDISMODULE_GET_API
(
UnregisterCommandFilter
);
REDISMODULE_GET_API
(
CommandFilterArgsCount
);
REDISMODULE_GET_API
(
CommandFilterArgGet
);
REDISMODULE_GET_API
(
CommandFilterArgInsert
);
REDISMODULE_GET_API
(
CommandFilterArgReplace
);
REDISMODULE_GET_API
(
CommandFilterArgDelete
);
#endif
if
(
RedisModule_IsModuleNameBusy
&&
RedisModule_IsModuleNameBusy
(
name
))
return
REDISMODULE_ERR
;
...
...
src/scripting.c
View file @
5e8caca0
...
...
@@ -462,6 +462,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
c
->
argc
=
argc
;
c
->
user
=
server
.
lua_caller
->
user
;
/* Process module hooks */
moduleCallCommandFilters
(
c
);
argv
=
c
->
argv
;
argc
=
c
->
argc
;
/* Log the command if debugging is active. */
if
(
ldb
.
active
&&
ldb
.
step
)
{
sds
cmdlog
=
sdsnew
(
"<redis>"
);
...
...
src/server.c
View file @
5e8caca0
...
...
@@ -3268,6 +3268,8 @@ void call(client *c, int flags) {
* other operations can be performed by the caller. Otherwise
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
int
processCommand
(
client
*
c
)
{
moduleCallCommandFilters
(
c
);
/* The QUIT command is handled separately. Normal command procs will
* go through checking for replication and QUIT will cause trouble
* when FORCE_REPLICATION is enabled and would be implemented in
...
...
src/server.h
View file @
5e8caca0
...
...
@@ -1492,7 +1492,7 @@ size_t moduleCount(void);
void
moduleAcquireGIL
(
void
);
void
moduleReleaseGIL
(
void
);
void
moduleNotifyKeyspaceEvent
(
int
type
,
const
char
*
event
,
robj
*
key
,
int
dbid
);
void
moduleCallCommandFilters
(
client
*
c
);
/* Utils */
long
long
ustime
(
void
);
...
...
tests/modules/commandfilter.tcl
0 → 100644
View file @
5e8caca0
set testmodule
[
file normalize src/modules/hellofilter.so
]
start_server
{
tags
{
"modules"
}}
{
r module load $testmodule log-key 0
test
{
Command Filter handles redirected commands
}
{
r set mykey @log
r lrange log-key 0 -1
}
"{set mykey @log}"
test
{
Command Filter can call RedisModule_CommandFilterArgDelete
}
{
r rpush mylist elem1 @delme elem2
r lrange mylist 0 -1
}
{
elem1 elem2
}
test
{
Command Filter can call RedisModule_CommandFilterArgInsert
}
{
r del mylist
r rpush mylist elem1 @insertbefore elem2 @insertafter elem3
r lrange mylist 0 -1
}
{
elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3
}
test
{
Command Filter can call RedisModule_CommandFilterArgReplace
}
{
r del mylist
r rpush mylist elem1 @replaceme elem2
r lrange mylist 0 -1
}
{
elem1 --replaced-- elem2
}
test
{
Command Filter applies on RM_Call
()
commands
}
{
r del log-key
r hellofilter.ping
r lrange log-key 0 -1
}
"{ping @log}"
test
{
Command Filter applies on Lua redis.call
()}
{
r del log-key
r eval
"redis.call('ping', '@log')"
0
r lrange log-key 0 -1
}
"{ping @log}"
test
{
Command Filter applies on Lua redis.call
()
that calls a module
}
{
r del log-key
r eval
"redis.call('hellofilter.ping')"
0
r lrange log-key 0 -1
}
"{ping @log}"
test
{
Command Filter is unregistered implicitly on module unload
}
{
r del log-key
r module unload hellofilter
r set mykey @log
r lrange log-key 0 -1
}
{}
r module load $testmodule log-key 0
test
{
Command Filter unregister works as expected
}
{
# Validate reloading succeeded
r del log-key
r set mykey @log
assert_equal
"{set mykey @log}"
[
r lrange log-key 0 -1
]
# Unregister
r hellofilter.unregister
r del log-key
r set mykey @log
r lrange log-key 0 -1
}
{}
r module unload hellofilter
r module load $testmodule log-key 1
test
{
Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected
}
{
r set mykey @log
assert_equal
"{set mykey @log}"
[
r lrange log-key 0 -1
]
r del log-key
r hellofilter.ping
assert_equal
{}
[
r lrange log-key 0 -1
]
r eval
"redis.call('hellofilter.ping')"
0
assert_equal
{}
[
r lrange log-key 0 -1
]
}
}
tests/test_helper.tcl
View file @
5e8caca0
...
...
@@ -63,6 +63,7 @@ set ::all_tests {
unit/lazyfree
unit/wait
unit/pendingquerybuf
modules/commandfilter
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment