HTTP Class
HTTP client class for GET, POST, PUT, PATCH, DELETE requests with JSON support and parallel requests.
HTTP Class
All HTTP functions are accessed via the HTTP class:
response = HTTP.get("https://api.example.com/users")
response = HTTP.post("https://api.example.com/users", { "name": "Alice" })
response = HTTP.request("DELETE", "https://api.example.com/users/1")
SSRF blocklist & redirects
Every URL passed to HTTP.* is validated up-front: schemes other than http/https are rejected, as are hosts that resolve to loopback / private / link-local IP ranges. A request to http://169.254.169.254/... (cloud metadata) or http://10.0.0.1/ fails immediately.
Auto-redirects are not followed by the synchronous HTTP.get / HTTP.post / HTTP.request paths — a 3xx response is returned as-is so a redirect-controlled Location cannot bypass the blocklist. Asynchronous and Model-driven HTTP (the reqwest-backed paths) follow redirects with a custom policy that re-runs the SSRF check on every hop.
Apps that need to follow a 3xx from HTTP.get should inspect response["status"] and response["headers"]["location"] and re-issue the request manually.
Basic HTTP Requests
HTTP.get(url, options?)
Perform an HTTP GET request. Returns a Future that resolves to the response body as a string.
resp = HTTP.get("https://api.example.com/users")
if resp["status"] == 200
println(resp["body"])
end
# Per-call timeout in seconds (overrides the 30s default for this request).
resp = HTTP.get("https://api.example.com/slow", { "timeout": 5 })
HTTP.* method accepts a
timeout key (in seconds, as an
Int or Float) in its trailing
options hash. It overrides the default 30s client timeout for that one request and must be positive;
a fractional value such as 0.5 is allowed. For
HTTP.request, the timeout key
lives in the headers hash and is consumed rather than sent as a header.
HTTP.get_jsonp(url, options?)
Fetch a JSONP endpoint and unwrap the callback(...) padding, returning the parsed value. Use it to consume legacy cross-origin APIs that only expose JSONP; put the ?callback=... name in the URL you pass.
feed = HTTP.get_jsonp("https://api.example.com/feed?callback=cb")
println(feed["items"][0])
HTTP.post(url, body, options?)
Perform an HTTP POST request with a body.
resp = HTTP.post(
"https://api.example.com/users",
"name=Alice",
{ "headers": { "Content-Type": "application/x-www-form-urlencoded" } }
)
HTTP.put(url, body, options?)
Perform an HTTP PUT request with a body.
resp = HTTP.put(
"https://api.example.com/users/1",
{ "name": "Alice Updated" }
)
HTTP.delete(url, options?)
Perform an HTTP DELETE request.
resp = HTTP.delete("https://api.example.com/users/1")
HTTP.patch(url, body, options?)
Perform an HTTP PATCH request with a body.
resp = HTTP.patch(
"https://api.example.com/users/1",
{ "email": "new_email@example.com" }
)
HTTP.head(url, options?)
Perform an HTTP HEAD request.
resp = HTTP.head("https://api.example.com/users")
println(resp["headers"])
JSON HTTP Methods
HTTP.get_json(url, options?)
GET request with automatic JSON parsing of response body.
data = HTTP.get_json("https://api.example.com/users/1")
println(data["body"]["name"])
HTTP.post_json(url, data, options?)
POST request with automatic JSON serialization.
resp = HTTP.post_json(
"https://api.example.com/users",
{ "name": "Alice", "email": "alice@example.com" }
)
HTTP.put_json(url, data, options?)
PUT request with automatic JSON serialization.
resp = HTTP.put_json(
"https://api.example.com/users/1",
{ "name": "Alice Updated", "email": "alice_updated@example.com" }
)
HTTP.patch_json(url, data, options?)
PATCH request with automatic JSON serialization.
resp = HTTP.patch_json(
"https://api.example.com/users/1",
{ "email": "new_email@example.com" }
)
Generic HTTP Request
HTTP.request(method, url, headers?, body?)
Perform any HTTP method (GET, POST, PUT, PATCH, DELETE, etc.). The headers hash accepts a timeout key (seconds) that is consumed as the per-call timeout rather than sent as a header.
resp = HTTP.request("DELETE", "https://api.example.com/users/1")
resp = HTTP.request("PATCH", url, headers, json)
# Custom header plus a 3-second per-call timeout.
resp = HTTP.request("GET", url, { "Authorization": "Bearer " + token, "timeout": 3 })
Status Code Helpers
http_ok(resp)
Check if status is exactly 200
http_success(resp)
Check if status is 2xx
http_redirect(resp)
Check if status is 3xx
http_client_error(resp)
Check if status is 4xx
http_server_error(resp)
Check if status is 5xx
JSON Helpers
HTTP.json_parse(string)
Parse a JSON string into a Soli value
HTTP.json_stringify(value)
Convert a Soli value to a JSON string
Parallel Requests
HTTP.get_all(urls, options?)
Execute multiple GET requests in parallel. Returns response bodies as strings; failed requests appear as {"error": ...} hashes. An optional trailing options hash (e.g. { "timeout": 5 }) applies to every request in the batch.
responses = HTTP.get_all([
"https://api.example.com/users",
"https://api.example.com/posts"
])
# Cap every request in the batch at 5 seconds.
responses = HTTP.get_all([
"https://api.example.com/users",
"https://api.example.com/posts"
], { "timeout": 5 })
HTTP.get_all_json(urls, options?)
Execute multiple GET requests in parallel and parse each body as JSON. Failed requests, non-2xx responses, or unparseable bodies appear as {"error": ...} hashes. Accepts the same optional trailing options hash (timeout, in seconds) as HTTP.get_all.
responses = HTTP.get_all_json([
"https://api.example.com/users.json",
"https://api.example.com/posts.json"
])
users = responses[0]
if users.has_key("error") {
print("users failed: " + users["error"])
}
HTTP.parallel(requests)
Execute multiple requests of different methods in parallel. Each request hash accepts method, url, optional headers, optional body, and an optional per-request timeout (seconds).
responses = HTTP.parallel([
{ "method": "GET", "url": "https://api.example.com/users", "timeout": 5 },
{ "method": "POST", "url": "https://api.example.com/logs", "body": "{}" }
])