Link-Vorschau HTTP GET verstößt gegen Spezifikation

Ich kämpfe seit geraumer Zeit mit einem Problem, das anscheinend noch nicht gemeldet wurde. Ich entschuldige mich für die ungewöhnliche Anzahl von beweglichen Teilen, werde aber versuchen, es kurz und bündig zu beschreiben.

Die TL;DR-Version lautet: Wenn ich einen Link in eine Nachricht einfüge, sendet das Ruby-Gem, das letztendlich den HTTP-GET-Request an diese URL stellt, um Einbettungsdaten zu suchen, eine HTTP-Anfrage, die von einigen HTTP-Proxys als nicht konform mit der Spezifikation betrachtet wird. Dies verhindert in einigen Fällen die Vorschau:

Die etwas längere Version ist folgende: Wir nutzen einen netten kleinen Dienst namens Gitbook.io für unsere Dokumentation. Gitbook ist eine gehostete Lösung und verwendet Cloudflare-Worker für interne Weiterleitungen auf ihrer Website. Zu diesen Cloudflare-Workern gehört die Verwendung der Node Fetch-API, um HTTP-Anfragen zu proxyen. Die Entwickler von Node Fetch sind EXTREM pedantisch in Bezug auf die Einhaltung der Spezifikation und lehnen jede GET-Anfrage ab, die einen HTTP-Body oder sogar einen Content-Length-Header enthält, selbst wenn dieser Header auf 0 gesetzt ist.

Und genau das passiert. Das Ruby-Gem, das die HTTP-Anfrage stellt, sendet einen

Content-Length: 0

Header, was die Node-Fetch-Proxy-Instanz extrem ärgert und dazu führt, dass die Anfrage vom Remote-Server abgelehnt wird. In verschiedenen Foren wurde viel darüber diskutiert, ob ein Request-Body bei einer GET-Anfrage oder sogar nur ein Content-Length-Header gemäß der HTTP-Spezifikation gültig ist. Ich habe kein Problem damit, aber das hat die Node-Fetch-Entwickler nicht davon abgehalten, jede eingereichte Issue zu schließen, in der sie gebeten wurden, eine solche Semantik zuzulassen.

Ich stecke leider in der Mitte fest:

Das bedeutet, ich wende mich hiermit an euch und frage, ob es eine Möglichkeit gibt, die für Linkvorschauen verwendeten HTTP-GET-Anfragen so zu steuern oder zu ändern, dass sie einen akzeptablen Satz von HTTP-Headern enthalten, sodass Proxys, die extrem pedantische Bibliotheken wie Node Fetch verwenden, diese Anfragen nicht ablehnen?

Wenn ihr es ausprobieren möchtet, hier ist eine Beispiel-URL, die auf Gitbooks Servern gehostet wird und ihren Node-Fetch-basierten Cloudflare-Worker verwendet:

6 „Gefällt mir“

@jamie.wilson / @techAPJ, habt ihr eine Idee, warum wir bei unseren Anfragen einen Content-Length von 0 senden? Könntet ihr dieses Verhalten bestätigen? Ich verstehe das bei HEAD, aber bei GET?

2 „Gefällt mir“

Hallo @sam, die HTTP-Anfragen scheinen von einer Ruby-Bibliothek namens httprb gestellt zu werden, die dieses Verhalten aufweist. Wenn du dir den Link im Aufzählungspunkt „Die HTTPrb-Bibliothek verweigert das Entfernen des Headers, da sie ihn für völlig gültig hält“ anschaust, kannst du sehen, wie der Entwickler dieser Bibliothek argumentiert, warum er die HTTP-Spezifikation zwar umgeht, aber nicht bricht.

Da ich im gesamten Internet nach jemandem gesucht habe, der einer Einigung zustimmt, konnte ich jemanden dazu bewegen, diesen Pull-Request an httprb zu senden, der das Problem möglicherweise löst.

Ich bin kein Ruby-Entwickler und weiß daher nicht einmal, wie man dies testen könnte. Ich gehe davon aus, dass dieses Gem irgendwann eine Version mit der Korrektur veröffentlichen wird und Discourse dann ebenfalls ein Update erhält, um diese zu nutzen. Es wäre großartig, wenn jemand eine Möglichkeit hätte, zu testen, ob es funktioniert. Der Reproduktionsfall ist sehr einfach – füge einfach den obigen Link zu meiner Gitbook-URL ein und prüfe, ob die Vorschau abgelehnt wird.

1 „Gefällt mir“

Ich sehe Folgendes (was mit dem Bild in deinem ersten Beitrag übereinstimmt):

Der Text ‘Getting Started Guide’ zeigt uns, dass die Anfrage erfolgreich war – er zieht diesen String aus dem og:title-Meta-Tag:

<meta property="og:title" content="Getting Started Guide" data-react-helmet="true">

Die Fehlermeldung/Warnung, dass die Beschreibung fehlt, ist ebenfalls korrekt. Der Inhalt der Seite lautet wie folgt:

<meta property="og:description" content="" data-react-helmet="true">

