2017-12-05 1 views
0

ich noch den rohen Inhalt der POST-Anfrage Körper bekommen müssen, wenn ich versuche request.body zugreifen eine Ausnahme Ich erhalte:Wie Anfrage Körper zuzugreifen, wenn Django Ruhe Framework und vermeiden RawPostDataException immer

django.http.request.RawPostDataException: 
You cannot access body after reading from request's data stream 

I Ich bin mir bewusst, dass es ratsam ist, request.data anstelle von request.body zu verwenden, wenn Sie Django Rest Framework verwenden, aber zum Zweck der Validierung der digitalen Signatur muss ich den Anfragetext in einer rohen und "unberührten" Form haben, da dies der 3rd-Party ist unterschrieben und was ich validieren muss.

Pseudocode:

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) 

Antwort

0

Ich habe interessante topic auf DRFs GitHub gefunden, aber es ist nicht das Problem vollständig decken. Ich habe den Fall untersucht und eine saubere Lösung gefunden. Überraschenderweise gab es keine solche Frage zu SO, also entschied ich mich, sie für die Öffentlichkeit nach der SO self-answer guidelines hinzuzufügen.

Der Schlüssel für Understaning das Problem und Lösung ist, wie die HttpRequest.body (source) funktioniert:

@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 

Wenn body Zugriff - wenn der self._body bereits gesetzt sein einfach zurückgegeben, andernfalls wird der interne Anforderung Strom gelesen wird und zugewiesen zu _body: self._body = self.read(). Seitdem fällt jeder weitere Zugriff auf body auf return self._body zurück. Zusätzlich wird vor dem Lesen des internen Anfrage-Streams eine if self._read_started Prüfung durchgeführt, die eine Exception auslöst, wenn "Lesen gestartet" ist.

Die self._read_started flague wird von der read() Methode (source) gesetzt:

def read(self, *args, **kwargs): 
    self._read_started = True 
    try: 
     return self._stream.read(*args, **kwargs) 
    except IOError as e: 
     six.reraise(UnreadablePostError, ...) 

Nun sollte es klar sein, dass die RawPostDataException wird nach dem Zugriff auf die request.body angehoben werden, wenn nur die read() Methode ohne aufgerufen wurde Zuweisen des Ergebnisses zu Anfragen self._body.

Jetzt können Sie einen Blick auf DRF JSONParser Klasse (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)) 

(I etwas ältere Version o DRF Quelle gewählt haben, denn nach dem Mai 2017 gibt es einige Performance-Verbesserungen haben, die den Schlüssel verschleiern Linie unseres Problems für das Verständnis)

Nun sollte es klar sein, dass der stream.read() Aufruf die _read_started flague setzt und daher ist es unmöglich, dass die body Eigenschaft des Strom erneut zuzugreifen (nach dem Parser).

Die Lösung

Das „no request.body“ -Ansatz ist eine DRF Absicht (glaube ich) so obwohl es technisch möglich ist, den Zugang zu request.body global zu aktivieren (über benutzerdefinierte Middleware) - es ohne nicht getan werden soll, tiefes Verständnis all seiner Konsequenzen.

Der Zugang zum request.body Eigenschaft kann auf folgende Weise gewährt ausdrücklich und lokal:

Sie müssen custom parser definieren:

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)) 

Dann kann es verwendet werden, wenn es notwendig ist, Zugriff auf die rohe Anfrage Inhalt:

@api_view(['POST']) 
@parser_classes((MyJSONParser,)) 
def example_view(request, format=None): 
    return Response({'received data': request.raw_body}) 

Während request.body bleibt weiterhin global unzugänglich (wie von DRF-Autoren beabsichtigt).

0

Ich könnte hier etwas fehlt, aber ich bin mir ziemlich sicher, dass Sie keine benutzerdefinierte Parser in diesem Fall definieren müssen ...

Sie können nur die JSONParser von DRF selbst verwenden:

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}) 
Verwandte Themen