Requests: Header "Transfer-Encoding: chunked" gesetzt, auch wenn Content-Length angegeben wird, was dazu führt, dass der Body nicht wirklich chunked wird

Erstellt am 4. Okt. 2013  ·  52Kommentare  ·  Quelle: psf/requests

Testskript

import requests
import time

def f():
    yield b"lol"
    time.sleep(2)
    yield b"man"

requests.post('http://127.0.0.1:8801/', data=f(), headers={"Content-Length": 6})

Tatsächliche Ergebnis

Auf dem Server empfangen:

$ nc -p 8801 -l
POST / HTTP/1.1
Host: 127.0.0.1:8801
User-Agent: python-requests/2.0.0 CPython/3.3.1 Linux/3.11.0-031100rc4-generic
Accept: */*
Transfer-Encoding: chunked
Content-Length: 6
Accept-Encoding: gzip, deflate, compress

lolman

Erwartetes Ergebnis

Habe nicht mit "Transfer-Encoding: chunked" gerechnet, da ich die Content-Length angegeben habe. Wenn Anforderungen darauf bestehen, eine Chunked-Transfer-Codierung durchzuführen, sollte die Inhaltslänge ignoriert und der Inhalt tatsächlich unterteilt werden (wie es der Fall ist, wenn kein Content-Length-Header angegeben ist).

Breaking API Change Bug

Hilfreichster Kommentar

@timuralp Ich bin weiterhin dagegen, dafür eine Flagge hinzuzufügen. Es ist einfach inakzeptabel, eine HTTP/1.1-Implementierung zu haben, die 2016 keine Chunked Transfer Encoding verarbeiten kann. Es war so lange eine Spezifikationsanforderung, dass die erste Spezifikation, die es erforderte, fast alt genug ist, um in den Vereinigten Staaten von Amerika abzustimmen: Ich glaube nicht, dass wir die Entitäten locker halten können, weil sie es nicht tun.

Aus meiner Sicht bleibt hier der Fehler, dass wir sowohl Content-Length als auch Transfer-Encoding falsch ausgeben können. Meine Perspektive ist natürlich unverbindlich. ;)

Alle 52 Kommentare

Ich stimme zu, wir sollten eine dieser beiden Optionen wählen. =)

Also hier kommentieren, wie ich es vor >6 Stunden hätte tun sollen:

Wenn Anforderungen darauf bestehen, eine Chunked-Transfer-Codierung durchzuführen, sollte die Inhaltslänge ignoriert und der Inhalt tatsächlich unterteilt werden (wie es der Fall ist, wenn kein Content-Length-Header angegeben ist).

Sie implizieren, dass wir in diesem Fall die Daten nicht wirklich aufgeteilt haben. Hast du dafür konkrete Beweise? Der Output allein reicht bei weitem nicht aus, um dies zu beweisen.

Außerdem funktioniert das Ausführen Ihres obigen Beispiels in 2.0.0 nicht: Ich erhalte diesen Traceback:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/api.py", line 88, in post
    return request('post', url, data=data, **kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/sessions.py", line 357, in request
    resp = self.send(prep, **send_kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/sessions.py", line 460, in send
    r = adapter.send(request, **kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/adapters.py", line 319, in send
    timeout=timeout
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py", line 541, in urlopen
    body=body, headers=headers)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py", line 366, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 955, in request
    self._send_request(method, url, body, headers)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 989, in _send_request
    self.endheaders(body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 951, in endheaders
    self._send_output(message_body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 815, in _send_output
    self.send(message_body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 787, in send
    self.sock.sendall(data)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
TypeError: must be string or buffer, not generator

Dies ist, soweit ich das beurteilen kann, nicht einmal ein tatsächliches Problem. Nicht zuletzt auf 2.0.0, welches die einzige derzeit unterstützte Version von Anfragen ist.

Habe nicht mit "Transfer-Encoding: chunked" gerechnet, da ich die Content-Length angegeben habe

Was hat Ihnen den Eindruck vermittelt, dass dies der Fall sein würde?

Was mir den Eindruck vermittelte, war die Tatsache, dass Requests nicht stückelten (auch wenn es den Header gesetzt hatte), sobald ich Content-Length gesetzt hatte.

Das Beispiel funktioniert mit Python 3.3.

Beim Versuch mit Python 3.3 explodiert es nicht, auf der anderen Seite hat es den Inhalt zerstückelt. Das Chunking ist nicht plattformabhängig, daher bin ich darüber gründlich verwirrt.

POST / HTTP/1.1
Host: 127.0.0.1:8801
Content-Length: 6
User-Agent: python-requests/1.2.3 CPython/3.3.2 Darwin/12.5.0
Transfer-Encoding: chunked
Accept: */*
Accept-Encoding: gzip, deflate, compress

3
lol
3
man
0

Zweitens würde ich gerne wissen, was Ihnen den Eindruck vermittelt hat, dass das Setzen des Headers Content-Length eine Chunk-Codierung von Daten verhindern würde.

@sigmavirus24 Sie verwenden Requests 2.0.0 nicht in Ihrer Python 3.3-Installation.

@ysangkok guter Punkt. :-) Mit 2.0 ist es definitiv kaputt. Unabhängig davon ist meine Meinung definitiv vor Gericht, wenn wir den Header Content-Length entfernen.

Sie haben wahrscheinlich recht @sigmavirus24 , wir sollten wahrscheinlich den Content-Length-Header

