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

curl 深度教程 / 第 07 章:POST 数据与上传

第 07 章:POST 数据与上传

POST 是 HTTP 中语义最丰富的方法。本章将深入探讨各种 POST 数据格式、文件上传技巧和编码细节。


7.1 POST 数据方式对比

curl 提供了多种发送 POST 数据的方式,适用于不同场景:

选项Content-Type适用场景特点
-d / --dataapplication/x-www-form-urlencoded表单数据自动 URL 编码
--data-raw同上@ 的原始数据不处理 @ 前缀
--data-urlencode同上需要额外编码的数据自动编码特殊字符
--data-binary保持原样二进制数据不做任何转换
-F / --formmultipart/form-data文件上传分段编码
--jsonapplication/jsonJSON 数据(curl 7.82+)简洁语法

7.2 表单数据提交

application/x-www-form-urlencoded

# 基本表单提交
curl -X POST https://httpbin.org/post \
  -d "username=admin&password=secret123&remember=true"

# 多个 -d 参数会自动拼接
curl https://httpbin.org/post \
  -d "username=admin" \
  -d "password=secret123" \
  -d "remember=true"

# 使用 --data-urlencode 自动处理特殊字符
curl https://httpbin.org/post \
  --data-urlencode "name=张三" \
  --data-urlencode "message=Hello World! @#$%" \
  --data-urlencode "city=北京"

# 从文件读取表单数据
curl https://httpbin.org/post -d @formdata.txt

# 从 stdin 读取
echo "key=value&foo=bar" | curl https://httpbin.org/post -d @-

表单中的特殊字符

字符URL 编码说明
空格%20+表单中通常用 +
&%26参数分隔符
=%3D键值分隔符
@%40
#%23片段标识
%%25转义前缀
+%2B
/%2F
中文UTF-8 编码后转义%E5%BC%A0%E4%B8%89
# 手动编码 vs 自动编码
# 手动(容易出错)
curl -d "name=%E5%BC%A0%E4%B8%89&msg=hello+world" https://httpbin.org/post

# 自动(推荐)
curl --data-urlencode "name=张三" \
     --data-urlencode "msg=hello world" \
     https://httpbin.org/post

7.3 JSON 数据

基本 JSON POST

# 使用 -H + -d 发送 JSON
curl -X POST https://httpbin.org/post \
  -H "Content-Type: application/json" \
  -d '{"name": "张三", "age": 30, "active": true}'

# 使用 --json 简写(curl 7.82+)
curl --json '{"name": "张三", "age": 30}' https://httpbin.org/post

# 从文件发送 JSON
curl -X POST https://httpbin.org/post \
  -H "Content-Type: application/json" \
  -d @payload.json

# 从 stdin 发送 JSON
echo '{"key": "value"}' | curl -X POST https://httpbin.org/post \
  -H "Content-Type: application/json" \
  -d @-

复杂 JSON 构建

# 使用 jq 动态构建 JSON
DATA=$(jq -n \
  --arg name "张三" \
  --arg email "zhangsan@example.com" \
  --argjson age 30 \
  --argjson active true \
  '{name: $name, email: $email, age: $age, active: $active}')

curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d "$DATA"

# 合并多个 JSON 文件
jq -s '.[0] * .[1]' base.json override.json | \
  curl -X POST https://api.example.com/data \
  -H "Content-Type: application/json" \
  -d @-

# 使用环境变量构建 JSON
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"$USER_NAME\", \"email\": \"$USER_EMAIL\"}"

# 更安全的方式:使用 jq 处理变量
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg name "$USER_NAME" --arg email "$USER_EMAIL" \
    '{name: $name, email: $email}')"

嵌套 JSON 和数组

# 复杂嵌套结构
curl -X POST https://api.example.com/orders \
  -H "Content-Type: application/json" \
  -d '{
    "customer": {
      "name": "张三",
      "address": {
        "city": "北京",
        "district": "朝阳区"
      }
    },
    "items": [
      {"product_id": 1, "quantity": 2, "price": 99.9},
      {"product_id": 5, "quantity": 1, "price": 199.5}
    ],
    "tags": ["vip", "first-order"],
    "metadata": {
      "source": "mobile_app",
      "version": "2.1.0"
    }
  }'

7.4 XML 数据

