patternMinor
Asynchronous request cloning using Lua + openresty
Viewed 0 times
asynchronousrequestcloningusingopenrestylua
Problem
I'm working on a project to clone a subset of requests going to our production servers to one or more non-production endpoints. I opted to do this in Nginx and Lua because at the time, I could not find a tool that fit the requirements I needed, namely, only forwarding all requests for a subset of session IDs (as opposed to a percentage of all requests).
Initially, I was simply using
This is my first Lua project in Nginx, and actually my first time touching Lua years, and nobody at work knows Lua well enough to give any advice, so I'd appreciate if anyone could point out any flaws in my methods or things that can be done better.
example nginx.conf:
store_token_from_uri.lua
```
ngx.req.read_body()
-- Extract the token from the URI if it is present
-- add "set $session_token "";" before to initialize variable
ngx.ctx.args = ngx.req.get_uri_args()
-- If the id argument is present, in the query string, that is our token.
if ngx.ctx.args.token then
ngx.var.session_token = ngx.ctx.args.token
elseif ngx.ctx.args.id then
Initially, I was simply using
ngx.timer.at to dispatch the subrequest, but I was informed that the server could run out of threads if the destination server was unavailable. So I looked at lua-resty-logger-socket and used the code that they use to buffer requests and modified it for my usage.This is my first Lua project in Nginx, and actually my first time touching Lua years, and nobody at work knows Lua well enough to give any advice, so I'd appreciate if anyone could point out any flaws in my methods or things that can be done better.
example nginx.conf:
location = /GenerateToken {
set $session_token "";
access_by_lua_file store_token_from_uri.lua;
body_filter_by_lua_file store_token_from_body.lua;
log_by_lua_block {
local dispatcher = require "dispatcher"
if not dispatcher.initialized() then
local ok, err = dispatcher.init {
servers = {
["127.0.0.1:" .. ngx.var.server_port] = .99
}
}
if not ok then
ngx.log(ngx.ERR, "failed to initialize the logger: ",
err)
return
end
end
dispatcher.queue_request(ngx.var.session_token)
}
}store_token_from_uri.lua
```
ngx.req.read_body()
-- Extract the token from the URI if it is present
-- add "set $session_token "";" before to initialize variable
ngx.ctx.args = ngx.req.get_uri_args()
-- If the id argument is present, in the query string, that is our token.
if ngx.ctx.args.token then
ngx.var.session_token = ngx.ctx.args.token
elseif ngx.ctx.args.id then
Solution
The
where, the value of
If you do not want the
In the
In the
ngx.var.session_token can be easily setup in store_token_from_uri.lua as follows:ngx.var.session_token = ngx.ctx.args.token or ngx.ctx.args.id or ngx.var.session_tokenwhere, the value of
ngx.var.session_token will be restored in case both ngx.ctx.args.token and ngx.ctx.args.id are nil. The if-elseif blocks are not required anymore.If you do not want the
start and end positions when using string.find, use the string.match function instead. The same can be used instead of your gsub call later in store_token_from_body.lua file. The characters - and . in a lua-pattern have special meaning. Escape them using a % character for a literal matching. Therefore:if string.find(ngx.var.uri, '/GenerateToken') then
local resp_body = ngx.arg[1]:sub(1, 8000)
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
if ngx.arg[2] then
-- For GET requests with a callback, extract the token from the callback response
if string.find(ngx.var.uri, '/GenerateToken%.js') and ngx.ctx.args.callback then
ngx.var.session_token = ngx.ctx.buffered:match "(%x+%-%x+%-%x+%-%x+%-%x+)"
-- Otherwise, the response should just contain the token.
else
-- Trim the response of any trailing newlines
ngx.var.session_token = ngx.ctx.buffered:match "^%s*(.-)%s*$"
end
-- Add the token to the query string so the remote server will not re-create a new token
ngx.ctx.args.token = ngx.var.session_token
ngx.req.set_uri_args(ngx.ctx.args)
end
endIn the
dispatcher.lua file, the code snippet for debug logging, can be put inside a separate function:function debug_log( msg, ... )
if debug then
ngx_log(DEBUG, msg, ...)
end
endIn the
_do_flush function, you are concatenating a string buffer repeatedly. Instead, store all errors inside a table, and return table.concat(errors, " "):local errors = {}
...
if err then
table.insert(errors, err)
end
...
return all_ok, table.concat(errors, " ")Code Snippets
ngx.var.session_token = ngx.ctx.args.token or ngx.ctx.args.id or ngx.var.session_tokenif string.find(ngx.var.uri, '/GenerateToken') then
local resp_body = ngx.arg[1]:sub(1, 8000)
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
if ngx.arg[2] then
-- For GET requests with a callback, extract the token from the callback response
if string.find(ngx.var.uri, '/GenerateToken%.js') and ngx.ctx.args.callback then
ngx.var.session_token = ngx.ctx.buffered:match "(%x+%-%x+%-%x+%-%x+%-%x+)"
-- Otherwise, the response should just contain the token.
else
-- Trim the response of any trailing newlines
ngx.var.session_token = ngx.ctx.buffered:match "^%s*(.-)%s*$"
end
-- Add the token to the query string so the remote server will not re-create a new token
ngx.ctx.args.token = ngx.var.session_token
ngx.req.set_uri_args(ngx.ctx.args)
end
endfunction debug_log( msg, ... )
if debug then
ngx_log(DEBUG, msg, ...)
end
endlocal errors = {}
...
if err then
table.insert(errors, err)
end
...
return all_ok, table.concat(errors, " ")Context
StackExchange Code Review Q#110277, answer score: 3
Revisions (0)
No revisions yet.