【问题标题】:how to access POST data inside tastypie custom Authentication如何访问美味派自定义身份验证中的 POST 数据
【发布时间】:2012-09-20 23:22:23
【问题描述】:

我正在尝试在美味派中编写自定义身份验证。基本上,我想使用 post 参数进行身份验证,我根本不想使用 django 身份验证,所以我的代码看起来像:

class MyAuthentication(Authentication):
   def is_authenticated(self, request, **kwargs):
       if request.method == 'POST':
           token = request.POST['token']
           key = request.POST['key']
           return is_key_valid(token,key)

这或多或少是个想法。问题是我不断收到以下错误:

"error_message": "You cannot access body after reading from request's data stream"

我知道这与我正在访问 POST 的事实有关,但我不知道是否有办法解决它。有任何想法吗? 谢谢。

编辑:也许我忘了提到最重要的事情。我正在使用在 github 中找到的技巧处理表单数据。我的资源来自多部分资源

class MultipartResource(object):
    def deserialize(self, request, data, format=None):
        if not format:
            format = request.META.get('CONTENT_TYPE', 'application/json')

        if format == 'application/x-www-form-urlencoded':
            return request.POST

        if format.startswith('multipart'):
            data = request.POST.copy()
            data.update(request.FILES)
            return data
        return super(MultipartResource, self).deserialize(request, data, format)

【问题讨论】:

  • 访问request.POST 并没有错... Django 将 POST 变量保存在 request 对象上,以便您完成此操作。我不认为这段代码直接有问题......我怀疑 TastyPie 做了一些奇怪的事情 - 也许检查您的请求是否有正确的 Content-type 标头等?作为一个侧面,使用例如,这是一个好主意。 token = request.POST.get('token', some_default) 或捕获并处理抛出的 KeyError 如果该参数不存在 - 可能通过返回 HTTP 401
  • 与django rest框架有类似的情况,我无法访问request.body,但数据在request.data中可用

标签: django tastypie


【解决方案1】:

问题是您的请求标头中的Content-Type 设置不正确。 [Reference]

Tastypie 仅识别 xmljsonyamlbplist。所以在发送 POST 请求时,需要将请求头中的Content-Type 设置为其中之一(例如,application/json)。

编辑

您似乎正在尝试通过以下方式发送包含文件的多部分表单 好吃。

Issac Kelly 关于 Tastypie 文件上传支持的一点背景知识 路线图 1.0 最终版(尚未发布):

  1. 实现一个 Base64FileField,它接受用于 PUT/POST 的 base64 编码文件(如问题 #42 中的文件),并为 GET 请求提供 URL。这将是主要的 sweetpie 存储库的一部分。
  2. 我们希望鼓励其他实施作为独立项目实施。有几种方法可以做到这一点,其中大多数都有些挑剔,并且它们都有不同的缺点,我们希望有其他选择,并记录每种方法的优缺点

这意味着至少目前,Tastypie 不正式支持多部分 上传文件。但是,据说在野外有叉子正在工作 好吧,this 是其中之一 他们。不过我还没有测试过。


现在让我尝试解释一下您遇到该错误的原因。

在 Tastypie resource.py, line 452:

def dispatch(self, request_type, request, **kwargs):
    """
    Handles the common operations (allowed HTTP method, authentication,
    throttling, method lookup) surrounding most CRUD interactions.
    """
    allowed_methods = getattr(self._meta, "%s_allowed_methods" % request_type, None)

    if 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
        request.method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']

    request_method = self.method_check(request, allowed=allowed_methods)
    method = getattr(self, "%s_%s" % (request_method, request_type), None)

    if method is None:
        raise ImmediateHttpResponse(response=http.HttpNotImplemented())

    self.is_authenticated(request)
    self.is_authorized(request)
    self.throttle_check(request)

    # All clear. Process the request.
    request = convert_post_to_put(request)
    response = method(request, **kwargs)

    # Add the throttled request.
    self.log_throttled_access(request)

    # If what comes back isn't a ``HttpResponse``, assume that the
    # request was accepted and that some action occurred. This also
    # prevents Django from freaking out.
    if not isinstance(response, HttpResponse):
        return http.HttpNoContent()

    return response

convert_post_to_put(request) 从这里调用。还有here is the code for convert_post_to_put

# Based off of ``piston.utils.coerce_put_post``. Similarly BSD-licensed.
# And no, the irony is not lost on me.
def convert_post_to_VERB(request, verb):
    """
    Force Django to process the VERB.
    """
    if request.method == verb:
        if hasattr(request, '_post'):
            del(request._post)
            del(request._files)

        try:
            request.method = "POST"
            request._load_post_and_files()
            request.method = verb
        except AttributeError:
            request.META['REQUEST_METHOD'] = 'POST'
            request._load_post_and_files()
            request.META['REQUEST_METHOD'] = verb
        setattr(request, verb, request.POST)

    return request


def convert_post_to_put(request):
    return convert_post_to_VERB(request, verb='PUT')

而且这种方法并不是真正打算像它那样处理多部分 阻止对request.body 的任何进一步访问的副作用,因为 _load_post_and_files() 方法会将_read_started 标志设置为True

Django request.body_load_post_and_files():

@property
def body(self):
    if not hasattr(self, '_body'):
        if self._read_started:
            raise Exception("You cannot access body after reading from request's data stream")
        try:
            self._body = self.read()
        except IOError as e:
            six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])
        self._stream = BytesIO(self._body)
    return self._body

