Como devo lidar com uma resposta HTTP cujo corpo leva tempo para gerar e cujos cabeçalhos não podem ser determinados até que a geração esteja completa?

5

Antecedentes

Meu aplicativo da web reside em um servidor centralizado na "rede" do produto e fornece os meios para gerenciar / configurar vários dispositivos distribuídos. O servidor também registra várias estatísticas que chegam de cada dispositivo, armazenando-as no disco em /var/log/ . A GUI da Web permite que os usuários façam o download desses registros.

Ele também tem um recurso para baixá-los em vários formatos diferentes, o que requer uma conversão / conversão on-the-fly. Essa conversão leva algum tempo (digamos, na ordem de trinta segundos) e resulta em arquivos de tamanho na região de 300MB.

Tudo isso é bom e um usuário pode aceitar que o download de um arquivo convertido demore um pouco. Mas eu me armei em um canto em termos de como efetivamente posso entregar esses arquivos.

Para os propósitos desta questão, não explorarei as soluções AJAX / JavaScript / Java / Flash / multi-passo / multi-página. Suponha que, do ponto de vista do agente do usuário, o download seja uma solicitação HTTP GET simples para um script CGI, clicando em um elemento <a> e nada mais.

Problema

Minha aplicação web é frouxamente arquitetada pelo MVC de tal forma que o controlador escolhido para satisfazer a ação solicitada (digamos, neste caso: ação "dispositivos" do controlador "getConvertedLog") executa sua lógica de negócios e define vários sinalizadores que descrevem como a resposta HTTP deve ser composta. Somente após o controlador concluir seu trabalho, a resposta HTTP será composta, com cabeçalhos de resposta gerados e o corpo transmitido a partir, nesse caso, de um arquivo temporário em disco.

O primeiro problema com isso é que o próprio controlador executa (ou, pelo menos, invoca) a conversão do arquivo, leva algum tempo para ser executado. Os cabeçalhos HTTP, consequentemente, não são gerados (e muito menos transferidos) por cerca de trinta segundos. Isso não só resulta em trinta segundos de literalmente nada acontecendo no navegador (pelo menos da minha experiência no Chrome), mas também coloca toda a solicitação em alto risco de um erro HTTP 504 Gateway Timeout de intervir roteadores.

Eu poderia embaralhar meu código um pouco para que alguns cabeçalhos de resposta HTTP possam ser transferidos para o navegador antes da conversão começar, para ao menos dar uma indicação de que algo está acontecendo (e, esperançosamente, evite o Tempo limite do gateway ). Mas antes que a conversão seja concluída, não tenho como saber quantos bytes incluirão o resultado. Portanto, não posso enviar um cabeçalho Content-Length significativo, para que o user-agent não possa exibir o progresso para o usuário. E para um arquivo de 300MB, não considero isso aceitável.

O segundo problema com isso é que, se houver um erro durante a conversão, o código de resposta HTTP deverá ser significativo. Por isso, não posso ter enviado um Status nesses cabeçalhos hipotéticos de pré-conversão.

Pergunta

O que você faria aqui? O mínimo que eu preciso fazer é indicar aos user-agents e proxies que o pedido foi aceito e uma resposta está chegando (ainda que lentamente), antes do sucesso ou falha e tamanho da resposta ter sido determinado ?

Acho que seria ideal se fosse legal e funcional enviar um conjunto muito pequeno de cabeçalhos, digamos:

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

… depois siga com os cabeçalhos restantes ( Set-Cookie , Cache-Control , Content-Length e assim por diante), alguns potencialmente substituindo os anteriores (como uma alteração em Status ) e, finalmente, o corpo da resposta .

Espero que o módulo CGI do Apache traduza e reordene alguns cabeçalhos (por exemplo, Status: 200 acaba na primeira linha da resposta como HTTP/1.1 200 OK ) pode ajudar aqui. Como o HTTP 102 Processing pode ajudar aqui?

( Atualização: "Pelo menos um CGI-Header deve ser fornecido, mas nenhum cabeçalho CGI pode ser repetido com o mesmo nome de campo." [ CGI 1.1, §9.2 ] Ratos.)

    
por Lightness Races in Orbit 08.07.2015 / 18:21
fonte

1 resposta

2

Este é um problema comum. O primeiro exemplo que vem à mente é na autenticação de cartão de crédito.

Como foi mencionado, você precisa conceitualmente bifurcar o processo, de modo que uma coisa responda ao cliente e outra o trabalho real.

Isso é realmente direto, porque o HTTP é um protocolo livre de estado, você pode ter uma página renderizada e completar a transação do cliente, enquanto continua funcionando no servidor (contanto que o serviço no servidor permita que você também, o PHP normalmente irá atrasar você se você demorar muito).

Então aqui está um plano. Quando você obtiver sua solicitação, crie seu arquivo temporário com um nome como /tmp/.part e redirecione o cliente para uma página diferente, à qual chegarei em breve. Envie o como um parâmetro dentro do link de redirecionamento ou como um cookie. Em seguida, conclua sua conexão com o cliente e processe o arquivo, armazenando os dados em /tmp/.part conforme você for. Quando terminar, renomeie /tmp/.part para / tmp /

Na página diferente, determinado parâmetro (de cookie ou parâmetro), e verifique a existência de / tmp / ou /tmp/.part Se o arquivo .part existir, ele ainda está sendo processado, seja gerar um arquivo html com uma metatag que avisa o usuário para recarregar em alguns segundos, ou dorme por alguns segundos e dá ao cliente um redirecionamento com o mesmo formato do chamador original.

Se / tmp / existir, está feito. Apresentar o usuário com o arquivo para download.

Você também pode adicionar algumas coletas de lixo em algum lugar para verificar se há arquivos .part que não foram alterados para > 1 hora ou arquivos reais com mais de 2 ou 3 horas de duração e excluí-los.

Se você tem um servidor que está expirando, então você tem 2 opções, através de reinício durante a atualização, ou através do cron.
Se você optar por reiniciar, codifique o arquivo .part com informações suficientes para selecionar o processo no meio e ter a verificação de estágio de atualização para um processo em execução e, se ele não encontrar um, reinicie o processo. Se você optar por passar pelo cron, você está mudando o acima para um sistema de gerenciamento de tarefas, onde em vez do cgi-bin iniciando o trabalho, você cria o pedido, e tem o cron checando periodicamente os pedidos (a existência de um arquivo .part) e onde ele encontra um, ele executa.

Em relação ao tratamento de erros, isso pode contribuir para a sua capacidade de reiniciar um processo. Se o .part contiver algum indicador de status, para informar a tela de recarga onde ele está, você poderá passar as condições de erro de volta para o usuário. Se o script em execução encontrar um problema, ele atualizará .part. Quando o cliente recarrega a página com o contexto, ele verifica o arquivo .part e retorna o status.

Vale a pena ter em mente, você tem que equilibrar a complicação de colocar em .part mais do que simplesmente a versão incompleta do que será ao criar um terceiro arquivo, como .status que o script de recarga pode usar.

    
por 18.01.2016 / 16:11
fonte

Tags