@@ -7,9 +7,22 @@ local round_robin = require 'resty.balancer.round_robin'
77local http_proxy = require ' resty.http.proxy'
88local file_reader = require (" resty.file" ).file_reader
99local file_size = require (" resty.file" ).file_size
10+ local get_client_body_reader = require (' resty.http.raw_request' ).get_client_body_reader
11+ local send_response = require (' resty.http.raw_response' ).send_response
12+ local http_ver = ngx .req .http_version
13+ local ngx_send_headers = ngx .send_headers
14+ local ngx_req = ngx .req
15+ local ngx_flush = ngx .flush
16+ local ngx_var = ngx .var
1017
1118local _M = { }
1219
20+ local http_methods_with_body = {
21+ POST = true ,
22+ PUT = true ,
23+ PATCH = true
24+ }
25+
1326function _M .reset ()
1427 _M .balancer = round_robin .new ()
1528 _M .resolver = resty_resolver
@@ -103,17 +116,70 @@ local function modify_chunked_request_headers(req, file)
103116 set_header (req .headers , " Content-Length" , contentLength )
104117end
105118
119+ local function handle_expect ()
120+ local expect = ngx_var .http_expect
121+ if type (expect ) == " table" then
122+ expect = expect [1 ]
123+ end
106124
107- local function forward_https_request ( proxy_uri , uri , skip_https_connect )
108- -- This is needed to call ngx.req.get_body_data() below.
109- ngx . req . read_body ()
125+ if expect and expect : lower () == " 100-continue " then
126+ ngx .status = 100
127+ local ok , err = ngx_send_headers ()
110128
111- local request = {
112- uri = uri ,
113- method = ngx .req .get_method (),
114- headers = ngx .req .get_headers (0 , true ),
115- path = format (' %s%s%s' , ngx .var .uri , ngx .var .is_args , ngx .var .query_string or ' ' ),
129+ if not ok then
130+ return nil , " failed to send response header: " .. (err or " unknown" )
131+ end
132+
133+ ok , err = ngx_flush (true )
134+ if not ok then
135+ return nil , " failed to flush response header: " .. (err or " unknown" )
136+ end
137+ end
138+ end
139+
140+ local function get_request_body (req , opts )
141+ local chunksize = 32 * 1024
142+ local sock , err
143+ local body = nil
144+ local encoding = ngx_var .http_transfer_encoding
145+
146+ if not opts .request_buffering then
147+ if http_ver () ~= 1.1 then
148+ ngx .log (ngx .ERR , " bad http version" )
149+ ngx .exit (ngx .HTTP_BAD_REQUEST )
150+ end
151+
152+ if type (encoding ) == " table" then
153+ encoding = encoding [1 ]
154+ end
155+
156+ _ , err = handle_expect ()
157+ if err then
158+ ngx .log (ngx .ERR , err )
159+ return ngx .exit (ngx .HTTP_BAD_GATEWAY )
160+ end
116161
162+ if encoding and encoding :lower () == " chunked" then
163+ -- The default ngx reader does not support chunked request
164+ -- so we will need to get the raw request socket and manually
165+ -- decode the chunked request
166+ sock , err = ngx .req .socket (true )
167+ body = get_client_body_reader (sock , chunksize , true )
168+ else
169+ sock , err = ngx .req .socket ()
170+ body = get_client_body_reader (sock , chunksize )
171+ end
172+
173+ if not sock then
174+ ngx .log (ngx .ERR , " unable to obtain request socket: " , err )
175+ return ngx .exit (ngx .HTTP_BAD_GATEWAY )
176+ end
177+
178+ req .body = body
179+ req .sock = sock
180+ else
181+ -- This is needed to call ngx.req.get_body_data() below.
182+ ngx .req .read_body ()
117183 -- We cannot use resty.http's .get_client_body_reader().
118184 -- In POST requests with HTTPS, the result of that call is nil, and it
119185 -- results in a time-out.
@@ -123,37 +189,53 @@ local function forward_https_request(proxy_uri, uri, skip_https_connect)
123189 -- read and need to be cached in a local file. This request will return
124190 -- nil, so after this we need to read the temp file.
125191 -- https://github.com/openresty/lua-nginx-module#ngxreqget_body_data
126- body = ngx .req .get_body_data (),
127- proxy_uri = proxy_uri
128- }
192+ req .body = ngx_req .get_body_data ()
193+
194+ if not req .body then
195+ local temp_file_path = ngx .req .get_body_file ()
196+ ngx .log (ngx .INFO , " HTTPS Proxy: Request body is bigger than client_body_buffer_size, read the content from path='" , temp_file_path , " '" )
197+
198+ if temp_file_path then
199+ body , err = file_reader (temp_file_path )
200+ if err then
201+ ngx .log (ngx .ERR , " HTTPS proxy: Failed to read temp body file, err: " , err )
202+ ngx .exit (ngx .HTTP_INTERNAL_SERVER_ERROR )
203+ end
204+ if encoding == " chunked" then
205+ -- If the body is smaller than "client_boby_buffer_size" the Content-Length header is
206+ -- set based on the size of the buffer. However, when the body is rendered to a file,
207+ -- we will need to calculate and manually set the Content-Length header based on the
208+ -- file size
209+ modify_chunked_request_headers (req , temp_file_path )
210+ end
211+ req .body = body
212+ end
213+ end
129214
130- if not request .body then
131- local temp_file_path = ngx .req .get_body_file ()
132- ngx .log (ngx .INFO , " HTTPS Proxy: Request body is bigger than client_body_buffer_size, read the content from path='" , temp_file_path , " '" )
133-
134- if temp_file_path then
135- local body , err = file_reader (temp_file_path )
136- if err then
137- ngx .log (ngx .ERR , " HTTPS proxy: Failed to read temp body file, err: " , err )
138- ngx .exit (ngx .HTTP_INTERNAL_SERVER_ERROR )
139- end
140- if ngx .var .http_transfer_encoding == " chunked" then
141- -- If the body is smaller than "client_boby_buffer_size" the Content-Length header is
142- -- set based on the size of the buffer. However, when the body is rendered to a file,
143- -- we will need to calculate and manually set the Content-Length header based on the
144- -- file size
145- modify_chunked_request_headers (request , temp_file_path )
146- end
147- request .body = body
215+ -- The whole request is buffered in the memory so remove the Transfer-Encoding: chunked
216+ if ngx .var .http_transfer_encoding == " chunked" then
217+ set_header (req .headers , " Transfer-Encoding" , nil )
148218 end
149219 end
220+ end
221+
222+ local function forward_https_request (proxy_uri , uri , proxy_opts )
223+ local method = ngx .req .get_method ()
224+ local chunksize = 32 * 1024
150225
151- -- The whole request is buffered in the memory so remove the Transfer-Encoding: chunked
152- if ngx .var .http_transfer_encoding == " chunked" then
153- set_header (request .headers , " Transfer-Encoding" , nil )
226+ local request = {
227+ uri = uri ,
228+ method = ngx .req .get_method (),
229+ headers = ngx .req .get_headers (0 , true ),
230+ path = format (' %s%s%s' , ngx .var .uri , ngx .var .is_args , ngx .var .query_string or ' ' ),
231+ proxy_uri = proxy_uri
232+ }
233+
234+ if http_methods_with_body [method ] then
235+ get_request_body (request , proxy_opts )
154236 end
155237
156- local httpc , err = http_proxy .new (request , skip_https_connect )
238+ local httpc , err = http_proxy .new (request , proxy_opts . skip_https_connect )
157239
158240 if not httpc then
159241 ngx .log (ngx .ERR , ' could not connect to proxy: ' , proxy_uri , ' err: ' , err )
@@ -165,8 +247,16 @@ local function forward_https_request(proxy_uri, uri, skip_https_connect)
165247 res , err = httpc :request (request )
166248
167249 if res then
168- httpc :proxy_response (res )
169- httpc :set_keepalive ()
250+ if not proxy_opts .request_buffering then
251+ local bytes , err = send_response (request .sock , res , chunksize )
252+ if not bytes then
253+ ngx .log (ngx .ERR , " failed to send response: " , err )
254+ return ngx .exit (ngx .HTTP_BAD_GATEWAY )
255+ end
256+ else
257+ httpc :proxy_response (res )
258+ httpc :set_keepalive ()
259+ end
170260 else
171261 ngx .log (ngx .ERR , ' failed to proxy request to: ' , proxy_uri , ' err : ' , err )
172262 return ngx .exit (ngx .HTTP_BAD_GATEWAY )
@@ -204,7 +294,11 @@ function _M.request(upstream, proxy_uri)
204294 return
205295 elseif uri .scheme == ' https' then
206296 upstream :rewrite_request ()
207- forward_https_request (proxy_uri , uri , upstream .skip_https_connect )
297+ local proxy_opts = {
298+ skip_https_connect = upstream .skip_https_connect ,
299+ request_buffering = upstream .request_buffering
300+ }
301+ forward_https_request (proxy_uri , uri , proxy_opts )
208302 return ngx .exit (ngx .OK ) -- terminate phase
209303 else
210304 ngx .log (ngx .ERR , ' could not connect to proxy: ' , proxy_uri , ' err: ' , ' invalid request scheme' )
0 commit comments