Giter Site home page Giter Site logo

lua-resty-requests's Introduction

Name

lua-resty-requests - Yet Another HTTP Library for OpenResty.

Build Status

resty -e 'print(require "resty.requests".get{ url = "https://github.com", stream = false }.content)'

Table of Contents

Status

This Lua module now can be considered as production ready.

Note since the v0.7.1 release, this module started using lua-resty-socket, for working in the non-yieldable phases, but still more efforts are needed, so DONOT use it in the init or init_worker phases (or other non-yieldable phases).

Features

  • HTTP/1.0, HTTP/1.1 and HTTP/2 (WIP).
  • SSL/TLS support.
  • Chunked data support.
  • Convenient interfaces to support features like json, authorization and etc.
  • Stream interfaces to read body.
  • HTTP/HTTPS proxy.
  • Latency metrics.
  • Session support.

Synopsis

local requests = require "resty.requests"

-- example url
local url = "http://example.com/index.html"

local r, err = requests.get(url)
if not r then
    ngx.log(ngx.ERR, err)
    return
end

-- read all body
local body = r:body()
ngx.print(body)

-- or you can iterate the response body
-- while true do
--     local chunk, err = r:iter_content(4096)
--     if not chunk then
--         ngx.log(ngx.ERR, err)
--         return
--     end
--
--     if chunk == "" then
--         break
--     end
--
--     ngx.print(chunk)
-- end

-- you can also use the non-stream mode
-- local opts = {
--     stream = false
-- }
--
-- local r, err = requests.get(url, opts)
-- if not r then
--     ngx.log(ngx.ERR, err)
-- end
--
-- ngx.print(r.content)

-- or you can use the shortcut way to make the code cleaner.
local r, err = requests.get { url = url, stream = false }

Installation

$ luarocks install lua-resty-requests
$ opm get tokers/lua-resty-requests
  • Manually:

Just tweeks the lua_package_path or the LUA_PATH environment variable, to add the installation path for this Lua module:

/path/to/lua-resty-requests/lib/resty/?.lua;

Methods

request

syntax: local r, err = requests.request(method, url, opts?)
syntax: *local r, err = requests.request { method = method, url = url, ... }

This is the pivotal method in lua-resty-requests, it will return a response object r. In the case of failure, nil, and a Lua string which describles the corresponding error will be given.