Die Bild-URL stammt aus dem og:image-Tag, der wie folgt aussieht:

<meta data-react-helmet="true" property="og:image" content="https://app.gitbook.com/share/space/thumbnail/-LA-UVvV3_TgzQyCXMWK.png">

Wenn ich https://app.gitbook.com/share/space/thumbnail/-LA-UVvV3_TgzQyCXMWK.png in meinen Browser kopiere (aktuelles Safari auf macOS), erhalte ich eine Fehlermeldung:

Error: could not handle the request

Derselbe Aufruf über curl liefert dieselbe Antwort:

curl -v https://app.gitbook.com/share/space/thumbnail/-LA-UVvV3_TgzQyCXMWK.png
*   Trying 104.18.8.111...
* TCP_NODELAY set
* Connected to app.gitbook.com (104.18.8.111) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=sni.cloudflaressl.com
*  start date: Jun 16 00:00:00 2021 GMT
*  expire date: Jun 15 23:59:59 2022 GMT
*  subjectAltName: host "app.gitbook.com" matched cert's "*.gitbook.com"
*  issuer: C=US; O=Cloudflare, Inc.; CN=Cloudflare Inc ECC CA-3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x142809200)
> GET /share/space/thumbnail/-LA-UVvV3_TgzQyCXMWK.png HTTP/2
> Host: app.gitbook.com
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 256)!
< HTTP/2 500
< date: Mon, 23 Aug 2021 17:40:04 GMT
< content-type: text/plain; charset=utf-8
< content-length: 36
< cf-ray: 68361fb8ea9b4009-YYZ
< age: 432
< vary: Accept-Encoding
< via: magic cache
< cf-cache-status: HIT
< expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< x-cache: HIT
< x-cloud-trace-context: 9d4cbd24a15138451c88b2ced35a32f1;o=1
< x-content-type-options: nosniff
< x-magic-hash: f46ac4bf6b6dc125a68e9ad566b48481631bb27eec2165532a7c0f538e93c4f6
< x-release: gitbook-28427-6.25.14
< server: cloudflare
<
Error: could not handle the request
* Connection #0 to host app.gitbook.com left intact
* Closing connection 0

Kannst du ein Bild sehen, wenn du die og:image-URL in deinen Browser kopierst?

Zusammenfassend: Die Onebox-Vorschau verhält sich offenbar wie erwartet, basierend auf der Antwort der ursprünglichen URL.

3 „Gefällt mir“

@jamie.wilson Danke für deine Zeit, dir das anzusehen. Könntest du jedoch klären, ob dein oben genannter Test mit der neuesten Version des httprb-Gems durchgeführt wurde, die den erwähnten Pull Request enthält, oder ob es sich um eine ältere Version der Bibliothek handelt?

Der ursprüngliche Fehler, den ich in der Onebox-Vorschau sah, war, dass die Ziel-URL einen 500-Statuscode zurückgab. Irgendwann vor meinem Beitrag begann die Onebox-Vorschau stattdessen die Meldung über die fehlenden OpenGraph-Metadaten anzuzeigen. Da ich dies monatelang vor der Kontaktaufnahme mit dem Gitbook-Support bereits untersucht habe, ist es möglich, dass sich in der Zwischenzeit etwas geändert hat.

Wenn die Gitbook-URL tatsächlich geladen wird und lediglich einige Metadaten fehlen oder Bilder nicht vorhanden sind, ist das etwas anderes als eine abgelehnte Anfrage. Ich weiß jedoch mit Sicherheit, dass alle Anfragen, die ich selbst sende und die einen HTTP-Header Content-Length: 0 enthalten, von den CloudFlare-Workern auf dem Remote-Server abgelehnt werden. Vielleicht hat sich der HTTP-Client geändert, der in Discourse für die Durchführung von Anfragen verwendet wird? Ich kenne den Discourse-Quellcode nicht und bin nicht einmal zu 100 % sicher, dass die httprb-Bibliothek die tatsächliche Quelle der HTTP-Anfragen ist.

Ich glaube nicht, dass wir die httprb-Gem überhaupt verwenden. Der Oneboxing-Prozess (d. h. das, was die Link-Vorschauen generiert) nutzt Net::HTTP aus Rbys Standardbibliothek sowie die Excon-Gem als Teil des Ablaufs.

Bei genauerer Betrachtung sehe ich, dass wir gelegentlich Anfragen mit einem Header Content-Length: 0 generieren. Im Fall der bereitgestellten URL behindert dies jedoch zumindest nicht die Generierung des Onebox.

Es mag ein Minor-Version-Update gegeben haben, aber nichts Wesentliches wie eine Neuarchitekturierung der Art, wie wir Anfragen stellen, oder der Bibliotheken, die wir dafür verwenden.

Es gab einige Änderungen, um Oneboxing allgemein robuster zu machen, was erklären könnte, warum URLs, die zuvor Fehler 500 zurückgegeben haben, jetzt erfolgreich einen Onebox generieren.