Hm, ich bin von meiner eigenen Argumentation nicht mehr ganz überzeugt. Ich suchte nach den alten Diskussionen, in denen es darum ging, dass Benutzer den Content-Length Header selbst setzen und ich muss mich an alte IRC-Konversationen erinnern.

Ich bin immer noch der Meinung, dass Benutzer diese Header wirklich nicht selbst setzen sollten und dass wir sie entfernen sollten, aber ich denke, dieses Thema weist auf einen viel wichtigeren Aspekt hin, nämlich das sehr unterschiedliche Verhalten von Anfragen in zwei verschiedenen Versionen von Python.

Auf 2.7 (wie ich gezeigt habe) ändert das Setzen des Content-Length Headers nichts daran, wie Anfragen die Daten hochladen. Auf 3.3 ist @ysangkok jedoch richtig, dass die Einstellung alles so schnell wie möglich sendet (es verwendet immer noch den Generator, sendet es jedoch nicht in einem tatsächlich stückigen Manor).

Eine einfache Möglichkeit, dies zu beheben, besteht darin, den Header bei Verwendung eines Generators zu entfernen (oder immer ein konsistentes Verhalten bereitzustellen).

Die andere einfache Möglichkeit besteht darin, die Konsistenz der API zu brechen, indem nicht immer die Chunked-Transfer-Codierung mit einem Generator verwendet wird. @Lukasa Dies festzulegen .

Um ehrlich zu sein, würde ich jedoch nie erwarten, dass das Setzen eines Headers das Verhalten bei der Verwendung eines Generators ändert.

Das ist sicherlich eine extrem schwierige Situation

Bläh. Ich werde nachdenken.

Dies ist auch keine bahnbrechende API-Änderung, die ich zögern würde, weil ich mich fragen muss, wie viele Leute sich tatsächlich auf dieses Verhalten verlassen.

Ich bin in eine Situation geraten, in der die Länge eines Generators (z. B. einer gestreamten Datei von einem anderen Server oder einer wirklich großen Festplatte in einer Datei) bekannt ist. Beispielsweise:

response = requests.get('http://example.com/mybig.iso', stream=True)
length = response.headers.get('Content-Length')

def exhaust(response):
    while True:
        out = response.raw.read(1024*1024)
        if not out:
            break
        yield out

response = requests.post('http://example.com/upload', data=exhaust(response), headers={'Content-Length': length})

Das könnte ein triftiger Grund sein, Content-Length beizubehalten. Die einzige Problemumgehung (die mir bekannt ist) besteht derzeit darin, den Generator im Speicher zu erschöpfen und die Bytes direkt zu übergeben.

@bryanhelmig verlangt der Server, den Sie hochladen, dass Sie den Content-Length-Header senden?

@bryanhelmig hast du die Kommentare im verlinkten Pull-Request gesehen?

Jedenfalls verstehe ich nicht, warum Content-Transfer-Encoding nicht nur ein Flag ist. Keine Notwendigkeit, Header zu löschen (oder irgendeine andere Art von Hand zu halten), es war nie eine Frage, ob die Content-Length richtig oder falsch ist, das eigentliche Problem ist, dass das Vorhandensein von Content-Length den Content-Transfer halb deaktiviert -Encoding, was überhaupt keinen Sinn macht. Aber nur Requests dazu zu bringen, Content-Length zu ignorieren, löst nicht das eigentliche Problem, nämlich dass Requests Content-Transfer-Encoding verwendet, wenn es sich danach anfühlt (klingt so, als ob das beim Lesen von einem Generator sein soll), obwohl viele Web- Server unterstützen es nicht einmal.

Das Ignorieren der Inhaltslänge wird die Anbieter verwirren. Wenn Sie (@sigmavirus24) darauf bestehen, es nicht zu übertragen, warum lösen Sie dann nicht einfach eine Ausnahme aus? Wie Sie sagten, wird diese Funktionalität wahrscheinlich nicht weit verbreitet.

In der Pull-Anfrage sagten Sie: "Der Anwendungsfall in der Ausgabe ist nur ein Beispiel für das Verhalten. Es gibt keine Rechtfertigung dafür, warum Sie tun, was Sie tun.". Ich stimme nicht zu, ich denke, der ursprüngliche Code in dieser Ausgabe ist ein völlig normales Verhalten, und tatsächlich denke ich, dass das Streamen von POST-Daten ein großer Anwendungsfall ist und dass es lächerlich ist, wenn man gezwungen ist, Content-Transfer-Encoding zu verwenden oder darauf zurückzugreifen Bibliotheken auf niedrigerer Ebene beim Streamen/Verwenden von Generatoren.

Um es zusammenzufassen: Content-Transfer-Encoding sollte ein Flag sein, unzulässige Parameterkombinationen sollten Ausnahmen provozieren und vom Benutzer bereitgestellte Flags sollten wenn möglich gesendet werden. Und natürlich sollte es nicht möglich sein, Content-Transfer-Encoding halb zu deaktivieren.

Halt halt halt.

Alle machen eine Verschnaufpause.

@ysangkok Sie können Streaming-Uploads ohne Generatoren problemlos durchführen. Provide Fordert ein dateiähnliches Objekt im Datenparameter an und das funktioniert. Ja, es ist nicht so einfach wie die Verwendung eines Generators, aber das ist in Ordnung, weil es immer noch nicht sehr schwer ist.