def read(self, *args, **kwargs):
    self._read_started = True
    return self._stream.read(*args, **kwargs)

def _load_post_and_files(self):
    # Populates self._post and self._files
    if self.method != 'POST':
        self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
        return
    if self._read_started and not hasattr(self, '_body'):
        self._mark_post_parse_error()
        return

    if self.META.get('CONTENT_TYPE', '').startswith('multipart'):
        if hasattr(self, '_body'):
            # Use already read data
            data = BytesIO(self._body)
        else:
            data = self
        try:
            self._post, self._files = self.parse_file_upload(self.META, data)
        except:
            # An error occured while parsing POST data. Since when
            # formatting the error the request handler might access
            # self.POST, set self._post and self._file to prevent
            # attempts to parse POST data again.
            # Mark that an error occured. This allows self.__repr__ to
            # be explicit about it instead of simply representing an
            # empty POST
            self._mark_post_parse_error()
            raise
    else:
        self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()

所以,你可以(虽然可能不应该)猴子补丁 Tastypie's convert_post_to_VERB() 方法通过设置 request._body 通过调用 request.body 然后立即设置 _read_started=False 以便 _load_post_and_files() 将从 _body 读取并且不会设置 _read_started=True:

def convert_post_to_VERB(request, verb):
    """
    Force Django to process the VERB.
    """
    if request.method == verb:
        if hasattr(request, '_post'):
            del(request._post)
            del(request._files)

        request.body  # now request._body is set
        request._read_started = False  # so it won't cause side effects

        try:
            request.method = "POST"
            request._load_post_and_files()
            request.method = verb
        except AttributeError:
            request.META['REQUEST_METHOD'] = 'POST'
            request._load_post_and_files()
            request.META['REQUEST_METHOD'] = verb
        setattr(request, verb, request.POST)

    return request

【讨论】:

  • 你是对的,但是,我正在使用父类来处理这个问题。忘记在问题中提及这一点。谢谢
  • @eran Tastypie 分段上传存在一些问题,无法修复,您使用的代码无法完全修复。请参阅我更新的答案以获得解释。
  • 我刚刚遇到了与 OP 相同的问题,根据您的出色解释,我尝试了以下非常简单的修复:在 MyAuthentication 类中,就在返回之前,添加以下行:@987654355 @。这似乎有效。
  • 如果request.method == verb 永远不会是True 用于POST 请求,为什么convert_post_to_VERB 会触发? request.method 是 POST,而 verb 是 PUT。整个方法不是无操作吗?
【解决方案2】:

您说您需要自定义身份验证,这很好,但请考虑改用 Authorization 标头。通过使用POST,您可以强制 Django 解析整个有效负载,假设数据是 urlencoded 或 multipart form 编码的。这实际上使得无法使用 JSON 或 YAML 等非格式负载。

class MyAuthentication(Authentication):
    def is_authenticated(self, request, **kwargs):
        auth_info = request.META.get('HTTP_AUTHORIZATION')
        # ...

【讨论】:

    【解决方案3】:

    当您第二次访问 request.body(或 request.raw_post_data,如果您仍在使用 Django 1.3)或我相信,如果您在访问 POST、GET、META 或 COOKIES 属性后访问它时会发生此错误.

    Tastypie 在处理 PUT 或 PATCH 请求时会访问 request.body (raw_post_data) 属性。

    考虑到这一点,在不知道更多细节的情况下,我会:

    • 检查这是否仅发生在 POST/PUT 中。如果是这样,那么您将不得不重写一些美味的方法或放弃您的身份验证方法。
    • 在代码中查找访问 request.body (raw_post_data) 的位置
    • 查找对可能尝试访问 body/raw_post_data 的第 3 方模块(可能是中间件)的调用

    希望这会有所帮助!

    【讨论】:

      【解决方案4】:

      我创建了一个非常适合我的实用方法。虽然我不确定这会如何影响 Django 的底层部分,但它确实有效:

      import io
      
      
      def copy_body(request):
          data = getattr(request, '_body', request.body)
          request._body = data
          request._stream = io.BytesIO(data)
          request._files = None
          return data
      

      我在中间件中使用它为request添加JSON属性:https://gist.github.com/antonagestam/9add2d69783287025907

      【讨论】:

        猜你喜欢
        • 2013-08-27
        • 2013-10-06
        • 1970-01-01
        • 2011-11-13
        • 2017-01-01
        • 2012-12-23
        • 2020-04-01
        • 2022-12-10
        • 1970-01-01
        相关资源
        最近更新 更多