
No primeiro artigo "Expires no Apache - Cache de arquivos no navegador", vimos como configurar o apache para utilizar o mod_expires para sugerir o cache de arquivos estáticos no navegador, com base na data de modificação. No segundo artigo "Expires no PHP - Cache de arquivos no navegador", vimos algo similar, mas aplicado a arquivos gerados dinamicamente.
Neste artigo, veremos um outro mecanismo usado para sugerir o armazenamento de um documento no cache do navegador. Este mecanismo é chamado ETag (Entity Tag).
Funcionamento do ETag
O funcionamento básico do cacheamento usando ETag é o seguinte:
- O navegador solicita um documento no servidor pela primeira vez.
- O servidor devolve o documento e, junto com ele, informa um Hash indicando a "versão" do documento.
- O navegador recebe o documento e o guarda em cache, juntamente com o Hash recebido.
- O navegador solicita o mesmo documento no servidor, informando o Hash que ele possui.
- O servidor recebe o Hash e verifica se o documento continua com o mesmo Hash. Se o Hash continua idêntico, o servidor informa ao navegador que ele pode usar o documento que ele tem em cache. Se o Hash mudou, o servidor devolve o documento inteiro, juntamente com seu novo Hash.
Note que o funcionamento é similar. A diferença é que se usa o Hash para determinar se um documento continua válido ou não.
O benefício do uso do ETag é uma redução significativa no tráfego de dados entre cliente e servidor.
Assim como a utilização do Expires, o ETag pode ser usado tanto para documentos estáticos quanto para documentos gerados dinamicamente. Nas próximas seções veremos como configurar o Apache para utilizar ETag no conteúdo estático e como implementar ETag nos scripts PHP que geram conteúdo dinâmico.
ETag para documentos estáticos
Para documentos estáticos, o ETag pode ser configurado no servidor HTTP. No Apache, isso é feito através da diretiva de configuração FileETag. A diretiva deve representar quais elementos serão usados para montar o ETag de cada arquivo, que podem ser:
- Inode: leva em conta o Inode do arquivo no sistema de arquivos do servidor.
- MTime: leva em conta a data de modificação do arquivo no servidor.
- Size: leva em conta o tamanho do arquivo.
- All: leva em conta todos os elementos possíveis.
- None: não inclui o ETag no arquivo.
A diretiva é configurada com a lista destes elementos separados por espaço e, opcionalmente, prefixados por "+" ou "-" indicando "levar em conta" e "não levar em conta", respectivamente.
A configuração deve ser aplicada a um grupo de arquivos específico, portanto, você deve especificar quais arquivos receberão esta configuração através de uma seção <File> ou <FileMatch>, conforme o exemplo, que aplica o ETag nos arquivos com extensão .gif, .jpg, .png, .css, .js, .pdf e .txt:
<FilesMatch "\.(gif|jpg|png|css|js|pdf|txt)"> FileETag All </FilesMatch>
Um problema com o ETag é que alguns servidores colocam os documentos estáticos distribuídos em vários servidores, para atender à alta demanda com boa performance. Só que em cada servidor, uma determinada cópia do arquivo provavelmente terá Inode diferente e data de última modificação diferente, apenas o tamanho igual. Porém, levar em conta apenas o tamanho do arquivo é perigoso já que um arquivo com uma letra errada pode ser corrigido e manter o mesmo tamanho. Uma possível solução é usar apenas o Expires, ou então usar MTime e Size, mas garantir que o MTime de todos servidores esteja igual (isso pode ser feito com o comando touch no Linux, que modifica a data de última modificação para uma data específica). Por exemplo:
$ touch -d "2013-03-06 20:46:00" arquivo.php
ETag para documentos dinâmicos
Para usufruir do recurso ETag em documentos dinâmicos, precisamos implementar a camada de negociação do servidor com o navegador.
Primeiramente, precisamos definir um mecanismo para geração do hash. Felizmente, com programação, podemos usar qualquer elemento para isso. Um bom exemplo é usar o algoritmo MD5 do conteúdo gerado. Você pode preferir concatenar com o tamanho do conteúdo gerado, para dificultar ainda mais as chances de hashes idênticos para o mesmo conteúdo. A restrição é que este hash precisa ser delimitado por aspas duplas. Então, antes de enviar o documento para o navegador, informamos pelo cabeçalho HTTP o Hash gerado, conforme o exemplo:
// Armazenamos todo conteudo do documento numa variavel $documento = ...; // Gerando o hash do documento $hash = '"' . md5($documento) . '"'; // Enviando o cabecalho HTTP header('Content-Type: text/html; charset=UTF-8'); header('ETag: ' . $hash); // Enviando o documento echo $documento; exit(0);
Se você já tem uma aplicação que realiza vários "echo", pode capturar todo conteúdo do documento através da utilização de Buffer do Output.
Bom, ao receber esta ETag, o navegador vai enviá-la para o script quando for acessá-lo novamente. Você pode capturar este valor pela variável superglobal $_SERVER['HTTP_IF_NONE_MATCH']. Então, o que temos que fazer é checar se recebemos este valor e, se recebemos, verificar se ele é válido (se é igual ao hash do documento que acabamos de gerar). Se o hash for válido, devolvemos o cabeçalho HTTP 304, indicando que o documento não foi modificado (da mesma forma que fizemos com o Expires):
// Armazenamos todo conteudo do documento numa variavel $documento = ...; // Gerando o hash do documento $hash = '"' . md5($documento) . '"'; // Se o navegador possui um hash valido: informar que o documento nao mudou if (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER) && $_SERVER['HTTP_IF_NONE_MATCH'] == $hash) { header('HTTP/1.1 304 Not Modified'); header('Date: ' . gmstrftime('%a, %d %b %Y %T %Z', $_SERVER['REQUEST_TIME'])); header('Cache-Control: '); header('Pragma: '); header('Expires: '); exit(0); } // Enviando o cabecalho HTTP header('Content-Type: text/html; charset=UTF-8'); header('ETag: ' . $hash); // Enviando o documento echo $documento; exit(0);
Talvez a diretiva "If-None-Match" passada pelo navegador não é colocada em $_SERVER['HTTP_IF_NONE_MATCH'] em algumas versões do PHP ou SAPI usada. Então, você pode precisar percorrer o cabeçalho HTTP enviado pelo navegador através de uma destas funções: getallheaders, apache_request_headers ou http_get_request_headers.
Observe que o processamento para gerar o documento continua existindo. O ganho em performance está em não precisar enviar todo o documento novamente para o navegador, se ele informou que já tem uma versão válida do mesmo.
Observações
O ETag pode ser usado em conjunto com o Expires. A especificação do HTTP diz que o cabeçalho 304 (documento não modificado) deve ser enviado apenas quando satisfazer as duas condições, ou seja, a validade do documento não expirou e o ETag continua o mesmo.
1 comentário
estou tentando melhorar a velocidade de cache do meu Blogger estou a tentar fazer tais modificações Obrigado por me Ajudar sorry
Postar um comentário
Nota: fique a vontade para expressar o que achou deste artigo ou do blog.
Dica: para acompanhar as respostas, acesse com uma conta do Google e marque a opção "Notifique-me".
Atenção: o blogger não permite inclusão de tags nos comentários, por isso, use algum site externo para postar seu código com dúvidas e deixe o link aqui. Exemplo: pastebin.com