# 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 |