In der Zwischenzeit sollten Anfragen nicht darauf hindeuten, dass Daten aufgeteilt werden, wenn dies nicht der Fall ist. Darin sind wir uns alle einig. Die Frage ist, was wir _in Ihrem speziellen Fall_ tun sollten: nämlich einen Generator und ein Content-Length bereitzustellen. Sie und @sigmavirus24 sind in dieser Frage zu Recht nicht einverstanden, _was in Ordnung ist_. Können wir jedoch bitte alle anerkennen, dass beide Lager rationale Gründe haben, ihre Position zu erwarten?

@ysangkok Sie haben gesagt, dass "das Ignorieren der @sigmavirus24 behauptet, dass das Ignorieren der sehr klaren Dokumentation, wenn sie mit einem Generator bereitgestellt wird, die Leute verwirren wird, die _das_ tun. Sie haben beide recht.

(Als Randbemerkung, die Tatsache, dass viele Server Transfer-Encoding nicht verstehen, ist nur eine wilde Behauptung, die auf keinen Beweisen basiert, die ich gesehen habe. Bis irgendwelche Beweise vorgelegt werden, ignoriere ich sie. )

Auf die eine oder andere Weise müssen wir uns aussuchen, was wir hier tun. Es ist möglich, dass die richtige Entscheidung darin besteht, eine Ausnahme auszulösen, wenn sowohl ein Generator als auch Content-Length bereitgestellt werden. Das ist machbar. Es macht den Fall von @bryanhelmig nicht einmal schlimmer, denn er sollte response.raw einfach durchreichen, anstatt ihn in einen Dekorateur zu wickeln ( zu Ihrem Vorteil, Bryan ).

Ich neige natürlich dazu, hier auf dem Zaun zu sitzen und eine YoureACrazyPerson Ausnahme zu werfen, aber ich kann verstehen, warum Sie beide glauben, was Sie glauben. Insbesondere Entscheidungen auf der Grundlage von vom Benutzer bereitgestellten Headern zu treffen, ist lahm und verwirrend, und wir sollten versuchen, dies nicht zu tun. Die folgenden sind jedoch harte Linien:

  1. Die Kontrolle von Transfer-Encoding wird kein Flag sein. Nicht jetzt, nie. Requests macht keine kleinen Sonderfall-Flags wie diese.
  2. Wir können nichts tun.
  3. Requests ist _nicht_ verpflichtet, alle Anwendungsfälle zu unterstützen. Ich werde jeden Anwendungsfall gerne unter einen Bus werfen, wenn dies die API verbessert.

verlangt der Server, auf den Sie hochladen, dass Sie den Content-Length-Header senden?

@sigmavirus24 tut es. :-( 411 für Versuche ohne Content-Length. Ziemlich nervig IMO.

Hast du die Kommentare im verlinkten Pull-Request gesehen?

@ysangkok habe ich gemacht.

er sollte nur antworten.roh direkt durch, anstatt sie in einen Dekorateur zu wickeln

@Lukasa Das war eigentlich meine ursprüngliche Bearbeitung, aber ich bin mir nicht sicher, was uns das in diesem Fall

Danke für die ausführlichen Antworten an alle. Es ist jedoch in Ordnung, wenn Benutzer in exotischen Situationen für eine von 100 Anfragen zu einer anderen Bibliothek wechseln müssen. Das Leben ist viel einfacher für die 99 anderen Fälle.

Es ist jedoch in Ordnung, wenn Benutzer in exotischen Situationen für eine von 100 Anfragen zu einer anderen Bibliothek wechseln müssen.

Ich bevorzuge Anfragen _Make Easy Things Easy & Hard Things Possible_ :)

@piotr-dobrogost Einverstanden, aber wenn das Ermöglichen einer schwierigen Sache es erfordert, eine einfache Sache schwieriger zu machen, lassen wir die einfache Sache lieber einfach. =)

Ein paar Dinge:

Für mich war dies (und wird es auch weiterhin sein, bis wir zu einer Entscheidung kommen) undefiniertes Verhalten

Als Randnotiz ist die Tatsache, dass viele Server Transfer-Encoding nicht verstehen, nur eine wilde Behauptung, die auf keinen Beweisen basiert, die ich gesehen habe. Solange keine Beweise vorliegen, ignoriere ich sie.

Es gibt einen Unterschied zwischen Servern, die ein Chunked Transfer-Encoding nicht verstehen, und Servern, die es nicht respektieren wollen. Ich vermute, letzteres ist im Fall von @bryanhelmig der Fall. Alternativ könnte die Anwendung von jemandem geschrieben worden sein, der Transfer-Encoding nicht versteht oder kennt und daher eine Inhaltslänge erfordert.

Es ist möglich, dass die richtige Entscheidung darin besteht, eine Ausnahme auszulösen, wenn sowohl ein Generator als auch Content-Length bereitgestellt werden. Das ist machbar.

Ausnahmen können in diesem Fall zu extrem sein. Wir geben im Allgemeinen keine Ausnahmen für andere als ungültige URLs aus. Die Art und Weise, wie wir die Daten- und Dateiparameter verarbeiten, kann Ausnahmen auslösen, aber wir machen dort keine Sonderfälle. Das heißt, wir haben darüber gesprochen, wie schlecht es ist, wenn Benutzer ihre eigenen Content-Length- und Host-Header angeben (unter anderem, die ich wahrscheinlich vergesse). Da es sich hierbei nicht um technisch ungültige Praktiken handelt, sondern um Praktiken, von denen wir abraten, schlage ich vor, dass wir stattdessen eine Warnung auslösen und dann in bestimmten gut dokumentierten Situationen das Richtige tun.

  • Wenn wir einen Host-Header erhalten, geben wir eine Warnung aus, löschen ihn jedoch nicht.
  • Wenn wir ein Objekt erhalten, dessen Größe wir bestimmen können und der Content-Length-Header bereitgestellt wird, sollten wir eine Warnung ausgeben, dass die Bereitstellung in einem solchen Fall nicht erfolgen sollte. Ich bin mir jedoch nicht sicher, ob wir ihre Einstellung überschreiben sollten.
  • Wenn wir einen Generator und einen Content-Length-Header erhalten, sollten wir eine Warnung auslösen und den Header entfernen.

