A library to provide dynamic (via json/API) load of lua byte code into nginx/openresty.
An openresty library provisioning dynamic (via JSON/API) load of lua code into nginx/openresty.
https://user-images.githubusercontent.com/55913/210108208-6556981d-a59f-43cb-b080-5a2185ea62f2.mp4
You can find the complete example in the usage
folder. The following steps will guide you through the basic usage:
Install the library: luarocks install resty-dynacode
Create a lua module to import and configure the library.
local dyna_controller = require "resty.dynacode.controller"
local controller = {} -- your module
dyna_controller.setup({
plugin_api_uri = "http://api:9090/response.json", -- the API providing the expected response
plugin_api_polling_interval = 15,
plugin_api_poll_at_init = true,
workers_max_jitter = 5,
shm = "cache_dict",
})
function controller.run()
dyna_controller.run()
end
return controller
And finally hooking up the phases at the nginx conf.
http {
# you must provide a shared memory space for caching
lua_shared_dict cache_dict 1m;
# spawning the pollers
init_worker_by_lua_block { require("controller").run() }
# hooking up all the phases (on http context)
rewrite_by_lua_block { require("controller").run() }
access_by_lua_block { require("controller").run() }
header_filter_by_lua_block { require("controller").run() }
body_filter_by_lua_block { require("controller").run() }
log_by_lua_block { require("controller").run() }
# the servers we want to add lua code
server {
listen 6060;
server_name dynamic.local.com;
location / {
content_by_lua_block { require("controller").run() }
}
}
}
Do what we already do with Lua, but without SIGHUP or deployment. It was inspired by a previous hackathon. Things this library enables you to do:
loadstring
) the lua code and share it through each workergraph LR
subgraph Nginx/Openresty Background
DynaCode -->|run each X seconds| Poller
Poller -->|already in cache| Cache[(Cache SHM)]
Poller -->|GET /plugins.json| Fetcher
Fetcher --> Cache
Cache --> Compiler[[Compiler]]
Compiler --> |share bytecode| LocalLuaVM([LocalLuaVM])
end
graph LR
subgraph Nginx/Openresty Request
Runner -->|library is| Enabled
Runner -->|host is not| Skippable
Runner -->|host matches| Regex
Runner -->|matches current| Phase
Runner -->|execute the function| LocalLuaVM([LocalLuaVM])
end
One can use events to expose metrics about the: poller
, fetcher
, caching
, compiler
, runner
, and etc.
You can create a CMS where you’ll input your code, AKA plugins. A plugin belongs to a server/domain (*
, regex, etc), it has an nginx phase (access, rewrite, log, etc), and the lua code it represents. Your CMS then must expose these plugins in a known API/structure.
{
"general": {
"status": "enabled",
"skip_domains": [
"[\\\\w\\\\d\\\\.\\\\-]*server.local.com"
]
},
"domains": [
{
"name": "dynamic.local.com",
"plugins": [
{
"name": "dynamic content",
"code": "ngx.say(\"olá mundo!\")\r\nngx.say(\"hello world!\")",
"phase": "content"
},
{
"name": "adding cors headers",
"code": "ngx.header[\"Access-Control-Allow-Origin\"] = \"http://dynamic.local.com\"",
"phase": "header_filter"
},
{
"name": "authentication required",
"code": "local token = ngx.var.arg_token or ngx.var.cookie_superstition\r\n\r\nif token ~= 'token' then\r\n return ngx.exit(ngx.HTTP_FORBIDDEN)\r\nelse\r\n ngx.header['Set-Cookie'] = {'superstition=token'}\r\nend",
"phase": "access"
}
]
}
]
}
Once a JSON API is running, the openresty/nginx will fetch
regularly the plugins (in background), compile
them, and save them to cache. When a regular user issues a request then the runner
will see if the current context (server name, phase, etc.) matches with the plugin spec/requirements, and run it.
Although this library was made to support most of the failures types through pcall
, fallbacks
, and sensible defaults
you can’t forget that a developer is still writing the code.
The following code will keep all nginx workers busy forever, effectively making it unreachable.
while true do print('The bullets, Just stop your crying') end
While one could try to solve that with quotas, but Luajit doesn’t allow us to use that.
What happens when plugin API is offline? If the plugins are already in memory, that’s fine. But when nginx was restarted/reloaded, it’s going to "lose"
all the cached data.
{
"general": {
},
"domains": [
{
"name": "dynamic.local.com",
"plugins": [1, 2]
}
]
"plugins": [
{
"id": 1,
"name": "dynamic content",
"code": "ngx.say(\"olá mundo!\")\r\nngx.say(\"hello world!\")",
"phase": "content"
},
{
"id": 2,
"name": "adding cors headers",
"code": "ngx.header[\"Access-Control-Allow-Origin\"] = \"http://dynamic.local.com\"",
"phase": "header_filter"
}
]
}
another way to have a plugin per multiple domains is to rely on
*
or regexes.*\.common.com
BG_UPDATED_PLUGINS
or a new event)BG_DIDNT_UPDATE_PLUGINS
)ngx_now
, tbl.logger
)on_compile_fail
, on_success
, ...
)