{"id":16407451,"url":"https://github.com/leandromoreira/lua-resty-dynacode","last_synced_at":"2025-10-04T00:32:02.356Z","repository":{"id":39594459,"uuid":"461281453","full_name":"leandromoreira/lua-resty-dynacode","owner":"leandromoreira","description":"A library to provide dynamic (via json/API) load of lua byte code into nginx/openresty.","archived":false,"fork":false,"pushed_at":"2023-01-19T16:15:37.000Z","size":1027,"stargazers_count":31,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-13T22:50:24.638Z","etag":null,"topics":["api","dynamic","lua","luajit","nginx","openresty"],"latest_commit_sha":null,"homepage":"https://leandromoreira.github.io/lua-resty-dynacode/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leandromoreira.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-02-19T18:40:30.000Z","updated_at":"2025-01-03T18:47:05.000Z","dependencies_parsed_at":"2023-02-11T09:15:22.628Z","dependency_job_id":null,"html_url":"https://github.com/leandromoreira/lua-resty-dynacode","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leandromoreira%2Flua-resty-dynacode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leandromoreira%2Flua-resty-dynacode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leandromoreira%2Flua-resty-dynacode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leandromoreira%2Flua-resty-dynacode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leandromoreira","download_url":"https://codeload.github.com/leandromoreira/lua-resty-dynacode/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235208692,"owners_count":18953000,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","dynamic","lua","luajit","nginx","openresty"],"created_at":"2024-10-11T06:13:57.667Z","updated_at":"2025-10-04T00:31:57.008Z","avatar_url":"https://github.com/leandromoreira.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lua Resty Dynacode\n\nAn openresty library provisioning dynamic (via JSON/API) load of lua code into nginx/openresty.\n\n# Quick Start\n\nhttps://user-images.githubusercontent.com/55913/210108208-6556981d-a59f-43cb-b080-5a2185ea62f2.mp4\n\nYou can find the complete example in the [`usage`](/usage) folder. The following steps will guide you through the basic usage:\n\nInstall the library: `luarocks install resty-dynacode`\n\nCreate a lua module to import and configure the library.\n\n```lua\nlocal dyna_controller = require \"resty.dynacode.controller\"\nlocal controller = {} -- your module\n\ndyna_controller.setup({\n  plugin_api_uri = \"http://api:9090/response.json\", -- the API providing the expected response\n  plugin_api_polling_interval = 15,\n  plugin_api_poll_at_init = true,\n  workers_max_jitter = 5,\n  shm = \"cache_dict\",\n})\n\nfunction controller.run()\n  dyna_controller.run()\nend\n\nreturn controller\n```\n\nAnd finally hooking up the phases at the nginx conf.\n\n```nginx\nhttp {\n  # you must provide a shared memory space for caching\n  lua_shared_dict cache_dict 1m;\n  # spawning the pollers\n  init_worker_by_lua_block   { require(\"controller\").run() }\n  # hooking up all the phases (on http context)\n  rewrite_by_lua_block       { require(\"controller\").run() }\n  access_by_lua_block        { require(\"controller\").run() }\n  header_filter_by_lua_block { require(\"controller\").run() }\n  body_filter_by_lua_block   { require(\"controller\").run() }\n  log_by_lua_block           { require(\"controller\").run() }\n\n  # the servers we want to add lua code\n  server {\n    listen 6060;\n    server_name  dynamic.local.com;\n\n    location / {\n      content_by_lua_block { require(\"controller\").run() }\n    }\n  }\n}\n```\n\n# Motivation\n\nDo what we already do with Lua, but without SIGHUP or deployment. It was [inspired by a previous hackathon](https://github.com/leandromoreira/edge-computing-resty#demo). Things this library enables you to do:\n\n* Debug (log/metrify specific IP/token/user agent/cookie)\n* Quick maneuvers:\n  * Block IP\n  * Deny requests per path/user agent/etc\n  * Drain a single server (302) / health check\n  * Turn on/off modules/variables\n  * ...\n* Chaos testing\n* Change any variables\n* Modify response body\n* Add response header (CORs, SCP, HSTS, X-Frame-Options,\n ...)\n* Really anything you can do with lua/openresty\n\n\n# How it works\n\n* in the **background**:\n  * start a [poller](/src/resty/dynacode/poller.lua#L40)\n  * fetch the [JSON API response](/usage/response.json) and save it to a [**shared memory**](/src/resty/dynacode/cache.lua#L67)\n  * compile (`loadstring`) the lua code and share it through [**each worker**](/src/resty/dynacode/controller.lua#L157)\n* at the **runtime (request cycle)**:\n  * select the proper domain (applying [regex against current host](/src/resty/dynacode/runner.lua#L88))\n  * select the applicable plugins (based on phase/applicability)\n  * [run them](/src/resty/dynacode/runner.lua#L102)\n\n## Background \n\n```mermaid\ngraph LR\n    subgraph Nginx/Openresty Background\n        DynaCode --\u003e|run each X seconds| Poller\n        Poller --\u003e|already in cache| Cache[(Cache SHM)]\n        Poller --\u003e|GET /plugins.json| Fetcher\n        Fetcher --\u003e Cache\n        Cache --\u003e Compiler[[Compiler]]\n        Compiler --\u003e |share bytecode| LocalLuaVM([LocalLuaVM])\n    end\n```\n\n## Request\n\n```mermaid\ngraph LR\n    subgraph Nginx/Openresty Request\n        Runner --\u003e|library is| Enabled\n        Runner --\u003e|host is not| Skippable\n        Runner --\u003e|host matches| Regex\n        Runner --\u003e|matches current| Phase\n        Runner --\u003e|execute the function| LocalLuaVM([LocalLuaVM])\n    end\n```\n\n# Observability\n\nOne [can use events](usage/src/controller.lua#L73) to expose metrics about the: `poller`, `fetcher`, `caching`, `compiler`, `runner`, and etc.\n\n# API format to provide functions\n\nYou 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.\n\n```json\n{\n   \"general\": {\n      \"status\": \"enabled\",\n      \"skip_domains\": [\n         \"[\\\\\\\\w\\\\\\\\d\\\\\\\\.\\\\\\\\-]*server.local.com\"\n      ]\n   },\n   \"domains\": [\n      {\n         \"name\": \"dynamic.local.com\",\n         \"plugins\": [\n            {\n               \"name\": \"dynamic content\",\n               \"code\": \"ngx.say(\\\"olá mundo!\\\")\\r\\nngx.say(\\\"hello world!\\\")\",\n               \"phase\": \"content\"\n            },\n            {\n               \"name\": \"adding cors headers\",\n               \"code\": \"ngx.header[\\\"Access-Control-Allow-Origin\\\"] = \\\"http://dynamic.local.com\\\"\",\n               \"phase\": \"header_filter\"\n            },\n            {\n               \"name\": \"authentication required\",\n               \"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\",\n               \"phase\": \"access\"\n            }\n         ]\n      }\n   ]\n}\n```\n\nOnce 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.\n\n\n# Warning\n\nAlthough 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.\n\nThe following code will keep all nginx workers busy forever, effectively making it unreachable.\n\n```lua\nwhile true do print('The bullets, Just stop your crying') end\n```\n\nWhile one could try to solve that with [quotas, but Luajit doesn't allow us to use that](https://github.com/Kong/kong-lua-sandbox#optionsquota).\n\nWhat 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.\n\n\n# Road map\n\n* evaluate if having plugins separated from domains might be helpful (re-use among domains)\n```json\n{\n   \"general\": {\n   },\n   \"domains\": [\n      {\n         \"name\": \"dynamic.local.com\",\n         \"plugins\": [1, 2]\n      }\n   ]\n   \"plugins\": [\n            {\n               \"id\": 1,\n               \"name\": \"dynamic content\",\n               \"code\": \"ngx.say(\\\"olá mundo!\\\")\\r\\nngx.say(\\\"hello world!\\\")\",\n               \"phase\": \"content\"\n            },\n            {\n               \"id\": 2,\n               \"name\": \"adding cors headers\",\n               \"code\": \"ngx.header[\\\"Access-Control-Allow-Origin\\\"] = \\\"http://dynamic.local.com\\\"\",\n               \"phase\": \"header_filter\"\n            }\n    ]\n\n}\n```\n\u003e *another way to have a plugin per multiple domains* is to rely on `*` or regexes `.*\\.common.com`\n* CMS probably would benefit from having plugins code at a git repo (linked through its git path, therefore tested and developed like any other lua code already) and only render them at the response time\n* measure the impact of lots of lua code being loaded (even though it's compressed), if there's any need to load the plugins per chunk/domain/whatever\n* make CMS run a lua compile phase to avoid uncompiled code being deployed\n* enable some way for user to setup the request for polling (providing authentication, and etc)\n* avoid re-compilation when no plugins were altered (should we emit `BG_UPDATED_PLUGINS` or a new event)\n* review the events adding arguments when necessary/possible (for instance `BG_DIDNT_UPDATE_PLUGINS`)\n* ~add a CMS for the complete example~\n* ~add a quick start for the complete example~\n* evaluate the lua-resty-mlcache rock to replace the current cache system\n* ~~publish a rock~~\n* evaluate if an off-line mode makes sense (saving a local api response for -HUP/restart without link to API)\n* ~~use / provide function direct access / local function instead of tables (`ngx_now`, `tbl.logger`)~~\n* ~~discuss the json format (making phases accessible without iterating through all plugins)~~\n* ~~offer events callbacks (like: `on_compile_fail`, `on_success`, `...`)~~\n  * maybe a vts plugin for metrics\n* ~~tests~~\n* ~~documentation~~ / ~~drawing~~ / ~~use cases~~\n* build, ~~lint~~\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleandromoreira%2Flua-resty-dynacode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleandromoreira%2Flua-resty-dynacode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleandromoreira%2Flua-resty-dynacode/lists"}