Como responder com cache em uma rota Laravel?

Há alguns cenários no Laravel onde uma resposta de uma rota precisa ser cacheada. Por exemplo, já passei por alguns cenários em que precisei que uma determinada rota respondesse com uma imagem ou um PDF e, como nesses casos, estas informações geralmente eram raramente ou nunca atualizadas, eu precisei de implementar o cache de resposta, para aliviar um pouco o servidor.

Mas como fazer isso em Laravel?

Respondendo 304 Not Modified com Laravel

Normalmente, o Laravel por padrão sempre irá responder normalmente com o status 200 para qualquer tipo de resposta que você usa (seja PDF, JSON, HTML ou imagens).

Como o objetivo é retornar uma resposta cacheada, precisamos utilizar o status 304 (not modified). O 304, no entanto, deverá ser utilizado caso esteja presente o header If-modified-since. Esse header sempre será enviado pelo navegador, após o header Last-modified em uma resposta. Melhor explicando: quando o navegador recebe Last-modified, nas próximas requisições, ele enviará If-Modified-since.

Suponhamos que determinada resposta é enviada pelo Laravel é baseada em um registro no banco de dados, e esse registro possui uma data de modificação updated_at. Podemos utilizar o campo updated_at, por exemplo, como ponto de partida para fazermos o cache de resposta. Ou seja, usaremos ele para devolver como Last-modified na resposta.

Exemplo:

$usuario = Usuario::findOrFail($id);

$pdf = $this->gerarPdfCurriculo($usuario);

return response($pdf, 200, [ 'Last-modified' => $usuario->updated_at->format(\DateTime::COOKIE) ]);

Note que o header last-modified tem uma formatação de data específica. A constante DateTime::COOKIE possui esse formato padronizado.

Além disso, para devolver o last-modified, usamos o status 200 inicialmente.

Na resposta, terá algo parecido com isso:

Last-Modified: Friday, 09-Aug-2019 01:34:20 UTC

Nota: Você poderá acessar essas informações ao acessar a ferramenta de desenvolvimento do Google Chrome ou Firefox, clicando na aba "Network" e na url de resposta.

Esse valor será enviado na próxima requisição que você fizer nessa mesma url, através do header If-modified-since. E é com ele que vamos trabalhar agora.

A lógica é a seguinte:

  • Comparar os valores de updated_at para verificar se é o mesmo de If-modified-since

  • Enviar uma resposta "vazia" informando o status 304.

Vamos fazer uma pequena modificação no código:

$usuario = Usuario::findOrFail($id);

$lastModified = $usuario->updated_at->format(\DateTime::COOKIE);

if ($lastModified === $request->header('If-modified-since')) { return response('', 304); }

$pdf = $this->gerarPdfCurriculo($usuario);

return response($pdf, 200, [ 'Last-modified' => $lastModified ]);

Para testar, basta agora atualizar a página. Verifique se o status é 304. Caso tudo tenha dado certo, o navegador se encarregará de recuperar o valor armazenado em cache da última requisição e exibirá.

Ou seja, não respondemos nada do servidor quando há o header if-modified-since informado e, mesmo assim, o navegador recuperou as informações em cache.

Eu particularmente, acho isso bastante útil para casos onde a criação de um PDF ou uma imagem é custoso para o servidor e, ao mesmo tempo, o cliente deseja acessa esse recurso várias vezes.

Já houve casos onde eu percebi que uma resposta para geração de um PDF demorava uns 2 segundos. Na segunda requisição, já não demora, pois o resultado foi armazenado em cache.

Observações

É importante informar alguns pontos para que os testes funcione:

  • Se você estiver utilizando a ferramenta de desenvolvedor do Chrome ou Firefox, certifique-se de fazer os testes deixando a opção Disable Cache desativada.

  • Se você customa usar http://localhost ou http://127.0.0.1, alguns navegadores poderão desabilitar o cache nas requisições automaticamente. Para resolver isso, você pode vincular o "php artisan serve" em um host diferente destes citados.