Skip to content

rune.http

Asynchronous HTTP requests. The request runs off the client’s event loop and your callback runs back on it, so a slow server never blocks the UI. For a worked example, see the Telegram recipe.

rune.http.get(url, opts?, callback?) -- GET
rune.http.post(url, body, opts?, callback?) -- POST; body sent as-is

Both accept the same opts and callback; both may be omitted (rune.http.get(url, callback) works). Without a callback the request is fire-and-forget.

rune.http.get(url, opts?, callback?)
  • url (string) — http:// or https:// only.
  • opts (table, optional) — headers (name → value table) and timeout (seconds, default 30).
  • callback (function, optional) — function(response, err). Exactly one of the two is set.
rune.http.get("https://api.example.com/who", function(resp, err)
if err then
rune.echo("[who] " .. err)
return
end
rune.echo("[who] " .. resp.body)
end)
rune.http.post(url, body, opts?, callback?)
  • body (string) — sent exactly as given. No Content-Type is set by default; pass one in opts.headers when the server needs it (application/x-www-form-urlencoded, application/json, …).
rune.http.post("https://example.com/hook", '{"event":"levelup"}',
{ headers = { ["Content-Type"] = "application/json" } })

Use rune.json to encode dynamic data. POST takes a string; setting a content type does not convert a table automatically.

local body, err = rune.json.encode({message = 'He said "hello"'})
if err then
rune.echo("JSON encode: " .. err)
return
end
rune.http.post("https://example.com/api", body, {
headers = { ["Content-Type"] = "application/json" },
}, function(resp, requestErr)
if requestErr then
rune.echo("Request failed: " .. requestErr)
return
end
if resp.status < 200 or resp.status >= 300 then
rune.echo("HTTP " .. resp.status)
return
end
-- This endpoint is expected to return JSON. A 204 has no body.
if resp.status == 204 then return end
local data, decodeErr = rune.json.decode(resp.body)
if decodeErr then
rune.echo("JSON decode: " .. decodeErr)
return
end
-- Use data here. JSON false and rune.json.null are valid results.
end)

Response bodies remain strings regardless of their content type. Decode only when the endpoint returns JSON; an empty body is not a JSON value.

Field Description
response.status HTTP status code (number)
response.body Response body (string)
response.headers Response headers, name → first value

err is set only when the request itself failed — DNS, timeout, TLS, unsupported scheme, or a response body over the 5 MB cap. A non-2xx status is a response, not an error: check response.status yourself.

  • The callback runs on the client’s event loop under the script watchdog, like every other callback; a callback that throws is reported through the standard error path.
  • /reload drops pending callbacks (they live in the Lua VM). A request still in flight completes, and its late result is silently discarded.
  • Redirects are followed (up to Go’s default of 10).

Related: Telegram recipe · Core · rune.timer