Uzyskaj surową treść POST w Python Flask niezależnie od nagłówka Content-Type

135

Wcześniej zapytałem, jak uzyskać dane otrzymane w żądaniu Flask, ponieważ request.databyło puste. Odpowiedź wyjaśniła, że request.datajest to surowa treść wiadomości, ale będzie pusta, jeśli dane formularza zostaną przeanalizowane. Jak mogę bezwarunkowo otrzymać surową treść postu?

@app.route('/', methods=['POST'])
def parse_request():
    data = request.data  # empty in some cases
    # always need raw data here, not parsed form data
ddinchev
źródło

Odpowiedzi:

224

Służy request.get_data()do pobierania surowych danych, niezależnie od typu zawartości. Dane są buforowane i można następnie dostępu request.data, request.json, request.formdo woli.

Jeśli uzyskasz dostęp request.datapierwszy, najpierw wywoła get_dataargument, aby przeanalizować dane formularza. Jeśli żądanie ma postać typu zawartości ( multipart/form-data, application/x-www-form-urlencodedlub application/x-url-encoded), a następnie dane surowe zostanie skonsumowane. request.datai request.jsonbędzie w tym przypadku puste.

cud2k
źródło
2
Wydaje się, że to się psuje, gdy używasz raven-python (Sentry), błąd i obejścia tutaj: github.com/getsentry/raven-python/issues/457
dequis
36

request.streamjest strumieniem nieprzetworzonych danych przekazanych do aplikacji przez serwer WSGI. Podczas czytania nie jest wykonywane żadne analizowanie, chociaż zwykle chcesz request.get_data()zamiast tego.

data = request.stream.read()

Strumień będzie pusty, jeśli został wcześniej odczytany przez request.datalub inny atrybut.

jd.
źródło
Dziękujemy za wskazanie, że strumień będzie pusty, jeśli wcześniej przeczytasz request.data! Prawie
dopadł
15

Stworzyłem oprogramowanie pośredniczące WSGI, które przechowuje surowe treści ze environ['wsgi.input']strumienia. Zapisałem wartość w środowisku WSGI, aby mieć do niej dostęp z request.environ['body_copy']poziomu mojej aplikacji.

Nie jest to konieczne w Werkzeug lub Flask, ponieważ request.get_data()spowoduje to pobranie surowych danych niezależnie od typu zawartości, ale z lepszą obsługą zachowania HTTP i WSGI.

Powoduje to wczytanie całego ciała do pamięci, co będzie problemem, jeśli na przykład opublikowany zostanie duży plik. Nie odczyta niczego, jeśli Content-Lengthbrakuje nagłówka, więc nie obsługuje żądań przesyłania strumieniowego.

from io import BytesIO

class WSGICopyBody(object):
    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):
        length = int(environ.get('CONTENT_LENGTH') or 0)
        body = environ['wsgi.input'].read(length)
        environ['body_copy'] = body
        # replace the stream since it was exhausted by read()
        environ['wsgi.input'] = BytesIO(body)
        return self.application(environ, start_response)

app.wsgi_app = WSGICopyBody(app.wsgi_app)
request.environ['body_copy']
jhaski
źródło
7

request.databędzie pusty, jeśli request.headers["Content-Type"]zostanie rozpoznany jako dane formularza, do którego zostaną przetworzone request.form. Aby uzyskać nieprzetworzone dane niezależnie od typu zawartości, użyj request.get_data().

request.datawywołań request.get_data(parse_form_data=True), co powoduje różne zachowanie danych formularza.

KevinH
źródło