Come dovrei gestire una risposta HTTP il cui corpo richiede tempo per generare e le cui intestazioni non possono essere determinate fino al completamento della generazione?

5

Sfondo

La mia applicazione Web vive su un server centralizzato nella "rete" del prodotto e fornisce i mezzi per gestire / configurare vari dispositivi distribuiti. Il server registra anche varie statistiche che arrivano da ciascun dispositivo, memorizzandole su disco in /var/log/ . La GUI Web consente agli utenti di scaricare tali registri.

Ha anche la possibilità di scaricarli in vari formati diversi, il che richiede una traduzione / conversione al volo. Questa conversione richiede un po 'di tempo (diciamo nell'ordine di trenta secondi) e genera file di dimensioni nella regione di 300 MB.

Tutto questo va bene e un utente può accettare che il download di un file convertito richiederà un po 'di tempo. Ma ho architettato me stesso in un angolo in termini di quanto effettivamente posso effettivamente consegnare questi file.

Ai fini di questa domanda, non esplorerò le soluzioni AJAX / JavaScript / Java / Flash / multi-step / multi-pagina. Supponiamo che, dal punto di vista dell'agente utente, il download sia una semplice richiesta HTTP GET a uno script CGI facendo clic su un elemento <a> , e nient'altro.

Problema

La mia applicazione web è vagamente architettonica MVC in modo tale che il controller scelto per soddisfare l'azione richiesta (diciamo, in questo caso: l'azione "dispositivi" del controller "getConvertedLog") esegue la sua logica di business e imposta vari flag che descrivono come deve essere composta la risposta HTTP. Solo dopo che il controller ha terminato il suo lavoro verrà composta la risposta HTTP, con le intestazioni di risposta generate e il corpo in streaming da, in questo caso, un file temporaneo su disco.

Il primo problema è che il controller esegue (o almeno invoca) la conversione del file, impiega del tempo per essere eseguito. Di conseguenza, le intestazioni HTTP non vengono generate (per non dire trasferite) per trenta secondi circa. Non solo ciò comporta trenta secondi di letteralmente nulla che accade nel browser (almeno dalla mia esperienza in Chrome) ma mette anche l'intera richiesta ad alto rischio di un errore HTTP 504 Timeout gateway dall'intervenire router.

Potrei mescolare il mio codice un po 'in modo che alcune intestazioni di risposta HTTP possano essere trasferite al browser prima la conversione abbia inizio, per fornire almeno un'indicazione che qualcosa sta accadendo (e, si spera, allontanare il Timeout del gateway ). Ma prima che la conversione sia completa, non ho modo di sapere quanti byte comprenderanno il risultato. Pertanto, non posso inviare un'intestazione Content-Length significativa, quindi l'utente-agente non può mostrare l'avanzamento all'utente. E per un file da 300 MB non lo ritengo accettabile.

Il secondo problema con questo è che, se c'è un errore durante la conversione, il codice di risposta HTTP dovrebbe essere significativo. Quindi non posso aver inviato un Status in queste ipotetiche intestazioni di pre-conversione.

Domanda

Cosa faresti qui? Quello che è il minimo che devo fare indica agli user-agent e ai proxy che la richiesta è stata accettata e una risposta sta arrivando (anche se lentamente), prima il successo o l'insuccesso e la dimensione della risposta è stata determinata ?

Suppongo che sarebbe l'ideale se fosse legale e funzionale inviare un set di intestazioni molto piccolo, ad esempio:

 Status: 200 OK
 Content-Type: application/zip
 Content-Disposition: attachment; filename="thefile.zip"

... quindi seguilo con le restanti intestazioni ( Set-Cookie , Cache-Control , Content-Length e così via), alcune sostituendo potenzialmente quelle precedenti (come una modifica in Status ) e, infine, il corpo della risposta .

Spero che il modulo CGI di Apache traduca e riordini alcune intestazioni (ad esempio Status: 200 finisce nella prima riga della risposta come HTTP/1.1 200 OK ) può aiutare qui. In che modo potrebbe l'elaborazione HTTP 102 in elaborazione qui?

( Aggiornamento: "Almeno un CGI-Header deve essere fornito, ma nessuna intestazione CGI può essere ripetuta con lo stesso nome-campo." [ CGI 1.1, §9.2 ]. Rats.)

    
posta Lightness Races in Orbit 08.07.2015 - 18:21
fonte

1 risposta

2

Questo è un problema comune. Il primo esempio che mi viene in mente è l'autenticazione con carta di credito.

Come è stato detto, è necessario concettualmente imporre il processo, in modo che una cosa risponda al cliente e un'altra funzioni il lavoro effettivo.

Questo è in realtà abbastanza semplice, perché HTTP è un protocollo libero da stato, puoi avere un rendering di pagina e completare la transazione del client, mentre continui a funzionare sul server (purché il servizio sul server ti permetta di troppo, PHP normalmente ti farà perdere tempo se impieghi troppo tempo).

Quindi ecco un piano. Quando ottieni la tua richiesta, crea il tuo file temporaneo, con un nome come /tmp/.part e reindirizza il client su una pagina diversa, che arriverò tra poco. Invia come parametro all'interno del link di reindirizzamento o come cookie. Quindi concludi la tua connessione con il client ed elabora il file, memorizzando i dati in /tmp/.part mentre vai. Una volta terminato, rinomina /tmp/.part in / tmp /

Sulla pagina diversa, dato parametro (da cookie o parametro), e verifica l'esistenza di / tmp / o /tmp/.part Se il file .part esiste, è ancora in elaborazione, o genera un file html con un meta tag che dice all'utente di ricaricarsi in pochi secondi, o di dormire per qualche secondo e dare al client un reindirizzamento con la stessa forma del chiamante originale.

Se / tmp / esiste, hai finito. Presenta l'utente con il file per il download.

Potresti anche aggiungere alcune raccolte di dati inutili da qualche parte per verificare se ci sono file .part che non sono stati modificati per > 1 ora o file effettivi con più di 2 o 3 ore di vita ed eliminarli.

Se hai un server che sta scadendo, allora hai 2 opzioni, tramite il riavvio durante l'aggiornamento o tramite cron.
Se si sceglie di riavviare, codificare il file .part con informazioni sufficienti per riprendere il processo a metà e avere il controllo di fase di aggiornamento per un processo in esecuzione e se non lo trova, riavviare il processo Se si sceglie di passare attraverso cron, si sta alla fine cambiando il precedente in un sistema di gestione dei lavori, dove invece del cgi-bin che avvia il lavoro, lo si fa creare la richiesta e si ha cron periodicamente verificando le richieste (l'esistenza di un file .part) e dove trova uno, lo esegue.

Riguardo alla gestione degli errori, ciò potrebbe alimentare la possibilità di riavviare un processo. Se il .part contiene un indicatore di stato, per comunicare alla schermata di ricarica dove si trova, è possibile restituire all'utente le condizioni di errore. Se lo script in esecuzione incontra un problema, aggiorna .part. Quando il client successivo ricarica la pagina con il suo contesto, guarda il file .part e restituisce lo stato.

Vale la pena ricordare, devi bilanciare la complicazione di mettere in .part più che semplicemente la versione incompleta di ciò che sarà vs creare un terzo file come .status che lo script di ricarica potrebbe usare.

    
risposta data 18.01.2016 - 16:11
fonte

Leggi altre domande sui tag