lua-nginx-module
lua-nginx-module copied to clipboard
ngx.resp.get_headers(): Why is the response header incomplete?
config: location =/version { default_type 'text/plain';
content_by_lua_file /usr/local/lmrs/lua/api/version.lua;
log_by_lua '
ngx.log(ngx.ERR,"header: ", require"cjson".encode(ngx.resp.get_headers()))
';
}
request: curl -i "http://127.0.0.1:8088/version" HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Tue, 10 Sep 2019 13:17:46 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: keep-alive
log: 2019/09/10 09:12:14 [error] 123144#0: *3 [lua] log_by_lua(lmrs.conf:195):2: header: {"transfer-encoding":"chunked","content-type":"text/plain","connection":"close"} while logging request, client: 127.0.0.1, server: , request: "GET /version HTTP/1.1", host: "127.0.0.1:8088"
Why is the response header incomplete?
Hello,
Nginx in general stores headers in a linked list. But this has a drawback of changing and accessing a header requires traversing the list to find it which may be slow. As an optimization Nginx also defined a bunch of "commonly used" headers that are not stored in the list, but rather in the struct itself, this guarantees O(1) access while changing/reading those "commonly used" headers.
See definitions of ngx_http_headers_out_t struct
ngx.resp.get_headers calls ngx_http_lua_ngx_resp_get_headers. In that function we did not handle all the cases for "fast path" headers. AFAIK only content-type, content-length, connection and transfer-encoding were handled.
From Nginx source that handles $sent_http_* variables, the following header support needs to be added for ngx.resp.get_headers to behave correctly:
- Location
- Last-Modified
- Keep-Alive
- Cache-Control
- Link
- Date
- Server
Note that the last two headers, Date and Server were not even handled by the $sent_http_* variables properly, presumably they were overlooked. This was confirmed with my local testing by reading $sent_http_server and $sent_http_date in log phase. Both returned nil.
Is there any work around for this? Our own requirements are to log in access logs the Location response header which works simply with $upstream_http_location. However we have additional requirements to mask out some secure data from the parameters in that url. I have some lua script that does that masking successfully but we can't pass it ngx.resp.get_headers()["location"].
I don't know much about nginx or lua, but I noticed some weird behavior, which let me to find this issue.
I was playing around with luafilters for headers, when I noticed, that ngx.resp.get_headers gives a different number of headers when called from set_by_lua_block and from header_filter_by_lua
consider the following example
set_by_lua_block $resp_headers_short{
local lua_resp_headers_short = ""
for k, v in pairs(ngx.resp.get_headers()) do
lua_resp_headers_short = lua_resp_headers_short .. k.."="..v.." "
end
return lua_resp_headers_short
}
set $resp_headers_full "";
header_filter_by_lua '
for k, v in pairs(ngx.resp.get_headers()) do
ngx.var.resp_headers_full = ngx.var.resp_headers_full .. k.."="..v.." "
end
';
the first block only produces connection=keep-alive, while the latter block produces all of them last-modified=Tue, 13 Apr 2021 15:13:59 GMT etag=\"6075b537-264\" accept-ranges=bytes content-type=text/html connection=keep-alive content-length=612 (sans date and server)
considering the comment above, I guess that header_filter_by_lua is executed at a different time, where fast-path headers are already part of the request.
Is there any work around for this?
use header_filter_by_lua, apparently
Is there any work around for this? Our own requirements are to log in access logs the Location response header which works simply with $upstream_http_location. However we have additional requirements to mask out some secure data from the parameters in that url. I have some lua script that does that masking successfully but we can't pass it ngx.resp.get_headers()["location"].
I had the same problem of trying to access the Location header in a log_by_lua block (though a different use case from you). @dndx's comment gave me the needed clue.
I was able to get it with ngx.var.sent_http_location. I guess for you it would be ngx.var.upstream_http_location.