Skip to content

Instantly share code, notes, and snippets.

@negasus
Last active November 10, 2022 10:35
Show Gist options
  • Save negasus/4ba472a3e5c0749a584d882a555f5cb5 to your computer and use it in GitHub Desktop.
Save negasus/4ba472a3e5c0749a584d882a555f5cb5 to your computer and use it in GitHub Desktop.

reproxy lua plugins

Usage

reproxy <other flags> --lua.enabled --lua.file=./script/one.lua --lua.file=./script/two.lua

LUA Script template

local function before(request, response)

end

local function after(request, response)

end

return {
    before = before,
    after = after
}

You can return only before, only after, both or empty table

Do not use after hook if you not use it. It's make some work, for example, read response body and pass it to script. It may increase latency for large payloads.

Only before hook example:

local function before(request, response)
    -- your code
end

return {
    before = before
}

API

before(request, response)

request

request function description
getRemoteAddr() Returns remote host:port
getURI() Returns Returns request URI (without host)
getMethod() Returns HTTP method of request. Returns capitalized string
addHeader(name, value) Add request header. Name and Value must be a string
removeHeader(name) Remove request header. Name must be a string
getHeader(name) Get request header value. Name must be a string
stop(code, content, headers) Stop request execution and return code, content and headers

response

response function description
addHeader(name, value) Add response header. Name and Value must be a string

after(request, response)

request

request function description
getHeader(name) Get request header value. Name must be a string

response

response function description
getBody() Returns response body. Returns a string
setBody(content) Set response body. Content must be a string
addHeader(name, value) Add response header. Name and Value must be a string
removeHeader(name) Remove response header. Name must be a string
getHeader(name) Returns response header. Name must be a string
getCode() Returns response status code. Returns a number
setCode(value) Set response status code. Value must be a number

Modules

Reproxy allows you to use some additional lua modules

log

Send message to reproxy log output

Module methods:

  • debug(message)
  • info(message)
  • warn(message)
  • error(message)

message must be a string

Example

local log = require('log')
log.debug('message')
log.info('message')
log.warn('message')
log.error('message')

http

Send http requests

Module consts:

  • MethodGet - returns GET
  • MethodHead - returns HEAD
  • MethodPost - returns POST
  • MethodPut - returns PUT
  • MethodPatch - returns PATCH
  • MethodDelete - returns DELETE
  • MethodConnect - returns CONNECT
  • MethodOptions - returns OPTIONS
  • MethodTrace - returns TRACE

Module methods:

  • get(url)
  • post(url, contentType, body)
  • request(method, url, options)

url, contentType, method must be a string, options must be a table

Request options fields (all fields is not required):

  • timeout - request timeout in seconds, default 30s
  • body - request body, default empty
  • headers - a headers table with strings key/value pairs

Request options example:

local opts = {
    timeout = 10,
    body = '{"foo": 42}',
    headers = {
        ['content-type'] = 'application/json',
        ['X-Authorization'] = 'some_token'
    }
}

All methods return response as first argument (a table) and string error as second argument, if error occurred

Response fields:

  • code - status code, number
  • body - response body, string
  • headers - response headers, table

Response example:

{
    code = 200,
    body = 'foobar',
    headers = {
        'content-type' = { 'application/json' },
        'custom' = { 'foo', 'bar' }
    }
}
http examples

get

local http = require('http')

local res, err = http.get('https://domain.com/foo/bar')
if err ~= nil then
    print('error', err)
    return
end

-- res is a lua table, for example:
-- {
--     code = 200,
--     headers = {
--         'Content-Type' = {'application/json'},
--         'Custom' = {'foo', 'bar'},
--         ...
--     },
--     body = '{"foo":"bar"}'
-- }

post

local http = require('http')

local res, err = http.post('https://domain.com/foo/bar', 'application/json', '{"foo":"bar"}')

You can omit contentType and body (but not both)

local http = require('http')

local res, err = http.post('https://domain.com/foo/bar') -- correct
local res, err = http.post('https://domain.com/foo/bar', 'application/json') -- error, contentType is present, but body is omit

request

local http = require('http')

local res, err = http.request(http.methodPost, 'https://domain.com/foo/bar', {
    headers = {
        ['content-type'] = 'application/json',
        ['X-Authorization'] = 'baz',
    },
    body = '{"foo":"bar"}',
    timeout = 30,
})

Plugins examples

Add response header

local function before(request, response)
    response.addHeader('X-Foo', 'bar')
end

return {
    before = before
}

Stop execution for POST requests

local function before(request, response)
    if request.getMethod() == 'POST' then
        request.stop(403, 'forbidden', {})
    end
end

return {
    before = before
}

Change a substring {NAME} to Foobar in the response body

local function after(request, response)
    local body = string.gsub(response.getBody(), "{NAME}", 'Foobar', 1)
    response.setBody(body)
end

return {
    after = after
}

Performance

I run tests with vegeta tool

echo "GET http://localhost:8000" | vegeta attack -duration=30s | tee results.bin | vegeta report

As backend payload I use index.html 313581 bytes

Before every test, I make single request curl http://localhost:8000

Clear run

reproxy --listen=localhost:8000 --assets.location=./var/web --assets.cache=1m

Result:

Requests      [total, rate, throughput]         1500, 50.03, 50.03
Duration      [total, attack, wait]             29.981s, 29.98s, 513.542µs
Latencies     [min, mean, 50, 90, 95, 99, max]  388.75µs, 1.172ms, 898.648µs, 1.993ms, 2.527ms, 4.427ms, 9.065ms
Bytes In      [total, mean]                     492000, 328.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:1500
Error Set:

Run with lua script, add response header in before (add some bytes count to response)

reproxy --listen=localhost:8000 --assets.location=./var/web --assets.cache=1m --lua.enabled --lua.file=./web/lua/plugin1.lua
local function before(request, response)
    response.addHeader('foo', 'bar')
end

return {
    before = before,
}

Result:

Requests      [total, rate, throughput]         1500, 50.03, 50.03
Duration      [total, attack, wait]             29.981s, 29.98s, 1.074ms
Latencies     [min, mean, 50, 90, 95, 99, max]  466.25µs, 1.961ms, 1.453ms, 3.555ms, 4.816ms, 7.849ms, 11.631ms
Bytes In      [total, mean]                     492000, 328.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:1500
Error Set:

Run with lua script, add response header in before (add some bytes count to response) and replace substring in after hook

reproxy --listen=localhost:8000 --assets.location=./var/web --assets.cache=1m --lua.enabled --lua.file=./web/lua/plugin1.lua
local function before(request, response)
    response.addHeader('foo', 'bar')
end

local function after(request, response)
    local body = string.gsub(response.getBody(), "{NAME}", 'Foobar', 1)
    response.setBody(body)
end

return {
    before = before,
    after = after
}

Result:

Requests      [total, rate, throughput]         1500, 50.03, 49.99
Duration      [total, attack, wait]             30.003s, 29.98s, 23.021ms
Latencies     [min, mean, 50, 90, 95, 99, max]  21.733ms, 23.245ms, 23.152ms, 24.008ms, 24.324ms, 25.697ms, 43.863ms
Bytes In      [total, mean]                     470371500, 313581.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:1500
Error Set:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment