Aperçu du lien HTTP GET enfreint la spécification

Je lutte contre un problème depuis un certain temps qui ne semble pas encore avoir été signalé. Je m’excuse pour le nombre inhabituel d’éléments en jeu, mais je vais essayer de le décrire succinctement.

En bref (TL;DR) : lorsque je colle un lien dans un message, le gem Ruby qui effectue finalement la requête HTTP GET vers cette URL pour récupérer les données d’intégration envoie une requête HTTP considérée par certains proxys comme non conforme à la spécification. Cela empêche les aperçus de fonctionner dans certains cas :

La version un peu plus longue est la suivante. Nous utilisons un petit service pratique appelé Gitbook.io pour notre documentation. Gitbook est une solution hébergée qui utilise des workers Cloudflare pour les redirections internes sur leur site. Une partie de ces workers Cloudflare consiste à utiliser l’API Node Fetch pour faire office de proxy pour les requêtes HTTP. Les développeurs de Node Fetch sont TRÈS pointilleux sur le respect de la spécification et ils rejettent toute requête GET qui contient un corps HTTP ou même un en-tête Content-Length, même si cet en-tête est défini sur 0.
Et c’est exactement ce qui se produit. Le gem Ruby qui effectue la requête HTTP envoie un en-tête de requête :

Content-Length: 0

ce qui exaspère le proxy Node Fetch et entraîne le rejet de la requête par le serveur distant. De nombreux débats ont eu lieu sur différents forums pour savoir si un corps de requête sur une requête GET, ou même simplement un en-tête Content-Length, est valide selon la spécification HTTP. Cela ne me pose pas de problème, mais cela n’a pas empêché les développeurs de Node Fetch de fermer toutes les issues ouvertes pour les supplier d’autoriser une telle sémantique.

Je suis malheureusement coincé au milieu de tout cela.

  • Le projet Node Fetch refuse de considérer ces requêtes HTTP comme valides.
  • Le support de Cloudflare refuse de m’aider car je n’ai pas le contrôle sur les workers basés sur Node en question.
  • Le support de Gitbook refuse de m’aider car ils sont d’accord avec les développeurs de Fetch (et je ne suis pas sûr qu’ils s’en soucient vraiment).
  • Et la bibliothèque HTTPrb refuse de supprimer l’en-tête car ils estiment qu’il est parfaitement valide.

Il ne me reste donc qu’à poster ici pour demander s’il existe un moyen de contrôler ou de modifier les requêtes HTTP GET effectuées pour les aperçus de liens afin d’inclure un ensemble acceptable d’en-têtes HTTP, de sorte que les proxys utilisant des bibliothèques incroyablement pointilleuses comme Node Fetch ne rejettent pas ces requêtes ?

Si vous souhaitez essayer, voici une URL d’exemple hébergée sur les serveurs de Gitbook et utilisant leur worker Cloudflare propulsé par Node Fetch :

6 « J'aime »

@jamie.wilson / @techAPJ avez-vous une idée de la raison pour laquelle nous envoyons un Content-Length de 0 avec nos requêtes ? Pouvez-vous confirmer ce comportement ? Je suppose que cela a du sens pour HEAD, mais pour GET ?

2 « J'aime »

Bonjour @sam, les requêtes HTTP semblent être effectuées par une bibliothèque Ruby appelée httprb, qui présente ce comportement. Si vous consultez le lien dans le point « La bibliothèque HTTPrb refuse de supprimer l’en-tête car elle considère qu’il est parfaitement valide », vous verrez le développeur de cette bibliothèque expliquer pourquoi il s’écarte de la spécification HTTP sans pour autant la violer.

Alors que je cherchais partout sur Internet à obtenir un accord, j’ai réussi à obtenir qu’une personne soumette cette pull request à httprb, ce qui pourrait résoudre le problème.

Je ne suis pas développeur Ruby, donc je ne saurais même pas comment tester cela. Je suppose qu’à terme, ce Gem publiera une version contenant la correction, et qu’ensuite Discourse mettra à jour pour commencer à l’utiliser. Il serait formidable que quelqu’un puisse tester si cela fonctionne. Le cas de reproduction est très simple : collez simplement le lien ci-dessus vers mon URL Gitbook et vérifiez si l’aperçu est rejeté.

1 « J'aime »

Je vois ce qui suit (ce qui correspond à l’image de votre premier message) :