# 发送 XML
curl -X POST https://soap.example.com/service \
  -H "Content-Type: application/xml; charset=utf-8" \
  -d '<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserRequest>
      <UserId>42</UserId>
    </GetUserRequest>
  </soap:Body>
</soap:Envelope>'

# 从文件发送 XML
curl -X POST https://soap.example.com/service \
  -H "Content-Type: text/xml; charset=utf-8" \
  -H "SOAPAction: GetUser" \
  -d @request.xml

# 发送简单 XML
curl -X POST https://api.example.com/data \
  -H "Content-Type: application/xml" \
  -d '<user><name>张三</name><age>30</age></user>'

7.5 文件上传

单文件上传

# 使用 -F 上传文件(multipart/form-data)
curl -X POST https://httpbin.org/post \
  -F "file=@/path/to/document.pdf"

# 指定文件的 MIME 类型
curl -X POST https://httpbin.org/post \
  -F "file=@document.pdf;type=application/pdf"

# 指定服务端接收的字段名
curl -X POST https://httpbin.org/post \
  -F "avatar=@photo.jpg;filename=profile.jpg"

# 从 stdin 上传(需指定文件名)
cat photo.jpg | curl -X POST https://httpbin.org/post \
  -F "file=@-;filename=photo.jpg"

多文件上传

# 上传多个文件到同一字段
curl -X POST https://api.example.com/upload \
  -F "files=@photo1.jpg" \
  -F "files=@photo2.jpg" \
  -F "files=@photo3.jpg"

# 上传多个文件到不同字段
curl -X POST https://api.example.com/upload \
  -F "avatar=@photo.jpg" \
  -F "document=@report.pdf" \
  -F "certificate=@cert.pem"

# 使用 glob 模式上传多个文件
curl -X POST https://api.example.com/upload \
  -F "files=@./images/*.jpg"  # 注意:curl 不支持 glob,需要循环
