bigywater.tech
Django · 19

HttpRequest 与请求体解析

request.body、request.POST、request.read() 都在读同一个流,先读谁、读没读过,结果大不相同。这篇把 3 种内容类型 × 4 种读取顺序全部跑一遍,再看大小限制。

静默为空
先 request.read() 再取 request.POST,不报错但得到空 QueryDict
multipart
先 POST 再 body 会抛 RawPostDataException;先 body 再 POST 都成功
2.5 MiB
DATA_UPLOAD_MAX_MEMORY_SIZE 默认值;超过一字节即 RequestDataTooBig

读取顺序矩阵

实测 · 本机执行输出
[urlencoded] 先 POST 再 body: POST={'a': ['1'], 'b': ['2']} | body=b'a=1&b=2'
[urlencoded] 先 body 再 POST: body=b'a=1&b=2' | POST={'a': ['1'], 'b': ['2']}
[urlencoded] 先 read 再 body: read()=b'a=1&b' | body → RawPostDataException: You cannot access body after reading from request's data stream
[urlencoded] 先 read 再 POST: read()=b'a=1&b' | POST={}
[multipart ] 先 POST 再 body: POST={'a': ['1']} | body → RawPostDataException: You cannot access body after reading from request's data stream
[multipart ] 先 body 再 POST: body=b'--BoUnDaRyStRiNg\r\nContent-Disposition: f' | POST={'a': ['1']}
[multipart ] 先 read 再 body: read()=b'--BoU' | body → RawPostDataException: You cannot access body after reading from request's data stream
[multipart ] 先 read 再 POST: read()=b'--BoU' | POST={}
[json      ] 先 POST 再 body: POST={} | body=b'{"a": 1}'
[json      ] 先 body 再 POST: body=b'{"a": 1}' | POST={}
[json      ] 先 read 再 body: read()=b'{"a":' | body → RawPostDataException: You cannot access body after reading from request's data stream
[json      ] 先 read 再 POST: read()=b'{"a":' | POST={}

规律:request.body 会把整个体读进内存并缓存;POST 的解析在 urlencoded 时基于缓存的体,在 multipart 时直接消耗流。所以 multipart 的「先 POST 再 body」失败。一旦手动 read() 过,流就被消耗了:body 明确报错,而 POST 静默变空,后者更危险。

PUT / PATCH

实测 · 本机执行输出
POST   request.POST = {'a': ['1'], 'b': ['2']}   request.body = b'a=1&b=2'
PUT    request.POST = {}   request.body = b'a=1&b=2'
PATCH  request.POST = {}   request.body = b'a=1&b=2'

Django 只对 POST 方法解析表单体。REST 风格的 PUT / PATCH 要自己解析 request.body(或用 DRF 之类的框架)。

大小限制

实测 · 本机执行输出
DATA_UPLOAD_MAX_MEMORY_SIZE 默认 = 2621440 字节(2.50 MiB)
JSON/二进制体 2621440 字节 → status 200  body 长度 2621440
JSON/二进制体 2621441 字节 → status 400  RequestDataTooBig: Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.
multipart 里的文件 2.5MiB+1000 字节 → status 400 RequestDataTooBig: Request body exceeded settings.DATA_UPLOA

DATA_UPLOAD_MAX_MEMORY_SIZE 限制「读进内存」的请求体:读取 request.body 时,体超过限制就抛 RequestDataTooBig(探测视图里我把它转成了 400)。multipart 请求整体超过限制时读 body 也一样;而 multipart 解析出的文件字段走另一套上传处理器与阈值(第 68、69 篇),这次没有单独测 request.POST / request.FILES 遇到大文件时的行为。

内部状态

实测 · 本机执行输出
读取前 _stream 类型: LimitedStream  是否已有 _body: False
读取 body 之后 是否有 _body: True  第二次 r.body is r.body: True
body 之后 read(): b'{"a": 1}'
body 之后 _stream 类型: BytesIO

读取 body 后 _stream 被换成 BytesIO,所以之后还能 read();反过来则不行。

分步演示:读取顺序

请求体解析
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。用 RequestFactory 构造请求对象并调用属性(不经过 WSGI 服务器);大小限制用 Client 打真实视图
    • 没有验证:真实 WSGI 服务器(gunicorn)与 ASGI 下体的读取差别、Transfer-Encoding: chunked、Content-Length 与实际体长度不一致的情形、multipart 里文件字段超过内存上限时的落盘阈值(FILE_UPLOAD_MAX_MEMORY_SIZE,第 68 篇)
    京ICP备2026067502号-1京公网安备11010502063487号