服务端5月29日 23:47
什么是 cURL?它在 Web 开发中有什么作用?cURL 是一个命令行数据传输工具,也是一套库,常用来通过 URL 发起 HTTP/HTTPS 请求。Web 开发里它最常见的作用是调试 API:不用写页面、不用打开 Postman,直接在终端复现 GET、POST、请求头、Cookie、鉴权和上传下载问题。
## 追问
### cURL 和 Postman 有什么区别?
Postman 更适合可视化调试和团队管理接口;cURL 更轻、更容易复制到终端、脚本、CI/CD 或线上机器里排查问题。很多接口文档也会直接给 cURL 示例,因为它可执行、可复现。
### cURL 在项目里通常怎么用?
前后端联调时,用它验证接口是否真的可访问;线上排障时,用它检查状态码、响应头、重定向和 TLS;自动化脚本里,用它做健康检查或触发 Webhook。
### 它只支持 HTTP 吗?
不是。cURL 支持 HTTP、HTTPS、FTP、SFTP、SMTP 等多种协议,只是 Web 开发中最常用的是 HTTP/HTTPS。
### 面试里怎么回答更像真用过?
可以说自己用 `curl -i` 看响应头,用 `-H` 带 token,用 `-d` 复现 POST 请求,用 `-v` 排查 DNS、TLS 或连接失败。这样比只背“命令行工具”更具体。
## 写段代码
```bash
# 查看接口是否可访问
curl -i https://api.example.com/users
# 带 Token 调 API
curl -H "Authorization: Bearer token" \
https://api.example.com/profile
# 提交 JSON
curl https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"John"}'
```标签
cURL
cURL(Client URL)是一个广泛使用的命令行工具和库,用于传输数据,支持多种协议,包括 HTTP、HTTPS、FTP、SFTP 等。cURL 非常强大,它允许你在命令行中执行各种数据传输操作,如下载和上传文件,以及与服务器交互。

