HTTP client utilities for synchronous requests with TLS/mTLS and redirect following. Supports Server-Sent Events streaming, URL encoding, HTML entity escaping, and multipart/URL-encoded form body building and parsing.
| Module | Description |
|---|---|
| web.server | Pre-forking HTTP/1.1 server with keep-alive, chunked transfer encoding, response compression (gzip/deflate), and optional TLS with SNI support. |
| Name | Signature |
|---|---|
url_escape | url_escape(str) -> encoded |
html_unescape | html_unescape(str) -> decoded |
html_escape | html_escape(str) -> escaped |
make_form_data | make_form_data(items) -> content_type, body |
request | request(uri, options, timeout) -> response, err |
sse_client | sse_client(uri, options, callbacks) -> client |
parse_args | parse_args(body) -> args |
parse_form_data | parse_form_data(boundary, body) -> args |
url_escape(
str) ->encoded
URL-encode a string for safe use in query parameters
Encodes str using application/x-www-form-urlencoded rules:
Newlines are normalised to \r\n before encoding
Spaces are encoded as +
All other characters that are not unreserved (A-Z a-z 0-9 - _ . ~ %)
are percent-encoded as %XX
html_unescape(
str) ->decoded
Decode HTML entities back to their original characters
Handles three entity forms:
Named entities: &, <, >, ", '
Decimal numeric references: &#NNN;
Hexadecimal numeric references: &#xNNN;
Codepoints above 255 are decoded to UTF-8 via std.utf.char.
html_escape(
str) ->escaped
Escape angle brackets in a string for safe HTML embedding
Only < and > are replaced (< and >). This is intentionally
limited to neutralising tags when embedding user-supplied values inside
HTML content or attributes. It is not a general-purpose HTML sanitizer —
&, ", and ' are left untouched.
make_form_data(
items) ->content_type,body
Build a multipart/form-data body from a list of items
Each item in the list is a table with the following fields:
name — form field name (required)
content — string content for text fields
path — file path; if set and file exists, reads content from file
mime — override MIME type (auto-detected from path if not set)
local web = require("web")
local ct, body = web.make_form_data({
{ name = "username", content = "alice" },
{ name = "avatar", path = "/tmp/photo.png" },
})
-- use ct as the Content-Type header value, body as the request body
local res = web.request("https://example.com/upload", {
method = "POST",
body = body,
headers = { ["content-type"] = ct },
})
request(
uri,options,timeout) ->response,err
Perform an HTTP request and return the response
Sends an HTTP request to the given URI. The options table may contain:
method — HTTP method (default: "GET")
body — request body string
headers — table of request headers
no_sni — disable SNI for HTTPS
follow_redirects — follow 3xx redirects (default: false); max 5 hops
tls_certfile — path to client certificate PEM file (for mTLS)
tls_keyfile — path to client private key PEM file (for mTLS)
tls_cafile — path to CA certificate bundle (overrides default)
tls_capath — path to CA certificate directory
tls_no_verify — disable server certificate verification
Returns a table with body, status, and headers fields on success,
or nil and an error message on failure. Default timeout is 1 second.
When follow_redirects is enabled and a redirect occurred, the response
also contains a final_url field with the URL of the last hop.
local web = require("web")
-- Simple GET
local res, err = web.request("https://api.example.com/data")
if res then print(res.body) end
-- POST with JSON body
local res, err = web.request("https://api.example.com/items", {
method = "POST",
body = '{"name":"foo"}',
headers = { ["content-type"] = "application/json" },
})
-- Follow redirects (up to 5 hops)
local res, err = web.request("https://example.com/old-path", {
follow_redirects = true,
})
if res then print(res.final_url or "no redirect") end
sse_client(
uri,options,callbacks) ->client
Create a Server-Sent Events (SSE) streaming client
Returns an SSE client object with the following methods:
client:connect() — connect to the SSE endpoint
client:update() — read and process available data (non-blocking)
client:close() — close the connection
client:is_connected() — check connection status
The options table may contain:
method — HTTP method (default: "GET")
body — request body string
headers — additional request headers
connect_timeout — connection timeout in seconds (default: 10)
handshake_timeout — TLS handshake timeout (default: 10)
send_timeout — send timeout (default: 30)
tls_certfile — path to client certificate PEM file (for mTLS)
tls_keyfile — path to client private key PEM file (for mTLS)
tls_cafile / tls_capath — TLS CA configuration
tls_no_verify — disable server certificate verification
The callbacks table may contain handler functions keyed by event name:
connect — called on successful connection
open(status, headers) — called when response headers are received
message(event) — default handler for SSE events
error(msg, status) — called on errors
close — called when the connection closes
done — called on [DONE] sentinel
Named event callbacks (e.g., callbacks.delta) are dispatched by SSE
event type. JSON data payloads are automatically decoded.
local web = require("web")
local client = web.sse_client(
"https://api.openai.com/v1/chat/completions",
{
method = "POST",
body = '{"model":"gpt-4o","stream":true,"messages":[...]}',
headers = {
["Authorization"] = "Bearer " .. api_key,
["content-type"] = "application/json",
},
},
{
-- dispatched for SSE events named "delta"
delta = function(data)
io.write((data.choices[1] or {}).delta and
data.choices[1].delta.content or "")
end,
done = function() print("\n[stream complete]") end,
error = function(msg, status)
print("SSE error " .. tostring(status) .. ": " .. msg)
end,
}
)
client:connect()
while client:is_connected() do
client:update()
end
parse_args(
body) ->args
Parse a URL-encoded form body into a key-value table
Parses an application/x-www-form-urlencoded body. + signs in values
are restored to spaces before URL-decoding; values are then HTML-escaped
via html_escape. Returns an empty table for non-string or empty input.
Key-value pairs missing the = separator are silently skipped.
parse_form_data(
boundary,body) ->args
Parse a multipart/form-data body into a key-value table
Parses a multipart/form-data body using the given boundary string.
Text fields are URL-decoded and HTML-escaped. File uploads are returned
as tables with filename, content, and content_type fields.