This guide describes how to make asynchronous calls from Rspamd plugins and rules.
Prior to 1.8.0, if you needed to perform an action involving network request (i.e. Redis query, Anti-virus scan), you had to use callback-style approach. You define callback and initiate an asynchronous request and stop the execution to allow other tasks proceed.
As soon as request is completed, callback is called.
-- define a callback
local function request_done(err, code, body)
if not err then
task:insert_result('REQUEST_DONE', 1.0, body)
end
...
end
-- initiate the request
api.start_request({
callback = request_done,
...
})
Rspamd 1.8.0 introduces a new pseudo-synchronous API. Now you can write code in a typical imperative manner without blocking other tasks.
Each operation that could potentially block creates a yielding point. Consequently, the code is paused until the operation is completed (similar to blocking), and it resumes only when there is a result. Meanwhile, other tasks are processed as usual.
Please note that synchronous mode requires symbol to be registered with coro
flag from the version 1.9 (see “full example”).
local err, response = api.do_request(...)
if not err then
task:insert_result('REQUEST_DONE', 1.0, response)
end
...
To use Sync with HTTP API, just remove callback parameter from call parameters. It returns two values:
nil
or string
containing error description if network or internal error happenednil
if error happened (note: HTTP-codes are returned with corresponding codes) or table
:
int
HTTP response codestring
Response bodytable
(header -> value) list of response headers -- define a callback
local function request_done(err, code, body)
if not err then
task:insert_result('HTTP_RESPONSE' .. code, 1.0, body)
end
...
end
-- initiate the request
rspamd_http.request({
url = 'http://127.0.0.1:18080/abc',
callback = request_done,
...
})
Please note that synchronous mode requires symbol to be registered with coro flag (see “full example”).
local err, response = rspamd_http.request({
url = 'http://127.0.0.1:18080/abc',
...
})
if not err then
task:insert_result('HTTP_SYNC', 1.0, response.content)
end
...
To work with DNS properly, a new module called rspamd_dns
has been introduced, which replaces the former task:get_resolver()
calls. The new API requires explicit specification of the type of request, rather than providing a set of resolve_*
methods.
local function dns_callback(_, to_resolve, results, err)
if not err then
...
end
end
task:get_resolver():resolve_a({
name = 'rspamd.com'
callback = dns_callback,
...
})
Please note that synchronous mode requires symbol to be registered with coro flag (see “full example”).
local is_ok, results = rspamd_dns.request({
type = 'a',
name = to_resolve ,
...
})
if is_ok then
task:insert_result('DNS_SYNC', 1.0, tostring(results[1]))
end
It is recommended to use lua_tcp_sync
module to work TCP.
local function http_read_cb(err, data, conn)
task:insert_result('HTTP_ASYNC_RESPONSE', 1.0, data or err)
...
end
rspamd_tcp:request({
callback = http_read_cb,
host = '127.0.0.1',
data = {'GET /request HTTP/1.1\r\nConnection: keep-alive\r\n\r\n'},
...
})
Please note that synchronous mode requires symbol to be registered with coro flag (see “full example”).
local is_ok, connection = tcp_sync.connect {
host = '127.0.0.1',
...
}
if not is_ok then
logger.errx(task, 'write error: %1', connection)
end
logger.errx(task, 'connect_sync %1, %2', is_ok, tostring(connection))
is_ok, err = connection:write('GET /request_sync HTTP/1.1\r\nConnection: keep-alive\r\n\r\n')
if not is_ok then
logger.errx(task, 'write error: %1', err)
end
is_ok, data = connection:read_once();
task:insert_result('HTTP_RESPONSE', 1.0, data or err)
local function redis_cb(err, data)
if not err then
task:insert_result('REDIS_ASYNC201809_ERROR', 1.0, err)
end
...
end
local attrs = {
callback = redis_cb
...
}
local request = {...}
redis_lua.request(redis_params, attrs, request)
Please note that synchronous mode requires symbol to be registered with coro flag (see “full example”).
local is_ok, connection = redis_lua.connect(...)
if not is_ok then
return
end
is_ok, err = connection:add_cmd('EVAL', {[[return "hello from lua on redis"]], 0})
if not is_ok then
return
end
is_ok,data = connection:exec()
if is_ok then
task:insert_result('REDIS', 1.0, data)
end
...