1 local http_headers = require "resty.http_headers"
3 local ngx_socket_tcp = ngx.socket.tcp
4 local ngx_req = ngx.req
5 local ngx_req_socket = ngx_req.socket
6 local ngx_req_get_headers = ngx_req.get_headers
7 local ngx_req_get_method = ngx_req.get_method
8 local str_gmatch = string.gmatch
9 local str_lower = string.lower
10 local str_upper = string.upper
11 local str_find = string.find
12 local str_sub = string.sub
13 local str_gsub = string.gsub
14 local tbl_concat = table.concat
15 local tbl_insert = table.insert
16 local ngx_encode_args = ngx.encode_args
17 local ngx_re_match = ngx.re.match
18 local ngx_re_gsub = ngx.re.gsub
19 local ngx_log = ngx.log
20 local ngx_DEBUG = ngx.DEBUG
21 local ngx_ERR = ngx.ERR
22 local ngx_NOTICE = ngx.NOTICE
23 local ngx_var = ngx.var
24 local co_yield = coroutine.yield
25 local co_create = coroutine.create
26 local co_status = coroutine.status
27 local co_resume = coroutine.resume
30 -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
31 local HOP_BY_HOP_HEADERS = {
32 ["connection"] = true,
33 ["keep-alive"] = true,
34 ["proxy-authenticate"] = true,
35 ["proxy-authorization"] = true,
38 ["transfer-encoding"] = true,
40 ["content-length"] = true, -- Not strictly hop-by-hop, but Nginx will deal
41 -- with this (may send chunked for example).
45 -- Reimplemented coroutine.wrap, returning "nil, err" if the coroutine cannot
46 -- be resumed. This protects user code from inifite loops when doing things like
48 -- local chunk, err = res.body_reader()
49 -- if chunk then -- <-- This could be a string msg in the core wrap function.
53 local co_wrap = function(func)
54 local co = co_create(func)
56 return nil, "could not create coroutine"
59 if co_status(co) == "suspended" then
60 return select(2, co_resume(co, ...))
62 return nil, "can't resume a " .. co_status(co) .. " coroutine"
72 _M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version
74 local mt = { __index = _M }
78 [1.0] = " HTTP/1.0\r\n",
79 [1.1] = " HTTP/1.1\r\n",
82 local DEFAULT_PARAMS = {
90 local sock, err = ngx_socket_tcp()
94 return setmetatable({ sock = sock, keepalive = true }, mt)
98 function _M.set_timeout(self, timeout)
99 local sock = self.sock
101 return nil, "not initialized"
104 return sock:settimeout(timeout)
108 function _M.ssl_handshake(self, ...)
109 local sock = self.sock
111 return nil, "not initialized"
116 return sock:sslhandshake(...)
120 function _M.connect(self, ...)
121 local sock = self.sock
123 return nil, "not initialized"
126 self.host = select(1, ...)
127 self.port = select(2, ...)
129 -- If port is not a number, this is likely a unix domain socket connection.
130 if type(self.port) ~= "number" then
134 self.keepalive = true
136 return sock:connect(...)
140 function _M.set_keepalive(self, ...)
141 local sock = self.sock
143 return nil, "not initialized"
146 if self.keepalive == true then
147 return sock:setkeepalive(...)
149 -- The server said we must close the connection, so we cannot setkeepalive.
150 -- If close() succeeds we return 2 instead of 1, to differentiate between
151 -- a normal setkeepalive() failure and an intentional close().
152 local res, err = sock:close()
154 return 2, "connection must be closed"
162 function _M.get_reused_times(self)
163 local sock = self.sock
165 return nil, "not initialized"
168 return sock:getreusedtimes()
172 function _M.close(self)
173 local sock = self.sock
175 return nil, "not initialized"
182 local function _should_receive_body(method, code)
183 if method == "HEAD" then return nil end
184 if code == 204 or code == 304 then return nil end
185 if code >= 100 and code < 200 then return nil end
190 function _M.parse_uri(self, uri)
191 local m, err = ngx_re_match(uri, [[^(http[s]?)://([^:/]+)(?::(\d+))?(.*)]],
196 return nil, "failed to match the uri: " .. uri .. ", " .. err
199 return nil, "bad uri: " .. uri
202 m[3] = tonumber(m[3])
204 if m[1] == "https" then
210 if not m[4] or "" == m[4] then m[4] = "/" end
216 local function _format_request(params)
217 local version = params.version
218 local headers = params.headers or {}
220 local query = params.query or ""
222 if type(query) == "table" then
223 query = "?" .. ngx_encode_args(query)
227 -- Initialize request
229 str_upper(params.method),
234 -- Pre-allocate slots for minimum headers and carriage return.
239 local c = 6 -- req table index it's faster to do this inline vs table.insert
242 for key, values in pairs(headers) do
243 if type(values) ~= "table" then
248 for _, value in pairs(values) do
249 req[c] = key .. ": " .. tostring(value) .. "\r\n"
257 return tbl_concat(req)
261 local function _receive_status(sock)
262 local line, err = sock:receive("*l")
264 return nil, nil, nil, err
267 return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)
272 local function _receive_headers(sock)
273 local headers = http_headers.new()
276 local line, err = sock:receive("*l")
281 for key, val in str_gmatch(line, "([^:%s]+):%s*(.+)") do
283 if type(headers[key]) ~= "table" then
284 headers[key] = { headers[key] }
286 tbl_insert(headers[key], tostring(val))
288 headers[key] = tostring(val)
291 until str_find(line, "^%s*$")
297 local function _chunked_body_reader(sock, default_chunk_size)
298 return co_wrap(function(max_chunk_size)
299 local max_chunk_size = max_chunk_size or default_chunk_size
304 -- If we still have data on this chunk
305 if max_chunk_size and remaining > 0 then
307 if remaining > max_chunk_size then
308 -- Consume up to max_chunk_size
309 length = max_chunk_size
310 remaining = remaining - max_chunk_size
312 -- Consume all remaining
316 else -- This is a fresh chunk
318 -- Receive the chunk size
319 local str, err = sock:receive("*l")
324 length = tonumber(str, 16)
327 co_yield(nil, "unable to read chunksize")
330 if max_chunk_size and length > max_chunk_size then
331 -- Consume up to max_chunk_size
332 remaining = length - max_chunk_size
333 length = max_chunk_size
338 local str, err = sock:receive(length)
343 max_chunk_size = co_yield(str) or default_chunk_size
345 -- If we're finished with this chunk, read the carriage return.
346 if remaining == 0 then
347 sock:receive(2) -- read \r\n
350 -- Read the last (zero length) chunk's carriage return
351 sock:receive(2) -- read \r\n
359 local function _body_reader(sock, content_length, default_chunk_size)
360 return co_wrap(function(max_chunk_size)
361 local max_chunk_size = max_chunk_size or default_chunk_size
363 if not content_length and max_chunk_size then
364 -- We have no length, but wish to stream.
365 -- HTTP 1.0 with no length will close connection, so read chunks to the end.
367 local str, err, partial = sock:receive(max_chunk_size)
368 if not str and err == "closed" then
369 max_chunk_size = tonumber(co_yield(partial, err) or default_chunk_size)
372 max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
373 if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end
375 if not max_chunk_size then
376 ngx_log(ngx_ERR, "Buffer size not specified, bailing")
381 elseif not content_length then
382 -- We have no length but don't wish to stream.
383 -- HTTP 1.0 with no length will close connection, so read to the end.
384 co_yield(sock:receive("*a"))
386 elseif not max_chunk_size then
387 -- We have a length and potentially keep-alive, but want everything.
388 co_yield(sock:receive(content_length))
391 -- We have a length and potentially a keep-alive, and wish to stream
395 local length = max_chunk_size
396 if received + length > content_length then
397 length = content_length - received
401 local str, err = sock:receive(length)
403 max_chunk_size = tonumber(co_yield(nil, err) or default_chunk_size)
405 received = received + length
407 max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
408 if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end
410 if not max_chunk_size then
411 ngx_log(ngx_ERR, "Buffer size not specified, bailing")
422 local function _no_body_reader()
427 local function _read_body(res)
428 local reader = res.body_reader
431 -- Most likely HEAD or 304 etc.
432 return nil, "no body to be read"
440 chunk, err = reader()
443 return nil, err, tbl_concat(chunks) -- Return any data so far.
451 return tbl_concat(chunks)
455 local function _trailer_reader(sock)
456 return co_wrap(function()
457 co_yield(_receive_headers(sock))
462 local function _read_trailers(res)
463 local reader = res.trailer_reader
465 return nil, "no trailers"
468 local trailers = reader()
469 setmetatable(res.headers, { __index = trailers })
473 local function _send_body(sock, body)
474 if type(body) == 'function' then
476 local chunk, err, partial = body()
479 local ok,err = sock:send(chunk)
484 elseif err ~= nil then
485 return nil, err, partial
489 elseif body ~= nil then
490 local bytes, err = sock:send(body)
500 local function _handle_continue(sock, body)
501 local status, version, reason, err = _receive_status(sock)
506 -- Only send body if we receive a 100 Continue
507 if status == 100 then
508 local ok, err = sock:receive("*l") -- Read carriage return
512 _send_body(sock, body)
514 return status, version, err
518 function _M.send_request(self, params)
520 setmetatable(params, { __index = DEFAULT_PARAMS })
522 local sock = self.sock
523 local body = params.body
524 local headers = http_headers.new()
526 local params_headers = params.headers
527 if params_headers then
528 -- We assign one by one so that the metatable can handle case insensitivity
529 -- for us. You can blame the spec for this inefficiency.
530 for k,v in pairs(params_headers) do
535 -- Ensure minimal headers are set
536 if type(body) == 'string' and not headers["Content-Length"] then
537 headers["Content-Length"] = #body
539 if not headers["Host"] then
540 if (str_sub(self.host, 1, 5) == "unix:") then
541 return nil, "Unable to generate a useful Host header for a unix domain socket. Please provide one."
543 -- If we have a port (i.e. not connected to a unix domain socket), and this
544 -- port is non-standard, append it to the Host heaer.
546 if self.ssl and self.port ~= 443 then
547 headers["Host"] = self.host .. ":" .. self.port
548 elseif not self.ssl and self.port ~= 80 then
549 headers["Host"] = self.host .. ":" .. self.port
551 headers["Host"] = self.host
554 headers["Host"] = self.host
557 if not headers["User-Agent"] then
558 headers["User-Agent"] = _M._USER_AGENT
560 if params.version == 1.0 and not headers["Connection"] then
561 headers["Connection"] = "Keep-Alive"
564 params.headers = headers
566 -- Format and send request
567 local req = _format_request(params)
568 ngx_log(ngx_DEBUG, "\n", req)
569 local bytes, err = sock:send(req)
575 -- Send the request body, unless we expect: continue, in which case
576 -- we handle this as part of reading the response.
577 if headers["Expect"] ~= "100-continue" then
578 local ok, err, partial = _send_body(sock, body)
580 return nil, err, partial
588 function _M.read_response(self, params)
589 local sock = self.sock
591 local status, version, reason, err
593 -- If we expect: continue, we need to handle this, sending the body if allowed.
594 -- If we don't get 100 back, then status is the actual status.
595 if params.headers["Expect"] == "100-continue" then
596 local _status, _version, _err = _handle_continue(sock, params.body)
599 elseif _status ~= 100 then
600 status, version, err = _status, _version, _err
604 -- Just read the status as normal.
606 status, version, reason, err = _receive_status(sock)
613 local res_headers, err = _receive_headers(sock)
614 if not res_headers then
618 -- keepalive is true by default. Determine if this is correct or not.
619 local ok, connection = pcall(str_lower, res_headers["Connection"])
621 if (version == 1.1 and connection == "close") or
622 (version == 1.0 and connection ~= "keep-alive") then
623 self.keepalive = false
626 -- no connection header
627 if version == 1.0 then
628 self.keepalive = false
632 local body_reader = _no_body_reader
633 local trailer_reader, err = nil, nil
634 local has_body = false
636 -- Receive the body_reader
637 if _should_receive_body(params.method, status) then
638 local ok, encoding = pcall(str_lower, res_headers["Transfer-Encoding"])
639 if ok and version == 1.1 and encoding == "chunked" then
640 body_reader, err = _chunked_body_reader(sock)
644 local ok, length = pcall(tonumber, res_headers["Content-Length"])
646 body_reader, err = _body_reader(sock, length)
652 if res_headers["Trailer"] then
653 trailer_reader, err = _trailer_reader(sock)
662 headers = res_headers,
664 body_reader = body_reader,
665 read_body = _read_body,
666 trailer_reader = trailer_reader,
667 read_trailers = _read_trailers,
673 function _M.request(self, params)
674 local res, err = self:send_request(params)
678 return self:read_response(params)
683 function _M.request_pipeline(self, requests)
684 for i, params in ipairs(requests) do
685 if params.headers and params.headers["Expect"] == "100-continue" then
686 return nil, "Cannot pipeline request specifying Expect: 100-continue"
689 local res, err = self:send_request(params)
696 for i, params in ipairs(requests) do
697 responses[i] = setmetatable({
699 response_read = false,
701 -- Read each actual response lazily, at the point the user tries
702 -- to access any of the fields.
703 __index = function(t, k)
705 if t.response_read == false then
706 res, err = _M.read_response(self, t.params)
707 t.response_read = true
710 ngx_log(ngx_ERR, err)
712 for rk, rv in pairs(res) do
725 function _M.request_uri(self, uri, params)
726 if not params then params = {} end
728 local parsed_uri, err = self:parse_uri(uri)
729 if not parsed_uri then
733 local scheme, host, port, path = unpack(parsed_uri)
734 if not params.path then params.path = path end
736 local c, err = self:connect(host, port)
741 if scheme == "https" then
743 if params.ssl_verify == false then
746 local ok, err = self:ssl_handshake(nil, host, verify)
752 local res, err = self:request(params)
757 local body, err = res:read_body()
764 local ok, err = self:set_keepalive()
766 ngx_log(ngx_ERR, err)
773 function _M.get_client_body_reader(self, chunksize, sock)
774 local chunksize = chunksize or 65536
777 ok, sock, err = pcall(ngx_req_socket)
780 return nil, sock -- pcall err
784 if err == "no body" then
792 local headers = ngx_req_get_headers()
793 local length = headers.content_length
794 local encoding = headers.transfer_encoding
796 return _body_reader(sock, tonumber(length), chunksize)
797 elseif encoding and str_lower(encoding) == 'chunked' then
798 -- Not yet supported by ngx_lua but should just work...
799 return _chunked_body_reader(sock, chunksize)
806 function _M.proxy_request(self, chunksize)
808 method = ngx_req_get_method(),
809 path = ngx_re_gsub(ngx_var.uri, "\\s", "%20", "jo") .. ngx_var.is_args .. (ngx_var.query_string or ""),
810 body = self:get_client_body_reader(chunksize),
811 headers = ngx_req_get_headers(),
816 function _M.proxy_response(self, response, chunksize)
818 ngx_log(ngx_ERR, "no response provided")
822 ngx.status = response.status
824 -- Filter out hop-by-hop headeres
825 for k,v in pairs(response.headers) do
826 if not HOP_BY_HOP_HEADERS[str_lower(k)] then
831 local reader = response.body_reader
833 local chunk, err = reader(chunksize)
835 ngx_log(ngx_ERR, err)
840 local res, err = ngx.print(chunk)
842 ngx_log(ngx_ERR, err)