Es ist nicht ratsam, Warnungen zu verwenden, um den Benutzer auf sanftere Weise zu informieren, was er tut. Es deckt auch die Tatsache ab, dass so viele unserer Benutzer sich nicht die Mühe machen, die Dokumentation zu lesen, und so wird die Bibliothek zu einer Art Selbstdokumentation. Dies gibt uns auch die Möglichkeit, das Verhalten in Zukunft zu ändern. Und noch besser, wenn Benutzer die Warnungen deaktivieren möchten, können sie dies, da Python eine Möglichkeit bietet, Warnungen stumm zu schalten.

Es gibt einen Unterschied zwischen Servern, die ein Chunked Transfer-Encoding nicht verstehen, und Servern, die es nicht respektieren wollen. Ich vermute, letzteres ist im Fall von @bryanhelmig der Fall. Alternativ könnte die Anwendung von jemandem geschrieben worden sein, der Transfer-Encoding nicht versteht oder kennt und daher eine Inhaltslänge erfordert.

Ich denke tatsächlich, dass dies in den meisten meiner Beispiele sehr wahrscheinlich der Fall ist. Wir (@zapier) versuchen, Dateien auf über ein Dutzend verschiedener APIs hochzuladen und die wenigen von ihnen, die eine Zeitüberschreitung der Inhaltslänge (scheinbar) mit Chunked Transfer-Encoding erfordern.

Als Randnotiz ist die Tatsache, dass viele Server Transfer-Encoding nicht verstehen, nur eine wilde Behauptung, die auf keinen Beweisen basiert, die ich gesehen habe. Solange keine Beweise vorliegen, ignoriere ich sie.

Ich könnte eine Testsuite zusammenstellen, wie verschiedene Dienste auf Content-Length/Transfer-Encoding reagieren, aber ich habe das Gefühl, dass selbst wenn es sich um falsch implementierte APIs handelt, dies nicht wirklich zum Design von Python-Anfragen raten sollte. Noch einfacher könnte ich nur Namen nennen, die auf meiner Erfahrung im Kampf gegen dieses Problem der letzten Woche basieren, aber noch einmal, wenn es sich um API/Server-Bugs handelt, was nützen solche Informationen für Python-Anfragen?

Ich schlage vor, dass wir stattdessen eine Warnung auslösen und dann in bestimmten gut dokumentierten Situationen das Richtige tun.

Stimmen Sie dem Standardverhalten zu, aber manchmal übertrumpft die Realität das "Richtige" (insbesondere wenn Sie keine Kontrolle über einen möglicherweise defekten Server haben). Es könnte schön sein, eine Technik zum Überschreiben zu dokumentieren (auch wenn sie dafür plädiert, dass der Benutzer viel Arbeit macht, wie zum Beispiel einen benutzerdefinierten Adapter zu schreiben).

Nur um es klarzustellen: mit "das Richtige" meine ich nicht unbedingt die RFC-Sache. (Falls das das ist was du denkst ich meinte)

Ich könnte eine Testsuite zusammenstellen, wie verschiedene Dienste auf Content-Length/Transfer-Encoding reagieren, aber ich habe das Gefühl, dass selbst wenn es sich um falsch implementierte APIs handelt, dies nicht wirklich zum Design von Python-Anfragen raten sollte.

Ich stimme zu, dass fehlerhafte Server unser Design nicht beeinflussen sollten.

Noch einfacher könnte ich nur Namen nennen, die auf meiner Erfahrung im Kampf gegen dieses Problem der letzten Woche basieren, aber noch einmal, wenn es sich um API/Server-Bugs handelt, was nützen solche Informationen für Python-Anfragen?

Wir wissen bereits, wie kaputt das Web ist. Unabhängig davon, ob wir den Header nach der Warnung des Benutzers löschen oder belassen sollten, könnten diese Daten nützlich sein.

Bläh. Das macht mich traurig.

Ok, ich denke, der Plan von @sigmavirus24 ist hier der beste, zumindest

Ein paar Dinge, die mir aufgefallen sind, lenken mich davon ab, euch ein paar detaillierte Daten zu beschaffen, aber hier ist ein Brain Dump von dem, was ich gesehen habe:

Der größte Übeltäter, den ich gesehen habe, sind API-Endpunkte, die einen mehrteiligen Upload erfordern: Sie benötigen normalerweise eine Inhaltslänge oder sie flippen aus, wenn Transfer-Encoding chunked ist (ich bin mir nicht sicher, welche). Das ist nicht überall der Fall, aber hier sind die Teile, die ich mit einem kurzen Grep aus unserer Quelle ziehen kann:

  1. Twitter macht mehrteilig und schlägt mit chunked Codierung/keine Länge
  2. Podio macht Multipart und schlägt mit chunked Codierung/keine Länge
  3. Facebook macht Multipart und arbeitet mit Chunked Encoding/keine Länge.
  4. Salesforce arbeitet mit mehreren Teilen und arbeitet mit chunked Codierung/keine Länge.

