强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 速查手册

常用命令速查

场景命令
简单 GETcurl https://example.com
保存到文件curl -o file.html https://example.com
使用远程文件名curl -O https://example.com/file.zip
POST JSONcurl -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 Tokencurl -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
获取 TTFBcurl -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 构建,错误码必须检查
DockerHEALTHCHECK + 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 深度教程全部 15 章的学习。现在,你已经掌握了从基础到高级的 curl 知识,可以自信地在实际工作中运用这些技能。记住:curl 的真正威力在于实践——多用、多试、多调试。