cURL 转代码背后:16 种语言的 HTTP 库怎么选
cURL 转代码背后:16 种语言的 HTTP 库怎么选
调试一个第三方 API 时,几乎每个工程师都做过同一件事:先在终端用 curl 测通,再把它翻译成项目用的语言。看起来是直译,但实际上每种语言都有 2–5 个主流 HTTP 库可选,"翻译"的过程其实是 选库。
我们的 cURL 转代码工具 支持 16 种语言的导出。本文用它能输出的目标语言为索引,讲清楚每个生态里 HTTP 库的设计差异——下次面对"这个项目该用哪个 HTTP 客户端"时,能基于场景而不是流行度来选。
一行 cURL,三种翻译
考虑这条命令:
curl -X POST https://api.example.com/users \
-H "Authorization: Bearer xxx" \
-d '{"name":"Alice"}'
它在不同语言里至少有三种风格的等价代码:
Python(requests):
import requests
requests.post(
"https://api.example.com/users",
headers={"Authorization": "Bearer xxx"},
json={"name": "Alice"},
)
Go(net/http):
body := strings.NewReader(`{"name":"Alice"}`)
req, _ := http.NewRequest("POST", "https://api.example.com/users", body)
req.Header.Set("Authorization", "Bearer xxx")
resp, err := http.DefaultClient.Do(req)
Rust(reqwest):
let client = reqwest::Client::new();
let resp = client.post("https://api.example.com/users")
.bearer_auth("xxx")
.json(&serde_json::json!({"name": "Alice"}))
.send().await?;
行数差近 3 倍。这不是"语言冗长"——是各生态的设计哲学差异:Python 倾向"一个函数搞定 80% 场景",Go 倾向"标准库不藏黑魔法",Rust 倾向"类型安全 + 异步 first"。
Python:requests vs httpx vs aiohttp
| 库 | 同步 | 异步 | HTTP/2 | 维护状态 |
|---|---|---|---|---|
| requests | ✅ | ❌ | ❌ | 维护模式(无新功能) |
| httpx | ✅ | ✅ | ✅ | 活跃 |
| aiohttp | ❌ | ✅ | ❌ | 活跃 |
新项目建议 httpx——API 和 requests 几乎一致,但原生支持 async/await 和 HTTP/2。requests 仍然能用,但它已经停止接受新功能(PSF 官方声明),HTTP/2 永远不会支持。
aiohttp 是更早期的 async 方案,API 风格类似 Node.js 的 http 模块——比 httpx 略底层,性能在 benchmark 里通常领先 5–10%,但只在已经用 aiohttp 写 server 的项目里值得引入。
JavaScript / TypeScript:fetch vs axios vs ky
// fetch(原生,Node 18+ 与浏览器统一 API)
const resp = await fetch(url, {
method: "POST",
headers: { Authorization: "Bearer xxx" },
body: JSON.stringify({ name: "Alice" }),
});
const data = await resp.json();
// axios
const { data } = await axios.post(url, { name: "Alice" }, {
headers: { Authorization: "Bearer xxx" },
});
差异:
- fetch 的非 2xx 不抛错,要自己
if (!resp.ok) throw...。axios 默认抛。这是 fetch 最多踩坑的地方 - fetch 不内置 timeout,要拿 AbortController 自己接。axios 一行
timeout: 5000 - axios 自动 JSON 解析 + 自动 stringify,fetch 全要手动
- axios 拦截器(interceptors)适合统一注入鉴权 / 日志 / 重试。fetch 没有,要自己封装
ky 是 fetch 上的一层薄封装,把上面四个痛点都补齐了,gzipped 后只有 ~5KB。新项目如果接受 modern bundler,ky > axios(55KB 体积差是真的)。
Node.js 服务端:fetch(Node 18+)已经够用,性能与 undici(底层实现)持平。axios 在 server 端的"传统优势"——retry / 拦截器——可以用更轻量的 got 替代。
Go:标准库 vs resty
Go 的 net/http 标准库是少见的"足够用就别引第三方"案例。但有两个细节卡了不少新人:
// ❌ 常见错误:忘记关 Body 导致连接泄露
resp, _ := http.Get("https://api.example.com")
data, _ := io.ReadAll(resp.Body)
// ✅ 正确
resp, err := http.Get("https://api.example.com")
if err != nil {
return err
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
Go 的 defer resp.Body.Close() 是 HTTP 客户端的必修课——不关会导致底层 TCP 连接无法回收,长时间运行的服务最终会撞上 "too many open files"。
如果项目里 HTTP 调用密集(多个 retry / 拦截器 / 链式 builder),go-resty/resty 能减少一半样板代码:
client := resty.New().
SetRetryCount(3).
SetTimeout(10 * time.Second)
resp, err := client.R().
SetAuthToken("xxx").
SetBody(map[string]string{"name": "Alice"}).
Post("https://api.example.com/users")
但简单的内部服务调用直接用 http.Client{Timeout: ...} 就够了。Go 生态的共识是:第三方 HTTP 库不是默认选择。
Java:HttpClient vs OkHttp
Java 11 引入了内置的 java.net.http.HttpClient,这是个分水岭——之前 Apache HttpClient / OkHttp / Retrofit 三国鼎立的局面有了官方答案。
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Authorization", "Bearer xxx")
.POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"Alice\"}"))
.build();
HttpResponse<String> resp = client.send(request, HttpResponse.BodyHandlers.ofString());
JDK HttpClient 支持 HTTP/2、WebSocket、async(sendAsync 返回 CompletableFuture),覆盖了 90% 场景。
OkHttp 仍然有自己的位置:
- Android 项目(Google 官方推荐,与 OkHttp 共享连接池可以省内存)
- 需要细粒度 interceptor(动态修改请求 / 响应、做 mock)
- gRPC(gRPC-Java 底层就是 OkHttp)
Apache HttpClient 5 在企业项目里仍然多,但新项目不建议——配置复杂度和 JDK HttpClient 接近,但功能没有领先。
Rust:reqwest vs hyper
reqwest 是 Rust 里事实上的标准——基于 hyper(底层)+ tokio(运行时)。除非你在写 server 或自定义 transport(QUIC、Unix socket),否则直接用 reqwest。
reqwest 默认 async;如果项目里没有 tokio,可以开 blocking feature 拿同步 API:
let resp = reqwest::blocking::Client::new()
.post("https://api.example.com/users")
.json(&json!({"name": "Alice"}))
.send()?;
但 Rust 社区强烈不建议 sync-only 项目——异步是 Rust 网络编程的默认范式,sync API 只是兼容层。
其他语言的简要建议
| 语言 | 首选 | 备选 | 备注 |
|---|---|---|---|
| Ruby | net/http + Faraday | HTTParty | Rails 项目跟 Faraday 走 |
| PHP | Guzzle | Symfony HttpClient | Laravel 内置 Guzzle |
| C# | HttpClient | RestSharp | HttpClient 一定要单例!每次 new 会泄露 socket |
| Kotlin | Ktor Client | OkHttp | 多平台项目用 Ktor |
| Swift | URLSession | Alamofire | Apple 平台 URLSession 就够 |
| Dart | http 包 + Dio | — | Flutter 通常用 Dio(拦截器丰富) |
| Elixir | Req | Finch / HTTPoison | Req 是 2023 后的新共识 |
| R | httr2 | httr | httr2 是 httr 的重写,新项目用 httr2 |
哪些 cURL 选项不能"无损翻译"
cURL 有 200+ 选项,但绝大多数 HTTP 库只覆盖核心 20–30 个。当你转换的命令包含下面这些时,要小心检查输出代码:
--resolve(DNS 覆盖):多数库不支持,要改用 hosts 文件或 custom resolver--unix-socket:仅 reqwest / httpx / OkHttp 等支持--cert-type与 mTLS:每个库的 cert 加载 API 不同,输出代码常需要手动调整-Fmultipart 复杂 case:part-level header / filename 重命名要重新写--data-binaryvs-d:后者会转换换行符,前者不会——某些 API 对此敏感--http2 --http2-prior-knowledge:cleartext HTTP/2 在多数库里需要专门 enable
curl-converter 工具 在前端用 curlconverter 库做解析,对常用选项覆盖完整;但遇到上面这些场景,输出代码会注释提示"manual adjustment needed",记得读完再 copy。
总结
经验法则:
- 标准库够用就别引第三方(Go / Java 11+ / Node 18+ / Swift)
- 同步 / 异步 / HTTP/2 中任意一个是硬需求就上现代库(httpx / fetch / reqwest / Ktor)
- 项目里 HTTP 调用密集(retry / 拦截器 / 链式 API 收益大)才上重型库(axios / OkHttp / resty)
- 永远显式设 timeout——所有语言的默认值要么是无穷大(fetch),要么是几分钟(Go 的 http.DefaultClient),都不适合生产
库的选择不影响功能正确性,但影响 团队心智成本——半年后接手的人能不能一眼看懂这段 HTTP 调用,往往比性能差 10% 更重要。
延伸阅读
- cURL 转代码工具:16 种语言一键导出,本文所有示例都能从工具直接生成
- curlconverter 项目主页:上游开源项目,可以提 issue 添加新语言
- MDN — Using Fetch API:fetch 标准与浏览器 / Node 差异