Como determinar o Content-Length de um arquivo compactado?
-
03-07-2019 - |
Pergunta
Agora eu estou tentando servir arquivos CSS e JS de um servidor que não me deixa permitir mod_gzip
ou mod_deflate
. Então eu escrevi um script PHP pequeno a compressa com GZIP e retorno para o usuário.
Exemplo de código:
$filename = "style.css";
if (!file_exists($filename) || !($info = stat($filename))) {
header("HTTP/1.1 404 Not Found");
die();
}
header("Date: ".gmdate("D, j M Y H:i:s e", time()));
header("Cache-Control: max-age=2592000");
header("Last-Modified: ".gmdate("D, j M Y H:i:s e", $info['mtime']));
header("Etag: ".sprintf("\"%x-%x-%x\"", $info['ino'], $info['size'], $info['mtime']));
header("Accept-Ranges: bytes");
header("Cache-Control: Expires ".gmdate("D, j M Y H:i:s e", $info['mtime']+2592000));
header("Content-Type: text/html");
ob_start("ob_gzhandler");
echo file_get_contents($filename);
ob_end_flush();
Eu estou tendo dois problemas agora. O primeiro é, estou tendo problemas para determinar o tamanho resultante do arquivo compactado para informar o navegador do conteúdo de comprimento. Normalmente, eu iria incluir esta linha:
header("Content-Length: ".$info["size"]);
Mas, se o fizer, os trava navegador ao tentar esperar por mais dados. Existe uma maneira de calcular o tamanho total? Ou devo ignorar esta directiva cabeçalho.
A outra questão é, sempre que eu ver este arquivo PHP no Firefox, ele tenta ter me baixar o resultado. No Chrome, apenas a exibe como eu poderia esperar. Alguma sugestão?
Editar: Graças ao SoapBox, eu substituiu o final do código com o seguinte:
header("Content-Encoding: gzip");
$compressed = gzencode(file_get_contents($filename), 5);
header("Content-Length: ".strlen($compressed));
die($compressed);
Isso funciona muito bem para o conteúdo de comprimento! Mas eu ainda estou recebendo Firefox para baixar o arquivo em vez de exibi-lo. : (
Editar Novamente:. Aqui está o código de fim de código modificado, cortesia de Cletus
// Start buffered output
ob_start();
// Check for gzip capability
if (stripos($_SERVER['HTTP_ACCEPT_ENCODING'], "gzip") !== false) {
ob_start("ob_gzhandler");
echo file_get_contents($filename);
ob_end_flush();
} else
echo file_get_contents($filename);
// Write the content length
header('Content-Length: '.ob_get_length());
ob_end_flush();
Eu vou começar uma nova pergunta para descobrir por que o Firefox continua tentando baixar o arquivo.
Solução
O problema aqui é que saber o tamanho do conteúdo que você precisa saber se deve ou não os suportes cliente gzip codificação e você delegado essa decisão usando ob_gzhandler. De cabeçalhos HTTP :
ob_start(); ob_start('ob_gzhandler'); ... output the page content... ob_end_flush(); // The ob_gzhandler one header('Content-Length: '.ob_get_length()); ob_end_flush(); // The main one
versão completa:
$filename = "style.css";
if (!file_exists($filename) || !($info = stat($filename))) {
header("HTTP/1.1 404 Not Found");
die();
}
header("Date: ".gmdate("D, j M Y H:i:s e", time()));
header("Cache-Control: max-age=2592000");
header("Last-Modified: ".gmdate("D, j M Y H:i:s e", $info['mtime']));
header("ETag: ".sprintf("\"%x-%x-%x\"", $info['ino'], $info['size'], $info['mtime']));
header("Accept-Ranges: bytes");
header("Expires: ".gmdate("D, j M Y H:i:s e", $info['mtime']+2592000));
header("Content-Type: text/css"); // note: this was text/html for some reason?
ob_start();
ob_start("ob_gzhandler");
echo file_get_contents($filename);
ob_end_flush();
header('Content-Length: '.ob_get_length());
ob_end_flush();
Esta é muito melhor do que tomar sobre o gzip codificação problema sozinho.
Outras dicas
Você precisa primeiro fazer todo o gzip e medir o resultado (ou segurando o conteúdo na memória, ou gravá-los no disco como você comprimir e, em seguida, stat'ing o arquivo compactado), em seguida, escrever o cabeçalho Content-Length e depois enviar o conteúdo do arquivo.
Ou uso Chunked codificação de transferência .
Para resolver o problema firefox, eu acho que você precisa incluir header( "Content-Encoding: gzip" );
para que o navegador sabe para descomprimir o conteúdo.
Como para o tamanho do conteúdo, você pode tentar apenas deixar este valor off, ou tentar descobrir uma maneira de usar "Transfer-Encoding: blocos" (você não pode jsut enviar este cabeçalho, você precisa formatar os dados especialmente para ele). É possível que ob_end_flush
automaticamente permite chunking.
Eu recomendo que você obter wireshark e captura o que o seu script php está enviando e compará-lo a um servidor corretamente comportar para ver o que cabeçalhos, etc estão faltando.