Wenn Sie URLs haben, die Sie teilen können und die derzeit während des Oneboxings Fehler zurückgeben (oder anderweitig nicht wie erwartet in einem anderen Teil von Discourse funktionieren), senden Sie sie mir gerne zu!

3 „Gefällt mir“

Ahh, das sind also sehr gute Informationen. Ich musste mir bis jetzt wilden Vermutungen darüber machen, welche Bibliotheken überhaupt involviert waren, hauptsächlich weil ich von dem Gitbook-Team, das die CloudFlare-Proxys verwaltet, kaum Hilfe erhalten habe.

Verstanden. Ich habe oben zwar nicht erwähnt, aber das einzige Stück Information, das ich von Gitbook bekommen konnte, war, dass der Fehler in ihren CloudFlare-Error-Logs, der die Vorschauanfragen von Discourse ablehnte, folgender war:

Eine Anfrage mit der Methode GET oder HEAD kann keinen Body haben.

Unklar ist, ob die GET-Anfrage von Discourse tatsächlich einen Body enthielt oder nur den Content-Length: 0-Header. In jedem Fall verstößt das laut einigen Personen (einschließlich denen bei Cloudflare) gegen die Fetch-Spezifikation.

Ja, irgendwann scheint sich der Onebox-Fehler von einem generischen 500er geändert zu haben und enthält nun einige Daten. Man kann nicht sagen, welche Bibliotheken aktualisiert wurden (und ich habe Discourse in dieser Zeit aktualisiert). Ich wünschte, ich hätte eine Möglichkeit, genau zu erfassen, welche Header von Discourse gesendet werden. Selbst wenn ich eine URL wie http://httpbin.org/get aufrufe, habe ich keine Möglichkeit zu „sehen“, was zurückgegeben wird, da die Ergebnisse vollständig von Onebox verarbeitet und nirgendwo protokolliert werden, soweit ich weiß.

Wenn der leere Content-Length-Header jetzt verschwunden ist, kann ich zumindest mit Gitbook zusammenarbeiten, um deren Embed-Funktionen zu reparieren (was nicht passieren wird, da sie derzeit ihre gesamte Anwendung von Grund auf neu schreiben und sich weigern, bestehende Fehler zu beheben :confused: – aber das ist zumindest nicht Discourses Problem).

Lassen Sie mich zunächst feststellen, dass ein Großteil dessen, was oben geschrieben wurde, weit über meinem Verständnis liegt, aber ich versuche verzweifelt, hier mitzuhalten. Es ist völlig in Ordnung, mir zu sagen, dass ich mich irre, falls ich mich in das falsche Thema einmische.

Wir sehen dieses Problem ziemlich häufig, da wir in unserer Community häufig Links zu unserem Help Center (Wissensdatenbank) posten.

Einige Beispiele für Links, die nicht Oneboxen:

https://help.republicwireless.com/hc/en-us/articles/115014150828--How-to-Add-an-E911-Address

Aus der Vorschau, während ich tippe:

1 „Gefällt mir“

Bei genauerer Untersuchung stellt sich heraus, dass es die Excon-Gem ist, die Content-Length: 0 hinzufügt, jedoch nicht bei GET-Anfragen.

Aber dieser Code ist bereits seit 8 oder 9 Jahren vorhanden, also war er wahrscheinlich nicht das Problem.

Die Datei Gemfile.lock zeigt Ihnen die von Discourse-Kern verwendeten Gems.

2 „Gefällt mir“

Diese Seite ist hinter einer Cloudflare-Captcha-Schleuse und blockiert Discourse daran, Informationen davon abzurufen :slightly_frowning_face:

2 „Gefällt mir“

Wenn diese Seite im Browser angesehen wird, enthält sie die erforderlichen Meta-Tags zum Erstellen eines Onebox. Beim Versuch, die URL abzurufen, scheint jedoch ein Fehler aufzutreten!

oneboxer preview url: https://help.republicwireless.com/hc/en-us/articles/115014150828--How-to-Add-an-E911-Address
headers: {"User-Agent"=>"Discourse Forum Onebox v2.8.0.beta4"}
helpers response code: 403

Das bedeutet, dass wir diese URL mit einem User-Agent von „Discourse Forum Onebox v2.8.0.beta4

2 „Gefällt mir“

:confused: Sie funktioniert jedoch seit Jahren einwandfrei.

Hier ist ein Beispiel, bei dem ein Link von derselben Website eine gültige Onebox anzeigt: https://forums.republicwireless.com/t/4-digit-pin-which-i-have-forgot/37655/2

Dieser verweist auf https://help.republicwireless.com/hc/en-us/articles/115012101188-Can-t-Get-Past-the-Screen-Lock-on-the-Phone

1 „Gefällt mir“

Cloudflare ändert seinen Algorithmus zur Erkennung von Bots ständig. Wenn Sie möchten, dass Discourse nicht blockiert wird, sollten Sie möglicherweise deren Support kontaktieren und nachfragen, warum die Anfrage blockiert wird.

5 „Gefällt mir“

Tut uns leid, wir schließen dies als veraltet. Wir benötigen eine neue Reproduktion der Probleme, um sie wieder zu öffnen.