Le texte « Getting Started Guide » nous indique que la requête a réussi : il extrait cette chaîne depuis la balise meta og:title :

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

L’erreur ou l’avertissement indiquant que la description est manquante est également correct. Le contenu de la page est le suivant :

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

L’URL de l’image provient de la balise og:image, qui est la suivante :

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

Si je copie et colle https://app.gitbook.com/share/space/thumbnail/-LA-UVvV3_TgzQyCXMWK.png dans mon navigateur (Safari récent sur MacOS), je reçois une erreur indiquant :

Error: could not handle the request

Effectuer la même requête via curl donne la même réponse :

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

Pouvez-vous voir une image si vous copiez et collez l’URL de og:image dans votre navigateur ?

En résumé : l’aperçu Onebox semble se comporter comme prévu, sur la base de la réponse de l’URL d’origine.

3 « J'aime »

@jamie.wilson Merci de votre temps pour examiner ce problème. Cependant, pourriez-vous préciser si votre test ci-dessus a été effectué avec la toute dernière version de la gemme httprb contenant la demande d’extraction mentionnée, ou s’il s’agissait d’une version plus ancienne de la bibliothèque ?

L’erreur initiale que je voyais dans l’aperçu Onebox indiquait que l’URL cible renvoyait un code d’état 500. À un moment donné avant que je ne publie ce message, l’aperçu Onebox a commencé à afficher à la place le message concernant les métadonnées opengraph manquantes. Ayant dépanné ce problème pendant des mois avant de contacter le support Gitbook, il est possible que quelque chose ait changé entre-temps.

Si l’URL Gitbook se charge effectivement mais qu’il manque simplement certaines métadonnées ou des images, cela est différent d’une requête rejetée. Cependant, je sais avec certitude que toute requête que j’envoie moi-même contenant un en-tête HTTP Content-Length: 0 est rejetée par les workers CloudFlare sur le serveur distant. Peut-être que le client HTTP utilisé pour effectuer les requêtes dans Discourse a changé ? Je ne connais rien au code source de Discourse et je ne suis même pas certain à 100 % que la bibliothèque httprb soit la véritable source des requêtes HTTP.

Je ne pense pas que nous utilisions du tout le gem httprb. Le processus de Oneboxing (c’est-à-dire ce qui génère les aperçus de liens) utilise Net::HTTP de la bibliothèque standard de Ruby, ainsi que le gem Excon dans le cadre du flux.

En creusant un peu plus, je constate que nous générons parfois des requêtes avec un en-tête Content-Length: 0. Cependant, dans le cas de l’URL fournie du moins, cela n’interfère pas avec la génération du Onebox.

Il est possible qu’une mise à jour mineure ait eu lieu, mais rien de majeur comme une refonte de la manière dont nous effectuons les requêtes ou des bibliothèques que nous utilisons pour cela.

Des modifications ont été apportées pour rendre le Oneboxing plus robuste en général, ce qui pourrait expliquer pourquoi des URLs qui renvoyaient auparavant une erreur 500 génèrent maintenant un Onebox avec succès.

Si vous avez des URLs que vous pouvez partager et qui retournent actuellement des erreurs lors du Oneboxing (ou qui ne fonctionnent pas comme prévu dans une autre partie de Discourse), n’hésitez pas à me les envoyer !

3 « J'aime »

Ah, c’est donc une information très précieuse. J’ai dû faire des suppositions hasardeuses sur les bibliothèques impliquées jusqu’à présent, principalement parce que l’équipe Gitbook, qui maintient les proxys CloudFlare, ne m’a presque pas aidé du tout.

C’est noté. Je ne pense pas l’avoir mentionné plus haut, mais la seule information que j’ai pu obtenir de Gitbook était que l’erreur dans leurs journaux d’erreurs CloudFlare, qui rejetait les requêtes d’aperçu de Discourse, était la suivante :

Une requête avec une méthode GET ou HEAD ne peut pas avoir de corps.

Ce qui n’est pas clair, c’est si la requête GET de Discourse contenait réellement un corps ou seulement l’en-tête Content-Length: 0. Dans les deux cas, cela violerait la spécification Fetch selon certaines personnes (y compris celles chez Cloudflare).

