Filters
nginx sends the header and the body of a response through two chains of
filters, whichever handler or upstream produced the response. ngx_mruby
adds one filter to each chain: mruby_output_header_filter (and its form
_code) runs Ruby on the header, and mruby_output_body_filter (and
_code) on the body. ngx_mruby adds its header filter when the
configuration has either directive, and its body filter when it has a
body filter.
Header filters
nginx's not-modified header filter
mruby_output_header_filter: the status and the headers; with a body filter, the header waits for itthe header filters of nginx's other modules
nginx writes the header
Body filters
nginx's range and copy body filters
mruby_output_body_filter: the whole body, with Content-Length; any other body passes unreadthe body filters of nginx's other modules
nginx's write filter sends the body
0c72e3970 (2026-10-19) and of nginx:src/http/ngx_http_mruby_module.clines 591 to 595, 1676 to 1677, 1955 to 1965, 2041 to 2054, 2085 to 2122 and 2135 to 2154config.inlines 48 to 69auto/moduleslines 165 to 187, 386 to 427 and 1494 to 1495 of nginx 1.31.6auto/modulelines 23 to 30 of nginx 1.31.6
- nginx calls the filters of each chain from the top. ngx_mruby’s filters
come after nginx’s not-modified header filter and its range and copy
body filters, and before the filters of nginx’s other modules, such as
gzipand the chunked filter, so a filter of ngx_mruby sees the response before nginx compresses or chunks it. nginx builds the chains in the order of its modules (auto/modules), where ngx_mruby stands after nginx’s own filter modules and before the copy, range body, not-modified and slice filters, and each filter goes in front of those before it. The slice filter exists in a build with--with-http_slice_module. - The header filter reads and changes the status and the headers through
Nginx::RequestandNginx::Headers_out. It runs before the body has been read: thereNginx::Filter#bodyreturns an empty string. - The body filter needs the length of the response. For a response with
Content-Length, ngx_mruby collects the whole body into one buffer, runs the script once with it inNginx::Filter#body, and sends what the script puts intoNginx::Filter#body=. A response withoutContent-Length, such as a chunked response of an upstream, passes the filter unread, and ngx_mruby logs that at thenoticelevel. - When a location has a body filter, ngx_mruby holds the header back
until the body filter has run, so that
Content-Lengthis the length of the new body. - Neither filter can wait:
Nginx::Async.sleepandNginx::Async::HTTP.sub_requestraise aRuntimeErrorthere, andNginx.rputsandNginx.echocannot be used.
The guide Change a response body has a sample of both filters that CI runs. The directive reference gives their syntax.