Подтвердить что ты не робот

Как получить доступ к данным POST внутри пользовательской аутентификации tastypie

Я пытаюсь написать пользовательскую аутентификацию в tastypie. В принципе, я хочу сделать аутентификацию с использованием параметров сообщения, и я не хочу вообще использовать django auth, поэтому мой код выглядит примерно так:

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 data stream"

Я понимаю, что это связано с тем, что я обращаюсь к POST, но я не мог понять, есть ли способ его решить. Есть идеи? Спасибо.

EDIT: Возможно, я забыл упоминание о самой важной вещи. Я обрабатываю данные формы, используя трюк, который я нашел в 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)
4b9b3361

Ответ 1

Проблема в том, что заголовки Content-Type в вашем запросе неправильно установлены. [ Ссылка]

Tastypie распознает только xml, json, yaml и bplist. Поэтому при отправке запроса POST вам нужно установить Content-Type в заголовках запроса на один из них (например, application/json).

ИЗМЕНИТЬ

Похоже, вы пытаетесь отправить многостраничную форму с файлами через Tastypie.

Небольшой фон для поддержки загрузки файлов Tastypie от Issac Kelly для roadmap 1.0 final (еще не выпущен):

  • Реализовать Base64FileField, который принимает файлы с кодировкой base64 (например, вопрос № 42) для PUT/POST и предоставляет URL-адрес для запросов GET. Это будет частью основного репетиции tastypie.
  • Мы хотели бы поощрять другие реализации для реализации в качестве независимых проектов. Там есть несколько способов сделать это, и большинство из них немного бледнеют, и все они имеют разные недостатки. Мы хотели бы иметь другие варианты и документировать плюсы и минусы каждого

Это означает, что на данный момент, по крайней мере, Tastypie официально не поддерживает multipart файл загружен. Однако есть вилки в дикой природе, которые предположительно работают ну, этот является одним из их. Я не тестировал его, хотя.


Теперь позвольте мне попытаться объяснить, почему вы сталкиваетесь с этой ошибкой.

В Tastypie resource.py, строка 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) вызывается отсюда. И вот код для 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')

И этот метод на самом деле не предназначен для обработки multipart, поскольку он побочный эффект предотвращения дальнейшего доступа к 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 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

Ответ 2

Вы говорите, что вам нужен пользовательский auth, который хорош, но, пожалуйста, подумайте об использовании заголовка Authorization. Используя POST, вы вынуждаете Django анализировать всю полезную нагрузку, предполагая, что данные закодированы в кодировке urlencoded или multipart. Это фактически делает невозможным использование неформальных полезных нагрузок, таких как 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 будет обращаться к атрибуту request.body(raw_post_data) при обработке запросов PUT или PATCH.

Имея это в виду и не зная больше деталей, я бы:

  • Проверьте, что это происходит только для POST/PUT. Если это так, то вам придется сделать некоторые переопределения некоторых методов tastypie или отказаться от вашего подхода к аутентификации.
  • Ищите места в коде, где вы обращаетесь к request.body(raw_post_data)
  • Ищите вызовы сторонних модулей (возможно, промежуточное ПО), которые могут пытаться получить доступ к телу /raw _post_data​​li >

Надеюсь, это поможет!

Ответ 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

Я использую его в промежуточном программном обеспечении, чтобы добавить атрибут JSON в request: https://gist.github.com/antonagestam/9add2d69783287025907