Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

crap.http

Outbound HTTP client for making requests from Lua hooks and init.lua.

Functions

crap.http.request(opts)

Make a blocking HTTP request.

Parameters:

  • opts (table):
    • url (string, required) — Request URL.
    • method (string, optional) — HTTP method. Default: "GET". Supported: GET, POST, PUT, PATCH, DELETE, HEAD.
    • headers (table, optional) — Request headers as key-value pairs.
    • body (string, optional) — Request body.
    • timeout (integer, optional) — Timeout in seconds. Default: 30.

Returns: table — Response with fields:

  • status (integer) — HTTP status code.
  • headers (table) — Response headers as key-value pairs.
  • body (string) — Response body as a string.

Errors: Throws a Lua error on transport failures (DNS, connection refused, timeout).

-- Simple GET
local resp = crap.http.request({ url = "https://api.example.com/data" })
if resp.status == 200 then
    local data = crap.util.json_decode(resp.body)
    crap.log.info("Got " .. #data .. " items")
end

-- POST with JSON body
local resp = crap.http.request({
    url = "https://api.example.com/webhook",
    method = "POST",
    headers = {
        ["Content-Type"] = "application/json",
        ["Authorization"] = "Bearer " .. crap.env.get("CRAP_API_TOKEN"),
    },
    body = crap.util.json_encode({ event = "document.created", id = ctx.data.id }),
    timeout = 10,
})

Notes

  • Uses reqwest (blocking HTTP client). Since Lua hooks run inside spawn_blocking, blocking I/O is correct and won’t stall the async runtime.
  • Non-2xx responses are not errors — they return normally with the status code. Only transport-level failures (DNS, timeout, connection refused) throw Lua errors.
  • Available in both init.lua and hooks.

Security

Private network blocking

When hooks.allow_private_networks is false (the default), crap.http.request resolves the URL hostname and rejects requests targeting loopback, private (RFC 1918), link-local, and unspecified IP addresses. This prevents SSRF attacks against internal services. Set allow_private_networks = true in crap.toml only if your hooks need to reach internal services.

DNS rebinding protection

DNS is resolved once during validation, checked against the SSRF policy, and the validated IP is pinned via reqwest::ClientBuilder::resolve(). The HTTP client connects to the exact validated address — no second DNS lookup occurs. Redirects are individually resolved, validated, and pinned before following.