Skip to content

Commit 97ab2c3

Browse files
committed
Merge branch 'master' of https://github.com/ledgetech/lua-resty-http into ledgetech-master
2 parents a0cd930 + 0ce55d6 commit 97ab2c3

12 files changed

+433
-45
lines changed

.github/workflows/test.yml

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
name: Test
22

3-
on:
4-
push:
5-
paths-ignore: # Skip if only docs are updated
6-
- '*.md'
7-
pull_request:
8-
branches: [master]
3+
on: [push, pull_request]
94

105
jobs:
116
luacheck:

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
t/servroot/
22
t/error.log
3-
lua-resty-http*
43
luacov*
4+
lua-resty-http-*/
5+
lua-resty-http-*.src.rock
6+
lua-resty-http-*.tar.gz

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ local httpc = require("resty.http").new()
9393
-- First establish a connection
9494
local ok, err = httpc:connect({
9595
scheme = "https",
96-
host = "127.0.0.1"
96+
host = "127.0.0.1",
9797
port = 8080,
9898
})
9999
if not ok then
@@ -241,7 +241,7 @@ The `params` table expects the following fields:
241241
* `path`: The path string. Defaults to `/`.
242242
* `query`: The query string, presented as either a literal string or Lua table..
243243
* `headers`: A table of request headers.
244-
* `body`: The request body as a string, or an iterator function (see [get\_client\_body\_reader](#get_client_body_reader)).
244+
* `body`: The request body as a string, a table of strings, or an iterator function yielding strings until nil when exhausted. Note that you must specify a `Content-Length` for the request body, or specify `Transfer-Encoding: chunked` and have your function implement the encoding. See also: [get\_client\_body\_reader](#get_client_body_reader)).
245245

246246
When the request is successful, `res` will contain the following fields:
247247

@@ -257,7 +257,7 @@ When the request is successful, `res` will contain the following fields:
257257

258258
`syntax: res, err = httpc:request_uri(uri, params)`
259259

260-
The single-shot interface (see [usage](#Usage)). Since this method performs an entire end-to-end request, options specified in the `params` can include anything found in both [connect](#connect) and [request](#request) documented above. Note also that fields in `params` will override relevant components of the `uri` if specified.
260+
The single-shot interface (see [usage](#Usage)). Since this method performs an entire end-to-end request, options specified in the `params` can include anything found in both [connect](#connect) and [request](#request) documented above. Note also that fields `path`, and `query`, in `params` will override relevant components of the `uri` if specified (`scheme`, `host`, and `port` will always be taken from the `uri`).
261261

262262
There are 3 additional parameters for controlling keepalives:
263263

lib/resty/http.lua

+71-30
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ end
106106

107107

108108
local _M = {
109-
_VERSION = '0.14',
109+
_VERSION = '0.16.1',
110110
}
111111
_M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version
112112

@@ -362,7 +362,21 @@ local function _receive_status(sock)
362362
return nil, nil, nil, err
363363
end
364364

365-
return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)
365+
local version = tonumber(str_sub(line, 6, 8))
366+
if not version then
367+
return nil, nil, nil,
368+
"couldn't parse HTTP version from response status line: " .. line
369+
end
370+
371+
local status = tonumber(str_sub(line, 10, 12))
372+
if not status then
373+
return nil, nil, nil,
374+
"couldn't parse status code from response status line: " .. line
375+
end
376+
377+
local reason = str_sub(line, 14)
378+
379+
return status, version, reason
366380
end
367381

368382

@@ -398,6 +412,23 @@ local function _receive_headers(sock)
398412
end
399413

400414

415+
local function transfer_encoding_is_chunked(headers)
416+
local te = headers["Transfer-Encoding"]
417+
if not te then
418+
return false
419+
end
420+
421+
-- Handle duplicate headers
422+
-- This shouldn't happen but can in the real world
423+
if type(te) ~= "string" then
424+
te = tbl_concat(te, ",")
425+
end
426+
427+
return str_find(str_lower(te), "chunked", 1, true) ~= nil
428+
end
429+
_M.transfer_encoding_is_chunked = transfer_encoding_is_chunked
430+
431+
401432
local function _chunked_body_reader(sock, default_chunk_size)
402433
return co_wrap(function(max_chunk_size)
403434
local remaining = 0
@@ -575,7 +606,7 @@ end
575606

576607

577608
local function _send_body(sock, body)
578-
if type(body) == 'function' then
609+
if type(body) == "function" then
579610
repeat
580611
local chunk, err, partial = body()
581612

@@ -627,12 +658,13 @@ function _M.send_request(self, params)
627658
local body = params.body
628659
local headers = http_headers.new()
629660

630-
local params_headers = params.headers or {}
631-
-- We assign one by one so that the metatable can handle case insensitivity
661+
-- We assign one-by-one so that the metatable can handle case insensitivity
632662
-- for us. You can blame the spec for this inefficiency.
663+
local params_headers = params.headers or {}
633664
for k, v in pairs(params_headers) do
634665
headers[k] = v
635666
end
667+
636668
if not headers["Proxy-Authorization"] then
637669
-- TODO: next major, change this to always override the provided
638670
-- header. Can't do that yet because it would be breaking.
@@ -641,15 +673,40 @@ function _M.send_request(self, params)
641673
headers["Proxy-Authorization"] = self.http_proxy_auth
642674
end
643675

644-
-- Ensure minimal headers are set
676+
-- Ensure we have appropriate message length or encoding.
677+
do
678+
local is_chunked = transfer_encoding_is_chunked(headers)
679+
680+
if is_chunked then
681+
-- If we have both Transfer-Encoding and Content-Length we MUST
682+
-- drop the Content-Length, to help prevent request smuggling.
683+
-- https://tools.ietf.org/html/rfc7230#section-3.3.3
684+
headers["Content-Length"] = nil
645685

646-
if not headers["Content-Length"] then
647-
if type(body) == 'string' then
648-
headers["Content-Length"] = #body
649-
elseif body == nil and EXPECTING_BODY[str_upper(params.method)] then
650-
headers["Content-Length"] = 0
686+
elseif not headers["Content-Length"] then
687+
-- A length was not given, try to calculate one.
688+
689+
local body_type = type(body)
690+
691+
if body_type == "function" then
692+
return nil, "Request body is a function but a length or chunked encoding is not specified"
693+
694+
elseif body_type == "table" then
695+
local length = 0
696+
for _, v in ipairs(body) do
697+
length = length + #tostring(v)
698+
end
699+
headers["Content-Length"] = length
700+
701+
elseif body == nil and EXPECTING_BODY[str_upper(params.method)] then
702+
headers["Content-Length"] = 0
703+
704+
elseif body ~= nil then
705+
headers["Content-Length"] = #tostring(body)
706+
end
651707
end
652708
end
709+
653710
if not headers["Host"] then
654711
if (str_sub(self.host, 1, 5) == "unix:") then
655712
return nil, "Unable to generate a useful Host header for a unix domain socket. Please provide one."
@@ -751,22 +808,8 @@ function _M.read_response(self, params)
751808
if _should_receive_body(params.method, status) then
752809
has_body = true
753810

754-
local te = res_headers["Transfer-Encoding"]
755-
756-
-- Handle duplicate headers
757-
-- This shouldn't happen but can in the real world
758-
if type(te) == "table" then
759-
te = tbl_concat(te, "")
760-
end
761-
762-
local ok, encoding = pcall(str_lower, te)
763-
if not ok then
764-
encoding = ""
765-
end
766-
767-
if version == 1.1 and str_find(encoding, "chunked", 1, true) ~= nil then
811+
if version == 1.1 and transfer_encoding_is_chunked(res_headers) then
768812
body_reader, err = _chunked_body_reader(sock)
769-
770813
else
771814
local ok, length = pcall(tonumber, res_headers["Content-Length"])
772815
if not ok then
@@ -775,9 +818,7 @@ function _M.read_response(self, params)
775818
end
776819

777820
body_reader, err = _body_reader(sock, length)
778-
779821
end
780-
781822
end
782823

783824
if res_headers["Trailer"] then
@@ -872,6 +913,7 @@ function _M.request_uri(self, uri, params)
872913
params.scheme, params.host, params.port, path, query = unpack(parsed_uri)
873914
params.path = params.path or path
874915
params.query = params.query or query
916+
params.ssl_server_name = params.ssl_server_name or params.host
875917
end
876918

877919
do
@@ -941,10 +983,9 @@ function _M.get_client_body_reader(_, chunksize, sock)
941983

942984
local headers = ngx_req_get_headers()
943985
local length = headers.content_length
944-
local encoding = headers.transfer_encoding
945986
if length then
946987
return _body_reader(sock, tonumber(length), chunksize)
947-
elseif encoding and str_lower(encoding) == 'chunked' then
988+
elseif transfer_encoding_is_chunked(headers) then
948989
-- Not yet supported by ngx_lua but should just work...
949990
return _chunked_body_reader(sock, chunksize)
950991
else

lib/resty/http_headers.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ local rawget, rawset, setmetatable =
44
local str_lower = string.lower
55

66
local _M = {
7-
_VERSION = '0.14',
7+
_VERSION = '0.16.1',
88
}
99

1010

lua-resty-http-0.15-0.rockspec renamed to lua-resty-http-0.16.1-0.rockspec

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package = "lua-resty-http"
2-
version = "0.15-0"
2+
version = "0.16.1-0"
33
source = {
44
url = "git://github.com/ledgetech/lua-resty-http",
5-
tag = "v0.15"
5+
tag = "v0.16.1"
66
}
77
description = {
88
summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.",

t/01-basic.t

+48
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,51 @@ OK
341341
--- no_error_log
342342
[error]
343343
[warn]
344+
345+
=== TEST 13: Should return error on invalid HTTP version in response status line
346+
--- http_config eval: $::HttpConfig
347+
--- config
348+
location = /a {
349+
content_by_lua_block {
350+
local http = require "resty.http"
351+
local httpc = http.new()
352+
local res, err = httpc:request_uri("http://127.0.0.1:12345")
353+
354+
assert(err == "couldn't parse HTTP version from response status line: TEAPOT/1.1 OMG")
355+
}
356+
}
357+
--- tcp_listen: 12345
358+
--- tcp_reply
359+
TEAPOT/1.1 OMG
360+
Server: Teapot
361+
362+
OK
363+
--- request
364+
GET /a
365+
--- no_error_log
366+
[error]
367+
[warn]
368+
369+
=== TEST 14: Should return error on invalid status code in response status line
370+
--- http_config eval: $::HttpConfig
371+
--- config
372+
location = /a {
373+
content_by_lua_block {
374+
local http = require "resty.http"
375+
local httpc = http.new()
376+
local res, err = httpc:request_uri("http://127.0.0.1:12345")
377+
378+
assert(err == "couldn't parse status code from response status line: HTTP/1.1 OMG")
379+
}
380+
}
381+
--- tcp_listen: 12345
382+
--- tcp_reply
383+
HTTP/1.1 OMG
384+
Server: Teapot
385+
386+
OK
387+
--- request
388+
GET /a
389+
--- no_error_log
390+
[error]
391+
[warn]

t/02-chunked.t

+84
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,87 @@ table
238238
--- no_error_log
239239
[error]
240240
[warn]
241+
242+
243+
=== TEST 5: transfer_encoding_is_chunked utility.
244+
--- http_config eval: $::HttpConfig
245+
--- config
246+
location = /a {
247+
content_by_lua_block {
248+
local http_headers = require("resty.http_headers")
249+
local http = require("resty.http")
250+
251+
local headers = http_headers:new()
252+
assert(http.transfer_encoding_is_chunked(headers) == false,
253+
"empty headers should return false")
254+
255+
headers["Transfer-Encoding"] = "chunked"
256+
assert(http.transfer_encoding_is_chunked(headers) == true,
257+
"te set to `chunked` should return true`")
258+
259+
headers["Transfer-Encoding"] = " ChuNkEd "
260+
assert(http.transfer_encoding_is_chunked(headers) == true,
261+
"te set to ` ChuNkEd ` should return true`")
262+
263+
headers["Transfer-Encoding"] = { "chunked", " ChuNkEd " }
264+
assert(http.transfer_encoding_is_chunked(headers) == true,
265+
"te set to table values containing `chunked` should return true`")
266+
267+
headers["Transfer-Encoding"] = "chunked"
268+
headers["Content-Length"] = 10
269+
assert(http.transfer_encoding_is_chunked(headers) == true,
270+
"transfer encoding should override content-length`")
271+
}
272+
}
273+
--- request
274+
GET /a
275+
--- no_error_log
276+
[error]
277+
[warn]
278+
279+
280+
=== TEST 6: Don't send Content-Length if Transfer-Encoding is specified
281+
--- http_config eval: $::HttpConfig
282+
--- config
283+
location = /a {
284+
content_by_lua_block {
285+
local httpc = require("resty.http").new()
286+
local yield = coroutine.yield
287+
288+
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b"
289+
290+
local res, err = assert(httpc:request_uri(uri, {
291+
body = coroutine.wrap(function()
292+
yield("3\r\n")
293+
yield("foo\r\n")
294+
295+
yield("3\r\n")
296+
yield("bar\r\n")
297+
298+
yield("0\r\n")
299+
yield("\r\n")
300+
end),
301+
headers = {
302+
["Transfer-Encoding"] = "chunked",
303+
["Content-Length"] = 42,
304+
},
305+
}))
306+
307+
ngx.say(res.body)
308+
}
309+
}
310+
location = /b {
311+
content_by_lua_block {
312+
ngx.req.read_body()
313+
ngx.say(ngx.req.get_headers()["Content-Length"])
314+
ngx.print(ngx.req.get_body_data())
315+
}
316+
}
317+
--- request
318+
GET /a
319+
--- response_body
320+
nil
321+
foobar
322+
--- no_error_log
323+
[error]
324+
[warn]

0 commit comments

Comments
 (0)