Der Grund, warum mir das frisch in den Sinn kommt, ist, dass wir einen benutzerdefinierten mehrteiligen Körpergenerator gebaut haben, der mit "faulen" Dateien arbeitet, die auch die Länge berechnen können. Dies hat zufällig viele der Probleme aufgeworfen, über die wir hier sprechen. Im Moment betrügen wir nur und machen getvalue() für die fehlgeschlagenen Upload-Endpunkte. Wir werden es wahrscheinlich eines Tages wiederholen.

Die anderen Beispiele sind schwieriger zu finden, aber die Dateifirmen (Box, Dropbox, SugarSync, etc...) verstehen es alle und funktionieren perfekt mit Chunked Encoding, also keine Sorge.

Hoffentlich wirft dies ein wenig mehr Licht auf unseren realen Anwendungsfall. Ich wünschte, ich könnte Ihnen mehr Informationen geben, um zu bestätigen, welche Header häufig welche Fehler verursachen (normalerweise handelt es sich dabei um Zeitüberschreitungen).

Dies sollte jetzt einfacher zu testen sein, da wir einige APIs haben, gegen die wir reproduzieren können, nämlich Twitter.

Ich habe genau den gleichen Anwendungsfall: Hochladen von Daten auf einen Server, der keine chunked Codierung unterstützt. Die Datenlänge ist im Voraus bekannt, kommt aber nicht aus einer Datei (von einem Generator).
Ich habe erwartet, dass das Festlegen eines Inhaltslängen-Headers die Längenberechnung in Anforderungen deaktiviert und auch die chunked transfer Codierung deaktiviert.
Inzwischen kann ich dieses Problem umgehen, indem ich ein zusätzliches 'len'-Attribut zu meinem Generator-Objekt hinzufüge, sodass Requests utils.super_len() etwas zurückgibt, so dass keine Chunked-Codierung von Requests gewählt wird: das ist hässlich und brillant.
Als Unix-Benutzer (und wie @piotr-dobrogost ) würde ich erwarten, dass der Programmierer weiß, was er tut, und die Bibliothek sollte sich daran halten: Die Bereitstellung eines Headers mit Inhaltslänge ist ein klarer Hinweis darauf, dass keine chunked Codierung verwendet werden soll. Dies steht jedoch im Widerspruch zu Ihrem obigen Satz: "Ich würde nie erwarten, dass das Setzen eines Headers das Verhalten bei der Verwendung eines Generators ändert". Nun, wenn klar dokumentiert ist, sehe ich den Sinn nicht. Es würde die API nicht brechen, oder?

@netheosgithub Ich würde argumentieren, dass die Änderung dieses Verhaltens absolut eine Änderung der API darstellt. Betrachten Sie den aktuellen Status der API aus der Dokumentation :

Requests unterstützt auch die Chunked-Transfer-Codierung für ausgehende und eingehende Anforderungen. Um eine Chunk-codierte Anfrage zu senden, stellen Sie einfach einen Generator (oder einen beliebigen Iterator ohne Länge) für Ihren Körper bereit.

Beachten Sie, dass die Dokumentation _nicht sagt_ "Um eine Chunk-codierte Anfrage zu senden, stellen Sie einfach einen Generator bereit und legen Sie den Content-Length-Header nicht fest." Dies macht dies zu einer API-Änderung in meinem Buch. Nicht irgendeine API-Änderung: eine schlechte. Die Vorstellung, dass das Festlegen einer Kopfzeile den Text der Anfrage ändert, macht mich sehr unwohl. Überlegen Sie, ob uns jemand um eine ähnliche Anfrage gebeten hat, bei der wir, wenn sie den Header Content-Type: application/json , den Parameter data JSON-kodieren sollten, anstatt ihn formularkodierend zu kodieren! Ich würde diese Anfrage sofort verwerfen.

Ich denke, wir sollten versuchen, die Wurzel des Problems anzugehen: Warum verwenden die Leute in dieser Situation Generatoren?

Derzeit ist die Bereitstellung eines Generators und die Angabe einer Inhaltslänge ein Fehler (es wird eine ungültige http-Anfrage generiert), daher sollte dieser Anwendungsfall von niemandem verwendet werden. Deshalb dachte ich, es würde die Programme Ihrer Benutzer nicht beschädigen.
Warum Generatoren? Daten werden nicht immer von einem dateiähnlichen Objekt bereitgestellt. Zum Beispiel möchte ich den Upload-Fortschritt beobachten, indem ich Daten Stück für Stück usw. herausgebe (sonst müsste ich read()-Methoden überschreiben).

Wenn nur Ihr Argument "ungültige HTTP-Anfrage" gültig war. Ich wünschte, ich würde in dieser Welt leben. Viele viele Server werden eine solche Kombination jedoch sicherlich gerne akzeptieren.

RFC 2616 bietet diesen aufschlussreichen Abschnitt zur Bestimmung der Nachrichtenlänge:

