Http
A minimal blocking HTTP POST transport for small, low-frequency, out-of-band sends — opt-in telemetry, crash-report upload, a leaderboard submit. It is deliberately narrow: one method, JSON only, no headers, no GET (ADR 0009).
Http is not a general web client, and it is not the game transport — for
connection-oriented gameplay traffic use Net (UDP via GameNetworkingSockets).
Import from the engine module:
import "engine" for HttpHttp.post(url, body)
Section titled “Http.post(url, body)”POST body to url with Content-Type: application/json. Blocking.
Returns: Map — always a Map with exactly three keys, never null:
"ok"(Bool) —trueonly when a response arrived with a 2xx status."status"(Num) — the HTTP status code.0means no response (transport error, bad URL, or the request was never sent)."body"(String) — the response body;""on any failure.
Parameters:
url(String) — a full URL including scheme:http://orhttps://, host, optional port, and path. A URL without a scheme fails.body(String) — the request body, sent asapplication/json. You build the JSON yourself; the engine does not serialize or validate it.
var res = Http.post("https://example.com/ingest", "{\"event\":\"level_end\"}")if (res["ok"]) { Logger.info("sent, HTTP %(res["status"])")} else if (res["status"] == 0) { Logger.warn("never reached the server") // offline, bad URL, or no networking} else { Logger.warn("server rejected it: HTTP %(res["status"])")}Fixed limits
Section titled “Fixed limits”These are pinned by ADR 0009 and are not configurable from script or config:
| Limit | Value |
|---|---|
| Method | POST only — no GET/PUT/DELETE |
| Content type | application/json, always |
| Request body cap | 1 MiB. A larger body is rejected before it is sent (ok=false, status=0) |
| Response body cap | none — the whole response is read into memory |
| Connect timeout | 5 seconds |
| Read timeout | 10 seconds |
| Redirects | followed automatically |
| Scheme | http:// or https://; HTTPS via the vendored OpenSSL |
| Custom headers | not supported — see below |
Passing a non-String for either argument is not an error: the value is silently treated as
"", so a non-string url becomes an empty URL and the call fails with
ok=false, status=0.
Authentication — tokens ride in the URL
Section titled “Authentication — tokens ride in the URL”There is no auth-header parameter. Http.post cannot set Authorization, an API-key
header, or any other header. The only place to put a credential is the URL query string:
Http.post("https://example.com/ingest?key=%(token)", payload)Platform availability
Section titled “Platform availability”Http.post links and runs on every platform, but only sends on networking-capable
(desktop) builds. On a build compiled without networking (console stubs) it never crashes and
never blocks — it returns ok=false, status=0, body="" immediately and logs a warning.
Because there is no error key, treat a status=0 result as “did not send” and move on;
never let a failed send block game progression.
Example — flushing a telemetry outbox
Section titled “Example — flushing a telemetry outbox”A realistic shape: buffer events during play, flush once at a boundary, and drop the batch rather than stall the game if it fails.
import "engine" for Http, Engine, Logger
class Telemetry { construct new(endpoint, token) { _endpoint = endpoint _token = token _outbox = [] // A unique, non-deterministic install id. Engine.entropy() is NOT Random -- // Random is seed-deterministic (ADR 0002), so an id built from it collides // across installs. _installId = "%(Engine.entropy())" }
// Cheap, non-blocking: safe to call during play. record(name) { if (_outbox.count < 128) _outbox.add("{\"e\":\"%(name)\"}") }
// BLOCKING: call at a level boundary or on quit -- never from update(dt). flush() { if (_outbox.isEmpty) return var payload = "{\"id\":\"%(_installId)\",\"events\":[%(_outbox.join(","))]}" var res = Http.post("%(_endpoint)?key=%(_token)", payload) if (res["ok"]) { _outbox.clear() } else { // status 0 = never reached the server; the reason is only in the engine log. Logger.warn("telemetry flush failed (HTTP %(res["status"])); dropping batch") _outbox.clear() // bounded outbox: do not retry-grow forever } }}Keep the outbox bounded (as above) — an unbounded retry queue on a player who is offline for a whole session grows without limit, and every flush attempt costs another blocking 15-second worst case.
See Net for the real-time game transport, PlayFlow for
dedicated-server matchmaking (which makes its own blocking HTTP calls), and
Random for the seeded, deterministic RNG that Engine.entropy() is
deliberately not.