Como fazer Cache de Imagens num Servidor IIS (Microsoft)
Guardar imagens e outros tipos de ficheiros (arquivos) em cache é muito importante para tornar o seu site mais rápido.
Aqui segue uma dica sobre como fazer cache de imagens (ou qualquer outro tipo de ficheiro) num servidor IIS (“Internet Information Services”) – o servidor proprietário da Microsoft® – sem ter que usar extensões especiais para as imagens (e.g. “ashx”).
No fim deste artigo, há uma secção com pergunta e respostas.
Introdução – o Problema com o IIS
Usando ASP .Net (a língua de programação web da Microsoft), não é possível “interceptar” pedidos (“HTTP Requests”) a certos tipos de ficheiros (arquivos) que não são directamente controlados pelo ASP .Net.
Quer dizer, o pedido vai directamente ao servidor para tratar deles, em vez de passar pelo sistema de .Net. (pronunciado “dot Net”) primeiro.

Não todos os pedidos são interceptados pelo .Net (por exemplo pedidos para ficheiros HTML, JPG, etc)
Por essa razão, é difícil de manipular pedidos a certos tipos de ficheiros usando código (programação). Não é o mesmo caso com servidores Apache.
A Solução
Há uma solução comum que consiste em alterar a extensão de cada ficheiro que precisa de ser tratado pelo .Net directamente e, depois, usar código para manipular esse pedido. Por exemplo, em vez de adicionar o seguinte a uma página:
<img src='imagem.png' />
adicionava-se:
<!-- Solução não Recomendada! -->
<img src='imagem.ashx' />
Quer dizer, usando uma extensão de “.ASHX”, o pedido podia ser interceptado pelo .Net – que depois podia adicionar cabeçalhos (‘HTTP Headers’) à resposta para conseguir efectuar o cache dos ficheiros.
A coisa que não gosto com essa solução é que torna a manutenção de um site com muitas imagens difícil. Uma pessoa não podia simplesmente adicionar uma imagem usando, por exemplo, o editor de Wordpress – em vez, teria que transferir as imagens todas usando FTP e, cada vez, alterar o código para que elas teriam uma extensao “ashx”, etc.
Quer dizer – é um método que lida a mais problemas que soluções.
A solução que eu prefiro também utiliza um método de interceptar o pedido, mas sem ter que alterar o nome ou a extensão da imagem i.e. é uma solução “transparente”. Uma vez implementada, não é necessário pensar mais nela.
Pré-requisitos
Para efectuar esta solução o servidor em que o seu site reside terá que estar equipado com um “software” chamado ISAPI Rewrite 3. (Há uma versão gratuita também – mas nunca a experimentei).
O que esse software faz é: dá mais controlo ao tráfego que passa pelo servidor para efectuar coisas como reescrever URL’s (“Endereços e nomes de ficheiros na Web”).
(A maneira mais fácil de explicar “Reescrever URL’s” é: “Tornar um endereço ‘mais bonito’” – tal como os “permalinks” do Wordpress).
Mesmo se não pretende guardar as suas imagens em cache, sugiro usar esse software na mesma. Haverão sempre situações onde o vai precisar.
Procure sempre uma empresa de alojamento que oferece isso como parte dos seus pacotes de alojamento usando um servidor IIS.
Como fazer
Basicamente, o que pretendemos fazer é:
- Criar um “script” (ficheiro de código programático) que tratará de todos os pedidos para imagens no seu servidor
- Esse script adicionará cabeçalhos (‘HTTP Headers’) a cada pedido para efectuar o cache de esse ficheiro no computador que o está a fazer, ou até num proxy.
A primeira coisa para fazer é adicionar os seguintes comandos ao seu ficheiro “.htaccess” (sim, o ISAPI Rewrite 3 utiliza um ficheiro “.htaccess”, tal como num servidor Apache. É muito menos potente mas útil na mesma
).
# comentários seguem o símbolo "#"
# Primeira regra: o ficheiro tem que existir
RewriteCond %{REQUEST_FILENAME} -f
# Segunda Regra: tem que ser um ficheiro com a extensão
# 'png', 'jpg', 'jpeg' ou 'gif'
RewriteCond %{REQUEST_URI} \.(png|jpg|jpeg|gif)$ [NC]
# Solução: "reescrever" o pedido para ser tratado por o seu
# script (código) que tratará de imagens.
# O endereço final será:
# http://o-seu-site.com/imagenscache?img=/nome-da-imagem/aqui.png
RewriteRule ^(.*)$ /imagenscache.aspx?img=$1 [L]
Adicione essas linhas ao seu ficheiro só após estar pronto com o seguinte passo!
Segundo, é preciso criar um script que tratará de todos os pedidos para imagens.
Para criá-lo, pode baixar e usar um produto gratuito da Microsoft chamado “Visual Web Developer Express“.
Crie um ficheiro ASPX e dê-lhe o nome “imagenscache.aspx”.
O código programático (escrito em C#) seria o seguinte:
protected void Page_Load(object sender, EventArgs e)
{
// verifique que o "query string" (1) existe e que (2) não está vazio
// Um "query string" é informação extra no fim de um endereço
// e começa com um "?"
// e.g.: http://o-seu-site.com/página.html?o-query-string=valor-dele
if (Request.QueryString.Count == 1 &&
Request.QueryString["img"] != null &&
Request.QueryString["img"].Trim() != "")
{
string filePath = Request.QueryString["img"].ToLower();
/******************** IMPORTANTE *************************
* Não dêem nenhuma informação sobre o conteúdo do servidor
* fora de imagens. Possivelmente até seria boa ideia adicionar
* uma verificação aqui que o ficheiro existe numa determinada
* pasta e bloquear acesso a outras!
* Não fiz isso para este exemplo.
***********************************************************/
// verifique que é uma imagem
if (!(filePath.EndsWith(".png")
|| filePath.EndsWith(".jpg")
|| filePath.EndsWith(".jpeg")
|| filePath.EndsWith(".gif")))
{
// somente uma imagem é que será aceite
EndResponse403();
}
try
{
filePath = Server.MapPath("~" + Request.QueryString["img"]);
}
catch
{
// apenas aqui por questões de segurança
EndResponse500();
}
string contentType = "";
string extension = Path.GetExtension(filePath).Replace(".", "");
switch (extension)
{
case "jpg":
case "jpeg":
contentType = "jpeg";
goto case "gif";
case "png":
case "gif":
// verifique que a imagem existe
if (File.Exists(filePath))
{
if (contentType.Equals(""))
contentType = extension;
contentType = "image/" + contentType;
// Envie o tipo de conteúdo
Response.AddHeader("Content-Type", contentType);
// O cache: Adicionei um ano - podem meter o tempo
// que quiserem
Response.Cache.SetExpires(DateTime.Today.AddYears(1));
// "Public" quer dizer que pode ser guardado em cache num
// proxy também. Se só quiser que seja guardado em caches
// privados i.e. no próprio computador que o está a aceder,
// remove a seguinte linha .
Response.Cache.SetCacheability(HttpCacheability.Public);
// ler o ficheiro
byte[] bytes;
try
{
bytes = File.ReadAllBytes(filePath);
}
catch (IOException ex)
{
EndResponse500();
return;
}
using (Stream writer = Response.OutputStream)
{
// Envie a imagem!
writer.Write(bytes, 0, bytes.Length);
}
}
else
{
/**********************************************************
* É importante verificar (1) que é uma imagem e (2) que
* está num determinada pasta ANTES de verificar se o
* ficheiro existe. É importante não enviar um código 403
* apenas quando o ficheiro existe e 404 se não existe.
* Isso forneceria demasiada informação sobre o conteúdo
* que reside no servidor
* ********************************************************/
// A imagem não existe. Envie erro 404.
EndResponse404();
}
break;
}
}
else
{
// Não contem nenhum query string - acesso directo proibido
EndResponse403();
}
}
// Vários métodos para facilitarem o envio de cabeçalhos de resposta
private void EndResponse(int httpStatusCode)
{
Response.Clear();
Response.StatusCode = httpStatusCode;
Response.End();
}
// 403 - Acesso proibido
private void EndResponse403()
{
EndResponse(403);
}
// 404 - Não existe
private void EndResponse404()
{
EndResponse(404);
}
// 500 - Erro interno no servidor
private void EndResponse500()
{
EndResponse(500);
}
É só isso!
O resultado seria o seguinte:

Cabeçalhos de HTTP mostram que a data de expiração é um ano no futuro
Cache: público (i.e. num computador privado ou num proxy).
Expiração: 1 ano após o pedido
Futuros pedidos ao seu site serão muito mais rápidos!
A solução é implementada uma vez e, após isso, é só continuar a actualizar e a trabalhar no seu site sem ter que preocupar sobre o cache de imagens.
Perguntas e Respostas
Utilizar um Script não adiciona tempo a processar o pedido?
Sim, não é uma solução perfeita, mas o tempo que demoraria para baixar ficheiros que não estão em cache (se são muitos) demoraria muito mais na maior parte dos casos.
É realmente necessário utilizar um Script? Não é possível configurar o servidor para tratar disso?
Creio que sim, não tenho a certeza. Mas uma pessoa não tem sempre acesso ao servidor. Com esta soluçao pelo menos teria um bocado de controlo sobre a situação sem ter que pedir ajuda à empresa que lhe está a fornecer o alojamento. Isso não é sempre possível.
Não seria mais fácil mudar o sistema para um servidor Apache?
Sim e não. Existem empresas que investiram muito num site feito em ASP .Net e, mudar o site, não seria possível simplesmente por causa de custos.
É verdade que a mesma solução num servidor Apache necessitava apenas duas ou três linhas num ficheiro .htaccess e estaria feito!
Esta solução existe aqui para quem não tiver essa opção neste momento.
Então, isto é a solução melhor, não é?
Creio que não – mas funciona
. Se alguém conhecer uma solução mais simples e fácil de implementar, digam-me.
Essa solução funciona com outros tipos de ficheiros?
Sim.
Adicionando várias extensões (por exemplo “.css”, “.js”) ao seu “script” e ao ficheiro .htaccess, podia utilizar a mesmo solução para fazer cache de qualquer tipo de ficheiro.
Também, alterando o código um bocado, podia designar um tempo de cache diferente a cada tipo de ficheiro (por exemplo, um ano para imagens, 3 meses para folhas de estilo, etc.).
A empresa que me aloja o site não fornece ISAPI Rewrite 3. O que faço?
Mude de empresa!
Se não poder – terá que utilizar a solução que utiliza extensões “.ashx”. Seria uma solução parecida com esta mas com um bocado mais de trabalho.
E, como já disse, possivelmente isto é uma coisa configurável no próprio servidor. (Não sei muito sobre servidores – mas se for uma coisa configurável, possivelmente podiam configurá-lo para si).
Tentei a solução e não funciona!
Verifique que o servidor em que o seu site reside utiliza mesmo o ISAPI Rewrite 3 e que está configurado correctamente.
Se está, e não funciona ainda, deixe-me um comentário. Todos fazemos erros – possivelmente fiz um aqui.
Também, se encontrarem qualquer erro no código acima – ou acham que podia ser feito de uma maneira melhor – digam-me por favor.