# 正确方式:
for f in ./images/*.jpg; do
  curl -X POST https://api.example.com/upload -F "files=@$f"
done

文件上传 + 额外表单字段

# 混合文件和表单字段
curl -X POST https://api.example.com/documents \
  -F "file=@report.pdf" \
  -F "title=Q1 财务报告" \
  -F "category=finance" \
  -F "tags=quarterly,report" \
  -F "visibility=private"

# 文件 + JSON 元数据
curl -X POST https://api.example.com/upload \
  -F "file=@image.jpg" \
  -F 'metadata={"title":"风景照","tags":["nature","sunset"]}'

大文件上传

# 使用 --data-binary 上传大文件(不缓存到内存)
curl -X PUT https://storage.example.com/bucket/largefile.bin \
  -H "Content-Type: application/octet-stream" \
  --data-binary @largefile.bin

# 流式上传(从管道)
cat largefile.bin | curl -X PUT https://storage.example.com/bucket/file \
  -H "Content-Type: application/octet-stream" \
  --data-binary @-

# 分块上传(需要服务端支持)
CHUNK_SIZE=10485760  # 10MB
FILE="largefile.bin"
FILE_SIZE=$(stat -c%s "$FILE")
UPLOAD_ID=$(curl -s -X POST "https://api.example.com/multipart/init" \
  -H "Content-Type: application/json" \
  -d "{\"filename\": \"$FILE\", \"size\": $FILE_SIZE}" \
  | jq -r '.upload_id')

OFFSET=0
PART=1
while [ "$OFFSET" -lt "$FILE_SIZE" ]; do
  dd if="$FILE" bs=$CHUNK_SIZE skip=$((PART-1)) count=1 2>/dev/null | \
    curl -X PUT "https://api.example.com/multipart/$UPLOAD_ID/part/$PART" \
      -H "Content-Type: application/octet-stream" \
      --data-binary @-
  OFFSET=$((OFFSET + CHUNK_SIZE))
  PART=$((PART + 1))
done

curl -X POST "https://api.example.com/multipart/$UPLOAD_ID/complete" \
  -H "Content-Type: application/json"

7.6 Multipart 详解

Multipart 格式结构

# multipart/form-data 的内部结构(-v 可以看到)
curl -v -X POST https://httpbin.org/post \
  -F "name=张三" \
  -F "file=@test.txt" 2>&1

# 实际发送的内容:
# Content-Type: multipart/form-data; boundary=------------------------abc123
#
# --------------------------abc123
# Content-Disposition: form-data; name="name"
#
# 张三
# --------------------------abc123
# Content-Disposition: form-data; name="file"; filename="test.txt"
# Content-Type: application/octet-stream
#
# 文件内容...
# --------------------------abc123--

自定义 Boundary

# curl 自动选择 boundary,一般不需要手动设置
# 如需查看实际 boundary,使用 -v 选项

# 手动发送 multipart 数据(高级用法)
curl -X POST https://api.example.com/upload \
  -H "Content-Type: multipart/form-data; boundary=MyBoundary123" \
  --data-binary \
'--MyBoundary123\r\n\
Content-Disposition: form-data; name="field1"\r\n\
\r\n\
value1\r\n\
--MyBoundary123\r\n\
Content-Disposition: form-data; name="file"; filename="test.txt"\r\n\
Content-Type: text/plain\r\n\
\r\n\
文件内容\r\n\
--MyBoundary123--\r\n'

7.7 编码问题

字符编码

# curl 使用 -d 时,默认使用 application/x-www-form-urlencoded
# 中文会被自动编码为 UTF-8 的百分号编码

# 查看实际发送的数据(-v)
curl -v https://httpbin.org/post \
  --data-urlencode "name=张三" 2>&1

# 显式指定 charset
curl -X POST https://api.example.com/data \
  -H "Content-Type: application/json; charset=utf-8" \
  -d '{"name": "张三"}'

# 发送 GBK 编码的数据(需先转换)
echo -n "张三" | iconv -f utf-8 -t gbk | \
  curl -X POST https://api.example.com/data \
  -H "Content-Type: application/x-www-form-urlencoded; charset=gbk" \
  --data-binary @-

Base64 编码上传

# 将文件编码为 Base64 后作为 JSON 字段上传
FILE_CONTENT=$(base64 -w 0 document.pdf)
curl -X POST https://api.example.com/upload \
  -H "Content-Type: application/json" \
  -d "{\"filename\": \"document.pdf\", \"content\": \"$FILE_CONTENT\"}"

# 使用 jq 安全处理
FILE_CONTENT=$(base64 -w 0 document.pdf)
jq -n --arg content "$FILE_CONTENT" \
  '{filename: "document.pdf", content: $content}' | \
  curl -X POST https://api.example.com/upload \
  -H "Content-Type: application/json" \
  -d @-

7.8 PUT vs POST 上传

# POST 上传(创建新资源,服务器分配 ID)
curl -X POST https://storage.example.com/files \
  -F "file=@document.pdf" \
  -F "name=报告"

# PUT 上传(上传到指定位置)
curl -X PUT https://storage.example.com/files/report-2026.pdf \
  -H "Content-Type: application/pdf" \
  --data-binary @document.pdf

# S3 风格的 PUT 上传
curl -X PUT "https://bucket.s3.amazonaws.com/path/to/file.pdf" \
  -H "Content-Type: application/pdf" \
  -H "Authorization: AWS4-HMAC-SHA256 Credential=..." \
  --data-binary @document.pdf

注意事项

  1. -d 默认 POST:使用 -d 时不需要 -X POST,但使用 --data-binary @file 时需要
  2. -F vs -d-F 用于 multipart/form-data(文件上传),-d 用于 URL 编码表单
  3. 二进制文件必须用 --data-binary-d 会将 \n 转换为回车换行
  4. JSON 中的引号:shell 中嵌套引号需要仔细处理,推荐用 jq 构建
  5. 大文件用流式上传:避免 --data-binary @file 对大文件的内存开销
# ❌ 错误:-d 会破坏二进制数据
curl -X POST https://api.example.com/upload -d @image.jpg

# ✅ 正确:使用 --data-binary
curl -X POST https://api.example.com/upload --data-binary @image.jpg

# ❌ 错误:shell 引号问题
curl -d '{"name": "It's broken"}' https://api.example.com/data

# ✅ 正确:使用 jq
jq -n --arg name "It's broken" '{name: $name}' | \
  curl -d @- https://api.example.com/data

扩展阅读


📖 下一章第 08 章:下载与传输管理 — 学习断点续传、限速、进度条、并行下载等高级传输技巧。