feat(http): Http.* poll-based HTTP/HTTPS client over a native NSURLConnection backend (#6)
Adds the Http.* namespace and its transport, the HTTP client from #6. The JSON companion the proposal called for already shipped as Json.* (#44). - runtime/native/http.ll: the macOS transport — NSURLConnection driven through the objc runtime C ABI (no ObjC/C source), run on a detached pthread so the frame never blocks; TLS is the system's, on by default. A fixed slot pool holds each in-flight request; the worker publishes status/body/response behind an atomic done flag (release/acquire). Spliced + Foundation linked only when a program uses Http.*. - runtime/native/http.ludic: the Http.* runtime — get/post/request, the open/set/body/send builder, poll/status/ok/text/body_len/header/free, plus a pure-Ludic response parser (Http.parse + case-insensitive header lookup) that is transport-independent and portable. - compiler: Http.* dispatch, g_uses_http splice, hs_* transport intrinsics + declarations, the conditional Foundation link, and a new \r string/char escape the protocol needs. - docs: a full docs/language/http section (16 pages); check-impl/check-docs green. - test: examples/library/http.ludic self-asserts the parser offline (Darwin-gated build via the canonical path, since it links Foundation). Verified end to end against real endpoints: HTTPS GET (200 + headers + body) and POST (body + custom header). HTTP is out-of-band and never feeds the deterministic sim. Reseeded; suites green (81 + 29). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
4eef5ebbce
commit
3df6640fa5
29 changed files with 24936 additions and 22396 deletions
9
docs/language/http/_section.md
Normal file
9
docs/language/http/_section.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
id: http
|
||||
title: Http
|
||||
order: 8
|
||||
---
|
||||
|
||||
A poll-based HTTP / HTTPS client for out-of-band data — leaderboards, cloud saves, remote config, telemetry, downloads. Open a request with <a href="http-get.html"><code>Http.get</code></a> / <a href="http-post.html"><code>Http.post</code></a> (or <a href="http-open.html"><code>Http.open</code></a> to add headers first), then each frame call <a href="http-poll.html"><code>Http.poll</code></a> — it returns <code>-1</code> while pending, <code>0</code> on error, or the status code — so the frame never blocks. Read the reply with <a href="http-status.html"><code>Http.status</code></a> / <a href="http-ok.html"><code>ok</code></a> / <a href="http-text.html"><code>text</code></a> / <a href="http-header.html"><code>header</code></a>, and pair it with <code>Json.*</code> for (de)serialization. TLS is the system's, on by default.
|
||||
|
||||
HTTP depends on the network and the wall clock, so — like <code>Net.*</code> and <code>Time.now</code> — it is <strong>out-of-band</strong> and must never feed the deterministic lockstep/replay simulation. The transport is macOS-only for now; the raw-response parser (<a href="http-parse.html"><code>Http.parse</code></a>) is pure and portable.
|
||||
24
docs/language/http/http-body.md
Normal file
24
docs/language/http/http-body.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
id: http-body
|
||||
name: Http.body
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.body
|
||||
sig: Http.body(handle, body) -> void
|
||||
tip: Set a string request body.
|
||||
order: 5
|
||||
ns: Http
|
||||
member: body
|
||||
---
|
||||
|
||||
Sets a NUL-terminated string request body on an opened request.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.open("POST", "https://example.com/api")
|
||||
Http.body(h, "{\"hi\":1}")
|
||||
Http.send(h)
|
||||
}
|
||||
}
|
||||
```
|
||||
25
docs/language/http/http-body_bytes.md
Normal file
25
docs/language/http/http-body_bytes.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
id: http-body_bytes
|
||||
name: Http.body_bytes
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.body_bytes
|
||||
sig: Http.body_bytes(handle, bytes, len) -> void
|
||||
tip: Set a raw byte request body.
|
||||
order: 6
|
||||
ns: Http
|
||||
member: body_bytes
|
||||
---
|
||||
|
||||
Sets a raw request body of <code>len</code> bytes (for binary uploads).
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let buf = bytes(4)
|
||||
let h = Http.open("POST", "https://example.com/upload")
|
||||
Http.body_bytes(h, buf, 4)
|
||||
Http.send(h)
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/language/http/http-body_len.md
Normal file
23
docs/language/http/http-body_len.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: http-body_len
|
||||
name: Http.body_len
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.body_len
|
||||
sig: Http.body_len(handle) -> int
|
||||
tip: The response body length in bytes.
|
||||
order: 12
|
||||
ns: Http
|
||||
member: body_len
|
||||
---
|
||||
|
||||
Returns the length of the response body in bytes.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.get("https://example.com")
|
||||
if Http.poll(h) > 0 { print(Http.body_len(h)) }
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/language/http/http-free.md
Normal file
23
docs/language/http/http-free.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: http-free
|
||||
name: Http.free
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.free
|
||||
sig: Http.free(handle) -> void
|
||||
tip: Release a request's resources.
|
||||
order: 14
|
||||
ns: Http
|
||||
member: free
|
||||
---
|
||||
|
||||
Releases a request handle and the resources it owns (the body copy and any retained response). Free each request once you are done reading it.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.get("https://example.com")
|
||||
if Http.poll(h) > 0 { Http.free(h) }
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/http/http-get.md
Normal file
22
docs/language/http/http-get.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: http-get
|
||||
name: Http.get
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.get
|
||||
sig: Http.get(url) -> int
|
||||
tip: Start an async GET request.
|
||||
order: 0
|
||||
ns: Http
|
||||
member: get
|
||||
---
|
||||
|
||||
Starts an asynchronous GET and returns a request handle. Poll it each frame with <a href="http-poll.html"><code>Http.poll</code></a>; the frame never blocks.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.get("https://example.com/scores")
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/language/http/http-header.md
Normal file
23
docs/language/http/http-header.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: http-header
|
||||
name: Http.header
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.header
|
||||
sig: Http.header(handle, name) -> str
|
||||
tip: A response header value (case-insensitive).
|
||||
order: 13
|
||||
ns: Http
|
||||
member: header
|
||||
---
|
||||
|
||||
Returns a response header value by name (case-insensitive), or an empty result if absent.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.get("https://example.com")
|
||||
if Http.poll(h) > 0 { print(Http.header(h, "Content-Type")) }
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/language/http/http-ok.md
Normal file
23
docs/language/http/http-ok.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: http-ok
|
||||
name: Http.ok
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.ok
|
||||
sig: Http.ok(handle) -> bool
|
||||
tip: Was the response status 2xx?
|
||||
order: 10
|
||||
ns: Http
|
||||
member: ok
|
||||
---
|
||||
|
||||
Returns whether the response status is in the 2xx success range.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.get("https://example.com")
|
||||
if Http.poll(h) > 0 { if Http.ok(h) { print(1) } }
|
||||
}
|
||||
}
|
||||
```
|
||||
25
docs/language/http/http-open.md
Normal file
25
docs/language/http/http-open.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
id: http-open
|
||||
name: Http.open
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.open
|
||||
sig: Http.open(method, url) -> int
|
||||
tip: Open a request to configure before sending.
|
||||
order: 3
|
||||
ns: Http
|
||||
member: open
|
||||
---
|
||||
|
||||
Opens a request without sending it, so headers and a body can be added first with <a href="http-set.html"><code>Http.set</code></a> / <a href="http-body.html"><code>Http.body</code></a>, then dispatched with <a href="http-send.html"><code>Http.send</code></a>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.open("POST", "https://example.com/api")
|
||||
Http.set(h, "Authorization", "Bearer token")
|
||||
Http.body(h, "{}")
|
||||
Http.send(h)
|
||||
}
|
||||
}
|
||||
```
|
||||
24
docs/language/http/http-parse.md
Normal file
24
docs/language/http/http-parse.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
id: http-parse
|
||||
name: Http.parse
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.parse
|
||||
sig: Http.parse(bytes, len) -> int
|
||||
tip: Parse a raw HTTP response into a handle.
|
||||
order: 15
|
||||
ns: Http
|
||||
member: parse
|
||||
---
|
||||
|
||||
Parses a raw HTTP/1.1 response (status line + headers + body) into a ready handle — useful for caches, custom transports, and tests. Transport-independent and pure.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let raw = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhi"
|
||||
let h = Http.parse(raw, Text.length(raw))
|
||||
print(Http.status(h))
|
||||
}
|
||||
}
|
||||
```
|
||||
24
docs/language/http/http-poll.md
Normal file
24
docs/language/http/http-poll.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
id: http-poll
|
||||
name: Http.poll
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.poll
|
||||
sig: Http.poll(handle) -> int
|
||||
tip: Poll a request; -1 pending, 0 error, else status.
|
||||
order: 8
|
||||
ns: Http
|
||||
member: poll
|
||||
---
|
||||
|
||||
Polls a request without blocking: returns <code>-1</code> while pending, <code>0</code> on a transport error, else the HTTP status code once the reply lands.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.get("https://example.com")
|
||||
let st = Http.poll(h)
|
||||
if st > 0 { print(Http.status(h)) }
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/http/http-post.md
Normal file
22
docs/language/http/http-post.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: http-post
|
||||
name: Http.post
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.post
|
||||
sig: Http.post(url, body) -> int
|
||||
tip: Start an async POST with a body.
|
||||
order: 1
|
||||
ns: Http
|
||||
member: post
|
||||
---
|
||||
|
||||
Starts an asynchronous POST with a string body, returning a request handle.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.post("https://example.com/score", "{\"score\":42}")
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/http/http-request.md
Normal file
22
docs/language/http/http-request.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: http-request
|
||||
name: Http.request
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.request
|
||||
sig: Http.request(method, url, body) -> int
|
||||
tip: Start a request with any method.
|
||||
order: 2
|
||||
ns: Http
|
||||
member: request
|
||||
---
|
||||
|
||||
Starts a request with an explicit method ("GET", "PUT", "DELETE", …) and optional body.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.request("PUT", "https://example.com/item/1", "payload")
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/language/http/http-send.md
Normal file
23
docs/language/http/http-send.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: http-send
|
||||
name: Http.send
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.send
|
||||
sig: Http.send(handle) -> void
|
||||
tip: Dispatch an opened request.
|
||||
order: 7
|
||||
ns: Http
|
||||
member: send
|
||||
---
|
||||
|
||||
Dispatches an opened request onto the background worker. After this the request is in flight; poll it.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.open("GET", "https://example.com")
|
||||
Http.send(h)
|
||||
}
|
||||
}
|
||||
```
|
||||
24
docs/language/http/http-set.md
Normal file
24
docs/language/http/http-set.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
id: http-set
|
||||
name: Http.set
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.set
|
||||
sig: Http.set(handle, name, value) -> void
|
||||
tip: Set a request header before sending.
|
||||
order: 4
|
||||
ns: Http
|
||||
member: set
|
||||
---
|
||||
|
||||
Sets a request header on an opened request (before <a href="http-send.html"><code>Http.send</code></a>).
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.open("GET", "https://example.com")
|
||||
Http.set(h, "Accept", "application/json")
|
||||
Http.send(h)
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/language/http/http-status.md
Normal file
23
docs/language/http/http-status.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: http-status
|
||||
name: Http.status
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.status
|
||||
sig: Http.status(handle) -> int
|
||||
tip: The response HTTP status code.
|
||||
order: 9
|
||||
ns: Http
|
||||
member: status
|
||||
---
|
||||
|
||||
Returns the cached HTTP status code (<code>0</code> until the reply lands or on error).
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.get("https://example.com")
|
||||
if Http.poll(h) > 0 { print(Http.status(h)) }
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/language/http/http-text.md
Normal file
23
docs/language/http/http-text.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: http-text
|
||||
name: Http.text
|
||||
category: http
|
||||
kind: namespace-method
|
||||
tokens: Http.text
|
||||
sig: Http.text(handle) -> str
|
||||
tip: The response body as text.
|
||||
order: 11
|
||||
ns: Http
|
||||
member: text
|
||||
---
|
||||
|
||||
Returns the response body as a NUL-terminated string. Pair with <code>Json.parse</code> (#44) for JSON APIs.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
entry {
|
||||
let h = Http.get("https://example.com")
|
||||
if Http.poll(h) > 0 { print(Http.text(h)) }
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue