# HTTP 客户端
`http/*`(该命名空间的客户端部分)通过 mbedTLS 提供非阻塞、
由 epoll 驱动的 HTTP 请求以支持 TLS。每个请求返回一个 Future;
调用方通过 `await` 获取响应映射。
## `http/*` 客户端标准库函数
| 函数 | 返回值 | 用途 |
|---|---|---|
| `[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 | 完全控制 |
| `[http/client]` | 客户端资源 | 创建可复用客户端(连接池) |
| `[http/close $client]` | nil | 关闭客户端句柄 |
| `[http/do $client :METHOD $url ?opts]` | Future | 通过客户端发起请求 |
| `[http/set-timeout $client ms]` | nil | 设置请求超时 |
## 基本用法
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` 是 HTTP 状态码;`$resp~body` 是作为字符串的响应体;
`$resp~headers` 是响应头的小写映射。
## 方法
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"]]
## 自定义请求头
传入带有 `:headers` 条目的选项映射:
set r [await [http/get "https://api.example.com/me" %{ :headers %{ :Authorization "Bearer $token" :Accept "application/json" | } }]]
请求头名称可以是字符串或关键字;运行时会将其规范化。
## 请求体
`http/post`、`http/put`、`http/patch` 接受一个请求体参数。原始请求体
传入字符串;JSON 请自行序列化:
def payload [json/stringify %{ :title "Hello" :body "World" }] set r [await [http/post "https://api.example.com/posts" $payload %{ :headers %{ :Content-Type "application/json" } }]]
## 完全控制请求
`[http/request :METHOD $url $opts]` 暴露所有选项:
set r [await [http/request :GET $url %{ :headers %{ :Authorization "Bearer $token" } :body $payload :timeout 5000 }]]
## 持久化客户端
每次一次性调用 `http/get` 等都会使用共享的默认客户端。若要在对同一
主机的众多请求之间复用连接池,请创建显式客户端:
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
当服务器支持 keep-alive 时,池化客户端会在多个请求之间复用 TCP
连接(以及 TLS 会话)。
## 并发请求
使用 `deftask` 并发地扇出请求(其函数体默认在新的操作系统线程上
运行)。每个任务体必须自包含(约束参见
并发 —— 捕获的局部变量不会进入任务的
运行时):
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}]
完整的异步机制参见 并发。
## 错误处理
错误返回 `%Err{ :message ... }`。使用 `try` 捕获,或用 `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 }
网络错误(DNS 失败、连接被拒绝、TLS 握手失败)都会以 `%Err` 形式
出现。非 2xx 的 HTTP 状态码不是错误 —— 它们会作为设置了
`$resp~status` 的响应返回。
## 超时
`[http/set-timeout $client ms]` 为客户端上的请求设置超时。传入 `nil`
则在共享的默认客户端上设置:
http/set-timeout nil 5000 # default for one-shot requests
超时会中止请求,并以 `%Err` 拒绝该 Future。
## JSON-GET 惯用法
一种常见模式:
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
## 快速参考
| 形式 | 返回值 | 用途 |
|---|---|---|
| `[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 | 完全控制 |
| `[http/client]` | client | 池化客户端 |
| `[http/do $client :METHOD $url ?opts]` | Future | 通过池化客户端 |
| `[http/set-timeout $client ms]` | nil | 超时 |
| `[await $future]` | response map | 阻塞等待响应 |