lua-resty-http

repository·master·Indexed 24 days ago

https://github.com/ledgetech/lua-resty-http

A high-performance Lua HTTP client driver for OpenResty and ngx_lua. It supports HTTP 1.0/1.1, SSL/TLS, connection keepalives, and streaming response bodies. The library provides a simple `request_uri` method for single-shot requests and a more granular `connect` and `request` workflow for streamed requests and request pipelining. It includes features for proxy configuration via `set_proxy_options`, socket timeout management, and a `get_client_body_reader` for streaming client request bodies.

Tokens
4K
Snippets
7
Records
17
Agent score
35%

What's inside lua-resty-http

  1. How single-shot and streamed requests work together

    master

    The library provides two distinct modes of operation based on your memory and connection management needs:

    1. Simple single-shot requests: Uses request_uri. It is easier to use but buffers the entire response body into memory. It is best for small responses where you don't want to manage the connection lifecycle.

    2. Streamed requests: Uses connect followed by request. This gives you fine-grained control. You can use res.body_reader to process the response in chunks, which is critical for predictable memory usage when dealing with large payloads. This mode also enables request pipelining over a single connection.

  2. Run tests and coverage inside Docker

    master

    To run the project in a containerized environment, use docker run to mount the current directory to /src inside the container. This allows you to execute Lua linting, tests, and coverage reports.

    First, start the interactive shell:

    docker run --rm -it -v "$(pwd)":/src -w /src lua-resty-http-builder:test bash

    Once inside the container, you can perform the following tasks:

    Check LUA linting and run all tests with coverage:

    luacheck lib && make coverage

    Run linting and a specific test file:

    luacheck lib && TEST_FILE=t/01-basic.t make coverage
    docker run --rm -it -v "$(pwd)":/src -w /src lua-resty-http-builder:test bash
    
    # Check LUA as well as run all tests and coverage:
    luacheck lib && make coverage
    
    # Run a single test file:
    luacheck lib && TEST_FILE=t/01-basic.t make coverage
  3. Perform streamed HTTP requests for large bodies

    master

    To handle large response bodies without high memory usage, use the streamed request pattern. This involves three distinct steps:

    1. Connect: Establish a connection using connect.
    2. Request: Send the request using request (providing path and Host header instead of a full URI).
    3. Stream: Use the body_reader iterator from the response object to read the body in chunks.

    Finally, use set_keepalive() to return the connection to the pool or close it. This pattern is essential for predictable memory usage and allows for request pipelining.

    local httpc = require("resty.http").new()
    
    -- First establish a connection
    local ok, err, ssl_session = httpc:connect({
        scheme = "https",
        host = "127.0.0.1",
        port = 8080,
    })
    if not ok then
        ngx.log(ngx.ERR, "connection failed: ", err)
        return
    end
    
    -- Then send using `request`, supplying a path and `Host` header instead of a
    -- full URI.
    local res, err = httpc:request({
        path = "/helloworld",
        headers = {
            ["Host"] = "example.com",
        },
    })
    if not res then
        ngx.log(ngx.ERR, "request failed: ", err)
        return
    end
    
    -- We can use the `body_reader` iterator, to stream the body according to our
    -- desired buffer size.
    local reader = res.body_reader
    local buffer_size = 8192
    
    repeat
        local buffer, err = reader(buffer_size)
        if err then
            ngx.log(ngx.ERR, err)
            break
        end
    
        if buffer then
            -- process
        end
    until not buffer
    
    local ok, err = httpc:set_keepalive()
    if not ok then
        ngx.say("failed to set keepalive: ", err)
        return
    end
  4. Perform single-shot HTTP requests

    master

    For simple use cases where you don't need to manage connections manually, use the request_uri method. This method handles the entire end-to-end request process: connecting, sending the request, and buffering the entire response body into memory. After the call, the connection is automatically returned to the connection pool or closed.

    Key features of request_uri:

    • Buffers the entire response body into res.body.
    • Supports connection keepalive via keepalive, keepalive_timeout, and keepalive_pool parameters.
    • Parameters in the params table (like path or query) will override components of the provided uri (except scheme, host, and port).
    local httpc = require("resty.http").new()
    
    -- Single-shot requests use the `request_uri` interface.
    local res, err = httpc:request_uri("http://example.com/helloworld", {
        method = "POST",
        body = "a=1&b=2",
        headers = {
            ["Content-Type"] = "application/x-www-form-urlencoded",
        },
    })
    if not res then
        ngx.log(ngx.ERR, "request failed: ", err)
        return
    end
    
    -- The `res` table contains the expected `status`, `headers` and `body` fields.
    local status = res.status
    local length = res.headers["Content-Length"]
    local body   = res.body
  5. Stream a client request body with get_client_body_reader

    master

    The get_client_body_reader method returns an iterator function used to read the downstream client's request body in a streaming fashion. This is useful for proxying large bodies without loading them entirely into memory.

    Usage Patterns:

    1. Manual iteration: Use the returned iterator in a loop with a specified chunk size.
    2. Proxying to upstream: Pass the returned iterator directly into the body field of a request parameter in httpc:request to stream the client body to an upstream server.

    syntax: reader, err = httpc:get_client_body_reader(chunksize?, sock?)

    • chunksize?: Optional default chunk size (defaults to 65536).
    • sock?: An optional already established socket.
    -- Example: Streaming client body to an upstream request
    local client_body_reader, err = httpc:get_client_body_reader()
    
    local res, err = httpc:request({
        path = "/helloworld",
        body = client_body_reader,
    })
  6. Execute multiple requests using request_pipeline

    master

    The request_pipeline method allows you to send a sequence of requests in order. It accepts a table where each element is a table of parameters for a single request.

    Important behaviors:

    • It returns a table of response handles.
    • Due to the nature of pipelining, responses are not actually read from the socket until you access a response field (like status or headers).
    • You must read the entire body (and any trailers) of a response before attempting to access the next response in the pipeline.
    • You should check a field like status first to ensure a socket read error hasn't occurred before processing the body.
    local responses = httpc:request_pipeline({
        { path = "/b" },
        { path = "/c" },
        { path = "/d" },
    })
    
    for _, r in ipairs(responses) do
        if not r.status then
            ngx.log(ngx.ERR, "socket read error")
            break
        end
    
        ngx.say(r.status)
        ngx.say(r:read_body())
    end
  7. Set socket timeouts

    master

    You can control the timeouts for socket operations using either a single value or specific thresholds for different stages of the connection.

    Set a single timeout: httpc:set_timeout(time) Sets the socket timeout (in milliseconds) for all subsequent operations.

    Set granular timeouts: httpc:set_timeouts(connect_timeout, send_timeout, read_timeout) Sets specific thresholds (in milliseconds) for:

    • connect_timeout: Establishing the connection.
    • send_timeout: Sending data.
    • read_timeout: Receiving data and using iterators.
  8. Manage connection lifecycle with `set_keepalive` and `close`

    master

    After completing a request in manual/streamed mode, you must manage the connection state.

    Return to pool with set_keepalive: ok, err = httpc:set_keepalive(max_idle_timeout, pool_size) This is the preferred way to finish a session. It conditionally closes the connection if the HTTP version or headers (like Connection: Close) require it, otherwise it places it in the pool for reuse.

    • Returns 1 on success.
    • Returns nil, err on error.
    • Returns 2, "connection must be closed" if the connection was closed because it couldn't be kept alive.

    Force close with close: ok, err = httpc:close() Closes the connection immediately.

  9. Connect to a server with `connect`

    master

    The connect method establishes a TCP/SSL connection and handles proxy configuration. It is the recommended way to start a manual connection session.

    Syntax: ok, err, ssl_session = httpc:connect(options)

    Options Table:

    • scheme: scheme to use (e.g., https), or nil for unix domain socket.
    • host: target host, or path to a unix domain socket.
    • port: port on target host (defaults to 80 or 443 based on scheme).
    • pool: custom connection pool name.
    • pool_debug: set to true to log the connection pool name at DEBUG level.
    • pool_size: connection pool size (per OpenResty docs).
    • backlog: TCP backlog (per OpenResty docs).
    • proxy_opts: sub-table for proxy configuration.
    • ssl_reused_session: SSL session reuse option.
    • ssl_verify: SSL verification (defaults to true).
    • ssl_server_name: SNI configuration.
    • ssl_send_status_req: SSL status request.
    • ssl_client_cert: client certificate (requires ngx_lua_http_module >= v0.10.23).
    • ssl_client_priv_key: client private key (requires ngx_lua_http_module >= v0.10.23).
  10. Parse a URI with parse_uri

    master

    The parse_uri utility function decomposes a URI string into its components.

    syntax: local scheme, host, port, path, query? = unpack(httpc:parse_uri(uri, query_in_path?))

    • uri: The string to parse.
    • query_in_path?: An optional boolean (defaults to true).
      • If true: The path return value includes the query string.
      • If false: The path contains only the path, and the query return value contains the arguments (without the ? delimiter).
  11. Read the entire response body or trailers

    master

    The response object provides two methods for retrieving the body:

    1. res:read_body(): Reads the entire response body into a single local string. Returns body, err.
    2. res:read_trailers(): Merges any HTTP trailers into the res.headers table. Note: This must be called after reading the body.