Die Übertragungslänge einer Nachricht ist die Länge des Nachrichtentexts, wie er in der Nachricht erscheint; das heißt, nachdem irgendwelche Transfercodierungen angewendet wurden. Wenn ein Nachrichtentext in einer Nachricht enthalten ist, wird die Übertragungslänge dieses Textkörpers durch einen der folgenden Punkte bestimmt (in der Rangfolge):

  1. Jede Antwortnachricht, die "NICHT" einen Nachrichtentext enthalten darf (wie die 1xx-, 204- und 304-Antworten und jede Antwort auf eine HEAD-Anfrage) wird immer mit der ersten leeren Zeile nach den Header-Feldern abgeschlossen, unabhängig von der Entität. Header-Felder, die in der Nachricht vorhanden sind.
  2. Wenn ein Transfer-Encoding-Header-Feld (Transfer-Encoding) vorhanden ist und einen anderen Wert als "Identität" hat, dann wird die Übertragungslänge durch die Verwendung der "chunked" Transfer-Codierung (Transfer Codings) definiert, es sei denn, die Nachricht ist durch Schließen der Verbindung beendet.
  3. Wenn ein Content-Length-Header-Feld (Content-Length) vorhanden ist, repräsentiert sein Dezimalwert in OCTETs sowohl die Entitätslänge als auch die Übertragungslänge. Das Content-Length-Header-Feld MUSS NICHT gesendet werden, wenn diese beiden Längen unterschiedlich sind (dh wenn ein Transfer-Encoding-Header-Feld vorhanden ist). Wenn eine Nachricht sowohl mit einem Transfer-Encoding-Header-Feld als auch einem Content-Length-Header-Feld empfangen wird, MUSS letzteres ignoriert werden.
    ...
    Nachrichten DÜRFEN NICHT sowohl ein Content-Length-Header-Feld als auch eine Nicht-Identitäts-Übertragungscodierung enthalten. Wenn die Nachricht eine nicht identitätsbezogene Übertragungscodierung enthält, MUSS die Inhaltslänge ignoriert werden.

Zur Klarstellung: Wir sind uns einig, dass Requests das Falsche tut. Wir sind uns jedoch _nicht_ einig, dass das Setzen eines Content-Length Headers das Chunking ausschalten sollte. Das ist das Bit, das Streit verursacht.

Ich sollte auch darauf hinweisen, dass jeder Server, der mit Chunked-Codierung ausfällt, auch gegen RFC 2616 verstößt:

Alle HTTP/1.1-Anwendungen MÜSSEN die "chunked" Übertragungscodierung empfangen und decodieren können und MÜSSEN Chunk-Erweiterungserweiterungen ignorieren, die sie nicht verstehen.

Dies ist kein Versuch zu sagen, dass Anfragen dieses Problem nicht beheben sollten, es soll lediglich darauf hingewiesen werden, dass alle Beteiligten gegen RFC 2616 verstoßen.

Ich denke, wir sollten versuchen, die Wurzel des Problems anzugehen: Warum verwenden die Leute in dieser Situation Generatoren?

Einer unserer Anwendungsfälle bestand darin, eine möglicherweise sehr große Datei mit einer bekannten Größe von einem Dienst zu nehmen und sie in einen zweiten Dienst hochzuladen, außer dass die Binärdaten nur einer der Teile eines mehrteiligen POST waren (mit JSON-Metadaten als dem anderen), die der zweite Service erforderlich.

Also haben wir den mehrteiligen Builder in einen Generator gepackt; eine, von der wir die Größe kennen könnten. Es schien eine sehr offensichtliche und nützliche Lösung zu sein, zumindest bis dieses Request-Feature/Bug uns gestoppt hat (ich glaube, wir haben jetzt eine Abzweigung, aber der Superlen-Trick könnte helfen, das rückgängig zu machen!).

Wir haben jetzt andere Anwendungsfälle von Generatoren mit Linse, aber ich müsste sie ausgraben.

Bryan

Am 18. Februar 2014 um 00:45 Uhr schrieb Cory Benfield [email protected] :

Ich denke, wir sollten versuchen, die Wurzel des Problems anzugehen: Warum verwenden die Leute in dieser Situation Generatoren?

@bryanhelmig Mm, ja. Ich denke, die richtige Lösung gibt es von nun an, den Streaming-Multipart-Encoder von Requests Toolbelt zu verwenden.

Im Moment ist mir jedoch unklar, warum es hier einen so starken Widerstand gegen die Verwendung dateiähnlicher Objekte gibt.

In Ausgabe #1895 trat dieses Problem sogar bei einem dateiähnlichen Objekt auf. Die Anfrage war ein PUT, und das dateiähnliche Objekt war zufällig sys.stdin, das tatsächlich so dateiähnlich ist, dass es eine Chunk-Codierung auslöste, wie es bei einer normalen Datei der Fall ist. Die Bereitstellung einer Inhaltslänge (die ich über externe Mittel gelernt habe) führte in dieser Situation dazu, dass HTTPAdapter.send nicht wie erwartet chunking machte, aber PreparedRequest.prepare_body bemerkte immer noch, dass das dateiähnliche Objekt iterierbar war und fügte einen Transfer-Encoding-Header hinzu wie auch immer. Der Server (Amazon S3) hat sich an diesem Header verschluckt, obwohl die Anfrage sonst funktioniert hätte.

@gholms Nein. sys.stdin ist so _nicht_ dateiartig, dass es Chunked Encoding ausgelöst hat. Wenn Sie uns ein Dateiobjekt übergeben, werden wir es streamen, nicht stückeln. Das Problem mit sys.stdin ist, dass es keine Länge hat, was unter die Beschreibung in den oben genannten Dokumenten fällt:

jeder Iterator ohne Länge

Die allgemeine Erwartung ist, dass Benutzer den Content-Length-Header niemals explizit festlegen. Dies liegt daran, dass Anforderungen möglicherweise die Einrichtung des Anforderungstexts beeinträchtigen.

