1. 概览与概念
- Content-Type:HTTP 请求/响应头,表示消息体的媒体类型(MIME type)。服务端用它决定如何解析请求体。
- 常见场景:
- 纯结构化数据(JSON) →
application/json
- 表单 + 文件上传 →
multipart/form-data
- 简单表单键值对(HTML 表单默认)→
application/x-www-form-urlencoded
- 纯文本 →
text/plain
- 原始二进制文件流 →
application/octet-stream
- 纯结构化数据(JSON) →
选择策略:如果只传结构化数据(没有文件),优先 application/json
;如果需要上传文件,使用 multipart/form-data
。
2. application/json
定义
请求体为 JSON 文本。服务器按 JSON 解析整个请求体。
何时使用
- 只传结构化数据(对象 / 数组 / 嵌套结构),不含文件。
- 常见于 RESTful API、微服务间通信、前端与后端交互。
请求头
Content-Type: application/json; charset=utf-8
客户端示例
curl
curl -X POST "http://example.com/api" \-H "Authorization: Bearer TOKEN" \-H "Content-Type: application/json" \-d '{"indexing_technique":"high_quality","process_rule":{"mode":"custom"}}'
Python (requests)
import requests
url = "http://example.com/api"
headers = {"Authorization": "Bearer TOKEN"}
data = {"indexing_technique": "high_quality", "process_rule": {"mode": "custom"}}
resp = requests.post(url, headers=headers, json=data) # 使用 json 参数,requests 会自动序列化并设置 Content-Type
Apifox/Postman:Body → 选择 raw → JSON,填入 JSON。
服务端解析
多数框架能自动解析 JSON:例如 Flask (request.json
) / FastAPI(声明模型)等。
注意点
- 必须是合法 JSON(不能有单引号、不允许在外层多包一层字符串)。
- 如果要上传文件,不能用
application/json
(文件会被二进制编码成 base64,但那不是推荐方式,且会增大尺寸)。
3. multipart/form-data
定义
表单分段(multipart),每个部分(part)都有自己的 headers(Content-Disposition
、可选 Content-Type
),适合混合文本字段与文件字段。
何时使用
- 需要上传文件(图片、文档、音频等)
- 同时需要传复杂 JSON(把 JSON 放在一个 text 字段里)和文件
请求头(示例)
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryX
boundary
是库自动生成,用于分隔各个 part。
客户端示例
curl
curl -X POST "http://example.com/upload" \-H "Authorization: Bearer TOKEN" \-F 'data={"indexing_technique":"high_quality"};type=text/plain' \-F 'file=@/path/to/file.png'
注意:不要在
-F 'data="{...}"'
外层再加额外的引号,这会让服务器把 data 当成字符串,造成解析失败(例如你遇到的indexing_technique=None
问题)。
Python (requests) — 推荐写法:
import requests, json
url = "http://example.com/upload"
headers = {"Authorization": "Bearer TOKEN"}
json_payload = {"indexing_technique": "high_quality"}
files = {# 文本字段: (filename, content, content_type) -> filename 为 None 或空"data": (None, json.dumps(json_payload), "text/plain"),# 文件字段"file": ("file.png", open("/path/to/file.png", "rb"))
}
resp = requests.post(url, headers=headers, files=files)
Apifox/Postman:Body → form-data,添加:
- key=
data
type=Text,值填{"indexing_technique":"high_quality"}
(不要额外的双引号) - key=
file
type=File,选择文件
服务端解析
- 在 Flask:使用
request.form
(文本字段)与request.files
(文件字段) - 在 FastAPI:使用
UploadFile
与Form()
来接收
示例(FastAPI)
from fastapi import FastAPI, File, UploadFile, Form
import json
app = FastAPI()@app.post('/upload')
async def upload(data: str = Form(...), file: UploadFile = File(...)):# data 是表单里的字符串,通常存放 JSON,需要 json.loadspayload = json.loads(data)content = await file.read()return {"received": payload, "filename": file.filename}
常见坑
- 把 JSON 用外层双引号包裹(整个 JSON 当作一个字符串)会被解析成字符串,导致字段缺失或类型错误。
- 指定了错误的 Content-Type(例如把
data
的 Content-Type 写成application/json
)在某些库下会影响解析方式——requests
的files
会给 part 自动附 content-type。
4. application/x-www-form-urlencoded
定义
表单键值对序列化为 key1=value1&key2=value2
,类似 HTML 表单的默认提交方式,不支持文件上传。
何时使用
- 简单表单提交(登录、查询参数)
- 浏览器表单提交(不含文件)
客户端示例
curl
curl -X POST "http://example.com/login" \-H "Content-Type: application/x-www-form-urlencoded" \-d "username=alice&password=secret"
Python (requests)
requests.post(url, data={"username": "alice", "password": "secret"})
Apifox/Postman:Body → x-www-form-urlencoded,按 key/value 填写。
服务端解析
- Flask:
request.form['username']
- FastAPI:使用
Form()
来接收
5. text/plain
与 application/octet-stream
text/plain
- 用于传输简单纯文本(非 JSON 结构),如传一段日志、一段脚本、或问题描述。
- 示例:
curl -X POST -H "Content-Type: text/plain" --data "hello world" http://...
application/octet-stream
-
二进制流,适用于直接上传文件的原始字节流(例如大文件或非表单上传的情形)。
-
示例:将文件直接作为请求体,而不是 multipart:
curl -X PUT "http://example.com/upload/raw" \-H "Content-Type: application/octet-stream" \--data-binary @/path/to/bigfile.bin
-
服务器端通常把整个请求体当成字节流读取并保存。
6. 服务端如何接收(示例)
FastAPI(包括 JSON、multipart、raw)
from fastapi import FastAPI, File, UploadFile, Form, Request
import json
app = FastAPI()@app.post('/json')
async def recv_json(payload: dict):# 当 Content-Type: application/json 且 body 是 JSON,FastAPI 会自动解析并传入 dictreturn {"ok": True, "payload": payload}@app.post('/upload-multipart')
async def upload_multipart(data: str = Form(...), file: UploadFile = File(...)):payload = json.loads(data)contents = await file.read()return {"ok": True, "name": file.filename, "payload": payload}@app.put('/upload-raw')
async def upload_raw(request: Request):# 适合 application/octet-streambody = await request.body()# 保存到文件示例with open('/tmp/out.bin', 'wb') as f:f.write(body)return {"ok": True, "size": len(body)}
Flask(简洁示例)
from flask import Flask, request, jsonify
import json
app = Flask(__name__)@app.route('/json', methods=['POST'])
def json_route():payload = request.get_json(force=True)return jsonify(ok=True, payload=payload)@app.route('/upload', methods=['POST'])
def upload():data = request.form.get('data')payload = json.loads(data)file = request.files['file']file.save('/tmp/' + file.filename)return jsonify(ok=True)@app.route('/raw', methods=['PUT'])
def raw():body = request.data # bytesopen('/tmp/out.bin', 'wb').write(body)return jsonify(ok=True, size=len(body))
7. 常见错误、坑与排查建议
-
外层多加引号导致 JSON 变成字符串
- 问题表现:服务端解析后字段为
None
或整个字段是一个字符串。 - 排查:打印收到的原始 body(或查看 request.form),看是否是
"{...}"
。
- 问题表现:服务端解析后字段为
-
错误的 Content-Type
- 例如把 multipart 的 part 标为
application/json
,或把整个请求标为text/plain
。 - 排查:抓包(浏览器 DevTools / tcpdump / ngrok / mitmproxy)或在服务端记录
request.headers
。
- 例如把 multipart 的 part 标为
-
字符编码问题(中文 / Emoji)
- 保证
charset=utf-8
,并在客户端使用 UTF-8 编码发送。
- 保证
-
大文件上传失败或超时
- 原因:服务器限制(Nginx client_max_body_size、框架上传限制)、超时。
- 解决:增大限制、分片上传、直接使用云存储的分片接口(S3 multipart upload)等。
-
boundary 被破坏
- 当手动拼接 multipart 而 boundary 未正确设置或被转义时会失败。建议使用客户端库(requests、curl、Postman)自动处理。
-
证书/代理/跨域问题
- POST 跨域:浏览器会发 OPTIONS 预检,请确保服务器允许 CORS 并处理 Content-Type。服务器需返回合适的
Access-Control-Allow-Headers
。
- POST 跨域:浏览器会发 OPTIONS 预检,请确保服务器允许 CORS 并处理 Content-Type。服务器需返回合适的
8. 性能、安全与兼容性建议
- 压缩:对于较大的 JSON 或文本,可启用
gzip
压缩(Content-Encoding: gzip
),但客户端/服务器需支持。注意:Content-Type
与Content-Encoding
是不同维度。 - 范围/分片上传:对于大文件,优先使用分片上传(客户端 + 服务端/云端支持),避免单次上传失败带来的重试成本。
- 限速与大小控制:服务端应在网关或 Nginx 上配置大小限制、请求超时和速率限制。
- 校验:文件上传建议校验
Content-Type
(魔数/文件头)与文件大小,并在可能的场景下进行病毒扫描。 - 认证:在上传接口强制认证与授权,避免未授权的大流量上传。