python - 使用 Django Rest Framework 时如何访问请求正文并避免获取 RawPostDataException

标签 python django rest django-rest-framework http-post

我需要获取 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/

相关文章:

python django 使用 lambda 和 if 语句进行排序

python - 何时在 python 中使用 "with"

Python 内置列表 : remove one item

django - 启动 django 服务器时,我不断收到 NotImplementedError 错误

python - 在 Django 中监控博客文章浏览量的最佳方法

django - 是否可以使用 Django 在另一个 StackedInline 中嵌入一个 StackedInline?

rest - 失眠者同时上传图片和发布数据

java - 不存在的可选字段与在休息请求正文中存在空值的字段之间的区别

python - Pandas 分组计数并填充无计数为0

java - PersonResource 与 spring-hateoas 中的 Person 是一样的吗