Schließlich ist die Idee, dass "man erwarten könnte", dass die Bereitstellung eines Content-Length-Headers das Chunking deaktiviert, auch nicht wahr: Ich würde erwarten, dass wir den Header entfernen. =)

Jedenfalls hat diese Diskussion lange genug gedauert. Wenn ein Iterator ohne Länge und ein vom Benutzer bereitgestellter Content-Length-Header bereitgestellt werden, haben wir folgende Optionen:

  1. Geben Sie eine Ausnahme aus.
  2. Blasen Sie den Content-Length-Header weg
  3. Nicht hacken.

Jede dieser drei Optionen bringt uns in Übereinstimmung mit dem RFC. Es ist klar, dass jeder, der dieses Thema anspricht, (3) bevorzugt. @sigmavirus24?

Ich war immer der Meinung, dass wir den Kopfball wegblasen sollten. Nach meiner Erfahrung denkt der Benutzer, dass er weiß, was er tut, aber selten genug wissen, um wirklich zu wissen, was er tut. Ich lasse mir generell gerne den User in den Fuß schießen und das haben wir bis jetzt gemacht und es hat uns nicht viel gebracht. Tatsächlich hatten wir zuvor die Möglichkeit, diesen Kopfball wegzublasen, und haben das ausdrücklich nicht getan. Das hat zu genau diesem Problem geführt. Seien wir logisch:

Unsere API und Dokumentation haben immer behauptet, dass die Übergabe eines Iterables ohne Möglichkeit zur Längenmessung bedeutet, dass wir eine Chunked-Transfer-Codierung verwenden. Nach der Spezifikation sollten wir in diesem Fall den Content-Length-Header wegblasen, da einige Server ihn nicht befolgen. Es sollte ignoriert werden. Wir machen nichts falsch, indem wir es unabhängig von der Codierung senden, der Server macht das Falsche, indem er es nicht ignoriert. Zum Schutz vor solchen schlechten Servern _sollten_ wir sie löschen.

Wenn wir in 3.0.0 beschließen, die API so zu ändern, dass das Bereitstellen des Headers keinen Brocken macht, ist das ein anderes Tier. Wir denken jedoch nicht an 3.0.0 und das ist das eigentliche Problem hier. Was können wir jetzt tun, um dieses Problem zu lösen. Was wir tun können, ist die Spezifikation zu befolgen.

Ich bin geneigt, dir @sigmavirus24 zuzustimmen. Ich werde sehen, ob ich bald ein paar Minuten mit Kenneth habe, um mit ihm darüber zu sprechen.

Ich sitze im selben Boot wie @bryanhelmig und @netheosgithub , ich habe einen Generator, bei dem ich im Voraus weiß, welche Größe die kombinierten Chunks haben werden, und einen Server, der keine chunked Uploads unterstützt (eine WSGI-App, WSGI nach meinem Forschung unterstützt keine Chunked-Codierung). Die Daten vom Generator sind zu groß, um in den RAM zu passen, daher kommt es nicht in Frage, die Blöcke vorher zu kombinieren und an Anfragen weiterzugeben.
Gab es zu diesem Thema neue Entwicklungen?

@jbaiter Dann müssen Sie ein Objekt übergeben, das sich wie eine Datei mit einem definierten __len__ verhält. Nehmen Sie zum Beispiel den MultipartEncoder des Werkzeuggürtels . Sie möchten Streaming, also brauchen Sie im Allgemeinen nur so etwas, das eine Methode read und eine Möglichkeit zur Bestimmung der Länge hat (vorzugsweise durch die Implementierung von __len__ ). Die API Ihres Objekts könnte etwa so aussehen:

class JBaiterStreamer(object):
    def __init__(self, size, iterator):
        self.size = size
        self.iterator = iterator

    def __len__(self):
        return self.size

    def read(self, *args):
        try:
            return next(self.iterator)
        except StopIteration:
            return b''

Zugegeben, ich habe nicht versucht zu sehen, ob dieser Code funktioniert, aber etwas in diese Richtung sollte es tun.

Danke @sigmavirus24 :+1: Genau das habe ich getan, ich habe mich nur gefragt, ob es inzwischen einen eleganteren Weg gibt

Vielleicht gibt es eine gute Möglichkeit, dies über den Toolbelt bereitzustellen, oder @Lukasa ?

@sigmavirus24 Absolut. =)

@sigmavirus24 "Wir machen nichts falsch, indem wir es unabhängig von der Codierung senden. Der Server macht das Falsche, indem er es nicht ignoriert." ist jetzt eigentlich falsch:

RFC 7230: "Ein Absender DARF KEIN Content-Length-Header-Feld in einer Nachricht senden, die ein Transfer-Encoding-Header-Feld enthält."

@ztane Dies ist ein anderer Fall als der, auf den sich diese Stapelüberlauffrage bezieht. Bitte lesen Sie in Zukunft eine Diskussion genau durch, bevor Sie Benachrichtigungen für alle Teilnehmer generieren.

@sigmavirus24 , die durch "Transfer-Encoding: chunked, content-length set => body not chunked" verursacht wurde. Und RFC 7230 verbietet die Einstellung von Content-Length mit Transfer-Encoding .

