Skip to content

一、高频踩坑场景 ​

在编写 API 接口自动化测试脚本时,很多开发者经常会遇到以下令人困惑的问题:

python
# 脚本这样写:
payload = {"username": "admin", "role": "tester"}
response = requests.post("https://api.example.com/users", data=payload)

服务端接口立刻报错:

  • 415 Unsupported Media Type(不支持的媒体格式)
  • 或 400 Bad Request: JSON parse error

而换成 requests.post(..., json=payload) 之后,接口立即返回成功。

产生这种现象的核心原因在于:Requests 库在底层对 data 和 json 参数采用了完全不同的序列化方式,并自动设置了不同的 Content-Type 请求头。


二、六大核心规则与行为矩阵 ​

在调用 requests.post(url, data=..., json=...) 且未显式在 headers 中定义 Content-Type 时,底层规则如下:

参数名称传入数据类型自动生成的 Content-Type实际发送的 HTTP Request Body 格式
datadictapplication/x-www-form-urlencodedusername=admin&role=tester(标准表单 URL 编码)
datastrtext/plain原始纯文本字符串原样发送
jsondictapplication/json{"username": "admin", "role": "tester"}(标准 JSON 串)
jsonstrapplication/json"..."(被自动封装后的 JSON 格式)

三、底层序列化过程深度剖析 ​

1. 为什么传字典给 data 会变成 Form 表单? ​

当把 Python 字典传给 data 时,Requests 会调用内部的 urllib.parse.urlencode() 方法,将键值对序列化为键名、等号与与号连接的表单格式:

python
# 客户端发送的报文:
POST /users HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&role=tester

如果服务端(如 Spring Boot 的 @RequestBody、FastAPI 的 Pydantic Model)期望的是 JSON 格式,此时因为协议头不匹配,服务端框架在过滤器层面就会直接阻断并抛出 415 或 400。

2. 传字典给 json 的底层动作 ​

当使用 json=payload 参数时,Requests 会在底层自动执行两个动作:

  1. 自动调用 json.dumps(payload) 将 Python 字典序列化为标准 JSON 字符串;
  2. 自动在 Request Headers 中追加 Content-Type: application/json。
python
# 客户端发送的标准 JSON 报文:
POST /users HTTP/1.1
Content-Type: application/json

{"username": "admin", "role": "tester"}

四、显式指定 Headers 时的覆盖机制与陷阱 ​

如果开发者既传了 data,又在请求头中强行指定了 Content-Type:

python
# 🔴 常见错误写法:
headers = {"Content-Type": "application/json"}
payload = {"username": "admin"}

# 此时发送的 body 依然是 username=admin,但 Header 却声称自己是 json!
requests.post(url, data=payload, headers=headers)

后果:服务端收到 Content-Type: application/json,满怀期待地尝试把 username=admin 当作 JSON 反序列化,直接抛出 JSONDecodeError: Expecting value: line 1 column 1。

✅ 规范使用建议: ​

  • 现代 RESTful API(前后端分离、微服务):一律推荐直接使用 json=payload 参数,省去手动 dumps 和手动写 header 的繁琐操作;
  • 传统 Form 表单上传 / OAuth 2.0 Token 换取接口:使用 data=dict 模拟原生浏览器表单提交;
  • 纯文本 / XML / 裸字节传输:使用 data=str 或 data=bytes 并显式声明所需的 Content-Type。

测试开发工程师 · 专注自动化与系统架构 | 邮箱: hansblog@atumsoul.win