如何在使用Django Rest Framework时访问请求体并避免获取RawPostDataException

问题描述 投票:1回答:2

当我尝试访问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)
python django rest django-rest-framework http-post
2个回答
3
投票

我可能在这里遗漏了一些东西,但我很确定在这种情况下你不需要定义自定义解析器......

您可以使用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})

3
投票

我在DRFs GitHub上找到了有趣的topic,但它没有完全覆盖这个问题。我调查了这个案子并提出了一个简洁的解决方案。令人惊讶的是,SO上没有这样的问题,所以我决定在SO self-answer guidelines之后将其添加到公众面前。

理解问题和解决方案的关键是HttpRequest.bodysource)如何工作:

@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属性不可能再次访问流(在解析器之后)。

The solution

“没有请求。身体”的方法是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作者所预期的那样)。

© www.soinside.com 2019 - 2024. All rights reserved.