@ztane vielen Dank, dass Sie diese Diskussion weiterhin nicht lesen. Bei diesem speziellen Fehler geht es darum, dass jemand einen Content-Length-Header von Hand setzt und erwartet, dass Anfragen keinen Chunk-Upload verwenden, wenn sie einen Generator bereitstellen (der immer einen Chunk-Upload mit Anfragen auslöst). Der von Ihnen bereitgestellte Stackoverflow-Link ist ein anderer Fehler, der an anderer Stelle behandelt wird. Sie trüben diese Diskussion mit irrelevanten Details, weil wir den Benutzern erlauben, sich selbst in die Füße zu schießen. Beispielsweise erlauben wir Benutzern, einen falschen Header mit Inhaltslänge oder einen ungültigen Host-Header anzugeben, oder im Grunde was immer sie wollen. Wir überwachen nicht alles und werden es auch nicht tun. Bitte konzentriere dein Gespräch in Zukunft auf das _richtige_ Problem.

Ich bin darüber gestolpert, als ich boto3 verwendet habe, um einen PUT aus einem Stream gegen AWS S3 (https://github.com/boto/botocore/issues/911) durchzuführen. In meinem Fall ist die Größe des Streams bekannt – es handelt sich um ein Objekt eines anderen Anbieters. Bei Verwendung von S3-Version-2-Signaturen wird das "Transfer-Encoding: chunked" nicht unterstützt und S3 gibt den 501-Fehler zurück. Die boto3-Dokumentation scheint zu implizieren, dass das Festlegen der Inhaltslänge dieses Problem umgehen würde, da sie Folgendes angeben:

ContentLength (integer) -- Size of the body in bytes. This parameter is useful when
the size of the body cannot be determined automatically. [1]

Während boto3 zwei Dinge tun sollte – die Dokumentation korrigieren und sicherstellen, dass „Transfer-Encoding: chunked“ nicht gesetzt ist – ist dieses Verhalten für den indirekten Verbraucher von Anfragen schwer zu debuggen. Wenn der Content-Length-Header ignoriert wird, würde das Auslösen einer Ausnahme zumindest deutlich machen, was vor sich geht. Wie es ist, wurde es durch die Tatsache verschleiert, dass S3 nur einen 501-Fehler mit der Erklärung zurückgibt, dass einer der Header nicht unterstützt wird (aber den Header nicht angibt).

Die vorgeschlagene Problemumgehung zum Umschließen und Bereitstellen der Länge funktioniert, scheint aber hässlich. Wäre die Erweiterung der API, um das Umschalten der Chunked-Codierung (unter Beibehaltung des aktuellen Verhaltens als Standard) zu ermöglichen, ein schmackhafter Weg nach vorne (im Gegensatz zur Verwendung des Content-Length-Headers als Flag)?

[1] http://boto3.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.put_object

@timuralp Ich bin weiterhin dagegen, dafür eine Flagge hinzuzufügen. Es ist einfach inakzeptabel, eine HTTP/1.1-Implementierung zu haben, die 2016 keine Chunked Transfer Encoding verarbeiten kann. Es war so lange eine Spezifikationsanforderung, dass die erste Spezifikation, die es erforderte, fast alt genug ist, um in den Vereinigten Staaten von Amerika abzustimmen: Ich glaube nicht, dass wir die Entitäten locker halten können, weil sie es nicht tun.

Aus meiner Sicht bleibt hier der Fehler, dass wir sowohl Content-Length als auch Transfer-Encoding falsch ausgeben können. Meine Perspektive ist natürlich unverbindlich. ;)

Wenn boto3 außerdem den Content-Length-Header zwangsweise überschreiben möchte, sollte es den vorbereiteten Anfragefluss verwenden, damit es den Transfer-Encoding Header entfernen kann.

@Lukasa @sigmavirus24 fair genug - danke für die prompte Antwort. Ich werde weiterhin versuchen, das Boto-Problem in diesem Projekt zu beheben.

Die Art und Weise, wie ich diese Unfähigkeit, zu kontrollieren, ob etwas zerlegt wird, umgangen habe, besteht darin, zu kontrollieren, ob mein POST den "Daten"- oder "Dateien"-Teil des Methodenaufrufs verwendet. Scheint gut zu funktionieren.

    req = Request('POST',url='http://apiendpointurl',
        headers=headers,
        data=fs)
    prepped = req.prepare()

    if 'Transfer-Encoding' in prepped.headers and prepped.headers['Transfer-Encoding'] == 'chunked':
        res=postAsFiles(headers, fs)
    else:
        res=postAsData(headers,fs)

wobei der einzige Unterschied zwischen postAsFiles und postAsData ist:

def postAsData(headers, fs):
    return requests.post(
        url='http://apiendpointurl',
        headers=headers,
        data=fs)

def postAsFiles(headers, fs):
    return requests.post(
        url='http://apiendpointurl',
        headers=headers,
        files=fs)

Mit #3897 löst Requests 3.0.0 in diesem Fall eine Ausnahme aus, die das Senden beider Header verhindert.

Benutzer, die ihren eigenen Content-Length-Header senden möchten, können die Header mithilfe des PreparedRequests- Flows ändern. Beachten Sie, dass Content-Length immer noch nicht für Daten funktioniert, die als Generator übergeben werden. Wenn der Benutzer die Inhaltslänge angeben MUSS, müssen Generatoren verwendet und in einer Zeichenfolge-/Datei-ähnlichen Darstellung übergeben werden. In den meisten Fällen sollten diese Header für die automatische Bearbeitung durch Requests einfach in Ruhe gelassen werden.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

avinassh picture avinassh  ·  4Kommentare

remram44 picture remram44  ·  4Kommentare

8key picture 8key  ·  3Kommentare

Matt3o12 picture Matt3o12  ·  3Kommentare

iLaus picture iLaus  ·  3Kommentare