curl 深度教程 / 第 15 章:最佳实践
第 15 章:最佳实践
经过 14 章的学习,本章为你总结 curl 在安全、性能、脚本化和日常工作中的最佳实践,以及常见陷阱和生产力技巧。
15.1 安全最佳实践
1. 永远使用 HTTPS
# ❌ 危险:明文传输
curl http://api.example.com/login -d "user=admin&pass=secret"
# ✅ 安全:使用 HTTPS
curl https://api.example.com/login -d "user=admin&pass=secret"
# 检查是否被重定向到 HTTPS
curl -sI http://example.com | grep -i "location.*https"
2. 不要在命令行中暴露密码
# ❌ 危险:密码出现在进程列表和历史记录中
curl -u admin:MySecretPassword https://api.example.com/admin
# ✅ 方式 1:交互式输入
curl -u admin https://api.example.com/admin
# curl 会提示输入密码,密码不会回显
# ✅ 方式 2:环境变量
export API_PASSWORD="secret"
curl -u "admin:$API_PASSWORD" https://api.example.com/admin
unset API_PASSWORD
# ✅ 方式 3:.netrc 文件
curl --netrc https://api.example.com/admin
# ~/.netrc: machine api.example.com login admin password secret
chmod 600 ~/.netrc
# ✅ 方式 4:配置文件
echo '--user admin:secret' > ~/.curl-secrets
chmod 600 ~/.curl-secrets
curl -K ~/.curl-secrets https://api.example.com/admin
# ✅ 方式 5:从密码管理器读取
TOKEN=$(op read "op://vault/api-token/password")
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/data
# 防止密码出现在历史记录中
export HISTCONTROL=ignorespace
curl -u admin:secret https://api.example.com # 前导空格不记录
3. 不要使用 -k(–insecure)
# ❌ 危险:完全禁用证书验证
curl -k https://api.example.com/data
# ✅ 正确:使用自签名证书时指定 CA
curl --cacert ca.crt https://dev.example.com/api
# ✅ 正确:导入 CA 到系统信任存储
sudo cp ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
curl https://dev.example.com/api
# 仅在测试代码中使用 -k,并添加注释
curl -k https://self-signed-dev.example.com/test # FIXME: 开发环境临时使用
4. 验证服务器证书和主机名
# curl 默认验证证书,但可以显式加强配置
# 强制 TLS 1.2+
curl --tlsv1.2 https://api.example.com
# 证书固定(防止 CA 被攻破后的中间人攻击)
curl --pinnedpubkey="sha256//YhKJG1O3TjMmDj..." https://api.example.com
# 验证证书中包含特定域名
curl -v https://api.example.com 2>&1 | grep "subject:"
5. 限制敏感响应不被记录
# 避免将 Token 和敏感数据写入日志文件
# ✅ 使用 -o 将响应体写入文件,而非 stdout
curl -s -H "Authorization: Bearer $TOKEN" \
https://api.example.com/sensitive \
-o response.json
# ✅ 在调试时过滤敏感信息
curl -v https://api.example.com/data 2>&1 \
| sed 's/Authorization: .*/Authorization: [REDACTED]/' \
| sed 's/Cookie: .*/Cookie: [REDACTED]/'
6. 限制下载大小和响应内容
# 限制最大下载大小(防止恶意大文件攻击)
curl --max-filesize 10485760 https://example.com/download
# 限制为 10MB
# 设置总超时(防止挂起)
curl --max-time 30 https://api.example.com/data
# 防止重定向到恶意站点(限制重定向次数)
curl -L --max-redirs 5 https://example.com
# 不跟随重定向到非 HTTPS
# 通过脚本检查 Location 头
REDIRECT=$(curl -sI http://example.com | grep -i location | awk '{print $2}' | tr -d '\r')
if echo "$REDIRECT" | grep -q "^https://"; then
curl -L "$REDIRECT"
else
echo "拒绝跟随非 HTTPS 重定向"
exit 1
fi
15.2 性能最佳实践
1. 复用连接
# curl 默认在同一进程中对同一主机复用 TCP 连接
# 使用 --next 串联多个请求到同一连接
curl https://api.example.com/users/1 \
--next https://api.example.com/users/2 \
--next https://api.example.com/users/3
# 避免不必要的连接重置
# ❌ 强制关闭连接
curl -H "Connection: close" https://api.example.com/data
# ✅ 让连接自然复用(默认行为)
curl https://api.example.com/data
2. 启用压缩
# 请求压缩响应(减少传输数据量)
curl --compressed https://example.com
# 等价于
curl -H "Accept-Encoding: gzip, deflate, br" https://example.com
3. 使用 HTTP/2
# HTTP/2 提供头部压缩和多路复用
curl --http2 https://api.example.com/data
# 验证是否使用了 HTTP/2
curl -sI --http2 https://api.example.com 2>&1 | head -1
# HTTP/2 200 → 成功
# HTTP/1.1 200 → 服务器不支持 H2
4. DNS 优化
# 使用 --resolve 绕过 DNS 查询(固定 IP 测试)
curl --resolve example.com:443:93.184.216.34 https://example.com
# 使用公共 DNS 加速解析(在 resolv.conf 或 NetworkManager 中配置)
# /etc/resolv.conf:
# nameserver 8.8.8.8
# nameserver 1.1.1.1
5. 并行请求
# 使用 xargs 并行执行多个 curl
cat urls.txt | xargs -n 1 -P 10 curl -sO
# 使用 GNU parallel(更灵活)
cat urls.txt | parallel -j 10 curl -sS {} -o "{#}.json"
# 使用 --next 与后台进程
for id in $(seq 1 20); do
curl -s "https://api.example.com/users/$id" -o "user_$id.json" &
done
wait
6. 限制速率以避免触发限流
# 控制发送速率
curl --limit-rate 1m -O https://example.com/largefile.zip
# 脚本中控制 QPS
while IFS= read -r url; do
curl -sO "$url" &
sleep 0.1 # 约 10 QPS
done < urls.txt
wait
15.3 脚本化最佳实践
1. 使用 set -euo pipefail
#!/bin/bash
set -euo pipefail
# set -e:命令失败立即退出
# set -u:使用未定义变量报错
# set -o pipefail:管道中任意命令失败则整个管道失败
TOKEN="${API_TOKEN:?API_TOKEN 未设置}"
BASE_URL="${BASE_URL:-https://api.example.com}"
# 使用函数封装错误处理
api_get() {
local url="$1"
local response
local http_code
response=$(curl -sS -w "\n%{http_code}" \
--connect-timeout 10 --max-time 30 \
-H "Authorization: Bearer $TOKEN" \
"$url") || {
echo "curl 请求失败: $url" >&2
return 1
}
http_code=$(echo "$response" | tail -1)
local body=$(echo "$response" | sed '$d')
if [[ ! "$http_code" =~ ^2 ]]; then
echo "API 错误 ($http_code): $url" >&2
echo "$body" >&2
return 1
fi
echo "$body"
}
USERS=$(api_get "$BASE_URL/users")
echo "$USERS" | jq '.[] | .name'
2. 使用 jq 安全构建 JSON
# ❌ 不安全:字符串拼接
curl -d "{\"name\": \"$NAME\", \"msg\": \"$MSG\"}" https://api.example.com
# ✅ 安全:使用 jq 正确转义
DATA=$(jq -n \
--arg name "$NAME" \
--arg msg "$MSG" \
'{name: $name, message: $msg}')
curl -d "$DATA" https://api.example.com
3. 检查依赖
#!/bin/bash
# 检查必要工具
for cmd in curl jq; do
if ! command -v "$cmd" &>/dev/null; then
echo "错误: 需要安装 $cmd" >&2
exit 1
fi
done
4. 使用 -w 进行请求统计
# 记录每次请求的性能数据
log_request() {
curl -o /dev/null -s \
-w '{"url":"%{url_effective}","code":%{http_code},"time":%{time_total},"size":%{size_download}}\n' \
"$@" >> requests.jsonl
}
log_request https://api.example.com/users
log_request https://api.example.com/orders
5. 处理超时和重试
# 可靠的请求封装
resilient_curl() {
curl -sS \
--connect-timeout 10 \
--max-time 60 \
--retry 3 \
--retry-delay 2 \
--retry-max-time 120 \
--retry-connrefused \
--retry-all-errors \
"$@"
}
15.4 常见陷阱
陷阱 1:URL 中的特殊字符
# ❌ & 被 shell 解释为后台运行
curl https://api.example.com/data?page=1&size=10
# ✅ 用引号包裹
curl "https://api.example.com/data?page=1&size=10"
# ❌ # 被 shell 解释为注释
curl https://example.com/page#section
# ✅ 用引号包裹
curl "https://example.com/page#section"
# ❌ ! 被 bash 历史展开
curl https://example.com/search?q=!important
# ✅ 使用单引号
curl 'https://example.com/search?q=!important'
陷阱 2:-d 自动设置 POST 方法
# ❌ 冗余写法(-d 已经隐含了 POST)
curl -X POST -d "data=value" https://api.example.com
# ✅ 简洁写法
curl -d "data=value" https://api.example.com
# ⚠️ 但如果用 PUT/PATCH 则必须显式指定
curl -X PUT -d "data=value" https://api.example.com/resource/1
curl -X PATCH -d "data=value" https://api.example.com/resource/1
陷阱 3:stdout vs stderr
# -v 输出到 stderr,响应体输出到 stdout
curl -v https://example.com > body.txt 2> debug.log
# body.txt 包含响应体
# debug.log 包含请求/响应头
# 如果混在一起
curl -v https://example.com > all.log 2>&1
# all.log 包含所有内容
# ❌ 错误:尝试将 -v 输出重定向到 stdout
curl -v https://example.com 2>&1 > file.txt # 无效!2>&1 必须在后面
# ✅ 正确
curl -v https://example.com > file.txt 2>&1
陷阱 4:-O 与 URL 路径
# -O 使用 URL 最后部分作为文件名
curl -O https://example.com/path/to/file.zip
# 保存为 file.zip(不是 path/to/file.zip)
# ❌ URL 没有明确文件名
curl -O https://api.example.com/users/42
# 保存为 42(不是你想要的)
# ✅ 使用 -o 指定文件名
curl -o user_42.json https://api.example.com/users/42
陷阱 5:POST 二进制数据
# ❌ -d 会破坏二进制数据
curl -X POST https://api.example.com/upload -d @image.png
# ✅ 使用 --data-binary
curl -X POST https://api.example.com/upload --data-binary @image.png
# ✅ 使用 -F 上传文件(multipart/form-data)
curl -F "file=@image.png" https://api.example.com/upload
陷阱 6:Cookie 不跨请求保持
# ❌ 每个 curl 命令都是独立的,Cookie 不会自动保持
curl -X POST https://example.com/login -d "user=admin&pass=secret"
curl https://example.com/dashboard # 不会带 Cookie!
# ✅ 使用 -b 和 -c 管理 Cookie
curl -c cookies.txt -X POST https://example.com/login \
-d "user=admin&pass=secret"
curl -b cookies.txt https://example.com/dashboard
陷阱 7:JSON 中的 shell 变量
# ❌ 危险:变量包含引号时会破坏 JSON
NAME="O'Brien"
curl -d "{\"name\": \"$NAME\"}" https://api.example.com
# JSON 语法错误!
# ✅ 安全:使用 jq
DATA=$(jq -n --arg name "$NAME" '{name: $name}')
curl -d "$DATA" https://api.example.com
15.5 生产力技巧
技巧 1:创建 curl 别名
# ~/.bashrc 或 ~/.zshrc
alias api='curl -sS --connect-timeout 10 --max-time 30'
alias api-json='curl -sS -H "Accept: application/json" | jq .'
alias api-post='curl -sS -X POST -H "Content-Type: application/json"'
alias api-token='curl -sS -H "Authorization: Bearer $API_TOKEN"'
# 使用
api https://api.example.com/users
api-json https://api.example.com/users
api-post -d '{"name":"test"}' https://api.example.com/users
api-token https://api.example.com/admin/users
技巧 2:使用配置文件组织常用选项
# ~/.curlrc(全局配置)
-A "Mozilla/5.0 (compatible; curl/8)"
--connect-timeout 10
--max-time 60
--compressed
-L
# project/.curlrc(项目级配置)
-H "Accept: application/json"
-H "Authorization: Bearer eyJ..."
-sS
技巧 3:使用 –next 连续请求
# 获取列表,然后获取详情
curl -s https://api.example.com/users \
| jq -r '.[].id' \
| head -5 \
| while read id; do
echo "--- 用户 $id ---"
curl -s "https://api.example.com/users/$id" | jq '{name, email}'
done
技巧 4:快速生成 HTTP 请求记录
# 将 curl 命令转为可复现的格式
curl -v https://api.example.com/data \
-H "Authorization: Bearer token" \
-d '{"key":"value"}' \
2>&1 | tee request.log
# 从日志生成可分享的 curl 命令
# 使用 curl --libcurl 生成 C 代码
curl --libcurl code.c https://api.example.com/data
技巧 5:管道组合常用操作
# 下载 + 解压 + 执行
curl -sL https://example.com/install.sh | bash
# 下载 + 验证 + 解压
curl -sLO https://example.com/app.tar.gz && \
echo "checksum app.tar.gz" | sha256sum -c - && \
tar xzf app.tar.gz
# API 调用 + 格式化输出
curl -s https://api.example.com/users | jq '.[] | {id, name, email}' -C
# 并行获取多个 API
curl -s https://api.example.com/users | jq -r '.[].id' | \
xargs -P 5 -I {} curl -s "https://api.example.com/users/{}"
技巧 6:使用环境变量文件
# .env 文件
API_BASE_URL=https://api.example.com
API_TOKEN=eyJhbGc...
API_VERSION=v2
# 加载环境变量
set -a; source .env; set +a
# 使用环境变量
curl -H "Authorization: Bearer $API_TOKEN" \
"$API_BASE_URL/$API_VERSION/users"
技巧 7:curl 作为简易 HTTP 客户端
# 下载 GitHub Release 资产
REPO="owner/repo"
TAG="v1.0.0"
ASSET="app-linux-amd64"
curl -sL "https://api.github.com/repos/$REPO/releases/tags/$TAG" \
| jq -r ".assets[] | select(.name | contains(\"$ASSET\")) | .browser_download_url" \
| xargs curl -LO
# 查询 GitHub API
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/owner/repo/pulls?state=open" \
| jq '.[] | {title: .title, user: .user.login, url: .html_url}'
# 检查 GitHub Actions 工作流状态
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/owner/repo/actions/runs?per_page=1" \
| jq '.workflow_runs[0] | {status, conclusion, created_at}'
15.6 curl 速查手册
常用命令速查
| 场景 | 命令 |
|---|---|
| 简单 GET | curl https://example.com |
| 保存到文件 | curl -o file.html https://example.com |
| 使用远程文件名 | curl -O https://example.com/file.zip |
| POST JSON | curl -X POST -H "Content-Type: application/json" -d '{}' URL |
| POST 表单 | curl -d "key=value" URL |
| 上传文件 | curl -F "file=@path" URL |
| 设置请求头 | curl -H "Header: Value" URL |
| Basic 认证 | curl -u user:pass URL |
| Bearer Token | curl -H "Authorization: Bearer TOKEN" URL |
| 跟随重定向 | curl -L URL |
| 查看响应头 | curl -I URL |
| 查看请求和响应 | curl -v URL |
| 忽略证书错误 | curl -k URL(仅测试!) |
| 使用代理 | curl -x http://proxy:8080 URL |
| 断点续传 | curl -C - -O URL |
| 限速 | curl --limit-rate 1m -O URL |
| 超时 | curl --connect-timeout 10 --max-time 60 URL |
| 重试 | curl --retry 3 --retry-delay 5 URL |
| 获取状态码 | curl -s -o /dev/null -w "%{http_code}" URL |
| 获取 TTFB | curl -s -o /dev/null -w "%{time_starttransfer}" URL |
| 获取 JSON 字段 | curl -s URL | jq '.field' |
常用 -w 变量速查
| 变量 | 含义 |
|---|---|
%{http_code} | HTTP 状态码 |
%{time_total} | 总耗时(秒) |
%{time_starttransfer} | TTFB(秒) |
%{time_namelookup} | DNS 时间(秒) |
%{time_connect} | TCP 时间(秒) |
%{time_appconnect} | TLS 时间(秒) |
%{size_download} | 下载字节数 |
%{speed_download} | 下载速度(字节/秒) |
%{url_effective} | 最终 URL |
%{redirect_url} | 重定向 URL |
%{num_redirects} | 重定向次数 |
%{content_type} | Content-Type |
%{json} | 全部变量的 JSON 格式 |
15.7 学习路径建议
初学者路径 (第 1-3 章)
├── curl 简介与生态
├── 安装与编译
└── 基本用法
↓
后端开发者路径 (第 4-7 章, 第 13 章)
├── HTTP 方法详解
├── 请求头与响应头
├── 认证机制
├── POST 数据与上传
└── API 测试
↓
运维/SRE 路径 (第 8-9 章, 第 12 章, 第 14 章)
├── 下载与传输管理
├── 传输选项与网络调优
├── 调试与诊断
└── Docker 中的 curl
↓
安全工程师路径 (第 6 章, 第 10 章)
├── 认证机制
└── TLS/SSL 安全
↓
效率提升 (第 11 章, 第 15 章)
├── 脚本编写
└── 最佳实践
15.8 回顾与总结
核心要点回顾
| 主题 | 核心要点 |
|---|---|
| 基本用法 | -X 指定方法,-H 设置头部,-d 发送数据,-o 保存输出 |
| 安全 | 永远用 HTTPS,不要硬编码密码,不要用 -k |
| 认证 | -u 用于 Basic,-H Authorization: Bearer 用于 Token |
| 调试 | -v 查看头信息,--trace 逐字节,-w 计时统计 |
| 脚本化 | 配置文件用 -K,JSON 用 jq 构建,错误码必须检查 |
| Docker | HEALTHCHECK + curl 是容器健康检查的标准模式 |
| 性能 | 连接复用、--compressed、HTTP/2、并行请求 |
curl 能做什么?
✅ HTTP/HTTPS API 调用与调试 ✅ 文件上传与下载 ✅ 认证与安全测试 ✅ 健康检查与监控 ✅ CI/CD 自动化 ✅ Docker 容器间通信 ✅ GraphQL/gRPC-Web 测试 ✅ TLS/SSL 诊断 ✅ Webhook 调试 ✅ 脚本化数据传输
curl 不擅长什么?
❌ 递归下载整站(用 wget) ❌ 彩色 API 输出(用 httpie) ❌ 浏览器自动化(用 Selenium/Playwright) ❌ 复杂的负载测试(用 k6/wrk) ❌ WebSocket 交互式调试(用 websocat)
扩展阅读
- curl 官方文档
- Everything curl — 免费在线书
- curl Cookbook
- curl GitHub Wiki
- curl 命令行选项完整列表
- curl 与 HTTP 规范的关系
- IETF HTTP 工作组
🎉 恭喜! 你已完成 curl 深度教程全部 15 章的学习。现在,你已经掌握了从基础到高级的 curl 知识,可以自信地在实际工作中运用这些技能。记住:curl 的真正威力在于实践——多用、多试、多调试。