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