curl 深度教程 / 第 07 章:POST 数据与上传
第 07 章:POST 数据与上传
POST 是 HTTP 中语义最丰富的方法。本章将深入探讨各种 POST 数据格式、文件上传技巧和编码细节。
7.1 POST 数据方式对比
curl 提供了多种发送 POST 数据的方式,适用于不同场景:
| 选项 | Content-Type | 适用场景 | 特点 |
|---|---|---|---|
-d / --data | application/x-www-form-urlencoded | 表单数据 | 自动 URL 编码 |
--data-raw | 同上 | 含 @ 的原始数据 | 不处理 @ 前缀 |
--data-urlencode | 同上 | 需要额外编码的数据 | 自动编码特殊字符 |
--data-binary | 保持原样 | 二进制数据 | 不做任何转换 |
-F / --form | multipart/form-data | 文件上传 | 分段编码 |
--json | application/json | JSON 数据(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 "[email protected]" \
--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 "[email protected];type=application/pdf"
# 指定服务端接收的字段名
curl -X POST https://httpbin.org/post \
-F "[email protected];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 "[email protected]" \
-F "[email protected]" \
-F "[email protected]"
# 上传多个文件到不同字段
curl -X POST https://api.example.com/upload \
-F "[email protected]" \
-F "[email protected]" \
-F "[email protected]"
# 使用 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 "[email protected]" \
-F "title=Q1 财务报告" \
-F "category=finance" \
-F "tags=quarterly,report" \
-F "visibility=private"
# 文件 + JSON 元数据
curl -X POST https://api.example.com/upload \
-F "[email protected]" \
-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 "[email protected]" 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 "[email protected]" \
-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
注意事项
- -d 默认 POST:使用
-d时不需要-X POST,但使用--data-binary @file时需要 - -F vs -d:
-F用于 multipart/form-data(文件上传),-d用于 URL 编码表单 - 二进制文件必须用
--data-binary:-d会将\n转换为回车换行 - JSON 中的引号:shell 中嵌套引号需要仔细处理,推荐用 jq 构建
- 大文件用流式上传:避免
--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 章:下载与传输管理 — 学习断点续传、限速、进度条、并行下载等高级传输技巧。