Skip to content

Commit 133dd95

Browse files
committed
Initial support to proxy request with Transfer-Encoding: chunked
1 parent 9d5763b commit 133dd95

File tree

2 files changed

+69
-21
lines changed

2 files changed

+69
-21
lines changed

gateway/src/apicast/http_proxy.lua

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ local http_proxy = require 'resty.http.proxy'
77
local file_reader = require("resty.file").file_reader
88
local chunked_reader = require('resty.http.chunked').chunked_reader
99
local chunked_writer = require('resty.http.chunked').chunked_writer
10+
local req_headers = ngx.req.get_headers
11+
local http_ver = ngx.req.http_version
12+
local ngx_send_headers = ngx.send_headers
13+
local ngx_flush = ngx.flush
1014

1115
local _M = { }
1216

@@ -84,20 +88,55 @@ local function absolute_url(uri)
8488
end
8589

8690
local function forward_https_request(proxy_uri, uri, skip_https_connect)
87-
local sock, err
91+
local sock, ok, err
8892
local chunksize = 32 * 1024
8993
local body
94+
local headers = req_headers()
95+
96+
local encoding = headers.transfer_encoding
97+
if type(encoding) == "table" then
98+
encoding = encoding[1]
99+
end
100+
101+
if encoding and encoding:lower() == "chunked" then
102+
if http_ver() ~= 1.1 then
103+
ngx.log(ngx.ERR, "bad http version")
104+
ngx.exit(ngx.HTTP_BAD_REQUEST)
105+
end
106+
local expect = headers["Expect"]
107+
108+
if type(expect) == "table" then
109+
expect = expect[1]
110+
end
111+
112+
if expect and expect:lower() == "100-continue" then
113+
ngx.status = 100
114+
ok, err = ngx_send_headers()
115+
116+
if not ok then
117+
ngx.log(ngx.ERR, "failed to send response header: " .. (err or "unknown"))
118+
end
119+
120+
ok, err = ngx_flush(true)
121+
if not ok then
122+
ngx.log(ngx.ERR, "failed to flush response header: " .. (err or "unknown"))
123+
end
124+
end
90125

91-
if ngx.req.get_headers()["Transfer-Encoding"] == "chunked" then
92126
-- The default ngx reader does not support chunked request
93127
-- so we will need to get the raw request socket and manually
94128
-- decode the chunked request
95129
sock, err = ngx.req.socket(true)
96130

97131
if not sock then
98-
return ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
132+
if err == "no body" then
133+
body = nil
134+
else
135+
return ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
136+
end
137+
else
138+
body = chunked_reader(sock, chunksize)
99139
end
100-
body = chunked_reader(sock, chunksize)
101140
else
102141
-- This is needed to call ngx.req.get_body_data() below.
103142
ngx.req.read_body()
@@ -157,7 +196,11 @@ local function forward_https_request(proxy_uri, uri, skip_https_connect)
157196
httpc:set_keepalive()
158197
else
159198
ngx.log(ngx.ERR, 'failed to proxy request to: ', proxy_uri, ' err : ', err)
160-
return ngx.exit(ngx.HTTP_BAD_GATEWAY)
199+
if sock then
200+
sock:send("HTTP/1.1 502 Bad Gateway\\r\\n")
201+
else
202+
return ngx.exit(ngx.HTTP_BAD_GATEWAY)
203+
end
161204
end
162205
end
163206

gateway/src/resty/http/chunked.lua

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ local function print_err(error_code, ...)
2121
return ngx.exit(error_code)
2222
end
2323

24-
-- TODO: support Trailers
24+
-- This is a copy of lua-resty-http _chunked_body_reader function but with
25+
-- extra bits to make chunked encoding work with raw socket
26+
-- https://github.com/ledgetech/lua-resty-http/blob/v0.16.1/lib/resty/http.lua#L418
27+
2528
-- chunked_reader return a body reader that translates the data read from sock
2629
-- out of HTTP "chunked" format before returning it
2730
--
@@ -54,7 +57,7 @@ function _M.chunked_reader(sock, max_chunk_size)
5457
-- chunk-size CRLF
5558
local line, err = sock:receive()
5659
if not line then
57-
co_yield(nil, "chunked_reader: failed to receive chunk size, err: " .. err)
60+
co_yield(nil, "chunked_reader: failed to receive chunk size, err: " .. (err or "unknown"))
5861
end
5962

