lua-resty-websocket

repository·master·Indexed 19 days ago

https://github.com/openresty/lua-resty-websocket

A high-performance, non-blocking Lua implementation of the WebSocket protocol (RFC 6455) designed for the OpenResty/ngx_lua environment. It provides both client and server implementations, supporting text and binary frames, control frames (Ping/Pong/Close), SSL/TLS connections, and connection pooling. Requires OpenResty 1.4.2.9+ and LuaJIT, or ngx_lua 0.9.0+.

Tokens
3.3K
Snippets
13
Records
16
Agent score
67%

What's inside lua-resty-websocket

  1. How WebSocket objects should be scoped

    master

    To avoid race conditions in concurrent requests, never store a resty.websocket object in a Lua module-level variable. Because Nginx workers handle multiple concurrent requests, a module-level variable would be shared across them, leading to data corruption.

    Best Practice: Always initiate WebSocket objects in function local variables or within the ngx.ctx table. These locations provide request-specific data copies.

  2. Configure connection pooling for the WebSocket client

    master

    The resty.websocket.client supports a connection pool to improve performance by reusing idle connections.

    When calling client:connect, you can provide a pool name and pool_size. To actually put a connection into the pool for reuse, you must call client:set_keepalive(max_idle_timeout, pool_size) instead of client:close().

    Advanced Pooling with backlog: If you specify the backlog option in connect, the module limits the total number of opened connections for that pool. If the pool is full, new connection attempts are queued. If the queue reaches the backlog size, subsequent attempts fail with "too many waiting connect operations".

  3. Install lua-resty-websocket

    master

    The easiest way to use this library is to use the latest OpenResty bundle, where it is bundled and enabled by default. Requires OpenResty 1.4.2.9+ and LuaJIT.

    If using a custom Nginx build with ngx_lua, ensure you have ngx_lua 0.9.0+ (and lua-bitop if not using LuaJIT). You must add the library path to your Nginx configuration using the lua_package_path directive.

    # nginx.conf
    http {
        lua_package_path "/path/to/lua-resty-websocket/lib/?.lua;;";
        ...
    }
  4. Implement a WebSocket Server with resty.websocket.server

    master

    Use resty.websocket.server to handle incoming WebSocket connections. The new method performs the handshake. You can then use recv_frame to listen for messages and various send_* methods to respond.

    Key Server Methods:

    • new(opts): Performs handshake. Options include max_payload_len, max_recv_len, max_send_len, send_masked, and timeout (ms).
    • recv_frame(): Returns data, typ (e.g., text, binary, ping, pong, close), and err. For close frames, err contains the status code.
    • send_text(text): Sends an unfragmented text frame.
    • send_binary(data): Sends an unfragmented binary frame.
    • send_ping(msg) / send_pong(msg): Sends control frames.
    • send_close(code, msg): Sends a close frame.
    local server = require "resty.websocket.server"
    
    local wb, err = server:new{
        timeout = 5000,
        max_payload_len = 65535,
    }
    if not wb then
        ngx.log(ngx.ERR, "failed to new websocket: ", err)
        return ngx.exit(444)
    end
    
    local data, typ, err = wb:recv_frame()
    
    if typ == "close" then
        local code = err
        wb:send_close(1000, "enough, enough!")
        return
    elseif typ == "ping" then
        wb:send_pong(data)
    elseif typ == "pong" then
        -- discard
    else
        ngx.log(ngx.INFO, "received ", typ, " with payload ", data)
    end
    
    wb:send_text("Hello world")
  5. Implement a WebSocket Client with resty.websocket.client

    master

    Use resty.websocket.client to connect to remote WebSocket services. It supports connection pooling and wss:// (SSL) connections.

    Key Client Methods:

    • new(opts): Instantiates the client. Options include max_payload_len, send_unmasked (default false), and timeout.
    • connect(uri, options): Connects to the URI. Returns ok, err, and res (the raw handshake response).
      • Connect Options: protocols, origin, pool, pool_size, backlog, ssl_verify, headers, client_cert, client_priv_key, host, server_name, key.
    • close(): Closes the connection.
    • set_keepalive(max_idle_timeout, pool_size): Puts the connection into the ngx_lua cosocket connection pool for reuse. Use this instead of close() if you want to reuse the connection.
    • send_text(text) / send_binary(data) / send_ping(msg) / send_pong(msg) / send_close(code, msg): Standard frame sending methods.
    local client = require "resty.websocket.client"
    local wb, err = client:new()
    local uri = "ws://127.0.0.1:80/s"
    local ok, err, res = wb:connect(uri)
    if not ok then
        ngx.say("failed to connect: " .. err)
        return
    end
    
    local data, typ, err = wb:recv_frame()
    if data then
        wb:send_text("copy: " .. data)
    end
    
    wb:send_close()
  6. Receive WebSocket frames with `recv_frame()`

    master

    The recv_frame() method reads the next frame from the WebSocket connection.

    Returns:

    • data: The payload of the frame.
    • typ: The opcode/type of the frame.
    • err: An error message if the operation failed.

    If a fatal error occurs (e.g., connection closed or protocol error), subsequent calls to recv_frame() will return a fatal error message. Note that a timeout is not considered a fatal error.

    local data, typ, err = ws:recv_frame()
    if not data and err ~= ": timeout" then
        -- handle fatal error
        return
    end
  7. Close the connection with `close()`

    master

    To gracefully terminate the connection, call close(). This method sends a WebSocket close frame (opcode 0x8) if the connection is not already closed, and then closes the underlying TCP socket.

    Alternatively, you can use send_close(code, msg) to send a specific close code and reason before closing the socket manually.

    Close Codes:

    • code: A numeric status code (up to 0x7fff).
    • msg: An optional string message explaining the closure.
    -- Graceful close
    local ok, err = wb:close()
    if not ok then
        ngx.log(ngx.ERR, "close failed: ", err)
    end
  8. Send control frames: `send_close()`, `send_ping()`, and `send_pong()`

    master

    Manage the WebSocket lifecycle and heartbeat using control frames.

    • send_close(code, msg): Sends a close frame. code must be a number $\le$ 0x7fff. msg is an optional string.
    • send_ping(data): Sends a ping frame (opcode 0x9).
    • send_pong(data): Sends a pong frame (opcode 0xa).
    -- Close the connection with a specific status code
    ws:send_close(1000, "Normal Closure")
    
    -- Send a ping for heartbeat
    ws:send_ping()
  9. Initialize a WebSocket server connection with `resty.websocket.server.new()`

    master

    Use resty.websocket.server.new(opts) to upgrade an HTTP request to a WebSocket connection. This function validates the handshake headers (Upgrade, Connection, Sec-WebSocket-Key, and Sec-WebSocket-Version) and sends the 101 Switching Protocols response.

    Options (opts)

    • max_payload_len: Maximum payload length (default: 65535).
    • max_recv_len: Maximum length for receiving frames (defaults to max_payload_len).
    • max_send_len: Maximum length for sending frames (defaults to max_payload_len).
    • send_masked: Boolean indicating if frames should be masked (default: nil).
    • timeout: Socket timeout in seconds.
    local server = require "resty.websocket.server"
    
    local ok, ws = server.new({
        max_payload_len = 65535,
        timeout = 10
    })
    
    if not ok then
        ngx.log(ngx.ERR, "failed to initialize websocket: ", ws)
        return
    end
  10. Connect to a WebSocket server with `connect()`

    master

    Establish a WebSocket connection by calling connect(uri, opts). The URI can be a standard WebSocket URL (ws:// or wss://) or a Unix domain socket (unix:).

    URI Formats:

    • Standard: ws://host:port/path or wss://host:port/path
    • Unix Socket: wss://unix:/path/to/socket:port/path

    Connection Options (opts):

    • protocols: A string or a table of strings representing Sec-WebSocket-Protocol values.
    • origin: The Origin header value.
    • host: Custom Host header value.
    • key: Custom Sec-WebSocket-Key.
    • headers: A table of custom HTTP headers to include in the handshake.
    • ssl_verify: SSL verification setting.
    • server_name: SSL server_name (must be a string).
    • client_cert & client_priv_key: Used for TLS client certificate authentication.
    • pool, pool_size, backlog: Options for connection pooling.

    Returns 1, nil, header on success (where header is the HTTP response), or nil, err on failure.

    local ok, res, err = wb:connect("ws://example.com/socket", {
        headers = {
            ["X-Custom-Header"] = "value"
        },
        origin = "http://example.com"
    })
    if not ok then
        ngx.log(ngx.ERR, "connection failed: ", err)
        return
    end
  11. Initialize a WebSocket client with `new()`

    master

    To use the WebSocket client, first create an instance using new(opts). This initializes the underlying TCP socket.

    Options (opts):

    • max_payload_len: Maximum payload length (default: 65535).
    • max_recv_len: Maximum length for receiving frames (defaults to max_payload_len).
    • max_send_len: Maximum length for sending frames (defaults to max_payload_len).
    • send_unmasked: Boolean. If true, frames are sent without masking.
    • timeout: Socket timeout in seconds.

    Returns the client object or nil, err.

    local client = require "resty.websocket.client"
    local wb, err = client.new({
        max_payload_len = 65535,
        timeout = 10
    })
    if not wb then
        ngx.log(ngx.ERR, "failed to create client: ", err)
        return
    end