Oui, à un moment donné, l’erreur Onebox semble avoir changé de l’erreur générique 500 pour maintenant inclure des données. On ne sait pas quelles bibliothèques ont pu être mises à jour (et j’ai mis à jour Discourse entre-temps). J’aimerais pouvoir capturer exactement quels en-têtes sont envoyés par Discourse, mais même si je contacte une URL comme http://httpbin.org/get, je n’ai aucun moyen de « voir » ce qui est renvoyé, car les résultats sont entièrement consommés par Onebox et ne sont enregistrés nulle part, à ma connaissance.

Si l’en-tête content-length vide a maintenant disparu, je peux au moins travailler avec Gitbook pour corriger leur système d’intégration (ce qui n’arrivera pas, car ils réécrivent actuellement toute leur application à partir de zéro et refusent de traiter les bogues existants :confused: mais ce n’est au moins pas un problème de Discourse).

Laissez-moi d’abord préciser que beaucoup de ce qui a été écrit ci-dessus me dépasse largement, mais je suis à court d’idées. Il est tout à fait acceptable de me dire que je suis un idiot si je me mêle du mauvais sujet.

Nous rencontrons ce problème assez fréquemment, car nous publions souvent des liens vers notre Centre d’aide (base de connaissances) dans notre Communauté.

Voici quelques exemples de liens qui échouent à générer un aperçu (Onebox) :

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

Depuis le panneau d’aperçu pendant que je tape :

1 « J'aime »

Après avoir approfondi l’analyse, c’est le gem Excon qui ajoute Content-Length: 0, mais pas sur les requêtes GET.

Cependant, ce code existe depuis 8 ou 9 ans, donc ce n’était probablement pas le problème.

Le fichier Gemfile.lock vous indiquera les gems utilisées par le cœur de Discourse.

2 « J'aime »

Ce site est protégé par un captcha Cloudflare qui empêche Discourse de récupérer les informations :slightly_frowning_face:

2 « J'aime »

Lorsqu’on consulte cette page dans un navigateur, elle contient bien les balises méta nécessaires pour construire un Onebox. Cependant, en tentant de récupérer l’URL, il semble que nous rencontrions une erreur !

aperçu de l'URL par oneboxer : https://help.republicwireless.com/hc/en-us/articles/115014150828--How-to-Add-an-E911-Address
en-têtes : {"User-Agent" => "Discourse Forum Onebox v2.8.0.beta4"}
code de réponse des helpers : 403

Cela signifie que nous avons demandé cette URL avec un agent utilisateur « Discourse Forum Onebox v2.8.0.beta4 », mais que le serveur web distant a renvoyé un code d’état 403.

De même, en utilisant l’outil en ligne de commande wget :

wget https://help.republicwireless.com/hc/en-us/articles/115014150828--How-to-Add-an-E911-Address
--2021-08-23 17:38:30--  https://help.republicwireless.com/hc/en-us/articles/115014150828--How-to-Add-an-E911-Address
Résolution de help.republicwireless.com (help.republicwireless.com)... 104.16.53.111, 104.16.51.111
Connexion à help.republicwireless.com (help.republicwireless.com)|104.16.53.111|:443... connectée.
Requête HTTP envoyée, en attente de la réponse... 403 Interdit

Ce qui indique la même chose… Nous envoyons une requête valide, mais le serveur web distant refuse de renvoyer un résultat. Est-il possible que les responsables de help.republicwireless.com débloquent ces requêtes valides ?

Ces deux sites ne possèdent pas les balises de titre/description OpenGraph, mais ils contiennent d’autres titres/descriptions auxquels le Onebox devrait pouvoir se rabattre. C’est un point que nous devrions examiner pour le corriger.

2 « J'aime »

:confused: Cela fonctionne pourtant depuis des années.

Voici un exemple où un lien provenant du même site génère une Onebox valide : https://forums.republicwireless.com/t/4-digit-pin-which-i-have-forgot/37655/2

Ce qui redirige vers https://help.republicwireless.com/hc/en-us/articles/115012101188-Can-t-Get-Past-the-Screen-Lock-on-the-Phone

1 « J'aime »

Cloudflare modifie constamment son algorithme de détection des robots. Par conséquent, si vous souhaitez éviter que Discourse soit bloqué, vous pouvez contacter leur support pour savoir pourquoi la requête est bloquée.

5 « J'aime »

Désolé, nous fermons ce ticket car il est obsolète. Nous aurons besoin d’une nouvelle reproduction des problèmes pour le rouvrir.