Caricamento infinito dietro Cloudflare

Abbiamo spostato il nostro server da un provider VPS a un altro e ho aggiornato l’istanza tramite launcher rebuild all’ultima versione, da 3.5.0.beta3 a 3.5.0.beta4.

L’istanza ha sempre funzionato bene dietro Cloudflare, ma ora tentare di accedervi porta a un’animazione di caricamento infinita con 5 pallini.

Ho una voce nel file hosts sul mio sistema locale per bypassare Cloudflare, poiché il mio ISP (Deutsche Telekom AG) ha delle pessime policy di peering, quindi l’accesso tramite Cloudflare è a volte molto lento. Quindi all’inizio non ho notato il problema, dato che l’accesso senza Cloudflare funziona bene. Poi ho aggiornato l’istanza e quindi ora non sono sicuro se il VPS modificato o l’aggiornamento di Discourse sia stato il cambiamento rilevante. Ho verificato tramite VPN e rete mobile che il problema sia effettivamente Cloudflare ora, non il cattivo peering del mio ISP, e anche altri utenti riscontrano lo stesso problema. Sia il vecchio che il nuovo VPS hanno IPv6 disponibile, e l’intero sistema è esattamente lo stesso, trasferito come file immagine grezzo.

Non ci sono messaggi di errore, né nel browser (console), né dal proxy del sistema host, né da Nginx all’interno del container, né da Rails o altrove. I documenti HTML e diversi script vengono caricati correttamente e confrontandoli con quelli serviti quando si bypassa Cloudflare, si nota che tutto (ciò che ho controllato) è identico. Anche gli header di risposta sembrano per lo più uguali, a parte alcuni specifici di Cloudflare, ovviamente. Le ultime cose che vedo caricarsi sono il mini profiler:

Ovviamente cancellare la cache del browser, usare finestre private, ecc. non hanno cambiato nulla. Anche cancellare/disabilitare la cache di Cloudflare non aiuta, quindi la cache non è il problema. Ho temporaneamente disabilitato completamente la cache di CF per l’intero forum.

È degno di nota il fatto che il forum venga eseguito su un sottopercorso dietro un proxy Apache sull’host, seguendo queste istruzioni: Serve Discourse from a subfolder (path prefix) instead of a subdomain
In precedenza, avevamo creato solo un link simbolico ln -s . forum invece dei link simbolici uploads/backups e raddoppiato le riscritture delle istruzioni, il che ha funzionato bene per anni (e anche ora senza Cloudflare), ma come parte dei miei sforzi di debug sono passato a queste istruzioni per assicurarmi che il proxy interno applicasse tutte le regole come previsto. L’header fidato è CF-Connecting-IP, anche se ho abilitato anche cloudflare.template.yml, anche se raddoppia un po’ le cose. E ho anche provato a cambiare avanti e indietro varie parti di questi template e delle istruzioni di cui sopra, anche nel tentativo di verificare se gli header IP del proxy facessero qualche differenza, dato che la mancanza di CF-Connecting-IP è una cosa quando si bypassa Cloudflare.

A questo punto sono completamente a corto di idee, non ho una singola traccia da dove possa provenire il problema, nessun log/output correlato da nessuna parte. Tramite Cloudflare, Discourse si blocca semplicemente nell’animazione di caricamento senza ulteriori tracce.

Spero che qualcuno abbia un’idea su come eseguire il debug di questo problema, o se ci sia stata una modifica tra 3.5.0.beta3 e 3.5.0.beta4 che potrebbe essere correlata. Suppongo che un downgrade sia problematico?

Questa è l’istanza: https://dietpi.com/forum/
EDIT: Ho disabilitato Cloudflare per ora. Ma c’è un CNAME che viene ancora passato attraverso Cloudflare, quindi questi due possono essere confrontati: https://www.dietpi.com/forum/

Problema interessante.

È semplicemente \u003chttps://www.dietpi.com/forum/\u003e che si blocca per sempre.

