HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Asynchronous request cloning using Lua + openresty

Submitted by: @import:stackexchange-codereview··
0
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 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 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_token


where, 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
end


In 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
end


In 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_token
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
end
function debug_log( msg, ... )
    if debug then
        ngx_log(DEBUG, msg, ...)
    end
end
local 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.