Krz*_*iek 2 python django rest http-post django-rest-framework
我需要获取POST请求正文的原始内容,但是当我尝试访问时却request.body遇到异常:
django.http.request.RawPostDataException:
You cannot access body after reading from request's data stream
我知道建议request.data不要request.body使用Django Rest Framework,而要使用它,但是为了验证数字签名,我必须将请求正文以原始且未经修饰的形式使用,因为这是3rd-party签名并我需要验证的内容。
伪代码:
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)
Krz*_*iek 17
我在 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检查,如果“读取已开始”,则会引发异常。
所述self._read_startedflague被设定由read()方法(源):
def read(self, *args, **kwargs):
    self._read_started = True
    try:
        return self._stream.read(*args, **kwargs)
    except IOError as e:
        six.reraise(UnreadablePostError, ...)
现在应该很清楚,如果只调用了方法而不将其结果分配给 requests RawPostDataException,request.body则在访问之后将引发。read()self._body
现在让我们看看 DRFJSONParser类(源代码):
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))
(我选择了稍微旧的版本 o DRF 源,因为在 2017 年 5 月之后有一些性能改进掩盖了理解我们问题的关键线)
现在应该清楚stream.read()调用设置了_read_started标志,因此body属性不可能再次访问流(在解析器之后)。
“无 request.body”方法是 DRF 的意图(我猜),所以尽管在技术上可以启用request.body全局访问(通过自定义中间件) - 它不应该在没有深入了解其所有后果的情况下完成。
request.body可以通过以下方式在本地明确授予对财产的访问权:
您需要定义自定义解析器:
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 作者的意图)。
我可能在这里丢失了一些东西,但是我敢肯定,在这种情况下,您不需要定义自定义解析器...
您可以只使用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})
自从提出这个问题以来已经有一段时间了,所以我不确定当时的框架是否存在一些差异,但是如果有人正在从解析器上的DRF 文档中搜索使用最新版本访问原始请求正文:
视图的有效解析器集始终定义为类列表。访问 request.data 时,REST 框架将检查传入请求的 Content-Type 标头,并确定使用哪个解析器来解析请求内容。
这意味着解析器在request.data访问时会延迟执行。因此,解决方案可以非常简单地读取request.body,并在访问之前将其缓存在某处request.data。无需编写自定义解析器。
def some_action(self, request):
  raw_body = request.body
  parsed_body = request.data['something']
  verify_signature(raw_body, request.data['key_or_something'])
| 归档时间: | 
 | 
| 查看次数: | 3309 次 | 
| 最近记录: |