$ wget https://www.dietpi.com/forum/
--2025-05-03 10:52:18--  https://www.dietpi.com/forum/
Resolving www.dietpi.com (www.dietpi.com)... 104.21.12.65, 172.67.193.183, 2606:4700:3035::6815:c41, ...
Connecting to www.dietpi.com (www.dietpi.com)|104.21.12.65|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: âindex.html.1â

    [<=>                                

La cosa interessante è che chiamate come \u003chttps://www.dietpi.com/forum/site.json\u003e hanno successo.

\u003chttps://www.dietpi.com/forum/t/why-there-are-two-kernals-in-my-raspberry-pi4/23355\u003e non funziona e si blocca per sempre, ma
\u003chttps://www.dietpi.com/forum/t/why-there-are-two-kernals-in-my-raspberry-pi4/23355.json\u003e sì.

1 Mi Piace

Interessante davvero. Mi accorgo solo ora che i documenti HTML non si caricano completamente ma si bloccano a un certo punto. Ho confrontato /forum/ in entrambi i casi e pensavo fossero identici, ma probabilmente mi stavo concentrando troppo sull’head, mentre parti del body in fondo mancano.

Quell’ultima riga quando si carica tramite Cloudflare:

      <discourse-assets-json>
        <div class="hidden" id="data-preloaded" data-preloaded="{&quot;topic_list&quot;:&quot;{&quot;users&quot;...&quot;:false,&quot;allowed_iframes&quot;:&quot;https://dietpi.com/forum/discobot/certificate.svg

Ho dovuto troncarla perché supera di gran lunga il limite di caratteri per un post. Il documento di solito continua così:

      <discourse-assets-json>
        <div class="hidden" id="data-preloaded" data-preloaded="{&quot;topic_list&quot;:&quot;{&quot;users&quot;...&quot;:false,&quot;allowed_iframes&quot;:&quot;https://dietpi.com/forum/discobot/certificate.svg&quot;,&quot;can_permanently_delete...

Le altre pagine si bloccano nello stesso punto. Penso che siamo vicini a qualcosa.

EDIT: Ah, aspetta, ho controllato male, altre pagine si bloccano altrove. Quindi non è questo particolare elemento/attributo HTML.

Sì, ogni pagina/documento HTML si blocca allo stesso carattere quando viene caricato tramite browser ancora e ancora, in finestra privata, ecc. Ma una pagina diversa si blocca in un punto diverso. E quando si caricano tramite curl, si bloccano sempre nello stesso punto, ma uno diverso, e wget di nuovo si blocca sempre in un punto, ma leggermente diverso. Molto strano.

Hai qualche ottimizzazione abilitata?

No, nessuna ottimizzazione dei (contenuti). Avevo abilitato la funzionalità 103 Early Hints, ma l’ho già disattivata nel tentativo di risolvere il problema. Ho provato lo stesso con le impostazioni del protocollo, ma neanche questo ha cambiato nulla:

Tra l’altro, non c’è l’header di risposta content-length, potrebbe causare un problema? Voglio dire, non esiste nemmeno quando si bypassa Cloudflare, ma probabilmente Cloudflare ha qualche problema? EDIT: No, sembra normale per le pagine dinamiche, lo stesso vale per le nostre pagine Wordpress e Matomo che tuttavia non causano problemi.

E un’altra scoperta giocando con curl. La stampa su STDOUT risulta nella visualizzazione dell’intero documento HTML, ma si blocca comunque e alla fine:

  <p class='powered-by-link'>Powered by <a href="https://www.discourse.org">Discourse</a>, best viewed with JavaScript enabled</p>
</footer>


  </body>

</html>

Ma quando si tenta di salvarlo tramite -o o semplice reindirizzamento, o anche solo tramite pipe in grep, si blocca in un punto diverso:

            <div class="link-bottom-line">
                <a href='/forum/c/general-discussion/7' class='badge-wrapper bullet'>
                  <span class='badge-category-bg' style='background-color: #F7941D'></span>
                  <span class='badge-category clear-badge'>
                    <span class='category-name'>General Discussion</span>
                  </span>
                </a>

E posso replicare al 100% questi stessi 73728 byte quando accedo a https://www.dietpi.com/forum/ con curl senza stamparlo subito sulla console. È così strano :face_with_monocle:.


Quindi:

  • Tutti i client si bloccano durante il caricamento di qualsiasi documento HTML dalla nostra istanza Discourse.
  • Ogni client si blocca allo stesso byte durante il caricamento della stessa pagina.
  • Client diversi si bloccano in punti diversi, ma allo stesso byte quando si ripete con lo stesso client.
  • Ogni pagina si blocca in un punto diverso del documento e con una dimensione scaricata diversa.
  • Lo stesso strumento curl si blocca in punti diversi quando si stampa solo su STDOUT rispetto a quando si utilizza la pipe o si memorizza il documento da qualche parte.
  • wget è in grado di scaricare l’intero documento (almeno https://www.dietpi.com/forum/) in un file, ma si blocca comunque alla fine, lo stesso quando curl https://www.dietpi.com/forum/ stampa l’intero documento sulla console, ma si blocca alla fine.

Penso che possa essere il buffering. Ma durante l’indagine, ho notato qualcos’altro.

wget -O - https://www.dietpi.com/forum/latest

Termina con

  </body>
</html>

Ma la connessione non viene mai chiusa.

Teoria: c’è un problema di configurazione da qualche parte in cui c’è una discrepanza nelle versioni HTTP o negli header (come la connessione keep alive) e questo diventa un problema solo quando un documento è più grande di X (sospetto 64KB).

Sì, wget scarica sempre l’intero documento e curl lo fa quando stampa direttamente sulla console, ma la connessione non viene chiusa. Lo stesso accade con documenti molto più piccoli, come ho testato uno da 14k su un argomento con solo 2 post. Ma anche quelli più piccoli di solito non vengono scaricati completamente da curl quando si usa il piping o si salvano su file, né nel browser.

Entrambi gli strumenti mostrano sempre HTTP/2 e in Cloudflare ho abilitato le richieste di origine HTTP/2. Ma vale la pena testare esplicitamente utilizzando altre versioni HTTP. Ieri ho disabilitato tutte le impostazioni del protocollo in Cloudflare viste nello screenshot sopra, e non ha aiutato. Ma ci riproverò. Posso anche abilitare i log di accesso sul server per vedere la richiesta in arrivo effettiva da Cloudflare.

Ho provato tutte le combinazioni di versioni HTTP (1.1-3) e TLS (1.2-1.3) supportate, ma non fa alcuna differenza. Ho anche disabilitato il supporto HTTP3, le richieste di origine HTTP2 riprendono questa connessione 0-RTT. Nessuna differenza, curl continua a bloccarsi esattamente agli stessi 73.728 byte di https://www.dietpi.com/forum/.

Per quanto riguarda la teoria delle dimensioni eccessive del documento, https://www.dietpi.com/dietpi-software.html ha 199.475 byte e si carica perfettamente. Dovrei menzionare che il server (stesso webserver) ospita un sito web statico, un’istanza MkDocs, Wordpress, Matomo, che funzionano tutti perfettamente. C’è anche un’istanza Grafana in cui il webserver frontale funge da proxy tramite socket UNIX.

Ma sono d’accordo che sembra essere correlato a buffer o dimensioni dei chunk o qualcosa del genere. È solo strano che la dimensione scaricata fino al blocco vari così tanto tra client e pagine, mentre rimane esattamente la stessa nonostante il cambio di versioni del protocollo, e che la connessione non venga nemmeno chiusa quando il documento è stato completamente scaricato. Come se mancasse il segnale di arresto, anche se a questo punto mi mancano informazioni su HTTP. Perciò ho pensato all’header content-length, ma quello ovviamente non è obbligatorio.

Il webserver funge anche da proxy per il container Discourse tramite socket UNIX. Potrei abilitare il listener TCP per rendere l’istanza Discourse ulteriormente disponibile senza il proxy (lasciando ovviamente Nginx all’interno del container).

Potresti provare KeepAlive Off in Apache?

Suppongo che ciò potrebbe almeno escludere il webserver, quindi varrebbe la pena provare.

1 Mi Piace

Nessuna modifica. Anche dalla documentazione di Apache:

Inoltre, una connessione Keep-Alive con un client HTTP/1.0 può essere utilizzata solo quando la lunghezza del contenuto è nota in anticipo.

Quindi, data la mancanza di content-length, probabilmente ha senso che non venga comunque utilizzato per questa richiesta.

Poiché richiede una ricompilazione, lo farò un po’ più tardi, quando l’attività del nostro sito web comune sarà al minimo. Ehm, sto solo pensando a HTTPS… sembra che dovrò apportare alcune modifiche personalizzate alla configurazione interna di Nginx per mantenere funzionante il socket UNIX e le connessioni HTTP normali, pur rimanendo in ascolto su una porta aggiuntiva per HTTPS con i certificati TLS dall’host, ma senza reindirizzamento/applicazione HTTPS. … e sarebbe interessante anche una porta TCP HTTP normale aggiuntiva, per i client che possono ignorare HSTS.

Stai per caso utilizzando RocketLoader in CloudFlare? So che con altri script causa problemi.
Hai anche svuotato la cache di CF?
Stai utilizzando regole in entrata su CF che potrebbero essere state collegate al tuo vecchio indirizzo IP VPS e non aggiornate a quello nuovo?

1 Mi Piace

Nessun RocketLoader: Si noti che, in base ai test precedenti con curl e wget, che non interpretano alcuna sintassi, quindi non caricano JavaScript, stili o altro, il problema è che il download del documento HTML grezzo si blocca sempre.

La cache di Cloudflare non è attiva per il forum, i documenti HTML grezzi non sono mai stati memorizzati nella cache.

Nessuna regola specifica per VPS. Generalmente nessuna regola per il forum, a parte quella per bypassare la cache. Il problema si presenta in entrambi i casi, quindi nemmeno la cache è il problema.

1 Mi Piace

Durante i test per bypassare il proxy Apache2 sull’host del container Discourse e disabilitare i reindirizzamenti HTTPS forzati su Cloudflare per testare le connessioni HTTP semplici tramite curl, ho finalmente trovato il colpevole su Cloudflare:

Non sono sicuro di cosa sia cambiato con il nostro passaggio al VPS e/o l’aggiornamento da Discourse 3.5.0.beta3 a 3.5.0.beta4 e/o coincidente su Discourse nello stesso periodo, ma sembra che qualcosa nei documenti HTML, CSS o JavaScript di Discourse causi il malfunzionamento della riscrittura HTTPS di Cloudflare per gli URL incorporati. Sembra che le richieste curl parziali e in sospeso non fossero realmente correlate, o forse lo sono. È strano che nella scheda di rete del browser si possa vedere il contenuto parziale del documento HTML, come se la funzionalità di riscrittura HTTPS lo facesse durante lo streaming attraverso il documento.

Forse qualcun altro ha un’istanza e un account Cloudflare per testare questo, sia che si tratti di un problema generale o correlato alla nostra particolare istanza/configurazione?

A proposito, per testare il bypass del proxy e anche HTTP, mantenendo attiva la connessione tramite proxy, la modifica manuale della configurazione Nginx all’interno del container in questo modo funziona perfettamente:

root@dietpi-discourse:/var/www/discourse# cat /etc/nginx/conf.d/outlets/server/10-http.conf
listen unix:/shared/nginx.http.sock;
set_real_ip_from unix:;
listen 8080;
listen [::]:8080;
listen 8443 ssl;
listen [::]:8443 ssl;
http2 on;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

ssl_certificate /shared/fullchain.cer;
ssl_certificate_key /shared/dietpi.com.key;

ssl_session_tickets off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:1m;

È importante rimuovere i reindirizzamenti HTTPS e l’header HSTS, ovviamente, ed esporre le porte aggiunte.

E un’altra scoperta: usiamo mod_sed per aggiungere il nostro codice di tracciamento Matomo a tutte le risposte text/html, proprio prima del tag di chiusura </head>. Disattivarlo per Discourse (o bypassare il proxy Apache2) risolve le cose, nonostante siano attive le riscritture automatiche HTTPS di Cloudflare. Disattivare uno dei due risolve le cose. Su tutte le altre pagine la combinazione funziona bene, anche su pagine molto grandi che abbiamo, più grandi delle pagine del forum che falliscono. Quindi forse i due filtri, prima mod_set sul nostro proxy e poi le riscritture degli URL incorporate da Cloudflare causano la rottura di qualcosa, correlato alle dimensioni del documento o dei chunk o altro.

Incorporiamo il tracker tramite la modifica del tema di Discourse ora, e ho inoltre disattivato le riscritture automatiche HTTPS di Cloudflare. Non c’è contenuto misto sul nostro intero sito web. E se c’è, è bene vederlo e correggerlo, invece di avere Cloudflare che lo maschera per sempre.

Sono abbastanza sicuro che non possa funzionare.

Non sono del tutto sicuro di quale problema tu stia cercando di risolvere, ma probabilmente devi abilitare force_https nel tuo app.yml.

4 Mi Piace

Immagino che solo dal nome “Cloudflare Automatic HTTPS Rewrites” possa essere frainteso. Cloudflare ha 2 funzionalità:

  • “Always Use HTTPS” reindirizza tutte le richieste HTTP semplici a HTTPS, proprio come fa force_https in Discourse. Entrambe erano precedentemente abilitate, e le ho disabilitate entrambe per testare se HTTPS avesse qualcosa a che fare con il problema o con le pagine Discourse in continuo caricamento e le richieste curl bloccate. Questo ha funzionato perfettamente, risolvendo persino l’intero problema anche per le richieste HTTPS, ma solo perché ho disabilitato “Cloudflare Automatic HTTPS Rewrites” nello stesso momento.
  • “Cloudflare Automatic HTTPS Rewrites” modifica documenti HTML, CSS e JavaScript per sostituire tutti gli URL HTTP semplici incorporati con varianti HTTPS, dove Cloudflare ritiene che l’host sia raggiungibile tramite HTTPS (basandosi sulla lista di precaricamento HSTS e simili). Questo serve a evitare avvisi di contenuto misto.

Forzare o non forzare HTTPS su Cloudflare, sul proxy host o su Discourse non ha importanza. Ciò che ha causato il problema è stata la combinazione del filtro mod_sed sul proxy host e le modifiche HTTP semplici incorporate da Cloudflare. Quindi due stadi in cui il contenuto dei documenti veniva filtrato. Il problema non era che ci fosse un effettivo cambiamento di contenuto (non c’è contenuto misto sul nostro sito, quindi “Cloudflare Automatic HTTPS Rewrites” non modifica effettivamente il corpo del documento), ma probabilmente correlato a chunk, buffer o timing.

1 Mi Piace

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.