# 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](../01-Language/concurrency) for the constraints — captured
locals don't cross into the task's runtime):
from "deft/stdext/async" import await-all def f1 [deftask { [http/get "https://api.example.com/a"] }] def f2 [deftask { [http/get "https://api.example.com/b"] }] def f3 [deftask { [http/get "https://api.example.com/c"] }] def results [await-all @{$f1 $f2 $f3}]
See [Concurrency](../01-Language/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 |