我需要获取 POST 请求正文的原始内容(作为字符串),但是当我尝试访问 request.body
时出现异常:
django.http.request.RawPostDataException:
You cannot access body after reading from request's data stream
我知道在使用 Django Rest Framework 时建议使用 request.data
而不是 request.body
,但为了验证数字签名我有以原始和“未修改”的形式获取请求正文,因为这是第 3 方签署的内容以及我需要验证的内容。
伪代码:
3rd_party_sign(json_data + secret_key) != validate_sign(json.dumps(request.data) + secret_key)
3rd_party_sign(json_data + secret_key) == validate_sign(request.body + secret_key)
最佳答案
我发现很有趣 topic在 DRFs GitHub 上,但它没有完全涵盖问题。我调查了此案并提出了一个巧妙的解决方案。令人惊讶的是,SO 上没有这样的问题,所以我决定在 SO self-answer guidelines 之后将其添加为公众。 .
理解问题和解决方案的关键是 HttpRequest.body
( source ) 是如何工作的:
@property
def body(self):
if not hasattr(self, '_body'):
if self._read_started:
raise RawPostDataException("You cannot access body after reading from request's data stream")
# (...)
try:
self._body = self.read()
except IOError as e:
raise UnreadablePostError(*e.args) from e
self._stream = BytesIO(self._body)
return self._body
访问 body
时 - 如果 self._body
已设置,则直接返回,否则读取内部请求流并将其分配给 _body:self ._body = self.read()
。从那时起,对 body
的任何进一步访问都会返回到 return self._body
。此外,在读取内部请求流之前,还有一个 if self._read_started
检查,如果“read has started”,则会引发异常。
self._read_started
标志由 read()
方法 ( source ) 设置:
def read(self, *args, **kwargs):
self._read_started = True
try:
return self._stream.read(*args, **kwargs)
except IOError as e:
six.reraise(UnreadablePostError, ...)
现在应该清楚了,如果只有 read()
,访问 request.body
后将引发 RawPostDataException
调用方法时未将其结果分配给请求 self._body
。
现在让我们看看 DRF JSONParser
类 ( source ):
class JSONParser(BaseParser):
media_type = 'application/json'
renderer_class = renderers.JSONRenderer
def parse(self, stream, media_type=None, parser_context=None):
parser_context = parser_context or {}
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
try:
data = stream.read().decode(encoding)
return json.loads(data)
except ValueError as exc:
raise ParseError('JSON parse error - %s' % six.text_type(exc))
(我选择了稍微旧一点的 DRF 源版本,因为在 2017 年 5 月之后有一些性能改进模糊了理解我们问题的关键线)
现在应该清楚 stream.read()
调用设置了 _read_started
标志,因此 body
属性是不可能的再次访问流(在解析器之后)。
解决方案
“no request.body”方法是 DRF 的意图(我猜)所以尽管在技术上可以在全局范围内(通过自定义中间件)启用对 request.body
的访问 - 它不应该是在没有深入了解其所有后果的情况下完成。
可以通过以下方式在本地显式授予对 request.body
属性的访问权限:
你需要定义custom parser :
import json
from django.conf import settings
from rest_framework.exceptions import ParseError
from rest_framework import renderers
from rest_framework.parsers import BaseParser
class MyJSONParser(BaseParser):
media_type = 'application/json'
renderer_class = renderers.JSONRenderer
def parse(self, stream, media_type=None, parser_context=None):
parser_context = parser_context or {}
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
request = parser_context.get('request')
try:
data = stream.read().decode(encoding)
setattr(request, 'raw_body', data) # setting a 'body' alike custom attr with raw POST content
return json.loads(data)
except ValueError as exc:
raise ParseError('JSON parse error - %s' % six.text_type(exc))
然后在需要访问原始请求内容的时候可以使用:
@api_view(['POST'])
@parser_classes((MyJSONParser,))
def example_view(request, format=None):
return Response({'received data': request.raw_body})
虽然 request.body
仍然是全局不可访问的(正如 DRF 作者的意图)。
关于python - 使用 Django Rest Framework 时如何访问请求正文并避免获取 RawPostDataException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47662496/