11local format = string.format
22local tostring = tostring
3+ local ngx_flush = ngx .flush
4+ local ngx_get_method = ngx .req .get_method
5+ local ngx_http_version = ngx .req .http_version
6+ local ngx_send_headers = ngx .send_headers
37
48local resty_url = require " resty.url"
59local resty_resolver = require ' resty.resolver'
610local round_robin = require ' resty.balancer.round_robin'
711local http_proxy = require ' resty.http.proxy'
812local file_reader = require (" resty.file" ).file_reader
913local file_size = require (" resty.file" ).file_size
14+ local client_body_reader = require (" resty.http.request_reader" ).get_client_body_reader
15+ local send_response = require (" resty.http.response_writer" ).send_response
1016local concat = table.concat
1117
1218local _M = { }
1319
20+ local http_methods_with_body = {
21+ POST = true ,
22+ PUT = true ,
23+ PATCH = true
24+ }
25+
26+ local DEFAULT_CHUNKSIZE = 32 * 1024
27+
1428function _M .reset ()
1529 _M .balancer = round_robin .new ()
1630 _M .resolver = resty_resolver
@@ -84,52 +98,105 @@ local function absolute_url(uri)
8498 )
8599end
86100
87- local function forward_https_request (proxy_uri , proxy_auth , uri , skip_https_connect )
88- -- This is needed to call ngx.req.get_body_data() below.
89- ngx .req .read_body ()
90-
91- -- We cannot use resty.http's .get_client_body_reader().
92- -- In POST requests with HTTPS, the result of that call is nil, and it
93- -- results in a time-out.
94- --
95- --
96- -- If ngx.req.get_body_data is nil, can be that the body is too big to
97- -- read and need to be cached in a local file. This request will return
98- -- nil, so after this we need to read the temp file.
99- -- https://github.com/openresty/lua-nginx-module#ngxreqget_body_data
100- local body = ngx .req .get_body_data ()
101+ local function handle_expect ()
102+ local expect = ngx .req .get_headers ()[" Expect" ]
103+ if type (expect ) == " table" then
104+ expect = expect [1 ]
105+ end
106+
107+ if expect and expect :lower () == " 100-continue" then
108+ ngx .status = 100
109+ local ok , err = ngx_send_headers ()
110+
111+ if not ok then
112+ return nil , " failed to send response header: " .. (err or " unknown" )
113+ end
114+
115+ ok , err = ngx_flush (true )
116+ if not ok then
117+ return nil , " failed to flush response header: " .. (err or " unknown" )
118+ end
119+ end
120+ end
121+
122+ local function forward_https_request (proxy_uri , uri , proxy_opts )
123+ local body , err
124+ local sock
125+ local opts = proxy_opts or {}
126+ local req_method = ngx_get_method ()
101127 local encoding = ngx .req .get_headers ()[" Transfer-Encoding" ]
128+ local is_chunked = encoding and encoding :lower () == " chunked"
129+
130+ if http_methods_with_body [req_method ] then
131+ if opts .request_unbuffered and ngx_http_version () == 1.1 then
132+ local _ , err = handle_expect ()
133+ if err then
134+ ngx .log (ngx .ERR , " failed to handle expect header, err: " , err )
135+ return ngx .exit (ngx .HTTP_INTERNAL_SERVER_ERROR )
136+ end
102137
103- if not body then
104- local temp_file_path = ngx .req .get_body_file ()
105- ngx .log (ngx .INFO , " HTTPS Proxy: Request body is bigger than client_body_buffer_size, read the content from path='" , temp_file_path , " '" )
106-
107- if temp_file_path then
108- body , err = file_reader (temp_file_path )
109- if err then
110- ngx .log (ngx .ERR , " HTTPS proxy: Failed to read temp body file, err: " , err )
111- ngx .exit (ngx .HTTP_INTERNAL_SERVER_ERROR )
112- end
113-
114- if encoding == " chunked" then
115- -- If the body is smaller than "client_boby_buffer_size" the Content-Length header is
116- -- set based on the size of the buffer. However, when the body is rendered to a file,
117- -- we will need to calculate and manually set the Content-Length header based on the
118- -- file size
119- local contentLength , err = file_size (temp_file_path )
120- if err then
121- ngx .log (ngx .ERR , " HTTPS proxy: Failed to set content length, err: " , err )
138+ if is_chunked then
139+ -- The default ngx reader does not support chunked request
140+ -- so we will need to get the raw request socket and manually
141+ -- decode the chunked request
142+ sock , err = ngx .req .socket (true )
143+ else
144+ sock , err = ngx .req .socket ()
145+ end
146+
147+ if not sock then
148+ ngx .log (ngx .ERR , " unable to obtain request socket: " , err )
149+ return ngx .exit (ngx .HTTP_INTERNAL_SERVER_ERROR )
150+ end
151+
152+ body = client_body_reader (sock , DEFAULT_CHUNKSIZE , is_chunked )
153+ else
154+ -- This is needed to call ngx.req.get_body_data() below.
155+ ngx .req .read_body ()
156+
157+ -- We cannot use resty.http's .get_client_body_reader().
158+ -- In POST requests with HTTPS, the result of that call is nil, and it
159+ -- results in a time-out.
160+ --
161+ --
162+ -- If ngx.req.get_body_data is nil, can be that the body is too big to
163+ -- read and need to be cached in a local file. This request will return
164+ -- nil, so after this we need to read the temp file.
165+ -- https://github.com/openresty/lua-nginx-module#ngxreqget_body_data
166+ body = ngx .req .get_body_data ()
167+
168+ if not body then
169+ local temp_file_path = ngx .req .get_body_file ()
170+ ngx .log (ngx .INFO , " HTTPS Proxy: Request body is bigger than client_body_buffer_size, read the content from path='" , temp_file_path , " '" )
171+
172+ if temp_file_path then
173+ body , err = file_reader (temp_file_path )
174+ if err then
175+ ngx .log (ngx .ERR , " HTTPS proxy: Failed to read temp body file, err: " , err )
122176 ngx .exit (ngx .HTTP_INTERNAL_SERVER_ERROR )
177+ end
178+
179+ if is_chunked then
180+ -- If the body is smaller than "client_boby_buffer_size" the Content-Length header is
181+ -- set based on the size of the buffer. However, when the body is rendered to a file,
182+ -- we will need to calculate and manually set the Content-Length header based on the
183+ -- file size
184+ local contentLength , err = file_size (temp_file_path )
185+ if err then
186+ ngx .log (ngx .ERR , " HTTPS proxy: Failed to set content length, err: " , err )
187+ ngx .exit (ngx .HTTP_INTERNAL_SERVER_ERROR )
188+ end
189+
190+ ngx .req .set_header (" Content-Length" , tostring (contentLength ))
191+ end
123192 end
124-
125- ngx .req .set_header (" Content-Length" , tostring (contentLength ))
126- end
127193 end
128- end
129194
130- -- The whole request is buffered in the memory so remove the Transfer-Encoding: chunked
131- if ngx .var .http_transfer_encoding == " chunked" then
132- ngx .req .set_header (" Transfer-Encoding" , nil )
195+ -- The whole request is buffered in the memory so remove the Transfer-Encoding: chunked
196+ if is_chunked then
197+ ngx .req .set_header (" Transfer-Encoding" , nil )
198+ end
199+ end
133200 end
134201
135202 local request = {
@@ -139,10 +206,10 @@ local function forward_https_request(proxy_uri, proxy_auth, uri, skip_https_conn
139206 path = format (' %s%s%s' , ngx .var .uri , ngx .var .is_args , ngx .var .query_string or ' ' ),
140207 body = body ,
141208 proxy_uri = proxy_uri ,
142- proxy_auth = proxy_auth
209+ proxy_auth = opts . proxy_auth
143210 }
144211
145- local httpc , err = http_proxy .new (request , skip_https_connect )
212+ local httpc , err = http_proxy .new (request , opts . skip_https_connect )
146213
147214 if not httpc then
148215 ngx .log (ngx .ERR , ' could not connect to proxy: ' , proxy_uri , ' err: ' , err )
@@ -154,8 +221,16 @@ local function forward_https_request(proxy_uri, proxy_auth, uri, skip_https_conn
154221 res , err = httpc :request (request )
155222
156223 if res then
157- httpc :proxy_response (res )
158- httpc :set_keepalive ()
224+ if opts .request_unbuffered and is_chunked then
225+ local bytes , err = send_response (sock , res , DEFAULT_CHUNKSIZE )
226+ if not bytes then
227+ ngx .log (ngx .ERR , " failed to send response: " , err )
228+ return sock :send (" HTTP/1.1 502 Bad Gateway" )
229+ end
230+ else
231+ httpc :proxy_response (res )
232+ httpc :set_keepalive ()
233+ end
159234 else
160235 ngx .log (ngx .ERR , ' failed to proxy request to: ' , proxy_uri , ' err : ' , err )
161236 return ngx .exit (ngx .HTTP_BAD_GATEWAY )
@@ -208,7 +283,13 @@ function _M.request(upstream, proxy_uri)
208283 return
209284 elseif uri .scheme == ' https' then
210285 upstream :rewrite_request ()
211- forward_https_request (proxy_uri , proxy_auth , uri , upstream .skip_https_connect )
286+ local proxy_opts = {
287+ proxy_auth = proxy_auth ,
288+ skip_https_connect = upstream .skip_https_connect ,
289+ request_unbuffered = upstream .request_unbuffered
290+ }
291+
292+ forward_https_request (proxy_uri , uri , proxy_opts )
212293 return ngx .exit (ngx .OK ) -- terminate phase
213294 else
214295 ngx .log (ngx .ERR , ' could not connect to proxy: ' , proxy_uri , ' err: ' , ' invalid request scheme' )
0 commit comments