The first parameter method, is the HTTP method that you want to use(same as HTTP's semantic), which takes a Lua string and the value can be:

  • GET
  • HEAD
  • POST
  • PUT
  • DELETE
  • OPTIONS
  • PATCH

The second parameter url, just takes the literal meaning(i.e. Uniform Resource Location), for instance, http://foo.com/blah?a=b, you can omit the scheme prefix and as the default scheme, http will be selected.

The third param, an optional Lua table, which contains a number of options:

  • headers holds the custom request headers.

  • allow_redirects specifies whether redirecting to the target url(specified by Location header) or not when the status code is 301, 302, 303, 307 or 308.

  • redirect_max_times specifies the redirect limits, default is 10.

  • body, the request body, can be:

    • a Lua string, or
    • a Lua function, without parameter and returns a piece of data (string) or an empty Lua string to represent EOF, or
    • a Lua table, each key-value pair will be concatenated with the "&", and Content-Type header will "application/x-www-form-urlencoded"
  • error_filter, holds a Lua function which takes two parameters, state and err. the parameter err describes the error and state is always one of these values(represents the current stage):

    • requests.CONNECT
    • requests.HANDSHAKE
    • requests.SEND_HEADER
    • requests.SEND_BODY
    • requests.RECV_HEADER
    • requests.RECV_BODY
    • requests.CLOSE

You can use the method requests.state to get the textual meaning of these values.

  • timeouts, an array-like table, timeouts[1], timeouts[2] and timeouts[3] represents connect timeout, send timeout and read timeout respectively (in milliseconds).

  • http10 specify whether the HTTP/1.0 should be used, default verion is HTTP/1.1.

  • http20 specify whether the HTTP/2 should be used, default verion is HTTP/1.1.

Note this is still unstable, caution should be exercised. Also, there are some limitations, see lua-resty-http2 for the details.

  • ssl holds a Lua table, with three fields:

    • verify, controls whether to perform SSL verification
    • server_name, is used to specify the server name for the new TLS extension Server Name Indication (SNI)
  • proxies specify proxy servers, the form is like

{
    http = { host = "127.0.0.1", port = 80 },
    https = { host = "192.168.1.3", port = 443 },
}

When using HTTPS proxy, a preceding CONNECT request will be sent to proxy server.

  • hooks, also a Lua table, represents the hook system that you can use to manipulate portions of the request process. Available hooks are:
    • response, will be triggered immediately after receiving the response headers

you can assign Lua functions to hooks, these functions accept the response object as the unique param.

local hooks = {
    response = function(r)
        ngx.log(ngx.WARN, "during requests process")
    end
}

Considering the convenience, there are also some "short path" options:

  • auth, to do the Basic HTTP Authorization, takes a Lua table contains user and pass, e.g. when auth is:
{
    user = "alex",
    pass = "123456"
}

Request header Authorization will be added, and the value is Basic YWxleDoxMjM0NTY=.

  • json, takes a Lua table, it will be serialized by cjson, the serialized data will be sent as the request body, and it takes the priority when both json and body are specified.

  • cookie, takes a Lua table, the key-value pairs will be organized according to the Cookie header's rule, e.g. cookie is:

{
    ["PHPSESSID"] = "298zf09hf012fh2",
    ["csrftoken"] = "u32t4o3tb3gg43"
}

The Cookie header will be PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43 .

  • stream, takes a boolean value, specifies whether reading the body in the stream mode, and it will be true by default.

state

syntax: local state_name = requests.state(state)

The method is used for getting the textual meaning of these values:

  • requests.CONNECT
  • requests.HANDSHAKE
  • requests.SEND_HEADER
  • requests.SEND_BODY
  • requests.RECV_HEADER
  • requests.RECV_BODY
  • requests.CLOSE

a Lua string "unknown" will be returned if state isn't one of the above values.

get

syntax: local r, err = requests.get(url, opts?)
syntax: local r, err = requests.get { url = url, ... }

Sends a HTTP GET request. This is identical with

requests.request("GET", url, opts)

head

syntax: local r, err = requests.head(url, opts?)
syntax: local r, err = requests.head { url = url, ... }

Sends a HTTP HEAD request. This is identical with

requests.request("HEAD", url, opts)

post

syntax: local r, err = requests.post(url, opts?)
syntax: local r, err = requests.post { url = url, ... }

Sends a HTTP POST request. This is identical with

requests.request("POST", url, opts)

put

syntax: local r, err = requests.put(url, opts?)
syntax: local r, err = requests.put { url = url, ... }

Sends a HTTP PUT request. This is identical with

requests.request("PUT", url, opts)

delete

syntax: local r, err = requests.delete(url, opts?)
syntax: local r, err = requests.delete { url = url, ... }

Sends a HTTP DELETE request. This is identical with

requests.request("DELETE", url, opts)

options

syntax: local r, err = requests.options(url, opts?)
syntax: local r, err = requests.options { url = url, ... }

Sends a HTTP OPTIONS request. This is identical with

requests.request("OPTIONS", url, opts)

patch

syntax: local r, err = requests.patch(url, opts?)
syntax: local r, err = requests.patch { url = url, ... }

Sends a HTTP PATCH request. This is identical with

requests.request("PATCH", url, opts)

Response Object

Methods like requests.get and others will return a response object r, which can be manipulated by the following methods and variables:

  • url, the url passed from caller
  • method, the request method, e.g. POST
  • status_line, the raw status line(received from the remote)
  • status_code, the HTTP status code
  • http_version, the HTTP version of response, e.g. HTTP/1.1
  • headers, a Lua table represents the HTTP response headers(case-insensitive)
  • close, holds a Lua function, used to close(keepalive) the underlying TCP connection
  • drop, is a Lua function, used for dropping the unread HTTP response body, will be invoked automatically when closing (if any unread data remains)
  • iter_content, which is also a Lua function, emits a part of response body(decoded from chunked format) each time called.

This function accepts an optional param size to specify the size of body that the caller wants, when absent, iter_content returns 8192 bytes when the response body is plain or returns a piece of chunked data if the resposne body is chunked.

In case of failure, nil and a Lua string described the error will be returned.

  • body, also holds a Lua function that returns the whole response body.

In case of failure, nil and a Lua string described the error will be returned.

  • json, holds a Lua function, serializes the body to a Lua table, note the Content-Type should be application/json. In case of failure, nil and an error string will be given.

  • content, the response body, only valid in the non-stream mode.

  • elapsed, a hash-like Lua table which represents the cost time (in seconds) for each stage.

    • elapsed.connect, cost time for the TCP 3-Way Handshake;
    • elapsed.handshake, cost time for the SSL/TLS handshake (if any);
    • elapsed.send_header, cost time for sending the HTTP request headers;
    • elapsed.send_body, cost time for sending the HTTP request body (if any);
    • elapsed.read_header, cost time for receiving the HTTP response headers;
    • elapsed.ttfb, the time to first byte.

Note When HTTP/2 protocol is applied, the elapsed.send_body (if any) will be same as elapsed.send_header.

Session

A session persists some data across multiple requests, like cookies data, authorization data and etc.

This mechanism now is still experimental.

A simple example:

s = requests.session()
local r, err = s:get("https://www.example.com")
ngx.say(r:body())

A session object has same interfaces with requests, i.e. those http methods.

TODO

  • other interesting features...

Author

Alex Zhang (张超) [email protected], UPYUN Inc.

Copyright and License

The bundle itself is licensed under the 2-clause BSD license.

Copyright (c) 2017-2019, Alex Zhang.

This module is licensed under the terms of the BSD license.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

See Also

lua-resty-requests's People

Contributors

chriskuehl avatar gealo avatar jetz avatar moonming avatar spacewander avatar timebug avatar tokers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lua-resty-requests's Issues

resty -e 'print(require "resty.requests")'

i had use the command to install

$  opm get tokers/lua-resty-requests```

but when i require, need a new package resty.socket.

resty -e 'print(require "resty.requests")'
ERROR: ./resty/requests/adapter.lua:4: module 'resty.socket' not found:
no field package.preload['resty.socket']
no file '/usr/local/Cellar/openresty/1.17.8.2_1/site/lualib/resty/socket.ljbc'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/site/lualib/resty/socket/init.ljbc'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/lualib/resty/socket.ljbc'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/lualib/resty/socket/init.ljbc'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/site/lualib/resty/socket.lua'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/site/lualib/resty/socket/init.lua'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/lualib/resty/socket.lua'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/lualib/resty/socket/init.lua'
no file './resty/socket.lua'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/luajit/share/luajit-2.1.0-beta3/resty/socket.lua'
no file '/usr/local/share/lua/5.1/resty/socket.lua'
no file '/usr/local/share/lua/5.1/resty/socket/init.lua'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/luajit/share/lua/5.1/resty/socket.lua'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/luajit/share/lua/5.1/resty/socket/init.lua'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/site/lualib/resty/socket.so'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/lualib/resty/socket.so'
no file './resty/socket.so'
no file '/usr/local/lib/lua/5.1/resty/socket.so'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/luajit/lib/lua/5.1/resty/socket.so'
no file '/usr/local/lib/lua/5.1/loadall.so'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/site/lualib/resty.so'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/lualib/resty.so'
no file './resty.so'
no file '/usr/local/lib/lua/5.1/resty.so'
no file '/usr/local/Cellar/openresty/1.17.8.2_1/luajit/lib/lua/5.1/resty.so'
no file '/usr/local/lib/lua/5.1/loadall.so'
stack traceback:
./resty/requests/adapter.lua:4: in main chunk
[C]: in function 'require'
./resty/requests/session.lua:5: in main chunk
[C]: in function 'require'
./resty/requests.lua:4: in main chunk
[C]: in function 'require'
(command line -e):1: in function 'inline_gen'
init_worker_by_lua:45: in function <init_worker_by_lua:44>
[C]: in function 'xpcall'
init_worker_by_lua:53: in function <init_worker_by_lua:51>

Proxy auth

Hello, thanks for the convenient library.
I have one question regarding proxy usage, can I pass authentication for a proxy?

readme typo

in readme.md

$ opm get thibaultcha/lua-resty-mlcache

shouldnt it be something else ?

read chunked responses ... doesn't seem to work in chunks

hi,

i'm reading chunked responses from a requests.post(...), however calling local chunk, err = r:iter_content() doesn't give me a chunk each time, but rather many of the chunks all joined together. i.e. the following outputs a single big chunk.

local chunk, err = r:iter_content()

ngx.log(ngx.ERR, "CHUNK")
ngx.log(ngx.ERR, chunk)
ngx.log(ngx.ERR, "END CHUNK")

if i access the same endpoint with, say, a js XHR i successfully get each of the chunks coming through separately.

if i set a very small chunk size in the r:iter_content() call, i can get an approximation of the desired behaviour (indicating that it is receiving the chunks from the server in a chunk like fashion ... and i can also confirm that the chunked encoding heading is being successfully read in the r.headers.

i am using the 7.3-1 from luarocks.

any tips on how i could debug this?

with thanks

What does response:close actually do?

Hey @tokers 👋🏾. Our team has observed a TCP resource leak of some kind when usinglua-resty-requests to make API calls in a OpenResty lua timer. All of our TCP requests begin failing with a lua tcp socket queued connect timed out after the timer has been running for a period of time. Explicitly calling response:close() after each request seems to fix (or significantly delay the onset of) the issue. What does the response:close method actually do? The documentation seems to state that is closes the underlying TCP connection instead of keeping it alive. Is this correct?

README timeouts misrepresent

timeouts, an array-like table, timeouts[1], timeouts[2] and timeouts[3] represents connect timeout, send timeout and read timeout respectively (in seconds).

I think it's in milliseconds instead of in seconds, just as tcpsock:settimeouts .

typo in README

README.markdown -- Installation -- OPM

opm get thibaultcha/lua-resty-requests

thibaultcha ???

opm get tokers/lua-resty-requests

Thank you for providing a useful resty module!

requests.get(url, { http2 = true }) 报错

ngx.socket sslhandshake: expecting 1 ~ 5 arguments (including the object), but seen 6
stack traceback:
coroutine 0:
[C]: in function 'sslhandshake'
...penresty/luajit/share/lua/5.1/resty/requests/adapter.lua:352: in function <...penresty/luajit/share/lua/5.1/resty/requests/adapter.lua:324>
...penresty/luajit/share/lua/5.1/resty/requests/adapter.lua:691: in function 'send'
...penresty/luajit/share/lua/5.1/resty/requests/session.lua:212: in function 'get'
/home///lua/http2.lua:5: in main chunk, client: 10.18.., server: ...*, request: "GET /test_new2 HTTP/2.0", host:

Compatible with "Expect: 100-Continue"

As described in the RFC7231 documentation, the Expect field-value is case-insensitive.

And there are some popular HTTP client libraries that use 100-Continue as the Expect field-value. Such as guzzle (an extensible PHP HTTP client).

So we should make this code more compatible.

Do you support grpc?

Here is my code:

 local p, err = proto.new("proto-file/student.proto")
if err then
    ngx.log(ngx.ERR, ("proto load error: %s"):format(err))
    return
end
local m = util.find_method(p, "htsc.grpc.streamtest.Greeter", "GetStudentFamily")
if not m then
	return
end
-- Build request binary as method input type from request data (query string, post body, or JSON body)
local encoded = pb.encode(m.input_type, util.map_message(m.input_type, default_values or {}))
local size = string.len(encoded)
 -- Prepend gRPC specific prefix data
-- request is compressed (always 0)
-- requesdt body size (4 bytes)
local prefix = {
	string.char(0),
	string.char(bit.band(bit.rshift(size, 24), 0xFF)),
	string.char(bit.band(bit.rshift(size, 16), 0xFF)),
	string.char(bit.band(bit.rshift(size, 8), 0xFF)),
	string.char(bit.band(size, 0xFF))
  }
  local message = table.concat(prefix, "") .. encoded
  local url = "192.168.88.72:50033"
  local opts = {
	  --stream = false,
	  http2 = true,
	  headers ={
		[":path"] = "/htsc.grpc.streamtest.Greeter/GetStudentFamily",
		["te"] = "trailers",
		["content-type"] = "application/grpc",
		["user-agent"] = "grpc-c++/1.24.0-dev grpc-c/8.0.0 (linux; chttp2; ganges)",
		["grpc-accept-encoding"] = "identity,deflate,gzip",
		["accept-encoding"] = "identity,gzip",
	  },
	  body = message  
  }
  local r, err = req.request("POST",url,opts)
  if not r then
	ngx.log(ngx.ERR, err)
	return
  end
  local body = r:body()
  ngx.print(body)

but the body is not available for the response,my grpc server return response OK, also have body

module 'resty.requests' not found

Hello I setup the module with the following command:
$ opm get tokers/lua-resty-requests
and I have the following test.lua file:

local requests = require "resty.requests"

This is the output I get:

> dofile("lua/test.lua")
lua/test.lua:1: module 'resty.requests' not found:
	no field package.preload['resty.requests']
	no file './resty/requests.lua'
	no file '/usr/share/lua/5.1/resty/requests.lua'
	no file '/usr/share/lua/5.1/resty/requests/init.lua'
	no file '/usr/lib64/lua/5.1/resty/requests.lua'
	no file '/usr/lib64/lua/5.1/resty/requests/init.lua'
	no file './resty/requests.so'
	no file '/usr/lib64/lua/5.1/resty/requests.so'
	no file '/usr/lib64/lua/5.1/loadall.so'
	no file './resty.so'
	no file '/usr/lib64/lua/5.1/resty.so'
	no file '/usr/lib64/lua/5.1/loadall.so'
stack traceback:
	[C]: in function 'require'
	lua/oauth_redirect.lua:1: in main chunk
	[C]: in function 'dofile'
	stdin:1: in main chunk
	[C]: ?

This is the output from installing the module:

# opm get tokers/lua-resty-requests
* Fetching tokers/lua-resty-requests  
  Downloading https://opm.openresty.org/api/pkg/tarball/tokers/lua-resty-requests-0.3.opm.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 12670  100 12670    0     0  13653      0 --:--:-- --:--:-- --:--:-- 13653
Package tokers/lua-resty-requests 0.3 installed successfully under /usr/local/openresty/site/ .

What am I missing? Thanks

how to send http2 request?

local requests = require "resty.requests"
local r,err=requests.request{
method = "GET",
url = "172.16.18.106:8066/portal/login.jsp",
{
http20 = true,
},
}
but i got accelog like this:
127.0.0.1 - - [09/Mar/2021:17:38:24 +0800] "GET / HTTP/1.1" 200 4833 "-" "curl/7.29.0" "-"
,protocl still is http1.1 ,but how to send http2 request?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.