返回文章列表

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 只是兼容层。

其他语言的简要建议

语言首选备选备注
Rubynet/http + FaradayHTTPartyRails 项目跟 Faraday 走
PHPGuzzleSymfony HttpClientLaravel 内置 Guzzle
C#HttpClientRestSharpHttpClient 一定要单例!每次 new 会泄露 socket
KotlinKtor ClientOkHttp多平台项目用 Ktor
SwiftURLSessionAlamofireApple 平台 URLSession 就够
Darthttp 包 + DioFlutter 通常用 Dio(拦截器丰富)
ElixirReqFinch / HTTPoisonReq 是 2023 后的新共识
Rhttr2httrhttr2 是 httr 的重写,新项目用 httr2

哪些 cURL 选项不能"无损翻译"

cURL 有 200+ 选项,但绝大多数 HTTP 库只覆盖核心 20–30 个。当你转换的命令包含下面这些时,要小心检查输出代码:

  • --resolve(DNS 覆盖):多数库不支持,要改用 hosts 文件或 custom resolver
  • --unix-socket:仅 reqwest / httpx / OkHttp 等支持
  • --cert-type 与 mTLS:每个库的 cert 加载 API 不同,输出代码常需要手动调整
  • -F multipart 复杂 case:part-level header / filename 重命名要重新写
  • --data-binary vs -d:后者会转换换行符,前者不会——某些 API 对此敏感
  • --http2 --http2-prior-knowledge:cleartext HTTP/2 在多数库里需要专门 enable

curl-converter 工具 在前端用 curlconverter 库做解析,对常用选项覆盖完整;但遇到上面这些场景,输出代码会注释提示"manual adjustment needed",记得读完再 copy。

总结

经验法则:

  1. 标准库够用就别引第三方(Go / Java 11+ / Node 18+ / Swift)
  2. 同步 / 异步 / HTTP/2 中任意一个是硬需求就上现代库(httpx / fetch / reqwest / Ktor)
  3. 项目里 HTTP 调用密集(retry / 拦截器 / 链式 API 收益大)才上重型库(axios / OkHttp / resty)
  4. 永远显式设 timeout——所有语言的默认值要么是无穷大(fetch),要么是几分钟(Go 的 http.DefaultClient),都不适合生产

库的选择不影响功能正确性,但影响 团队心智成本——半年后接手的人能不能一眼看懂这段 HTTP 调用,往往比性能差 10% 更重要。

延伸阅读