http.server

index · http

Overview

Coroutine-per-connection HTTP/1.1 server using LEV async I/O.

Supported features:

Runs inside a lev.run() event loop.

Functions

NameSignature
content_mimecontent_mime(value) -> mime
parse_request_lineparse_request_line(line) -> method, query, args, http_minor
server:process_requestserver:process_request(client, client_ip, count) -> connection_state, err
attachattach(servers) -> ok, err
serve_allserve_all(servers, opts) -> ok, err
server:serveserver:serve() -> ok, err
server:configureserver:configure(config) -> ok, err
newnew(config, handle, app) -> server, err

content_mime(value) -> mime

yields an empty string.

parse_request_line(line) -> method, query, args, http_minor

HTTP/1.0 or HTTP/1.1 request line.

server:process_request(client, client_ip, count) -> connection_state, err

Process a single HTTP request on an accepted connection

Reads one HTTP/1.1 request from client, invokes self.handle, sends the response, and returns the connection disposition string.

Return values:

count is the number of requests handled on this connection. When it reaches cfg.requests_per_connection the response is sent with Connection: close.

The context table passed to self.handle contains:

If handle returns all-nil values the request is assumed to have been proxied directly on the socket and the connection is kept alive without writing a response.

After a response is sent, the optional cfg.on_request callback fires with request/response info; see new() for the contract.

attach(servers) -> ok, err

Bind every listener of every server and spawn their accept loops.

Must be called from inside a running LEV loop. For each server instance: pre-parses its TLS identities once, binds every cfg.listen spec (or the single cfg.ip/cfg.port pair when listen is absent), and spawns one detached accept coroutine per listener. Registers no loop-global signal handlers; that is serve_all's job. Any bind failure closes the listeners bound so far and fails the whole attach.

serve_all(servers, opts) -> ok, err

Serve one or more server instances in a single LEV event loop.

Enters the LEV event loop once for all given server instances: registers the loop-global signal handlers, binds and attaches every listener, and starts a shared housekeeping coroutine. SIGTERM sets the shutdown flag on every server; accept loops drain within their accept timeout and a 5s timer stops the loop for stragglers.

opts.on_start is called inside the loop after all listeners are bound — the place for applications to spawn their own periodic coroutines.

server:serve() -> ok, err

Start accepting connections in a coroutine-per-connection event loop

Single-server sugar for serve_all({ self }): enters the LEV event loop, binds the configured listeners, then accepts connections. Each connection is handled in a spawned coroutine.

For each accepted connection:

  1. Performs the TLS handshake if cfg.ssl is set
  2. Calls process_request in a keep-alive loop until the client closes or cfg.requests_per_connection is reached
  3. Closes the socket

The connection_limit setting controls the maximum number of concurrent connections per server instance (default 64), shared across all of its listeners.

server:configure(config) -> ok, err

Apply configuration and validate TLS cert/key paths

Merges config into the server's current configuration table and updates the logger level. If config.ssl is present, validates that cert/key files exist. TLS identities are pre-parsed once before the accept loop, while the handshake itself still happens per-connection inside starttls().

The ssl field format:

ssl = {
    default = { cert = "path/to/cert.pem", key = "path/to/key.pem" },
    hosts   = {
        ["example.com"] = { cert = "...", key = "..." },
    },
}

Returns nil, err if a certificate or key file cannot be found.

new(config, handle, app) -> server, err

Create a new HTTP server instance

Creates and configures an HTTP server. The handle function is called for each request with (method, query, args, headers, body, context) and must return content, status, response_headers. The args string is the request's query string without the leading ?. The optional app table is an opaque application context passed to the handler as context.app — the place for process-lifetime state such as stores and caches.

Several server instances (each with its own handler, TLS identities and connection budget) can share one event loop via serve_all; a single instance may also bind several addresses via listen.

The config table may override these defaults:

TLS configuration (ssl field):

{
  default = { cert = "path/to/cert", key = "path/to/key" },
  hosts = {
    ["domain.com"] = { cert = "...", key = "..." },
  },
}

The server uses a coroutine-per-connection model: each accepted connection is handled in a spawned coroutine within lev.run().

local server = require("http.server")

local srv, err = server.new(
    {
        ip   = "0.0.0.0",
        port = 8443,
        ssl  = {
            default = {
                cert = "/etc/tls/cert.pem",
                key  = "/etc/tls/key.pem",
            },
        },
    },
    function(method, query, args, headers, body, ctx)
        return "Hello!", 200, { ["content-type"] = "text/plain" }
    end
)
if not srv then error(err) end
srv:serve()  -- blocks forever