6063
size = tonumber(line, 16)
@@ -74,29 +77,31 @@ function _M.chunked_reader(sock, max_chunk_size)
7477
-- Receive the chunk
7578
local chunk, err = sock:receive(size)
7679
if not chunk then
77-
co_yield(nil, "chunked_reader: failed to receive chunk of size " .. size .. " err: " .. err)
80+
co_yield(nil, "chunked_reader: failed to receive chunk of size " .. size .. " err: " .. (err or "unknown"))
7881
end
7982

80-
-- We're at the end of a chunk, read the next two bytes
81-
-- and verify they are "\r\n"
82-
local data, err = sock:receive(2)
83-
if not data then
84-
co_yield(nil, "chunked_reader: failed to receive chunk terminator, err: " .. err)
83+
if remaining == 0 then
84+
-- We're at the end of a chunk, read the next two bytes
85+
-- and verify they are "\r\n"
86+
local data, err = sock:receive(2)
87+
if not data then
88+
co_yield(nil, "chunked_reader: failed to receive chunk terminator, err: " .. (err or "unknown"))
89+
end
8590
end
8691

87-
chunk = string.format("%x\r\n", size) .. chunk .. data
92+
chunk = string.format("%x\r\n", size) .. chunk .. cr_lf
8893

8994
co_yield(chunk)
9095
else
9196
-- we're at the end of a chunk, read the next two
9297
-- bytes to verify they are "\r\n".
9398
local chunk, err = sock:receive(2)
9499
if not chunk then
95-
co_yield(nil, "chunked_reader: failed to receive chunk terminator, err: " .. err)
100+
co_yield(nil, "chunked_reader: failed to receive chunk terminator, err: " .. (err or "unknown"))
96101
end
97102

98103
if chunk ~= "\r\n" then
99-
co_yield(nil, "chunked_reader: bad chunk terminator: " .. chunk)
104+
co_yield(nil, "chunked_reader: bad chunk terminator")
100105
end
101106

102107
eof = true
@@ -118,22 +123,22 @@ function _M.chunked_writer(sock, res, chunksize)
118123
local status = "HTTP/1.1 " .. res.status .. " " .. res.reason .. cr_lf
119124
bytes, err = send(sock, status)
120125
if not bytes then
121-
print_err(503, "chunked_writer: failed to send status line, err: ", err)
126+
print_err(503, "chunked_writer: failed to send status line, err: " .. (err or "unknown"))
122127
end
123128

124129
-- Rest of header
125130
for k, v in pairs(res.headers) do
126131
local header = k .. ": " .. v .. cr_lf
127132
bytes, err = send(sock, header)
128133
if not bytes then
129-
print_err(503, "chunked_writer: failed to send header, err: ", err)
134+
print_err(503, "chunked_writer: failed to send header, err: " .. (err or "unknown"))
130135
end
131136
end
132137

133138
-- End-of-header
134139
bytes, err = send(sock, cr_lf)
135140
if not bytes then
136-
print_err(503, "chunked_writer: failed to send end of header, err: ", err)
141+
print_err(503, "chunked_writer: failed to send end of header, err: " .. (err or "unknown"))
137142
end
138143

139144
-- Write body and trailer
@@ -145,13 +150,13 @@ function _M.chunked_writer(sock, res, chunksize)
145150

146151
chunk, read_err = reader(chunksize)
147152
if read_err then
148-
print_err(503, "chunked_writer: failed to read body, err: ", err)
153+
print_err(503, "chunked_writer: failed to read body, err: " .. (err or "unknown"))
149154
end
150155

151156
if chunk then
152157
bytes, err = send(sock, chunk)
153158
if not bytes then
154-
print_err("chunked_writer: failed to send body, err: ", err)
159+
print_err(503, "chunked_writer: failed to send body, err: " .. (err or "unknown"))
155160
end
156161
end
157162
until not chunk

0 commit comments

Comments
 (0)