Coroutine-per-connection HTTP/1.1 server using LEV async I/O.
Supported features:
Runs inside a lev.run() event loop.
| Name | Signature |
|---|---|
| content_mime | content_mime(value) -> mime |
| parse_request_line | parse_request_line(line) -> method, query, args, http_minor |
| server:process_request | server:process_request(client, client_ip, count) -> connection_state, err |
| attach | attach(servers) -> ok, err |
| serve_all | serve_all(servers, opts) -> ok, err |
| server:serve | server:serve() -> ok, err |
| server:configure | server:configure(config) -> ok, err |
| new | new(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:
"keep-alive" — request handled; the connection may be reused"close" — response sent with Connection: close; caller must closenil, err — fatal read/write error; caller must close the connectioncount 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:
logger — the server loggerclient — the LEV TCP socket (for proxying)cfg — a snapshot of the server configapp — the application context passed to new() (may be nil)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:
cfg.ssl is setprocess_request in a keep-alive loop until the client closes
or cfg.requests_per_connection is reachedThe 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:
ip — bind address (default: "127.0.0.1")port — listen port (default: 8080)listen — optional array of { ip = ..., port = ... } bind specs; when
set it takes precedence over ip/portbacklog — listen backlog (default: 256)connection_limit — max concurrent connections (default: 64)requests_per_connection — requests before connection closes (default: 512)max_body_size — max request body in bytes (default: 5 MB)request_line_limit — max header/request line (default: 8 KB)keepalive_idle_timeout — keep-alive idle timeout (default: 15)request_header_timeout — header read timeout (default: 10)request_body_timeout — body read timeout (default: 30)tls_handshake_timeout — TLS handshake timeout (default: 10)ssl — TLS configuration (see below)compression — compression settings tablelog_level — log level (default: "access")log_headers — request headers to logon_request — optional callback invoked after each response is sent,
with one table argument: { host, method, query, status, elapsed, size }
(status is a number, elapsed in seconds, size the response body
size in bytes). It fires only on the normal response path — proxied
requests and premature protocol errors do not reach it. The callback
runs under pcall; a failure is logged and never affects the requestTLS 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