当我尝试访问request.body
时,我需要获取POST请求主体的原始内容我得到一个异常:
django.http.request.RawPostDataException:
You cannot access body after reading from request's data stream
我知道建议在使用Django Rest Framework时使用request.data
而不是request.body
,但为了验证数字签名,我必须以原始和“未触及”的形式提供请求体,因为这是第三方签名以及我需要验证的内容。
伪代码:
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)
我可能在这里遗漏了一些东西,但我很确定在这种情况下你不需要定义自定义解析器......
您可以使用DRF本身的JSONParser:
from rest_framework.decorators import api_view
from rest_framework.decorators import parser_classes
from rest_framework.parsers import JSONParser
@api_view(['POST'])
@parser_classes((JSONParser,))
def example_view(request, format=None):
"""
A view that can accept POST requests with JSON content.
"""
return Response({'received data': request.data})
我在DRFs GitHub上找到了有趣的topic,但它没有完全覆盖这个问题。我调查了这个案子并提出了一个简洁的解决方案。令人惊讶的是,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, ...)
现在应该清楚的是,如果只调用RawPostDataException
方法而没有将其结果分配给请求request.body
,则在访问read()
之后将引发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
属性不可能再次访问流(在解析器之后)。
“没有请求。身体”的方法是DRF意图(我猜)所以尽管技术上可以在全球范围内(通过自定义中间件)启用对request.body
的访问 - 但如果不深入了解其所有后果,就不应该这样做。
可以通过以下方式明确和本地授予对request.body
财产的访问权限:
你需要定义custom parser:
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作者所预期的那样)。