OpenResty Integration in APISIX#
APISIX runs on top of OpenResty (NGINX + LuaJIT) and ships its own extended runtime — apisix-runtime — which bundles a specific OpenResty version, the apisix-nginx-module, and supporting C modules. Several APISIX features only activate when this runtime is present.
Runtime Detection and the use_apisix_base Flag#
At startup, APISIX checks the openresty -V output for the string apisix-nginx-module . The result sets the use_apisix_base boolean, which the nginx config template uses to conditionally emit runtime-only directives :
apisix_delay_client_max_body_check on;
apisix_mirror_on_demand on;
apisix_delay_client_max_body_check defers nginx's client_max_body_size enforcement so that Lua code in the rewrite phase (e.g., client-control) can override the limit before the kernel reads the body. It is only emitted when use_apisix_base is true. Other conditionally-gated features include privileged-agent Prometheus, gRPC :authority header handling, and on-demand proxy mirroring .
The pinned runtime version lives in .requirements (APISIX_RUNTIME=...). As of PR #13412, the runtime was bumped to 1.3.6, which upgrades the underlying OpenResty to 1.29.2.4 . Runtime selection now comes solely from .requirements; the old OPENRESTY_VERSION CI environment variable has been removed .
Client Body Size: Static and Dynamic Limits#
Static limit — The client_max_body_size directive is rendered globally in the http block of the generated nginx.conf :
client_max_body_size {* http.client_max_body_size *};
Dynamic per-route limit — The client-control plugin (priority 22000, rewrite phase) can override this limit at request time using an FFI call into apisix-nginx-module :
apisix_ngx_client.set_client_max_body_size(conf.max_body_size)
If max_body_size is configured and a Content-Length header is present, the plugin checks the size immediately and returns 413 before reading any body. The FFI call also overrides what NGINX enforces when the body is actually read . This plugin requires apisix-runtime; it returns 501 on plain OpenResty .
Phase Ordering: Global Rules and client-control Compatibility#
A subtle ordering issue arises when global rules include an access-phase plugin that reads the request body (e.g., a logger with include_req_body). If global-rule access runs before route rewrite, the client-control FFI override has not yet been applied, causing a 413 for bodies that exceed the static client_max_body_size.
PR #13345 fixed this by splitting global rule execution into explicit rewrite and access sub-phases :
Before: global_rules rewrite+access → route/service rewrite → route/service access
After: global_rules rewrite → route/service rewrite → global_rules access → route/service access
The client-control rewrite runs in the second step, so the FFI override is in place when global-rule access reads the body in the fourth step. The change is in apisix/init.lua and apisix/plugin.lua .
Request Body Handling: core.request.get_body#
apisix/core/request.lua get_body is the central helper for reading request bodies in plugins. Key behaviors:
- Early Content-Length check: If a
max_sizeargument is passed and aContent-Lengthheader is present, the size is validated before callingngx.req.read_body(). If the limit is exceeded, theExpectheader is cleared to suppress100-continue. - Post-read size check: After reading, the actual body bytes are checked again; if the body spilled to a temp file, the file size is checked .
- Stale HTTP/2 guard (historically): An earlier guard rejected HTTP/2 and HTTP/3 requests that lacked
Content-Length. PR #13428 removed this guard because the underlying lua-nginx-module behavior that required it was already reverted upstream; newer OpenResty no longer rejectsngx.req.read_body()for such requests .
Chunked Transfer Encoding and forward-auth#
When the forward-auth plugin is configured with request_method: POST, it buffers the client body and re-posts it to the auth service. A historical bug forwarded the client's Transfer-Encoding: chunked, Content-Length, and Expect headers verbatim — even though the body was already de-chunked and buffered by NGINX. PR #13642 fixed this by stripping those framing headers on the POST path and only forwarding Content-Encoding (which still applies to the buffered bytes, since NGINX does not decode request content-encoding) .
Key Source References#
| Area | File |
|---|---|
Runtime detection (use_apisix_base) | apisix/cli/env.lua |
| nginx config template | apisix/cli/ngx_tpl.lua |
| Dynamic body size limit | apisix/plugins/client-control.lua |
| Body reading helper | apisix/core/request.lua |
| Global rule phase split | apisix/init.lua, apisix/plugin.lua |