服务端5月29日 23:47
如何用 cURL 发送 GET 和 POST 请求?GET 用来从服务端取数据,参数通常放在 URL 查询串里;POST 用来提交数据,数据通常放在请求体里。用 cURL 时,GET 最常见的是直接请求 URL,POST 常用 `-d` 或 `--data` 传请求体。注意:`curl -d` 默认会发 POST,不一定非要写 `-X POST`。
## 追问
### GET 和 POST 最大区别是什么?
GET 的参数暴露在 URL 中,适合查询;POST 的数据放在 body 中,适合提交表单、JSON 或文件。GET 通常可缓存、幂等;POST 通常不缓存,也不保证幂等。
### `-d` 和 `-G` 一起用是什么意思?
`-d` 默认把数据放进请求体并触发 POST;加 `-G` 后,cURL 会把这些参数拼到 URL 后面,仍然发 GET。
### 实际调接口时最常用哪些参数?
常用 `-H` 加请求头,`-d` 传 JSON 或表单,`-i` 看响应头和响应体,`-I` 只看响应头,`-v` 排查连接、TLS、重定向等问题。
### 发送 JSON 时容易踩什么坑?
最常见是忘了加 `Content-Type: application/json`,或者 JSON 字符串引号没转义好。另一个坑是误以为 `-X POST` 必须写,其实有 `-d` 时 cURL 已经会用 POST。
## 写段代码
```bash
# GET:查询用户
curl "https://api.example.com/users?page=1&limit=10"
# GET:带请求头
curl -H "Authorization: Bearer token" \
https://api.example.com/users
# POST:提交 JSON
curl https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@example.com"}'
# POST:上传文件
curl https://api.example.com/upload \
-F "file=@/path/to/file.pdf"
```服务端5月29日 22:48
cURL 如何发送和接收 JSON 数据?发送 JSON:curl -X POST URL -H "Content-Type: application/json" -d '{"key":"value"}'。必须加 Content-Type 头,否则服务器按 form-data 解析。单引号包裹 JSON 防 shell 解析双引号。接收 JSON:curl -s URL | jq . 格式化输出,或 jq .field 提取字段。从文件发送:-d @data.json。
## 追问
### 单引号里怎么写单引号?
Shell 中单引号不能嵌套。方案:把 JSON 写进文件用 -d @body.json(最推荐);混引号;或用双引号+转义。
### jq 常用操作有哪些?
格式化:jq .;提取字段:jq .name;数组过滤:jq '.[] | select(.status=="active")';计数:jq length;构造新对象:jq '{name: .user}';jq -e 让 false/null 返回 exit code 1。
### 如何验证 JSON 响应是否符合预期?
curl -s URL | jq -e '.status==200 and .data.id != null'——条件为 true 输出 true 且 exit 0,为 false 输出 false 且 exit 1。更复杂的验证用 JSON Schema:ajv validate -s schema.json。
### PATCH 请求发 JSON 局部更新怎么写?
curl -X PATCH URL -H "Content-Type: application/json" -d '{"name":"new name"}'。只传要改的字段。需确认 API 支持PATCH 语义——有些 API 的 PATCH 实际是 PUT 行为。
### 如何发送 JSON 数组?
curl -X POST URL -H "Content-Type: application/json" -d '{"items":[{"id":1},{"id":2}]}'。批量操作 API 常用这种格式。服务端5月29日 22:48
cURL -X 参数什么作用?PUT/DELETE/PATCH 怎么用?-X 指定 HTTP 方法,默认 GET。-X POST/PUT/DELETE/PATCH 分别发送对应请求。POST 提交数据用 -d;PUT 全量更新用 -X PUT -d data;DELETE 删资源用 -X DELETE;PATCH 局部更新用 -X PATCH -d partial_data。注意:-d 默认就是 POST,-X POST 可省略。但 PUT/DELETE/PATCH 必须显式指定 -X。
## 追问
### -X POST 和 -d 有什么区别?
-d 发送 POST body 数据,自动设 Content-Type: application/x-www-form-urlencoded。只有 -d 没有 -X 时 cURL 自动用 POST。-X POST 只改方法不改 body,需配合 -d。
### 怎么发 JSON 格式的请求?
加 -H "Content-Type: application/json" -d '{"key":"value"}'。不加 Content-Type 服务器可能按 form-data 解析。单引号包裹 JSON 防 shell 解析双引号。
### PUT 和 PATCH 实际区别?
PUT 全量替换——传整个对象,没传的字段被清空。PATCH 局部更新——只传要改的字段。API 设计规范遵循这个语义。
### DELETE 请求能带 body 吗?
技术上可以但 RFC 不推荐。需要带条件的删除用 URL 参数:DELETE /api/users/123?force=true。
### 幂等性是什么?
多次调用结果一样。GET/PUT/DELETE 幂等,POST/PATCH 非幂等。PUT 幂等因为每次全量覆盖到同一状态。服务端5月29日 22:48
cURL 如何处理 HTTPS 和 SSL/TLS 证书验证?默认 cURL 验证 SSL 证书(CA 证书+域名匹配+有效期),验证失败报 SSL certificate problem。跳过验证:-k 或 --insecure(仅测试用,生产禁用)。指定 CA 证书:--cacert /path/to/ca.pem。客户端证书(mTLS):--cert client.pem --key client.key。指定 TLS 版本:--tlsv1.2 或 --tlsv1.3。查看证书信息:curl -vI URL 2>&1 | grep SSL。
## 追问
### 为什么 -k 不安全?
-k 跳过证书验证 = 不验证服务器身份,中间人可以伪造任何 HTTPS 站点。你在 -k 模式下输入的密码/Token 全部暴露。仅用于测试环境和自签名证书。
### 自签名证书怎么正确处理?
把自签名 CA 导入系统信任库,或用 --cacert 指定自签名 CA 文件。这样 cURL 正常验证而不需要 -k。
### mTLS 是什么场景?
双向 TLS:服务器验证客户端证书+客户端验证服务器证书。企业 API 网关、K8s apiserver、零信任网络常用。
### 如何查看证书链和过期时间?
curl -vI URL 2>&1 | grep -E "subject:|expire:|issuer:"。或用 openssl s_client。
### TLS 1.2 和 1.3 有什么区别?
1.3 握手从 2-RTT 降到 1-RTT,移除了不安全的密码套件,强制前向保密。cURL 7.54+ 支持 TLS 1.3。服务端5月29日 22:48
cURL 支持哪些协议?FTP/SFTP 怎么用?cURL 支持 20+ 协议:HTTP/HTTPS/FTP/SFTP/SCP/SMTP/IMAP/LDAP/RTSP 等。FTP 上传:curl -T local.txt ftp://server/remote.txt --user user:pass。SFTP 下载:curl sftp://server/file.txt --user user:pass -o local.txt,用 --key ~/.ssh/id_rsa 指定私钥。SFTP 比 FTP 安全,FTP 明文传输密码不推荐生产使用。
## 追问
### FTP 和 SFTP 有什么区别?
FTP 明文传输(密码和数据都裸奔),端口 21+20。SFTP 基于 SSH 加密,端口 22。FTPS 是 FTP+TLS。新项目一律用 SFTP。
### 如何断点续传?
下载续传:curl -C - -O URL(自动检测断点)。上传续传:curl -C - -T file ftp://server/。HTTP 不支持续传上传。
### 如何列 FTP 目录?
curl ftp://server/path/ --user user:pass(尾部斜杠表示列目录)。加 -l 只列文件名。
### cURL 和 scp 命令的区别?
scp 语法简单(scp file user@host:path),cURL 支持多协议和断点续传。scp 更直观,cURL 更灵活。
### 如何批量上传文件?
用 glob:curl -T "file[01-99].txt" ftp://server/dir/。或 shell 循环。大文件加 --limit-rate 1M 限速。服务端5月29日 22:48
cURL 如何处理 HTTP 认证?Basic Auth/Bearer/OAuth 怎么用?三种认证:Basic Auth——curl -u user:pass URL(密码明文 base64 编码必须配合 HTTPS);Bearer Token——curl -H "Authorization: Bearer TOKEN" URL;OAuth 2.0——先 curl token 端点拿 access_token,再带 token 请求 API。cURL 无内置 OAuth 流程,需手动实现 token 获取和刷新。
## 追问
### -u 和 -H Authorization 有什么区别?
-u user:pass 自动生成 Authorization: Basic 头,更简洁。-H 手动写原始头,适合精确控制。安全做法:用环境变量 -u user:$API_KEY 避免密码出现在 shell history。
### Bearer Token 过期了怎么刷新?
脚本模式:先 curl refresh_token 端点拿新 access_token,替换环境变量,再请求 API。生产建议用 SDK 自动刷新。
### OAuth 2.0 授权码流程怎么用 cURL 实现?
分三步:1.浏览器访问授权 URL 手动授权拿 code;2.curl -X POST token-url 换 token;3.curl -H "Authorization: Bearer TOKEN" 请求资源。第 1 步必须浏览器完成。
### API Key 和 Bearer Token 有什么区别?
API Key 是静态字符串,不过期除非手动轮换。Bearer Token 有过期时间,由 OAuth 签发,可精细控制权限范围(scope)。API Key 适合服务间调用,Bearer Token 适合用户级授权。
### .netrc 文件有什么用?
存储登录凭证:machine api.example.com login user password pass。cURL 加 -n 自动读取,不用 -u 传密码。chmod 600 保护文件权限。服务端5月29日 22:48
cURL 如何处理 Cookie 和会话管理?发送 Cookie:-b "name=value" 发单个,-b cookies.txt 从文件读。保存 Cookie:-c cookies.txt 把响应 Set-Cookie 写入文件。同时发送和保存:-b cookies.txt -c cookies.txt(同一文件读写维持会话)。会话管理就是循环请求时每次 -b -c 同一个 cookie 文件,服务器通过 session_id 识别用户。
## 追问
### -b 和 -H Cookie 有什么区别?
-b "key=val" 自动生成 Cookie 头,更简洁。-H "Cookie: key=val" 手动写原始头,适合精确控制。多个 Cookie:-b "a=1" -b "b=2" 或 -H "Cookie: a=1; b=2"。
### Cookie 文件格式是什么?
Netscape 格式:每行 tab 分隔 7 个字段——domain\tflag\tpath\tsecure\texpiration\tname\tvalue。由 -c 自动生成。
### 如何模拟登录后访问受保护页面?
curl -c cookies.txt -X POST login-url -d user=xxx -d pass=xxx,然后 curl -b cookies.txt protected-url。JWT 认证则不同——登录响应里的 token 需手动提取放到 Authorization 头。
### Cookie 和 Session 有什么区别?
Cookie 存客户端,Session 存服务器端。Cookie 里的 session_id 是钥匙,服务器用这把钥匙找 Session 数据。cURL 模拟的是 Cookie 传输层。
### 如何调试 Cookie 问题?
加 -v 查看 Cookie 和 Set-Cookie:curl -v -b cookies.txt URL 2>&1 | grep -i cookie。常见问题:Domain/Path 不匹配、Secure 标记在 HTTP 下不发送、SameSite 跨站不携带。服务端5月29日 22:48
cURL 如何处理大文件下载和断点续传?断点续传:curl -C - -O URL(-C - 自动检测已下载部分继续)。原理:cURL 发 Range 头请求剩余部分,服务器返回 206 Partial Content。首次下载中断后重新执行同一命令即可续传。大文件优化:--limit-rate 2M 限速、--max-time 3600 超时保护、-# 显示进度条。前提:服务器必须支持 Range 头。
## 追问
### 怎么判断服务器是否支持断点续传?
curl -I URL 看响应头:Accept-Ranges: bytes 表示支持,没有则不支持。
### 下载中断后文件名不对怎么办?
-O 用 URL 最后一段作为文件名,重定向后可能不对。用 -o 指定文件名,或 -L 跟随重定向后再用 -O。
### 如何并行下载大文件?
cURL 本身不支持分段并行下载。用 aria2c -x 16 -s 16 URL(16 连接并行)。或手动分段 curl -r 并合并。
### 下载到一半磁盘满了怎么办?
cURL 不会自动清理部分文件。脚本中 df -h 检查磁盘空间,超过 90% 停止下载并告警。大文件场景用 --max-filesize 限制大小。
### 如何验证下载文件完整性?
curl URL -o file && shasum -a 256 -c checksum.txt。或 curl URL | sha256sum 对比预期哈希。服务端5月29日 22:48
如何用 cURL 编写 API 自动化测试脚本?cURL 测试脚本 = cURL 命令 + shell 判断 + 状态码/响应体验证。核心模式:curl -s -o /dev/null -w "%{http_code}" URL 拿状态码;curl -s URL | jq .field 提取响应体字段;for/while 循环批量测试。进阶:--fail 返回非零表示 HTTP 错误、--max-time 10 超时控制、--retry 3 重试。
## 追问
### cURL 和 Postman 做测试哪个好?
cURL 适合 CI/CD 管道和脚本化——轻量、无 GUI 依赖、易版本控制。Postman 适合手动探索和团队协作。生产环境两者结合:开发用 Postman 调试,CI 用 cURL 验证。
### 怎么验证响应体内容?
curl -s URL | jq -e .status==200,-e 让 jq 在结果为 false 时返回 exit code 1。多字段验证:jq -e ".status==200 and .data.id != null"。
### 如何做性能基准测试?
curl -s -w "time_total: %{time_total}s\n" -o /dev/null URL。批量测用循环+awk 算平均值。注意 cURL 不含 DNS 缓存预热,首次请求偏慢。
### 怎么处理需要登录的接口?
先 curl 登录拿 token:TOKEN=$(curl -s -X POST login-url -d user/pass | jq -r .token),后续带 -H "Authorization: Bearer $TOKEN"。
### cURL 脚本的缺点?
无测试报告、无并行执行、错误定位不友好。超过 20 个接口建议上专用测试框架(pytest+requests/Jest+supertest),cURL 适合轻量级冒烟测试和 CI 快速验证。服务端5月28日 02:37
cURL 如何设置请求头(Headers)?在 cURL 中,请求头(Request Headers)用于向服务器传递元数据,比如认证凭证、内容类型、客户端标识等。API 调试和接口对接时,设置请求头是最常见的操作之一。
## 基本语法
使用 `-H` 或 `--header` 参数添加请求头,格式必须为 `"Name: Value"`:
```bash
curl -H "Header-Name: Header-Value" https://api.example.com
```
设置多个请求头时,每个 `-H` 单独写一个:
```bash
curl -H "Content-Type: application/json" \
-H "Authorization: Bearer token123" \
-H "Accept: application/json" \
https://api.example.com/users
```
## 常用请求头
```bash
# Content-Type — 指定请求体格式
curl -H "Content-Type: application/json" \
-d '{"name":"test"}' \
https://api.example.com/users
# Authorization — 身份认证(Bearer Token)
curl -H "Authorization: Bearer your_token_here" \
https://api.example.com/protected
# Authorization — HTTP Basic Auth
curl -H "Authorization: Basic $(echo -n 'user:pass' | base64)" \
https://api.example.com/protected
# Accept — 告诉服务器你期望的响应格式
curl -H "Accept: application/json" \
https://api.example.com/users
# User-Agent — 标识客户端
curl -H "User-Agent: MyApp/1.0" \
https://api.example.com/data
# Cookie — 发送 Cookie
curl -H "Cookie: session_id=abc123; user_id=456" \
https://api.example.com/profile
```
| 请求头 | 用途 | 常见值 |
|--------|------|--------|
| Content-Type | 请求体格式 | application/json, application/x-www-form-urlencoded, multipart/form-data |
| Authorization | 身份认证 | Bearer token, Basic base64(user:pass) |
| Accept | 期望的响应格式 | application/json, text/html, */* |
| User-Agent | 客户端标识 | Mozilla/5.0, MyApp/1.0 |
| Cookie | 发送 Cookie | session_id=abc123 |
| Cache-Control | 缓存控制 | no-cache, no-store |
| Referer | 来源页面 | https://example.com/page |
## 快捷选项
cURL 为几个常用请求头提供了专用选项,比 `-H` 更简洁:
```bash
# -A / --user-agent — 设置 User-Agent
curl -A "MyApp/1.0" https://api.example.com/data
# -e / --referer — 设置 Referer
curl -e "https://example.com/page" https://api.example.com/data
# -b / --cookie — 设置 Cookie(也支持从文件读取)
curl -b "session_id=abc123; user_id=456" https://api.example.com/profile
# -b 从文件读取 Cookie
curl -b cookies.txt https://api.example.com/profile
```
## 删除请求头
cURL 默认会发送一些内部请求头(如 Host、Accept、User-Agent)。如果想删除某个默认头,将值设为空:
```bash
# 删除默认的 Accept 头
curl -H "Accept:" https://api.example.com
# 删除默认的 User-Agent(某些反爬场景需要)
curl -H "User-Agent:" https://api.example.com
```
冒号后面没有任何内容,cURL 就不会发送该头部。
对于没有值的头部字段,在名称后加分号:
```bash
curl -H "X-Empty-Header;" https://api.example.com
```
## 从文件读取请求头
请求头较多时,可以写入文件,用 `@` 引用:
```bash
# headers.txt 内容:
# Content-Type: application/json
# Authorization: Bearer token123
# X-Custom-Header: custom-value
curl -H @headers.txt https://api.example.com/users
```
每行一个请求头,格式与 `-H` 参数一致。
## 验证请求头是否生效
加 `-v`(verbose)参数可以看到实际发出的请求头,以 `>` 开头的行就是发出的头部:
```bash
curl -v -H "Authorization: Bearer token123" \
https://api.example.com/protected
# 输出中可以看到:
# > GET /protected HTTP/2
# > Host: api.example.com
# > Authorization: Bearer token123
# > User-Agent: curl/8.1.2
# > Accept: */*
```
也可以用 httpbin.org 快速验证,它会把收到的请求头原样返回:
```bash
curl -H "X-Test: hello" https://httpbin.org/headers
# 返回 JSON 中会显示你发送的所有请求头
```
如果只想看响应头(以 `<` 开头的行),用 `-I`(HEAD 请求)或 `-D -`(转储响应头到 stdout)。
## 重复头部的处理
多次用 `-H` 设置同一个头部名称时,行为取决于 cURL 的内部实现:
```bash
# 对标准头部(如 User-Agent),后者覆盖前者
curl -H "User-Agent: Agent1" -H "User-Agent: Agent2" URL
# 实际发送:User-Agent: Agent2
# 对自定义头部,cURL 可能发送多个同名头部
curl -H "X-Custom: value1" -H "X-Custom: value2" URL
# 可能发送两个 X-Custom 头部
```
大多数服务器按照 RFC 7230 将同名头部合并为逗号分隔的单个值。如果需要覆盖而非追加,用 `-v` 确认实际发送结果,或确保只写一次该头部。
## 特殊场景
**发送压缩请求体:**
```bash
# 告诉服务器请求体是 gzip 压缩的
curl -H "Content-Encoding: gzip" \
--data-binary @compressed.gz \
https://api.example.com/upload
```
**CORS 预检请求:**
```bash
# 模拟浏览器发送 OPTIONS 预检
curl -X OPTIONS \
-H "Origin: https://example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: X-Custom-Header" \
https://api.example.com/data
```
**带摘要认证(Digest Auth):**
```bash
# curl 内置摘要认证支持,不必手动构造 Authorization 头
curl --digest -u user:pass https://api.example.com/protected
```
**发送 multipart 表单时自动设置的头部:**
```bash
# -F 会自动设置 Content-Type: multipart/form-data; boundary=...
# 不要手动设置 Content-Type,否则会覆盖 boundary
curl -F "file=@photo.jpg" \
-F "name=test" \
https://api.example.com/upload
```
## 常见踩坑
**1. 冒号后面没有空格**
```bash
# 可能出问题 — 部分服务端解析失败
curl -H "Content-Type:application/json" URL
# 推荐 — 冒号后加空格
curl -H "Content-Type: application/json" URL
```
HTTP 规范允许冒号后无空格,但实际中部分服务端解析会出问题,建议始终加空格。
**2. 值含特殊字符未加引号**
```bash
# 错误 — 分号会被 shell 解释
curl -H Cookie: session=abc; user=123 URL
# 正确 — 整个头用双引号包裹
curl -H "Cookie: session=abc; user=123" URL
```
**3. multipart 表单手动设置了 Content-Type**
```bash
# 错误 — 覆盖了 boundary,服务端无法解析
curl -H "Content-Type: multipart/form-data" -F "file=@data.bin" URL
# 正确 — 让 -F 自动设置 Content-Type
curl -F "file=@data.bin" URL
```
**4. 后设置的头部覆盖前一个**
```bash
# 最终 User-Agent 是 Agent2
curl -H "User-Agent: Agent1" \
-H "User-Agent: Agent2" \
URL
```
对标准头部是覆盖行为,用 `-v` 确认实际发送结果。服务端5月28日 02:37
cURL 如何处理 URL 编码和特殊字符?在 cURL 中处理 URL 编码和特殊字符是日常请求中绕不开的问题——查询参数里的空格、中文、`&` 和 `=` 都可能在传输中被误解析。理解 cURL 提供的编码机制,以及何时需要手动编码,能避免大量调试时间。
### URL 编码的核心规则
URL 编码(Percent Encoding)将非安全字符转换为 `%XX` 格式,`XX` 是字符 UTF-8 字节的十六进制表示。RFC 3986 规定,只有字母、数字和 `-_.~` 属于无需编码的"未保留字符"。
```bash
# 常见字符的编码映射
空格 -> %20
& -> %26
= -> %3D
+ -> %2B
% -> %25
# -> %23
中文 -> %E4%B8%AD%E6%96%87
```
需要区分两种编码场景:**URL 路径编码**遵循 RFC 3986,空格编码为 `%20`;**表单提交编码**(application/x-www-form-urlencoded)遵循 HTML 规范,空格编码为 `+`。cURL 的 `--data-urlencode` 使用的就是表单编码规则。
### --data-urlencode 的四种语法
`--data-urlencode` 是 cURL 处理编码的主力参数,但它支持多种写法,行为各不相同:
```bash
# 1. key=value:对 value 部分 URL 编码
curl --data-urlencode "name=hello world" https://api.example.com
# 发送:name=hello+world(value 编码,key 不编码)
# 2. =value:对整个 value 编码,不带 key
curl --data-urlencode "=hello world" https://api.example.com
# 发送:hello+world
# 3. key@filename:读取文件内容作为 value 并编码
curl --data-urlencode "content@/tmp/payload.txt" https://api.example.com
# 文件内容会被 URL 编码后作为 content 的值
# 4. @filename:读取文件内容并编码,不带 key
curl --data-urlencode "@/tmp/raw_data.txt" https://api.example.com
# 文件内容整体编码后发送
```
面试追问:为什么 `--data-urlencode "name=value"` 只编码 value 而不编码 key?因为 key 是开发者可控的固定字符串,通常不含特殊字符;而 value 来自用户输入,不可控,必须编码。
### GET 请求中的编码:-G 配合 --data-urlencode
`--data-urlencode` 默认以 POST 方式发送数据。加上 `-G`(或 `--get`)后,数据会被追加到 URL 查询字符串中:
```bash
# 构建带编码的 GET 请求
curl -G https://api.example.com/search \
--data-urlencode "q=hello world" \
--data-urlencode "category=技术&编程"
# 实际请求:https://api.example.com/search?q=hello+world&category=%E6%8A%80%E6%9C%AF%26%E7%BC%96%E7%A8%8B
```
如果不加 `-G`,同样的命令会把数据放进请求体,变成 POST 请求——这是 cURL 新手最常犯的错误之一。
### 手动编码:当 --data-urlencode 不够用时
有些场景下 `--data-urlencode` 无法覆盖需求,比如 URL 路径中包含中文、需要对整个 URL 做编码处理等。
```bash
# 使用 Python 编码(最通用)
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('hello world & 中文'))")
curl "https://api.example.com/search?q=$ENCODED"
# 使用 jq 编码(适合管道操作)
ENCODED=$(jq -nr --arg s 'hello world' '$s | @uri')
curl "https://api.example.com/search?q=$ENCODED"
# 利用 cURL 自身做编码(巧妙但可读性差)
encode() {
local data="$(curl -s -o /dev/null -w '%{url_effective}' --get --data-urlencode "$1" "")"
echo "${data#/?}"
}
ENCODED=$(encode "hello world & 中文")
curl "https://api.example.com/search?q=$ENCODED"
```
### 双编码陷阱
对已经编码过的字符串再次编码,会产生双重编码(double-encoding),这是最难排查的一类 bug:
```bash
# 正确:只编码一次
curl -G https://api.example.com/search \
--data-urlencode "q=hello%20world"
# 服务端收到:q=hello%20world → 解码为 "hello world"
# 错误:--data-urlencode 又对 %20 做了一次编码
# 结果 %20 变成了 %2520
# 服务端收到:q=hello%2520world → 解码为 "hello%20world"
```
避免方法:如果一个值已经是编码后的,不要再通过 `--data-urlencode` 处理。用 `-d` 代替,或者确保输入始终是未编码的原始值。
```bash
# 如果值已经是编码后的,用 -d 直接发送
curl -G https://api.example.com/search \
-d "q=hello%20world"
# 如果值是原始值,用 --data-urlencode
curl -G https://api.example.com/search \
--data-urlencode "q=hello world"
```
### 查询参数中 & 和 = 的歧义
URL 中 `&` 是参数分隔符,`=` 是键值分隔符。当参数值本身包含这些字符时,不加编码会导致参数解析错误:
```bash
# 错误:& 被误认为参数分隔符
curl "https://api.example.com/search?q=foo&bar"
# 服务端理解为两个参数:q=foo 和 bar(无值)
# 正确方式一:手动编码
curl "https://api.example.com/search?q=foo%26bar"
# 正确方式二:用 --data-urlencode 自动处理
curl -G https://api.example.com/search \
--data-urlencode "q=foo&bar"
```
### 路径中的特殊字符
URL 路径(`?` 之前的部分)中的特殊字符处理与查询参数不同。cURL 默认会对路径中的部分字符做处理:
```bash
# 路径中的空格需要编码
curl "https://api.example.com/files/my%20document.pdf"
# 路径中的中文需要编码
curl "https://api.example.com/files/%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6.pdf"
# --path-as-is:阻止 cURL 对路径做任何处理(保留原始路径)
# 不加此参数时,cURL 会把 /../ 和 /./ 规范化
curl --path-as-is "https://api.example.com/../secret.txt"
```
### JSON 请求体中的特殊字符
JSON 请求体不走 URL 编码,但有自己的转义规则——双引号、反斜杠、控制字符需要转义:
```bash
# 直接写 JSON 时需要手动转义
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","bio":"Line1\nLine2\tTabbed"}'
# 用 jq 生成 JSON,自动处理转义(推荐)
jq -n '{name: "张三", bio: "Line1\nLine2"}' | \
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d @-
```
### 表单提交的混合编码
实际开发中经常需要同时发送已编码字段和待编码字段:
```bash
curl -X POST https://api.example.com/submit \
-d "id=123&status=active" \
--data-urlencode "content=Special chars: & = ?"
```
`-d` 和 `--data-urlencode` 可以混用。cURL 会将所有数据合并为一条请求体,`-d` 的部分原样发送,`--data-urlencode` 的部分自动编码。
### 调试编码问题的方法
编码问题的排查关键在于确认"实际发送的内容到底是什么":
```bash
# 方法一:用 -v 查看完整请求(包含编码后的 URL)
curl -v -G https://api.example.com/search \
--data-urlencode "q=hello world"
# 方法二:用 --trace-ascii 把完整请求写入文件
curl --trace-ascii /tmp/trace.log -G https://api.example.com/search \
--data-urlencode "q=hello world"
# 然后查看 trace.log 确认实际 URL
# 方法三:用 -w 输出编码后的实际 URL
curl -s -o /dev/null -w '%{url_effective}\n' -G https://api.example.com/search \
--data-urlencode "q=hello world"
# 输出:https://api.example.com/search?q=hello+world
```
### 实战脚本
```bash
#!/bin/bash
# URL 编码处理封装
# 编码函数(依赖 python3)
urlencode() {
python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$1"
}
API_BASE="https://api.example.com/v1"
# GET 请求:自动编码查询参数
curl -G "${API_BASE}/search" \
--data-urlencode "q=hello world & 中文" \
-H "Accept: application/json"
# POST 请求:混合编码字段
curl -X POST "${API_BASE}/submit" \
-d "type=article" \
--data-urlencode "title=深入理解 cURL 编码" \
--data-urlencode "content=包含 & 和 = 的内容"
# 路径中含中文:手动编码路径部分
ENCODED_NAME=$(urlencode "中文文件")
curl "${API_BASE}/files/${ENCODED_NAME}.pdf"
```
面试中常被追问的关键区别:`--data-urlencode` 编码的是 value 部分,`-d` 原样发送;表单编码空格变 `+`,路径编码空格变 `%20`;已编码的值不要再过 `--data-urlencode`,否则会双编码。掌握这三条,cURL 的编码问题基本不会踩坑。服务端5月28日 02:37
cURL 性能优化有哪些关键手段?## 为什么要关注 cURL 性能
cURL 是后端开发、运维和测试中最常用的命令行 HTTP 工具。默认配置下,cURL 每次请求都重新建立 TCP 连接和 TLS 握手,批量调用时性能损耗显著。掌握超时、重试、连接复用、并发和压缩等优化手段,能让生产环境的 API 调用速度提升数倍。
## 超时与速率控制
超时是生产环境的第一道防线。cURL 提供两级超时:
- `--connect-timeout`:TCP 连接建立的最大等待时间
- `--max-time`:整个请求(含传输)的最大耗时
```bash
# 连接超时 10 秒,整体超时 30 秒
curl --connect-timeout 10 --max-time 30 https://api.example.com
# 7.68.0+ 支持毫秒级超时
curl --connect-timeout 3.5 --max-time 10.5 https://api.example.com
```
遇到慢速传输时,`--speed-time` 和 `--speed-limit` 可以主动中断卡住的连接:
```bash
# 如果连续 5 秒速度低于 100 字节/秒,自动中断
curl --speed-time 5 --speed-limit 100 https://api.example.com/large-file.zip -O
```
速率限制用 `--limit-rate`,在带宽敏感场景下控制下载速度:
```bash
curl --limit-rate 1M https://example.com/large-file.zip -O
```
## 重试机制
网络请求天生不可靠,重试是保障可靠性的核心手段。
```bash
# 失败自动重试 3 次
curl --retry 3 https://api.example.com
# 重试间隔 2 秒,防止立即重试加重服务端压力
curl --retry 3 --retry-delay 2 https://api.example.com
# 限定重试总耗时,避免无限等待
curl --retry 5 --retry-delay 1 --retry-max-time 30 https://api.example.com
# 连接被拒绝时也重试(默认只重试超时类错误)
curl --retry 3 --retry-connrefused https://api.example.com
```
关键细节:`--retry` 默认只对超时、5xx 错误和连接失败重试,不会对 4xx 重试。如果需要针对特定 HTTP 状态码重试,需要脚本层面处理。`--retry-delay` 只在两次重试之间生效,首次请求不受影响。
## 连接复用
HTTP Keep-Alive 是 cURL 性能优化中收益最高的一项。一次 TCP + TLS 握手通常需要 100-300ms,复用连接直接省掉这笔开销。
```bash
# 保持连接 60 秒
curl --keepalive-time 60 https://api.example.com
```
命令行 cURL 在单次执行中自动复用连接。但跨进程调用时无法复用——这是命令行 cURL 的固有限制,需要 libcurl 句柄复用才能解决:
```c
// libcurl 句柄复用示例
CURL *handle = curl_easy_init();
// 第一次请求
curl_easy_setopt(handle, CURLOPT_URL, "https://api.example.com/users");
curl_easy_perform(handle);
// 第二次请求复用同一连接
curl_easy_setopt(handle, CURLOPT_URL, "https://api.example.com/products");
curl_easy_perform(handle);
curl_easy_cleanup(handle);
```
多 handle 场景下,用 `curl_share_setopt` 共享 DNS 缓存和 Cookie,进一步减少重复开销。
DNS 解析也有缓存收益。`--resolve` 可以跳过 DNS 查询:
```bash
# 直接指定 IP,跳过 DNS 解析
curl --resolve api.example.com:443:203.0.113.50 https://api.example.com
```
这对调试 CDN 回源、绕过 DNS 劫持、压测时固定后端 IP 都有用。
## HTTP/2 多路复用
HTTP/2 在单条 TCP 连接上并行传输多个请求,从根本上解决了 HTTP/1.1 的队头阻塞问题:
```bash
# 强制使用 HTTP/2
curl --http2 https://api.example.com
# 优先协商 HTTP/2,失败回退 HTTP/1.1
curl --http2-prior-knowledge https://api.example.com
```
配合连接复用,HTTP/2 的收益最大:多个 API 请求共享一条连接,省去多次握手和队头等待。适合微服务网关、GraphQL 批量查询等场景。
## 并发请求
cURL 7.66+ 原生支持并行传输,使用 `-Z`(`--parallel`)标志:
```bash
# 并行下载多个 URL
curl -Z -OL https://example.com/a.json https://example.com/b.json
# 控制最大并发数
curl -Z --parallel-max 5 -OL https://example.com/file{1..10}.zip
```
旧版本用 shell 方式实现并发:
```bash
# 后台进程 + wait
for url in "${urls[@]}"; do
curl -s "$url" -o "$(basename $url).json" &
done
wait
# GNU Parallel,更精细的并发控制
cat urls.txt | parallel -j 4 curl -s {} -o {/}.json
```
选择建议:少量 URL 用 `-Z` 即可,批量任务推荐 GNU Parallel,便于控制并发数和失败重试。
## 压缩传输
`--compressed` 让 cURL 在请求头添加 `Accept-Encoding`,服务端返回压缩响应后自动解压:
```bash
curl --compressed https://api.example.com
```
JSON API 响应通常能压缩 60-80%,对移动端和带宽敏感场景效果显著。也可以手动指定压缩算法:
```bash
curl -H "Accept-Encoding: gzip, deflate, br" https://api.example.com
```
注意:`--compressed` 在服务端不支持压缩时不会报错,cURL 会正常接收未压缩的响应。
## TCP 与 TLS 优化
`--tcp-nodelay` 禁用 Nagle 算法,减少小包传输延迟,适合交互式 API 调用:
```bash
curl --tcp-nodelay --tcp-fastopen https://api.example.com
```
TLS 握手是 HTTPS 请求中耗时的环节,优化点包括:
```bash
# 强制使用 TLS 1.2 及以上(拒绝旧协议)
curl --tlsv1.2 --tls-max tls1.3 https://api.example.com
# TLS 会话复用(libcurl 句柄复用时自动生效)
# CA 缓存减少证书链重复加载(libcurl 7.84+)
```
生产环境务必验证证书,不要用 `-k` 跳过验证。密码等凭证不要写在命令行里:
```bash
# -u 只输用户名,cURL 会提示输入密码
curl -u "username" https://api.example.com
# 更好:用环境变量
curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com
```
## 性能分析
`-w`(`--write-out`)是 cURL 性能诊断的核心工具,可以输出请求各阶段耗时:
```bash
curl -w "DNS: %{time_namelookup}s
Connect: %{time_connect}s
SSL: %{time_appconnect}s
TTFB: %{time_starttransfer}s
Total: %{time_total}s
Size: %{size_download}B
Speed: %{speed_download}B/s
" -o /dev/null -s https://api.example.com
```
各指标含义:
| 指标 | 含义 | 关注场景 |
|------|------|----------|
| `time_namelookup` | DNS 解析耗时 | CDN 选路、DNS 劫持排查 |
| `time_connect` | TCP 连接建立耗时 | 网络延迟、连接池耗尽 |
| `time_appconnect` | TLS 握手完成耗时 | 证书链过长、协议协商慢 |
| `time_starttransfer` | 首字节到达耗时(TTFB) | 服务端处理慢、排队过长 |
| `time_total` | 整体耗时 | 端到端性能评估 |
定位思路:如果 `time_namelookup` 高,查 DNS;`time_connect` 高,查网络或连接池;`time_appconnect` 高,查 TLS 配置;`time_starttransfer` 高但前面指标正常,查服务端。
把格式写入文件可以复用:
```bash
cat > perf-format.txt << 'EOF'
timestamp:%{time_total} dns:%{time_namelookup} connect:%{time_connect} ssl:%{time_appconnect} ttfb:%{time_starttransfer} size:%{size_download} speed:%{speed_download}
EOF
curl -w "@perf-format.txt" -o /dev/null -s https://api.example.com >> perf.log
```
## 大文件与断点续传
```bash
# 分块下载
curl -r 0-10485760 https://example.com/large-file.zip -o part1.zip
curl -r 10485761-20971520 https://example.com/large-file.zip -o part2.zip
# 断点续传
curl -C - -O https://example.com/large-file.zip
```
分块下载 + 断点续传组合使用:先分块下载,某块中断后用 `-C -` 续传,最后用 `cat part*.zip > large-file.zip` 合并。
流式处理避免大文件占满内存:
```bash
# 流式处理 JSON 响应
curl -s https://api.example.com/stream | jq '.[] | .name'
```
## 生产级脚本模板
```bash
#!/bin/bash
# 生产级 API 调用脚本
API_URL="https://api.example.com/v1/data"
TOKEN="${API_TOKEN:-$(cat ~/.api_token)}"
TIMEOUT=30
RETRY=3
api_call() {
curl -s -S --connect-timeout 10 --max-time "$TIMEOUT" --retry "$RETRY" --retry-delay 2 --retry-connrefused --compressed --tlsv1.2 -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Accept: application/json" -H "User-Agent: MyApp/1.0" -w "
{"status":%{http_code},"time":%{time_total},"size":%{size_download}}
" "$@"
}
response=$(api_call "$API_URL")
if [ $? -eq 0 ]; then
echo "$response" | jq '.'
else
echo "Request failed" >&2
exit 1
fi
```
可维护性方面,常用配置写入 `~/.curlrc`:
```
verbose
connect-timeout = 10
max-time = 60
retry = 3
```
日志监控脚本示例:
```bash
#!/bin/bash
while true; do
STATUS=$(curl -w "%{http_code}" -o /dev/null -s --max-time 5 https://api.example.com/health)
echo "$(date '+%Y-%m-%d %H:%M:%S') | Status: $STATUS"
[ "$STATUS" != "200" ] && echo "API unhealthy!" | mail -s "API Alert" admin@example.com
sleep 60
done
```
## 优化效果对比
| 优化项 | 优化前 | 优化后 | 典型提升 |
|--------|--------|--------|----------|
| 连接复用 | 每次新建连接 | Keep-Alive | 延迟降低 30-50% |
| 压缩传输 | 原始大小 | Gzip/Brotli | 体积减少 60-80% |
| HTTP/2 多路复用 | 队头阻塞 | 单连接并行 | 并发延迟降低 50%+ |
| 并发请求 | 串行执行 | 并行处理 | 吞吐提升 3-5 倍 |
| DNS 缓存 | 每次解析 | --resolve/本地缓存 | 延迟降低 10-20ms |
| 断点续传 | 重新下载 | 续传 | 节省已完成部分的带宽 |
## 追问
- cURL 的 `--retry` 和应用层面的指数退避重试有什么区别?什么时候该用哪种?
- 命令行 cURL 的连接复用有什么局限?libcurl 句柄复用如何突破这个限制?
- `--compressed` 在服务端不支持压缩时行为是什么?会不会导致请求失败?
- HTTP/2 多路复用和 `-Z` 并行传输有什么区别?各自适合什么场景?服务端5月28日 02:36
如何使用 cURL 进行 API 调试和排错?cURL 是开发者在 API 开发中最常接触的命令行工具,但多数人只停留在 `curl -X GET` 的层面。遇到请求超时、证书报错、重定向异常等问题时,如果不知道 cURL 的调试参数,排查就像盲人摸象。
## -v:你的第一道诊断线
`-v`(verbose)是 cURL 调试的核心开关,它会输出完整的请求-响应交互过程:
```bash
curl -v https://api.example.com/users
```
输出中以不同前缀区分信息来源:
- `>` 发出的请求行和请求头
- `<` 收到的响应头
- `*` 连接建立和 TLS 握手细节
当 API 返回 401 时,先看 `>` 部分确认 Authorization 头是否真的发出去了;返回 301/302 时,看 `< Location` 确认跳转目标。大部分问题在 `-v` 输出中就能定位。
## -w:量化请求的每个阶段
`-w`(write-out)把请求拆解为可量化的时间指标,是定位性能瓶颈的关键:
```bash
curl -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
-o /dev/null -s https://api.example.com
```
各指标的含义和典型排查思路:
| 指标 | 含义 | 偏高时排查方向 |
|------|------|----------------|
| `time_namelookup` | DNS 解析耗时 | 检查 DNS 配置或切换 DNS 服务器 |
| `time_connect` | TCP 连接耗时 | 网络链路问题或服务端负载高 |
| `time_appconnect` | TLS 握手耗时 | 证书链过长或协商算法不匹配 |
| `time_starttransfer` | 首字节时间(TTFB) | 服务端处理慢,需排查后端逻辑 |
| `time_total` | 请求总耗时 | 综合判断,大文件传输时主要受下载速度影响 |
如果 `time_namelookup` 占了总时间的 80%,问题在 DNS 而非服务端;如果 TTFB 正常但 `time_total` 很高,说明是响应体太大或网络带宽瓶颈。
## --trace:完整的请求审计日志
`-v` 不够用时,`--trace-ascii` 记录每一个字节的收发:
```bash
# 文本格式日志(可读性好)
curl --trace-ascii debug.log https://api.example.com
# 十六进制格式(排查编码/二进制问题)
curl --trace debug.hex https://api.example.com
```
`--trace` 会记录请求体和响应体的完整内容,适合排查 POST 请求的 body 是否正确发送、响应中是否存在隐藏字符等问题。注意敏感信息(如 token)也会被记录,不要在生产环境随意使用。
## 超时与重试:让请求可控
线上环境的请求不能无限等待,必须设置超时:
```bash
# 连接超时 5 秒,整体超时 10 秒
curl --connect-timeout 5 --max-time 10 https://api.example.com
# 失败后重试 3 次,间隔 2 秒
curl --retry 3 --retry-delay 2 https://api.example.com
```
`--connect-timeout` 控制 TCP 建连阶段的最大等待时间,`--max-time` 控制整个请求(含下载)的上限。两者要配合使用——只设 `--connect-timeout` 而不设 `--max-time`,慢速下载仍可能卡住。
## SSL/TLS 证书问题排查
证书相关错误是 HTTPS 请求中最常见的坑:
```bash
# 查看证书链和协商细节
curl -v https://api.example.com 2>&1 | grep -A 10 "SSL connection"
# 跳过证书验证(仅限本地调试)
curl -k https://self-signed.badssl.com
# 指定 CA 证书
curl --cacert /path/to/ca.crt https://api.example.com
# 指定客户端证书(mTLS 场景)
curl --cert client.pem --key client-key.pem https://mtls.example.com
```
`-k` 只能用于临时测试,永远不要在正式环境跳过证书验证。生产环境中遇到证书错误,应使用 `--cacert` 指定正确的 CA 证书或 `--resolve` 绕过 DNS 指向正确的服务端。
## 重定向与认证的隐藏陷阱
cURL 默认不跟随重定向,需要加 `-L`:
```bash
# 跟随重定向并显示每一步的状态码
curl -L -v https://example.com 2>&1 | grep -E "(< HTTP|< Location)"
```
重定向有一个容易被忽略的安全行为:当 Location 指向不同域名时,cURL 会自动丢弃 `Authorization` 头,防止凭据泄露到第三方。如果需要跨域传递认证信息,需要显式用 `-H` 重新添加。
## DNS 与网络层诊断
当请求连不上服务器时,从 DNS 和网络层开始排查:
```bash
# 指定 DNS 服务器(绕过本地 DNS 污染)
curl --dns-servers 8.8.8.8 https://api.example.com
# 手动映射域名到 IP(跳过 DNS 解析)
curl --resolve example.com:443:192.168.1.100 https://example.com
# 强制 IPv4(IPv6 连接异常时排查)
curl -4 https://api.example.com
# 测试端口连通性
curl -v telnet://example.com:80
```
`--resolve` 在调试负载均衡、灰度发布时特别有用——可以把域名直接指向特定后端实例,绕过 LB 层。
## 组合调试实战脚本
把常用的调试步骤封装成脚本,遇到问题直接执行:
```bash
#!/bin/bash
# api-debug.sh — 快速诊断 API 请求
URL=$1
[ -z "$URL" ] && echo "Usage: $0 <url>" && exit 1
echo "=== 连通性检查 ==="
curl -o /dev/null -s -w "HTTP %{http_code} | TTFB %{time_starttransfer}s | Total %{time_total}s\n" "$URL"
echo ""
echo "=== 响应头 ==="
curl -s -I "$URL"
echo ""
echo "=== 重定向链 ==="
curl -s -I -L "$URL" 2>&1 | grep -E "^(HTTP|Location)"
echo ""
echo "=== 各阶段耗时 ==="
curl -o /dev/null -s -w "DNS %{time_namelookup}s | TCP %{time_connect}s | TLS %{time_appconnect}s | TTFB %{time_starttransfer}s | Total %{time_total}s\n" "$URL"
```
用法:`bash api-debug.sh https://api.example.com/users`
## 面试高频追问
**Q: -v 和 --trace-ascii 有什么区别?**
`-v` 只输出请求头和响应头,不记录 body;`--trace-ascii` 记录完整的请求和响应内容,包括 body。排查请求体问题时必须用 `--trace-ascii`。
**Q: curl -w 的 time_starttransfer 和 time_total 差异大说明什么?**
TTFB 小但 Total 大,说明服务端响应快但传输慢——可能是响应体太大、网络带宽不足,或服务端在流式传输。反之如果 TTFB 本身就高,瓶颈在服务端处理速度。
**Q: 为什么 curl -L 跟随重定向后 Authorization 头丢了?**
这是 cURL 的安全设计。当重定向目标与原始域名不同时,cURL 自动移除敏感头(Authorization、Cookie 等),防止凭据被发送到第三方服务器。这是 RFC 7235 的安全要求。
服务端5月28日 02:35
cURL 如何实现文件上传功能?cURL 支持多种文件上传方式,核心区别在于 `Content-Type` 和请求体的组织形式。面试中最高频考察的是 `-F` 表单上传和 `-T` PUT 上传的区别。
### -F 表单上传(multipart/form-data)
`-F` 是最常用的上传方式,模拟浏览器表单提交,自动设置 `Content-Type: multipart/form-data`:
```bash
# 基本上传
curl -X POST https://api.example.com/upload \
-F "file=@/path/to/document.pdf"
# 指定 MIME 类型(服务器可能根据类型做不同处理)
curl -X POST https://api.example.com/upload \
-F "file=@/path/to/image.png;type=image/png"
# 指定服务器端接收的文件名
curl -X POST https://api.example.com/upload \
-F "file=@/path/to/local.txt;filename=uploaded.txt"
```
`@` 符号告诉 cURL 读取文件内容而非当作普通字符串。如果误写成 `-F "file=/path/to/file"`,服务器收到的是字面字符串而非文件内容,这是新手最常见的错误。
### -T PUT 上传(原始文件流)
`-T` 或 `--upload-file` 将文件作为请求体直接发送,默认使用 HTTP PUT 方法:
```bash
# HTTP PUT 上传
curl -T /path/to/file.pdf https://api.example.com/files/document.pdf
# FTP 上传
curl -T /path/to/file.zip ftp://ftp.example.com/upload/ \
--user username:password
# SFTP 上传
curl -T /path/to/file.zip sftp://example.com/upload/ \
--user username:password
```
`-T` 与 `-F` 的关键区别:`-T` 发送的是原始文件流,`Content-Type` 默认为 `application/octet-stream`,不会封装成 multipart 格式。RESTful API 中资源更新(如替换已有文件)常用这种方式。
### 多文件上传
```bash
# 不同字段名上传多个文件
curl -X POST https://api.example.com/upload \
-F "avatar=@/path/to/avatar.jpg" \
-F "resume=@/path/to/resume.pdf"
# 数组形式上传(后端用 files[] 接收)
curl -X POST https://api.example.com/upload \
-F "files[]=@/path/to/file1.pdf" \
-F "files[]=@/path/to/file2.jpg"
# 混合文件和普通表单字段
curl -X POST https://api.example.com/submit \
-F "name=张三" \
-F "email=zhangsan@example.com" \
-F "avatar=@/path/to/avatar.jpg"
```
### 二进制上传与 Base64 编码
直接发送原始二进制数据,适用于 API 要求 `application/octet-stream` 的场景:
```bash
curl -X POST https://api.example.com/upload \
-H "Content-Type: application/octet-stream" \
--data-binary @/path/to/file.bin
# 从标准输入读取
cat file.bin | curl -X POST https://api.example.com/upload \
-H "Content-Type: application/octet-stream" \
--data-binary @-
```
某些 API 只接受 JSON 请求体,此时需要 Base64 编码:
```bash
curl -X POST https://api.example.com/upload \
-H "Content-Type: application/json" \
-d "{\"file\":\"$(base64 -w 0 /path/to/file.pdf)\",\"filename\":\"document.pdf\"}"
```
Base64 编码会使数据体积增加约 33%,大文件场景下应优先使用 `-F` 表单上传。
### 大文件上传与断点续传
```bash
# 显示上传进度条
curl -X POST https://api.example.com/upload \
-F "file=@/path/to/large.zip" \
--progress-bar
# 断点续传(需要服务器支持 Range 或 resumable upload)
curl -C - -X POST https://api.example.com/upload \
-F "file=@/path/to/large.zip"
# 设置超时避免长时间挂起
curl --max-time 600 -F "file=@large.zip" \
https://api.example.com/upload
```
`-C -` 表示自动从上次中断的位置继续传输。注意:真正的断点续传需要服务端支持,对于 multipart 上传,多数服务器并不支持续传,这时需要使用服务端提供的分块上传 API(先获取 uploadId,逐块上传后合并)。
### 带认证的上传
```bash
# Bearer Token 认证
curl -X POST https://api.example.com/upload \
-H "Authorization: Bearer your_token" \
-F "file=@/path/to/file.pdf"
# AWS S3 预签名 URL 上传(PUT 方式)
curl -X PUT "https://presigned-url-here" \
-H "Content-Type: application/pdf" \
--data-binary @/path/to/file.pdf
# 基本认证
curl -u username:password -F "file=@file.pdf" \
https://api.example.com/upload
```
### 关键参数速查
| 参数 | 作用 | 适用场景 |
|------|------|----------|
| `-F` / `--form` | multipart 表单上传 | Web 表单、API 文件字段 |
| `-T` / `--upload-file` | PUT 原始文件流上传 | RESTful 资源替换、FTP/SFTP |
| `@` | 读取文件内容 | `-F` 和 `--data-binary` 中 |
| `;type=` | 指定 MIME 类型 | `-F` 中覆盖自动检测 |
| `;filename=` | 指定服务端文件名 | `-F` 中需要改名的场景 |
| `--data-binary` | 发送原始二进制数据 | `application/octet-stream` |
| `-C -` | 断点续传 | 大文件中断后恢复 |
| `--progress-bar` | 显示进度条 | 大文件上传监控 |
### 面试常见追问
**-F 和 -T 上传有什么区别?**
`-F` 封装为 multipart/form-data 格式,可以同时传文件和其他字段,适合表单场景。`-T` 发送原始文件流作为请求体,默认 PUT 方法,适合直接替换资源或 FTP/SFTP 上传。
**上传文件时 @ 符号的作用是什么?**
`@` 告诉 cURL 读取后面路径的文件内容。不加 `@` 时,cURL 会把路径字符串当作普通值发送。
**如何上传超过服务器限制的大文件?**
分两种情况:如果是服务器 `Content-Length` 限制,需服务端调整配置;如果是需要分块上传,要调用服务端提供的 chunked upload API,cURL 本身不自动分块。
服务端5月28日 01:54
cURL 如何处理 HTTP 重定向?cURL 处理 HTTP 重定向是日常开发和面试中的高频考点。理解 cURL 重定向机制,核心在于掌握三个层面:跟随重定向的开关控制、不同状态码下的方法切换行为、以及跨域重定向的安全边界。
## 面试核心答案
cURL 默认**不跟随重定向**,需加 `-L`(或 `--location`)才会自动追踪 3xx 响应。重定向时,301/302/303 会将 POST 请求降级为 GET,而 307/308 会保持原始方法。通过 `--post301`/`--post302` 可以覆盖默认行为。跨域重定向时,cURL 默认不转发 Authorization 等敏感头,需 `--location-trusted` 显式允许。
## 重定向的基本控制
cURL 遇到 3xx 响应时,默认只返回重定向响应头,不会自动跳转到新地址。使用 `-L` 开启跟随重定向:
```bash
# 默认:不跟随,只返回 301/302 响应
curl http://example.com
# 跟随重定向到最终地址
curl -L http://example.com
curl --location http://example.com
```
用 `-v` 可以看到完整的重定向过程,包括每一跳的 Location 头和状态码,排查问题时非常实用:
```bash
curl -L -v http://example.com
```
## 为什么 301/302 会把 POST 变成 GET
这是面试中最常追问的点。原因在于 HTTP 规范的历史演进:
- **301 Moved Permanently** 和 **302 Found** 在 RFC 1945(HTTP/1.0)时代就定义了,当时的客户端实现普遍把重定向后的 POST 改为 GET。后来 RFC 7231 将这个行为正式标准化——301/302 重定向后,客户端"可以"将方法改为 GET。
- **303 See Other** 是 HTTP/1.1 新增的,明确要求重定向后必须使用 GET,典型场景是 POST 表单提交后跳转到结果页。
- **307 Temporary Redirect** 和 **308 Permanent Redirect** 是后来补充的状态码,设计动机就是解决 301/302 方法降级的问题。307/308 明确要求保持原始请求方法不变。
| 状态码 | 含义 | 方法变化 | 适用场景 |
| --- | --- | --- | --- |
| 301 | 永久重定向 | POST → GET | 网址永久迁移 |
| 302 | 临时重定向 | POST → GET | 临时跳转(旧规范) |
| 303 | 另见其他 | POST → GET | POST 后跳结果页 |
| 307 | 临时重定向 | 保持不变 | API 临时切换地址 |
| 308 | 永久重定向 | 保持不变 | API 永久迁移 |
在 cURL 中控制 POST 重定向行为:
```bash
# 默认:301/302 重定向后 POST 变 GET
curl -L -X POST -d "data=test" http://example.com/submit
# 301 重定向时保持 POST(遵循 RFC 7231)
curl -L --post301 -X POST -d "data=test" http://example.com/submit
# 302 重定向时保持 POST
curl -L --post302 -X POST -d "data=test" http://example.com/submit
# 303 重定向时保持 POST
curl -L --post303 -X POST -d "data=test" http://example.com/submit
```
## 重定向次数限制
cURL 默认最多跟随 50 次重定向,超过则报错。生产环境中应主动设置合理上限,防止重定向循环:
```bash
# 限制最多 5 次重定向
curl -L --max-redirs 5 http://example.com
# 允许无限重定向(有循环风险,不建议)
curl -L --max-redirs -1 http://example.com
```
## 查看重定向链
调试重定向问题时,需要知道中间经历了哪些跳转。cURL 提供了 `-w` 格式化输出来获取关键信息:
```bash
# 查看最终到达的 URL
curl -L -w "Final URL: %{url_effective}\n" -o /dev/null -s http://example.com
# 查看总重定向次数
curl -L -w "Redirects: %{num_redirects}\n" -o /dev/null -s http://example.com
# 同时获取最终 URL、重定向次数和状态码
curl -L -w "\n最终URL: %{url_effective}\n重定向次数: %{num_redirects}\nHTTP状态: %{http_code}\n" \
-o /dev/null -s http://example.com
```
用 `-v` 配合 grep 可以查看每一跳的 Location 头:
```bash
curl -L -v http://example.com 2>&1 | grep -E "(< HTTP|< Location)"
```
## 跨域重定向与安全
cURL 在跨域重定向时的安全行为容易被忽视,也是面试加分项:
**敏感头不自动转发。** 当重定向到不同域名时,cURL 默认不会转发 `Authorization`、`Cookie` 等敏感请求头。这是安全设计——防止凭据意外泄露给第三方域名。
```bash
# 跨域重定向时,Authorization 不会发给新域名
curl -L -H "Authorization: Bearer token123" http://api.example.com/old
# 显式允许转发敏感头(仅在信任目标域名时使用)
curl -L --location-trusted -H "Authorization: Bearer token123" \
http://api.example.com/old
```
**Cookie 的 SameSite 限制。** 现代浏览器的 SameSite 策略会影响跨站重定向时的 Cookie 携带,但 cURL 作为命令行工具不受此限制。cURL 中 Cookie 的跨域行为完全由 `-c`(写入 cookie jar)和 `-b`(发送 cookie)参数控制:
```bash
# 手动管理跨域重定向的 Cookie
curl -L -c cookies.txt -b cookies.txt -v http://example.com
```
**限制重定向协议。** 安全场景下,可以阻止 HTTP→HTTPS 之外的重定向,防止降级攻击:
```bash
# 只允许重定向到 HTTPS
curl -L --proto-redir =https http://example.com
```
## 常见问题与排查
**重定向循环。** 服务端配置错误可能导致 A→B→A 的无限循环。用 `--max-redirs` 设置上限,观察 `-v` 输出中的循环跳转即可定位。
**POST 数据丢失。** 301/302 重定向后 POST 变 GET,请求体丢失。解决方案:使用 `--post301`/`--post302`,或让服务端返回 307/308。
**HTTPS 证书错误导致重定向失败。** 从 HTTP 重定向到 HTTPS 时,如果目标证书有问题会中断。开发环境可用 `-k` 跳过验证,生产环境必须修复证书。
```bash
# 开发环境临时跳过 SSL 验证
curl -L -k https://example.com
```
## 实战:完整的重定向感知请求
将关键参数组合使用,构建一个对重定向行为完全可控的请求:
```bash
curl -L --max-redirs 5 \
--post302 \
--proto-redir =https \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer token123" \
-d '{"action":"submit"}' \
-w "\n最终URL: %{url_effective}\n重定向次数: %{num_redirects}\nHTTP状态: %{http_code}\n" \
http://api.example.com/submit
```
这段命令同时控制了:跟随重定向(`-L`)、次数上限(`--max-redirs 5`)、POST 方法保持(`--post302`)、只允许重定向到 HTTPS(`--proto-redir =https`)、以及最终结果的格式化输出。
## 追问方向
- **cURL 能处理 HTML meta 重定向或 JavaScript 重定向吗?** 不能。cURL 只处理 HTTP 层的 3xx 响应,不解析 HTML 也不执行 JS。
- **`--location-trusted` 有什么安全风险?** 会将 Authorization 等敏感头转发给重定向目标域名,如果目标不可信(如开放重定向漏洞),凭据会被窃取。
- **重定向链中如何逐跳传递自定义头?** cURL 默认只对首次请求添加自定义头,重定向后的请求不保留。需要用 `-H` 配合 `--location-trusted` 或通过重定向后 URL 的路径判断手动处理。
服务端5月28日 01:54
cURL 的 -v、-i、-I、-s 参数有什么区别?cURL 是后端开发和运维中最常用的命令行工具之一,而 `-v`、`-i`、`-I`、`-s` 这四个参数控制着输出的内容和格式。很多人混用它们,实际上它们的输出目标、请求方式和适用场景完全不同。
### 一张图看清区别
| 参数 | 全称 | 请求方法 | 输出内容 | 输出目标 |
| ---- | -------------- | ----- | ------------- | ------ |
| `-v` | `--verbose` | 不改变 | 连接过程 + 请求头 + 响应头 | stderr |
| `-i` | `--include` | 不改变 | 响应头 + 响应体 | stdout |
| `-I` | `--head` | HEAD | 仅响应头 | stdout |
| `-s` | `--silent` | 不改变 | 仅响应体(无进度条) | stdout |
> 关键区别:`-v` 的输出写入 stderr,其余三个写入 stdout。这意味着 `-v` 不会干扰管道操作,而 `-i` 和 `-I` 的响应头会混入 stdout 数据流。
### 逐个拆解
#### -v:看懂完整的通信过程
```bash
curl -v https://api.example.com
```
输出长这样:
```
* Trying 93.184.216.34:443...
* Connected to api.example.com (93.184.216.34) port 443
* SSL connection using TLS_AES_256_GCM_SHA384
> GET / HTTP/1.1
> Host: api.example.com
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 42
<
{"status":"ok"}
```
输出符号的含义:
- `*` 开头 —— 连接和 TLS 握手信息
- `>` 开头 —— 发送给服务器的请求头
- `<` 开头 —— 服务器返回的响应头
**为什么 -v 写入 stderr 而不是 stdout?** 这是 curl 的设计哲学:stdout 留给实际数据(响应体),stderr 留给诊断信息。所以你可以这样用:
```bash
# 调试信息存文件,响应体正常输出
curl -v https://api.example.com 2>debug.log
# 管道不会被打断
curl -v https://api.example.com 2>/dev/null | jq .status
```
#### -i:响应头和响应体一起拿
```bash
curl -i https://api.example.com
```
输出:
```
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 42
Date: Mon, 01 Mar 2026 10:00:00 GMT
{"status":"ok"}
```
响应头和响应体之间用一个空行分隔。-i 不改变请求方法,该发 GET 还是 GET,该发 POST 还是 POST,只是把响应头也一并输出。
**注意**:因为响应头混入了 stdout,直接管道给 `jq` 会解析失败。解决办法:
```bash
# 用 -D - 把响应头写到 stderr,响应体单独给 jq
curl -s -D - https://api.example.com 2>/dev/null | jq .
```
#### -I:只看响应头,不发请求体
```bash
curl -I https://api.example.com
```
输出:
```
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 42
Date: Mon, 01 Mar 2026 10:00:00 GMT
ETag: "abc123"
Cache-Control: max-age=3600
```
`-I` 会把请求方法改成 HEAD。大多数服务器对 HEAD 请求和 GET 请求返回相同的响应头,但不是所有:
- 有些 CDN 对 HEAD 请求返回的 `Content-Length` 可能为 0
- 某些 API 网关对 HEAD 和 GET 的路由规则不同
- 少数服务器直接拒绝 HEAD 请求
如果需要 GET 请求的响应头,用这个代替:
```bash
curl -s -D - -o /dev/null https://api.example.com
```
#### -s:安静干活,只出结果
```bash
# 不显示进度条,只输出响应体
curl -s https://api.example.com
```
`-s` 关闭进度条和错误信息,但不会关闭响应体输出。在脚本里特别有用:
```bash
# 获取数据并解析
response=$(curl -s https://api.example.com/users)
echo "$response" | jq ".[] | .name"
```
**-s 的坑**:静默模式下连错误信息也被吞了。网络超时、DNS 解析失败都不会有任何提示。所以实际使用中,`-s` 几乎总是和 `-S` 搭配:
```bash
# 静默但保留错误输出
curl -sS https://api.example.com
```
### 常见组合技巧
```bash
# 检查 HTTP 状态码(脚本健康检查)
curl -s -o /dev/null -w "%{http_code}" https://api.example.com
# 调试 SSL 证书问题
curl -v https://api.example.com 2>&1 | grep -E "SSL|TLS|certificate"
# 静默拿响应头(实际发 GET 请求)
curl -s -D - -o /dev/null https://api.example.com
# 完整调试 + 保存日志
curl -v https://api.example.com -o response.json 2>debug.log
# 检查 CDN 缓存是否命中
curl -I -s https://cdn.example.com/image.jpg | grep -i "x-cache"
```
### 面试追问方向
**Q: -v 和 -i 都能看到响应头,有什么本质区别?**
A: 三个关键区别:(1)-v 同时显示请求头和响应头,-i 只显示响应头;(2)-v 还包含连接过程(DNS、TLS 握手),-i 只包含最终响应;(3)-v 写入 stderr,-i 写入 stdout,管道处理时行为完全不同。
**Q: 为什么 curl -I 有时候拿不到正确的 Content-Length?**
A: 因为 -I 发送的是 HEAD 请求,有些服务器对 HEAD 请求返回的 Content-Length 为 0 或者不返回该字段。如果需要 GET 请求的真实 Content-Length,应该用 `curl -s -D - -o /dev/null` 代替。
**Q: -sS 组合是什么意思?为什么不直接用 -s?**
A: -s 关闭了进度条和所有错误输出,包括网络超时、连接拒绝等重要信息。-S(--show-error)在 -s 模式下重新启用错误输出。脚本中几乎总是需要 -sS 而非单独 -s,否则故障排查时完全不知道请求为什么失败。
### 速查表
| 你想做什么 | 用什么参数 |
| ---------------- | ------------------ |
| 排查请求为什么没发出去 | `-v` |
| 看服务器返回了什么响应头 | `-i` 或 `-D -` |
| 只检查状态码或缓存头 | `-I` 或 `-I -s` |
| 脚本里安静拿数据 | `-sS` |
| 获取 HTTP 状态码 | `-s -o /dev/null -w "%{http_code}"` |
| 保存调试日志 | `-v 2>debug.log` |服务端5月28日 01:53
cURL 和 wget 有什么区别?cURL 和 wget 是 Linux 系统中最常用的两个命令行网络工具,面试中经常被放在一起考察。很多候选人只能说出"cURL 能测 API,wget 能递归下载",但这远远不够。这道题的真正考点在于理解两个工具的**设计哲学差异**以及**底层架构不同**如何决定了它们各自的适用场景。
### 一句话概括核心区别
**cURL 是网络传输的瑞士军刀,wget 是文件下载的可靠管家。**
cURL 的设计目标是做一个通用的数据传输工具——它不关心你传什么、怎么传,只管把数据从 A 搬到 B。wget 的设计目标是可靠地下载文件——网络断了自动重试,链接坏了自动转换,一切都为了让文件安稳落地。
这个底层设计哲学的差异,直接导致了两者在协议支持、功能特性和使用场景上的全面分歧。
### 核心差异对比
| 维度 | cURL | wget |
|------|------|------|
| 设计目标 | 通用数据传输 | 可靠文件下载 |
| 底层架构 | 基于 libcurl 库 | 独立程序,无库形式 |
| 协议支持 | 20+ 种(HTTP/1.1、HTTP/2、HTTP/3、FTP、SFTP、SCP、SMTP、LDAP 等) | HTTP/HTTPS/FTP |
| 数据方向 | 双向(上传 + 下载) | 单向(仅下载) |
| 递归下载 | 不支持 | 支持,可镜像整站 |
| 并行传输 | 支持(7.66+ 版本 `-Z` 参数) | 不支持 |
| 后台下载 | 需配合 nohup | 原生支持 `-b` |
| 输出方式 | 默认输出到 stdout | 默认保存为文件 |
| 重定向处理 | 需手动加 `-L` | 自动跟随 |
| 库集成 | libcurl 可嵌入应用程序 | 无库形式,无法嵌入 |
### 协议支持:为什么 cURL 能做 wget 做不了的事
cURL 支持超过 20 种协议,这意味着它不仅是 HTTP 客户端,还能发送邮件、操作 LDAP 目录、通过 SFTP/SCP 传输文件:
```bash
# 通过 SMTP 发送邮件
curl smtp://mail.example.com --mail-from sender@test.com --mail-rcpt receiver@test.com -T mail.txt
# SFTP 上传文件(wget 完全不支持上传)
curl -T backup.tar.gz sftp://user@host.example.com/backup/
# 查询 LDAP 目录
curl ldap://ldap.example.com/cn=admin,dc=example,dc=com
```
wget 只支持 HTTP/HTTPS/FTP 三种协议。这不是偷懒,而是设计上的取舍——专注做好下载这一件事。
### 递归下载:wget 的杀手级特性
递归下载是 wget 独有的能力,也是它和 cURL 最大的功能分界线:
```bash
# 镜像整个网站(最常用的递归下载场景)
wget --mirror --convert-links --adjust-extension --page-requisites --no-parent https://example.com/docs/
# 从文件列表批量下载
wget -i download-urls.txt
# 限定深度递归
wget --recursive --level=2 --no-parent https://example.com/data/
```
cURL 无法递归下载。如果你需要对网站做离线备份、数据抓取或批量归档,wget 是唯一的选择。
### API 交互:cURL 的主场
cURL 在 API 开发测试领域几乎是事实标准。大多数 API 文档的示例代码都用 cURL 编写,这不是偶然:
```bash
# 发送 JSON 请求
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9" -d '{"name":"test","role":"admin"}'
# 只看响应头(调试必备)
curl -I https://api.example.com/health
# 提取 HTTP 状态码(脚本判断用)
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/status
```
wget 虽然也能发 POST 请求(`--post-data`),但不支持自定义请求方法、无法设置复杂 Header、不能方便地查看响应细节,在实际 API 调试中几乎不可用。
### 底层架构差异:libcurl vs 独立程序
这是面试中能拉开差距的知识点。
**cURL 基于 libcurl 库**。libcurl 是一个成熟的跨平台 C 库,提供了稳定的 API 接口,几乎所有主流编程语言都有对应的绑定(Python 的 pycurl、PHP 的 curl 扩展、Node.js 的 node-libcurl 等)。这意味着你在代码中用这些库发 HTTP 请求时,底层跑的就是 libcurl,和命令行的 cURL 是同一套实现。
**wget 没有库形式**。它是一个独立的命令行程序,无法嵌入到应用程序中。如果你需要在代码中实现下载功能,要么自己写 HTTP 逻辑,要么用 libcurl。
```bash
# 验证:Python 的 requests 库底层不是 libcurl
# 但 pycurl 是 libcurl 的 Python 绑定
import pycurl # 底层就是 libcurl
```
这个架构差异也解释了为什么 Docker 官方镜像和大多数 CI/CD 环境预装的是 cURL 而不是 wget——因为很多构建工具依赖 libcurl。
### 现代特性对比
cURL 在持续演进,许多现代特性 wget 尚不支持:
| 特性 | cURL | wget |
|------|------|------|
| HTTP/2 | 支持(`--http2`) | 不支持 |
| HTTP/3 (QUIC) | 支持(`--http3`) | 不支持 |
| 并行传输 | 支持(`-Z`) | 不支持 |
| 自动解压 | gzip/brotli/zstd | gzip |
| SOCKS 代理 | SOCKS4/SOCKS5 | 不支持 |
| 多种认证 | Basic/Digest/NTLM/AWS v4 | Basic/Digest |
### 生产环境中的常见坑
**坑 1:cURL 不自动跟随重定向**
很多 API 会返回 301/302 重定向,cURL 默认不会跟随,导致你以为请求失败了:
```bash
# 错误:返回 301 但不跳转
curl https://example.com/old-api
# 正确:加 -L 跟随重定向
curl -L https://example.com/old-api
```
wget 默认自动跟随重定向,不需要额外参数。
**坑 2:wget 下载大文件磁盘不够**
wget 默认行为是直接把响应写入文件,如果磁盘空间不足会下载失败且留下不完整的文件:
```bash
# 先检查文件大小再决定是否下载
wget --spider https://example.com/huge-file.zip 2>&1 | grep Length
```
**坑 3:cURL 在脚本中不检查 HTTP 错误码**
cURL 默认不会因为 4xx/5xx 而返回非零退出码,这在脚本中容易漏掉错误:
```bash
# 错误:脚本不会因 404 而中断
curl https://api.example.com/not-exist
# 正确:加 --fail 让 4xx/5xx 返回非零退出码
curl --fail https://api.example.com/not-exist
```
**坑 4:Docker 构建中用 wget 代替 cURL**
某些精简 Docker 镜像没有预装 cURL,有人会改用 wget 测试连通性。但 wget 的输出格式不同,且不支持 `-I`(只看头部),推荐在 Dockerfile 中安装 cURL:
```dockerfile
RUN apt-get update && apt-get install -y curl --no-install-recommends
```
### 面试回答模板
**简短版(30 秒):**
cURL 是通用网络传输工具,支持 20+ 协议,基于 libcurl 库可嵌入应用,擅长 API 交互和双向数据传输;wget 是专注文件下载的工具,支持递归下载和网站镜像,擅长批量下载和后台任务。选 cURL 做接口调试和脚本自动化,选 wget 做文件下载和站点归档。
**详细版(2 分钟):**
在简短版基础上补充三点:一是底层架构差异,cURL 基于 libcurl 库可被程序调用,wget 是独立程序无法嵌入;二是现代特性,cURL 已支持 HTTP/2、HTTP/3 和并行传输,wget 在协议层面更新较慢;三是常见陷阱,比如 cURL 不自动跟随重定向需要加 `-L`,以及 `--fail` 参数在脚本中的重要性。
### 选择决策速查
| 场景 | 推荐 | 原因 |
|------|------|------|
| API 开发调试 | cURL | 方法/头部/认证灵活可控 |
| 简单文件下载 | 两者皆可 | wget 更直觉,cURL 更可控 |
| 网站离线镜像 | wget | 递归下载能力不可替代 |
| 多协议传输 | cURL | 支持 SFTP/SCP/SMTP 等 |
| 脚本自动化 | cURL | 输出灵活,适合管道处理 |
| 批量下载 | wget | `-i` 参数支持从文件读 URL |
| Docker/CI 环境 | cURL | 预装率高,libcurl 被广泛依赖 |
| 后台无人值守下载 | wget | `-b` 原生后台支持 |
实际工作中两者都应掌握。cURL 更适合开发者的日常交互,wget 更适合运维的批量下载场景。如果只能装一个,选 cURL——它的能力范围更广,且 libcurl 是很多工具链的底层依赖。服务端5月28日 01:52
cURL 如何配置和使用代理服务器?代理是 cURL 在企业网络和调试场景中的核心能力。掌握代理配置,才能在内网受限、抓包分析、隐私保护等场景下灵活使用 cURL。
### 代理的工作原理
cURL 通过代理服务器中转请求,流程是:cURL → 代理服务器 → 目标服务器。HTTP 代理对 HTTP 请求直接转发,对 HTTPS 请求使用 CONNECT 方法建立隧道;SOCKS 代理则在传输层转发流量,不关心上层协议。
理解这点很重要:**HTTP 代理转发 HTTPS 时,代理看不到请求内容**,因为它只建立了一条加密隧道。这也是为什么 HTTPS 代理和 SOCKS5h 更适合隐私敏感场景。
### 基本代理设置
```bash
# HTTP 代理
curl -x http://proxy.example.com:8080 https://api.example.com
curl --proxy http://proxy.example.com:8080 https://api.example.com
# HTTPS 代理(代理连接本身加密)
curl -x https://proxy.example.com:443 https://api.example.com
# SOCKS5 代理
curl -x socks5://proxy.example.com:1080 https://api.example.com
# SOCKS5h(DNS 由代理服务器解析,防止 DNS 泄露)
curl -x socks5h://proxy.example.com:1080 https://api.example.com
```
`-x` 和 `--proxy` 完全等价。socks5 和 socks5h 的区别在于 DNS 解析位置:前者由本地解析,后者由代理服务器解析。如果你在意隐私,始终优先用 socks5h。
### 代理类型怎么选
| 代理类型 | 协议格式 | 核心区别 | 适用场景 |
| -------- | -------- | -------- | -------- |
| HTTP | http:// | 明文连接代理,HTTPS 请求走 CONNECT 隧道 | 企业缓存代理、简单转发 |
| HTTPS | https:// | 到代理的连接加密 | 需要加密代理通信的场景 |
| SOCKS4 | socks4:// | 仅 TCP 转发,无认证 | 旧系统兼容 |
| SOCKS5 | socks5:// | 支持 UDP 和多种认证方式 | 通用代理 |
| SOCKS5h | socks5h:// | DNS 由代理端解析 | 隐私保护、防 DNS 泄露 |
选型建议:日常开发用 HTTP 代理即可;需要 UDP 或认证选 SOCKS5;隐私场景选 SOCKS5h。
### 代理认证
代理服务器通常需要身份验证。cURL 支持三种认证方式:
```bash
# 方式一:在代理 URL 中嵌入凭据
curl -x http://user:password@proxy.example.com:8080 https://api.example.com
# 方式二:用 -U 单独指定凭据
curl -x http://proxy.example.com:8080 -U user:password https://api.example.com
# NTLM 认证(Windows 域环境常见)
curl -x http://proxy.example.com:8080 --proxy-ntlm -U user:password https://api.example.com
# Digest 认证
curl -x http://proxy.example.com:8080 --proxy-digest -U user:password https://api.example.com
```
注意:`-U` 方式下密码会出现在进程列表中。在共享服务器上,优先用 `~/.curlrc` 或环境变量 `CURL_PROXY_USER` 来避免密码暴露。
### 环境变量与持久化配置
每次手动指定代理很繁琐,cURL 会自动读取以下环境变量:
```bash
# 设置代理环境变量
export http_proxy="http://proxy.example.com:8080"
export https_proxy="http://proxy.example.com:8080"
export no_proxy="localhost,127.0.0.1,.example.com"
# 设置后直接使用,无需 -x 参数
curl https://api.example.com
# 临时忽略环境变量
curl --noproxy "*" https://api.example.com
```
小写变量名(http_proxy)是通用约定,大写(HTTP_PROXY)部分工具也会读取。`no_proxy` 指定不走代理的域名,支持通配符。
更持久的方案是写入配置文件:
```bash
# ~/.curlrc
proxy = "http://proxy.example.com:8080"
proxy-user = "username:password"
noproxy = "localhost,127.0.0.1"
```
写入后所有 cURL 请求默认走代理。需要临时跳过时用 `curl -q`(忽略 .curlrc)或 `--noproxy`。
### 代理绕过
并非所有请求都需要走代理。内网地址、本地服务应该直连:
```bash
# 绕过指定域名
curl --noproxy "localhost,127.0.0.1,internal.example.com" \
-x http://proxy.example.com:8080 \
https://api.example.com
# 绕过所有代理(直连)
curl --noproxy "*" https://api.example.com
```
`--noproxy` 的值支持逗号分隔的域名列表,也支持 `.example.com` 这样的域名后缀匹配。
### 代理调试技巧
代理不工作时,按以下步骤排查:
```bash
# 第一步:查看完整的连接过程
curl -v -x http://proxy.example.com:8080 https://api.example.com 2>&1 | grep -i proxy
# 第二步:测试代理本身是否可达
curl -v -x http://proxy.example.com:8080 http://www.google.com
# 第三步:通过代理查看出口 IP(确认代理生效)
curl -x http://proxy.example.com:8080 https://api.ipify.org
```
如果 `-v` 输出中看到 `Connected to proxy.example.com` 说明到代理的连接成功;如果卡在 `Proxy auth required` 说明认证问题;如果完全没有 proxy 相关输出,检查环境变量是否被正确加载。
### 高级配置
**代理隧道**:HTTP 代理默认用 CONNECT 方法为 HTTPS 建立隧道。某些旧代理不支持 CONNECT,可以显式指定:
```bash
curl -x http://proxy.example.com:8080 --proxy-tunnel https://api.example.com
```
**自定义代理请求头**:某些代理会校验 User-Agent,可以通过 `--proxy-header` 添加:
```bash
curl --proxy-header "User-Agent: MyApp/1.0" \
-x http://proxy.example.com:8080 \
https://api.example.com
```
**代理 TLS 配置**:HTTPS 代理可能要求特定的 TLS 版本或证书:
```bash
# 指定 TLS 版本
curl -x https://proxy.example.com:443 --proxy-tlsv1.2 https://api.example.com
# 指定代理 CA 证书
curl -x https://proxy.example.com:443 --proxy-cacert /path/to/proxy-ca.crt https://api.example.com
# 跳过代理证书验证(仅调试用)
curl -x https://proxy.example.com:443 --proxy-insecure https://api.example.com
```
### 实战场景
**场景一:企业内网访问外部 API**
企业网络通常有统一出口代理,需要域账号认证:
```bash
curl -x http://corporate-proxy.company.com:8080 \
--proxy-ntlm \
-U "COMPANY\\username:password" \
--noproxy "localhost,127.0.0.1,*.internal.company.com" \
https://api.github.com/user
```
注意 Windows 域用户名中的反斜杠需要转义为 `\\`。`--noproxy` 确保内网地址不走代理。
**场景二:用 SSH 隧道做临时代理**
没有代理服务器时,可以借助远程机器创建 SOCKS 代理:
```bash
# 先在本地建立 SSH 隧道(后台运行)
ssh -D 1080 -f -C -q -N user@remote-server
# 然后通过隧道访问
curl -x socks5h://localhost:1080 https://api.example.com
```
`-D 1080` 在本地 1080 端口开 SOCKS 代理,`-f -C -q -N` 让 SSH 在后台压缩静默运行。用完后 `kill` 对应 SSH 进程即可。
**场景三:配合抓包工具调试**
Charles 或 Fiddler 本质上是 HTTP 代理,默认监听 8888 端口:
```bash
curl -x http://localhost:8888 -k https://api.example.com
```
`-k` 跳过证书验证,因为抓包工具使用自签证书。调试 HTTPS 请求时这一步必不可少。
### 常见问题
**代理连接超时**
默认超时可能太短,特别是跨地域代理。增加超时:
```bash
curl -x http://proxy.example.com:8080 --connect-timeout 30 --max-time 60 https://api.example.com
```
**HTTPS 通过 HTTP 代理失败**
正常情况下 HTTP 代理会自动用 CONNECT 方法处理 HTTPS。如果失败,检查代理是否禁止了 CONNECT,或者显式启用隧道:
```bash
curl -x http://proxy.example.com:8080 --proxy-tunnel https://api.example.com
```
**DNS 泄露**
使用 socks5 代理时,DNS 请求仍在本地发出,可能暴露访问意图。换成 socks5h 让代理端解析 DNS:
```bash
curl -x socks5h://proxy.example.com:1080 https://api.example.com
```
**代理认证失败**
先确认认证类型。大多数代理用 Basic 认证,企业环境可能用 NTLM 或 Digest。用 `-v` 查看代理返回的 `Proxy-Authenticate` 头,确定认证方式后再加对应参数。服务端5月28日 01:27
cURL 中 -d 和 --data 有什么区别?## -d 和 --data 是什么关系?
`-d` 和 `--data` 是同一个参数的短格式和长格式,功能完全等价,没有任何行为差异。
```bash
# 下面两条命令完全等价
curl -d "name=value" https://api.example.com
curl --data "name=value" https://api.example.com
```
cURL 的大多数参数都有这种短/长格式对应关系,比如 `-X` 和 `--request`、`-H` 和 `--header`。选择哪种写法纯属个人偏好:命令行简短操作用 `-d`,脚本中为了可读性用 `--data`。
## cURL 有哪些数据发送方式?
`-d` 只是 cURL 数据发送家族中的一个。不同参数对应不同的数据处理逻辑和 Content-Type:
| 参数 | 数据处理方式 | 默认 Content-Type | 典型场景 |
|------|------------|-------------------|----------|
| `-d` / `--data` | 发送数据,`@` 读取文件并去除回车换行 | application/x-www-form-urlencoded | 表单提交、API 调用 |
| `--data-ascii` | 与 `-d` 完全相同 | application/x-www-form-urlencoded | 明确语义时使用 |
| `--data-binary` | 发送原始数据,`@` 读取文件不做任何处理 | 不自动设置 | 二进制文件、保留换行的文本 |
| `--data-urlencode` | 自动对特殊字符做 URL 编码 | application/x-www-form-urlencoded | 参数含空格或特殊字符 |
| `--data-raw` | 原样发送,`@` 不被解析为文件路径 | application/x-www-form-urlencoded | 数据内容本身含 `@` 符号 |
| `-F` / `--form` | multipart/form-data 编码 | multipart/form-data | 文件上传、混合表单 |
| `--json` | 发送 JSON,自动设置 Content-Type | application/json | REST API JSON 交互 |
## -d 和 -F 的核心区别是什么?
这是面试高频追问点。`-d` 发送的是整体编码的请求体,`-F` 发送的是分段编码的请求体:
```bash
# -d:整个请求体作为一个 URL 编码字符串
curl -d "name=张三&age=25" https://api.example.com/form
# 请求体:name=张三&age=25
# Content-Type: application/x-www-form-urlencoded
# -F:每个字段单独编码,用 boundary 分隔
curl -F "name=张三" -F "file=@photo.jpg" https://api.example.com/upload
# Content-Type: multipart/form-data; boundary=----WebKitFormBoundary...
```
简单记忆:纯键值对用 `-d`,涉及文件上传用 `-F`。两者不能混用——如果同时指定,cURL 只会使用后出现的那个。
## 如何发送 JSON 数据?
用 `-d` 配合手动设置 Content-Type,或者直接用 `--json`(cURL 7.82.0+):
```bash
# 方式一:-d + 手动设置 Header(兼容性最好)
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","age":25}'
# 方式二:--json(7.82.0+,自动设置 Content-Type)
curl --json '{"name":"张三","age":25}' https://api.example.com/users
```
注意:`-d` 不会自动转义或编码 JSON 中的特殊字符。如果 JSON 数据来自用户输入或文件,务必确保格式合法:
```bash
# 从文件读取 JSON
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d @payload.json
```
## --data-binary 和 -d 读取文件有什么不同?
这是另一个容易踩坑的点。当用 `@` 从文件读取数据时,`-d` 会静默去除回车符和换行符,`--data-binary` 则原样发送:
```bash
# 准备一个包含换行的文件
# data.txt 内容:
# line1
# line2
# line3
# -d 读取文件:换行被去除,数据变成 "line1line2line3"
curl -d @data.txt https://api.example.com
# --data-binary 读取文件:换行完整保留
curl --data-binary @data.txt https://api.example.com
```
发送 JSON、XML、二进制文件时,永远用 `--data-binary` 而不是 `-d`,否则数据可能被静默破坏。
## --data-urlencode 和 --data-raw 什么时候用?
```bash
# --data-urlencode:参数值含空格或特殊字符时
curl --data-urlencode "query=hello world!" https://api.example.com/search
# 实际发送:query=hello%20world%21
# --data-raw:数据内容本身包含 @ 符号,不想被解释为文件路径
curl --data-raw "@channel notify" https://api.example.com/webhook
# 发送的就是字面量 @channel notify,不会去找叫 channel 的文件
```
## 多个 -d 参数如何处理?
多次使用 `-d` 时,cURL 会用 `&` 将各段拼接起来:
```bash
curl -d "name=张三" -d "email=zhangsan@example.com" https://api.example.com/form
# 等价于
curl -d "name=张三&email=zhangsan@example.com" https://api.example.com/form
```
也可以混合使用不同数据参数,但要注意各自的编码规则:
```bash
curl -d "title=测试" \
--data-urlencode "content=Hello World!" \
--data-binary @attachment.pdf \
https://api.example.com/mixed
```
## 常见踩坑点
**1. 使用 `-d` 就隐含了 POST 请求**
`-d` 会自动将请求方法设为 POST,不需要额外写 `-X POST`。如果你同时写了 `-X GET -d "data"`,cURL 会发送一个带 body 的 GET 请求——这在大多数服务端会被忽略或拒绝。
**2. Content-Type 需要自己管**
`-d` 默认设置 `application/x-www-form-urlencoded`。如果你要发 JSON 却忘记加 `-H "Content-Type: application/json"`,服务端可能按表单解析导致 400 错误。
**3. @ 符号的双重含义**
`-d @filename` 读取文件内容,`-d @-` 从标准输入读取。如果你的数据本身包含 `@`,必须用 `--data-raw`。
**4. -d 和 -F 不能混用**
一个请求中只能选一种数据提交方式。如果两者都写了,cURL 只认后出现的那个,前面的会被静默忽略。