Skip to content

Commit a689e61

Browse files
feature: add timer context support for get_client_body_reader.
1 parent 7837f60 commit a689e61

2 files changed

Lines changed: 101 additions & 3 deletions

File tree

lib/resty/http.lua

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,7 @@ function _M.request_uri(self, uri, params)
972972
end
973973

974974

975-
function _M.get_client_body_reader(_, chunksize, sock)
975+
function _M.get_client_body_reader(_, chunksize, sock, headers)
976976
chunksize = chunksize or 65536
977977

978978
if not sock then
@@ -992,8 +992,11 @@ function _M.get_client_body_reader(_, chunksize, sock)
992992
end
993993
end
994994

995-
local headers = ngx_req_get_headers()
996-
local length = headers.content_length
995+
if not headers then
996+
headers = ngx_req_get_headers()
997+
end
998+
999+
local length = headers["Content-Length"]
9971000
if length then
9981001
return _body_reader(sock, tonumber(length), chunksize)
9991002
elseif transfer_encoding_is_chunked(headers) then

t/10-clientbodyreader.t

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ our $HttpConfig = qq{
1010
lua_package_path "$pwd/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;";
1111
error_log logs/error.log debug;
1212
13+
lua_shared_dict test_dict 1m;
14+
1315
init_by_lua_block {
1416
if $ENV{TEST_COVERAGE} == 1 then
1517
jit.off()
@@ -120,3 +122,96 @@ bar\r
120122
chunked request bodies not supported yet
121123
--- no_error_log
122124
[warn]
125+
126+
127+
=== TEST 4: Read a body in a timer context with explicitly provided socket and headers
128+
--- http_config eval: $::HttpConfig
129+
--- config
130+
location = /b {
131+
content_by_lua_block {
132+
ngx.header["Content-Length"] = 3
133+
ngx.print("foo")
134+
}
135+
}
136+
137+
location = /a {
138+
content_by_lua_block {
139+
-- ngx.var / ngx.req.* are not available inside a timer, so
140+
-- capture what we need and pass it in explicitly.
141+
local port = ngx.var.server_port
142+
143+
local function handler(premature, port)
144+
local sock = ngx.socket.tcp()
145+
local ok, err = sock:connect("127.0.0.1", port)
146+
if not ok then
147+
ngx.shared.test_dict:set("body", "connect failed: " .. err)
148+
return
149+
end
150+
151+
sock:send("GET /b HTTP/1.0\r\nHost: localhost\r\n\r\n")
152+
153+
-- Read the status line and response headers off the socket.
154+
local content_length
155+
while true do
156+
local line = sock:receive("*l")
157+
if not line or line == "" then
158+
break
159+
end
160+
local k, v = line:match("^([^:]+):%s*(.+)$")
161+
if k and k:lower() == "content-length" then
162+
content_length = tonumber(v)
163+
end
164+
end
165+
166+
-- Use the explicit socket + headers overloads since
167+
-- ngx.req.socket() / ngx.req.get_headers() do not work here.
168+
local httpc = require("resty.http").new()
169+
local headers = { ["Content-Length"] = content_length }
170+
local reader, err = httpc:get_client_body_reader(8192, sock, headers)
171+
if not reader then
172+
ngx.shared.test_dict:set("body", "no reader: " .. (err or "nil"))
173+
return
174+
end
175+
176+
local body = {}
177+
repeat
178+
local buffer, err = reader()
179+
if err then
180+
ngx.shared.test_dict:set("body", "read error: " .. err)
181+
return
182+
end
183+
if buffer then
184+
body[#body + 1] = buffer
185+
end
186+
until not buffer
187+
188+
sock:close()
189+
ngx.shared.test_dict:set("body", table.concat(body))
190+
end
191+
192+
local ok, err = ngx.timer.at(0, handler, port)
193+
if not ok then
194+
ngx.say("failed to create timer: ", err)
195+
return
196+
end
197+
198+
-- Wait for the timer to finish and store its result.
199+
local body
200+
for _ = 1, 100 do
201+
body = ngx.shared.test_dict:get("body")
202+
if body then
203+
break
204+
end
205+
ngx.sleep(0.01)
206+
end
207+
208+
ngx.say(body)
209+
}
210+
}
211+
--- request
212+
GET /a
213+
--- response_body
214+
foo
215+
--- no_error_log
216+
[error]
217+
[warn]

0 commit comments

Comments
 (0)