rontolisp:fetch
(rontolisp:fetch url &optional options)
Starts an outgoing HTTP request, modeled on the JavaScript fetch API, and
immediately returns a future while the request runs asynchronously. A
future is an opaque value (it prints as #<FUTURE> and satisfies
rontolisp:futurep); pass it to
rontolisp:await to suspend until the
response arrives and obtain the result property list
(:status <integer> :headers <alist> :body <stream>); the :body stream is
drained with rontolisp:read-all.
Because the request is already in flight when fetch returns, several requests
can overlap:
(let ((p1 (rontolisp:fetch "https://httpbin.ik.am/status/200"))
(p2 (rontolisp:fetch "https://httpbin.ik.am/status/201"))) ; both requests running
(list (rontolisp:await p1) (rontolisp:await p2)))
Options
The optional second argument is an options property list. Recognized keys:
:method— the HTTP method as a string (default"GET"). Supported methods areGET,HEAD,POST,PUT,DELETE,OPTIONSandPATCH, matched case-insensitively; any other method is an error.:headers— request headers, an alist of(name . value)string pairs.:body— the request body as a string (omit for no body).
Every request carries User-Agent: rontolisp/<version> (<git-commit>) — the version
and abbreviated commit rontolisp:version reports, the
commit omitted when the build had no git repository — unless :headers already names
that field (matched case-insensitively, so your own spelling and value win). The
browser playground and a --host-fetch reactor leave the field to the host, which
owns it there.
The options are validated when fetch is called (like the JavaScript fetch,
which throws synchronously on invalid arguments).
;; GET with request headers (an alist of (name . value) string pairs)
(rontolisp:fetch "https://httpbin.ik.am/get"
'(:headers (("Accept" . "application/json"))))
;; POST with a request body
(rontolisp:fetch "https://httpbin.ik.am/post"
'(:method "POST"
:headers (("Content-Type" . "application/json"))
:body "{\"name\":\"rontolisp\"}"))
Result
fetch itself returns the future. Awaiting it yields the property list
(:status <integer> :headers <alist> :body <stream>), where :headers is an
alist of (name . value) response-header pairs and :body is an
asynchronous stream of the body's octet chunks ((unsigned-byte 8)
vectors, the bytes as they arrive) — drain it to one decoded string with
rontolisp:read-all, take the chunks one at a
time with rontolisp:stream-read, or answer the
stream itself as a served response body to relay the reply byte-exact:
(let ((res (rontolisp:await (rontolisp:fetch "https://httpbin.ik.am/get"))))
(print (getf res :status)) ; => 200
(print (rontolisp:await (rontolisp:read-all (getf res :body))))
; => "{...}"
(print (getf res :headers))) ; => (("content-type" . "application/json") ...)
:body is that stream on every backend; on the JVM (whose client takes the
whole reply at once) it holds one chunk.
A JSON response body parses into Lisp values with
rontolisp:json-parse, and
rontolisp:json-stringify builds a JSON
request :body from an s-expression.
Backend support
- Interpreter and JVM: use the JDK
java.net.http.HttpClient; the request runs on a background thread from the momentfetchreturns. - WASM: component-only, over the async
wasi:http@0.3.0— fetch is ordinary Lisp glue calling the wit-importedwasi:http/client@0.3.0, so the component is uniformly WASI 0.3. The future wraps the in-flight asyncclient.sendsubtask, so multiple requests genuinely overlap. Compile with--componentand run withwasmtime run -S http=y(wasmtime 46+;-S http=ymakes the host providewasi:http). fetch remains a compile error in Preview 1 (core-module) mode, which has no hostwasi:http; the generic future operations (await,then,futurep) compile in every mode. fetch also works inside arontolisp:http-handlerserve component (a proxy-style handler): run it withwasmtime serve— the serve host provideswasi:http/clientby default, no-S http=yneeded. --no-wasireactor with--host-fetch: the same source compiles on a reactor (which imports no WASI), lowered onto two injected host imports —env.fetch(request-json) -> response-head-jsonandenv.readResponseBody(ptr, cap) -> i32— over the host's own HTTP client (a Cloudflare Worker'sfetchbehind JSPI, or any synchronous implementation). The result plist is the same,:bodythe same asynchronous stream: the head arrives with the call and the body is pulled a chunk at a time as the drain asks for it, so a large or binary reply never becomes a JSON string. The future is settled at creation (the host call blocked the stack until the headers): requests never overlap, and a transport failure before the head signals at thefetchcall rather than atawait— one during the body signals at the drain, as on every other backend. Without the flag,--no-wasikeeps the compile error.- Browser playground: truly asynchronous. The interpreter runs in a Web
Worker;
fetchhands the request to the page's main thread, which runs the real browserfetch()(subject to CORS) while the program continues, so requests overlap, andawaitblocks the worker until the response arrives. When cross-origin isolation is unavailable (SharedArrayBufferdisabled) the playground falls back to a synchronous request per fetch — programs behave the same, requests simply do not overlap.
Limitations
- The method must be one of
GET,HEAD,POST,PUT,DELETE,OPTIONS,PATCH. An unsupported:methodis an error: the interpreter and JVM reject it atfetchtime; the WASM backend resolves the method statically and rejects a statically-known unsupported:methodat compile time (a method computed at runtime cannot be checked there and is treated as GET, while a runtime-computed:bodyis sent normally). - A failed request (for example a refused connection) surfaces when the future
is awaited — the same timing as a JavaScript
awaitrejection: every backend signals an error there (on WASM it is arontolisp:wit-errorcondition, catchable withhandler-case). A request that cannot even be started (for example a malformed URL, or an unsupported runtime-computed method on the interpreter/JVM) makesfetchitself error or, on WASM, returnnilinstead of a future — and awaitingnilyieldsnil.