# HTTP Client `http/*` (the client half of the namespace) provides non-blocking, epoll-driven HTTP requests via mbedTLS for TLS. Each request returns a Future; the caller `await`s it for the response map. ## `http/*` Client Stdlib functions | Function | Returns | Purpose | |---|---|---| | `[http/get $url ?opts]` | Future | GET request | | `[http/post $url $body ?opts]` | Future | POST request | | `[http/put $url $body ?opts]` | Future | PUT request | | `[http/patch $url $body ?opts]` | Future | PATCH request | | `[http/delete $url ?opts]` | Future | DELETE request | | `[http/head $url ?opts]` | Future | HEAD request | | `[http/request :METHOD $url $opts]` | Future | Full control | | `[http/client]` | client resource | Create reusable client (pooling) | | `[http/close $client]` | nil | Close client handle | | `[http/do $client :METHOD $url ?opts]` | Future | Request via client | | `[http/set-timeout $client ms]` | nil | Set request timeout | ## Basic Usage
set resp [await [http/get "https://api.github.com/repos/ziglang/zig"]]
echo $resp~status                              # => 200
echo $resp~body                                # => JSON string

set body [json/parse $resp~body]
echo $body~stargazers_count
`$resp~status` is the HTTP status code; `$resp~body` is the response body as a string; `$resp~headers` is a lowercased map of headers. ## Methods
set r1 [await [http/get    "https://example.com"]]
set r2 [await [http/post   "https://api.example.com" $payload]]
set r3 [await [http/put    "https://api.example.com/users/1" $payload]]
set r4 [await [http/patch  "https://api.example.com/users/1" $payload]]
set r5 [await [http/delete "https://api.example.com/users/1"]]
set r6 [await [http/head   "https://example.com/large-file"]]
## Custom Headers Pass an options map with a `:headers` entry:
set r [await [http/get "https://api.example.com/me" %{
    :headers %{
        :Authorization "Bearer $token"
        :Accept "application/json"
    |
    }
}]]
Header names can be strings or keywords; the runtime normalises them. ## Request Body `http/post`, `http/put`, `http/patch` take a body argument. Pass a string for raw bodies; serialise JSON yourself:
def payload [json/stringify %{
    :title "Hello"
    :body  "World"
}]

set r [await [http/post "https://api.example.com/posts" $payload %{
    :headers %{ :Content-Type "application/json" }
}]]
## Full-Control Request `[http/request :METHOD $url $opts]` exposes every option:
set r [await [http/request :GET $url %{
    :headers %{ :Authorization "Bearer $token" }
    :body $payload
    :timeout 5000
}]]
## Persistent Client Each one-shot `http/get` etc. uses a shared default client. For connection pooling across many requests to the same host, create an explicit client:
def c [http/client]
http/set-timeout $c 5000

set r1 [await [http/do $c :GET "https://api.example.com/users"]]
set r2 [await [http/do $c :GET "https://api.example.com/users/1"]]

http/close $c
A pooled client reuses TCP connections (and TLS sessions) across requests when the server supports keep-alive. ## Concurrent Requests Use `deftask` to fan out requests concurrently (its body runs on a fresh OS thread by default). Each task body must be self-contained (see Concurrency for the constraints — captured locals don't cross into the task's runtime):
from "deft/stdext/async" import await-all

def f1 [deftask { [await [http/get "https://api.example.com/a"]] }]
def f2 [deftask { [await [http/get "https://api.example.com/b"]] }]
def f3 [deftask { [await [http/get "https://api.example.com/c"]] }]
def results [await-all @{$f1 $f2 $f3}]
See Concurrency for the full async story. ## Error Handling Errors return `%Err{ :message ... }`. Catch with `try` or check with `err?`:
set r [http/get $url]

try {
    set resp [await $r]
    echo $resp~status
} catch e {
    echo "request failed: $e~message"
}

# Or use err? on the awaited result
set resp [await $r]
if [err? $resp] {
    echo "failed: $resp~message"
} {
    echo $resp~body
}
Network errors (DNS failure, refused connection, TLS handshake failure) all surface as `%Err`. Non-2xx HTTP status codes are NOT errors — they're returned as a response with `$resp~status` set. ## Timeouts `[http/set-timeout $client ms]` sets a timeout for requests on a client. Pass `nil` to set on the shared default client:
http/set-timeout nil 5000                      # default for one-shot requests
Timeouts abort the request and reject the Future with an `%Err`. ## JSON-GET Idiom A common pattern:
def fetch-json {|url|
    @doc "Fetch a URL and parse the body as JSON."
    set resp [await [http/get $url]]
    if [err? $resp] { return $resp }
    [json/parse $resp~body]
}

set data [fetch-json "https://api.github.com/repos/ziglang/zig"]
echo $data~stargazers_count
## Quick Reference | Form | Returns | Purpose | |---|---|---| | `[http/get $url ?opts]` | Future | GET | | `[http/post $url $body ?opts]` | Future | POST | | `[http/put $url $body ?opts]` | Future | PUT | | `[http/patch $url $body ?opts]` | Future | PATCH | | `[http/delete $url ?opts]` | Future | DELETE | | `[http/head $url ?opts]` | Future | HEAD | | `[http/request :METHOD $url $opts]` | Future | Full control | | `[http/client]` | client | Pooled client | | `[http/do $client :METHOD $url ?opts]` | Future | Via pooled client | | `[http/set-timeout $client ms]` | nil | Timeout | | `[await $future]` | response map | Block for response |