Também estávamos com esse bug, mas estávamos usando uma biblioteca de gerenciamento de ativos (Cassette). Após uma extensa investigação sobre esse problema, descobrimos que a causa raiz desse problema está na combinação de ASP.NET, IIS e Cassette. Não tenho certeza se esse é o seu problema (usando a Headers
API e não a Cache
API), mas o padrão parece ser o mesmo.
Bug # 1
Cassette define o Vary: Accept-Encoding
cabeçalho como parte de sua resposta a um pacote, pois pode codificar o conteúdo com gzip / deflate:
No entanto, o cache de saída do ASP.NET sempre retornará a resposta que foi armazenada em cache primeiro. Por exemplo, se a primeira solicitação tiver Accept-Encoding: gzip
e Cassette retornar conteúdo compactado com gzip, o cache de saída do ASP.NET armazenará o URL em cache como Content-Encoding: gzip
. A próxima solicitação para o mesmo URL, mas com uma codificação aceitável diferente (por exemplo Accept-Encoding: deflate
), retornará a resposta em cache com Content-Encoding: gzip
.
Esse bug é causado pelo Cassette usando a HttpResponseBase.Cache
API para definir as configurações do cache de saída (por exemplo Cache-Control: public
), mas usando a HttpResponseBase.Headers
API para definir o Vary: Accept-Encoding
cabeçalho. O problema é que o ASP.NET nãoOutputCacheModule
está ciente dos cabeçalhos de resposta; funciona apenas através da API. Ou seja, espera que o desenvolvedor use uma API invisivelmente acoplada em vez de apenas HTTP padrão.Cache
Bug # 2
Ao usar o IIS 7.5 (Windows Server 2008 R2), o bug nº 1 pode causar um problema separado nos caches do kernel e do usuário do IIS. Por exemplo, depois que um pacote é armazenado em cache com êxito Content-Encoding: gzip
, é possível vê-lo no cache do kernel do IIS com netsh http show cachestate
. Ele mostra uma resposta com 200 códigos de status e codificação de conteúdo de "gzip". Se a próxima solicitação tiver uma codificação aceitável diferente (por exemplo
Accept-Encoding: deflate
) e um If-None-Match
cabeçalho que corresponda ao hash do pacote configurável, a solicitação nos caches do kernel e do modo de usuário do IIS será considerada um erro . Assim, fazendo com que o pedido seja tratado pelo Cassette, que retorna um 304:
No entanto, assim que o kernel e os modos de usuário do IIS processarem a resposta, eles verão que a resposta para a URL foi alterada e o cache deve ser atualizado. Se o cache do kernel do IIS for verificado netsh http show cachestate
novamente, a resposta em cache 200 será substituída por uma resposta 304. Todas as solicitações subsequentes ao pacote configurável, independentemente Accept-Encoding
e If-None-Match
retornarão uma resposta 304. Vimos os efeitos devastadores desse bug em que todos os usuários receberam um 304 para nosso script principal por causa de uma solicitação aleatória que teve um inesperado Accept-Encoding
e If-None-Match
.
O problema parece ser que os caches do kernel do IIS e do modo de usuário não podem variar com base no Accept-Encoding
cabeçalho. Como prova disso, usando a Cache
API com a solução alternativa abaixo, os caches do kernel e do modo de usuário do IIS parecem sempre ser ignorados (apenas o cache de saída do ASP.NET é usado). Isso pode ser confirmado verificando se netsh http show cachestate
está vazio com a solução alternativa abaixo. O ASP.NET se comunica diretamente com o trabalhador do IIS para habilitar ou desabilitar seletivamente os kernel do IIS e os caches do modo de usuário por solicitação.
Não foi possível reproduzir esse bug nas versões mais recentes do IIS (por exemplo, IIS Express 10). No entanto, o bug nº 1 ainda era reproduzível.
Nossa correção original para esse bug foi desativar o cache do modo kernel / usuário do IIS apenas para solicitações de cassete, como as mencionadas anteriormente. Ao fazer isso, descobrimos o bug nº 1 ao implantar uma camada extra de cache na frente de nossos servidores da web. A razão que o hack string de consulta trabalhou é porque o OutputCacheModule
irá gravar um cache miss se a Cache
API não foi usada para variar baseado no QueryString
e se o pedido tem umQueryString
.
Gambiarra
Planejamos nos afastar do Cassette de qualquer maneira. Portanto, em vez de manter nosso próprio fork do Cassette (ou tentar obter uma fusão de relações públicas), optamos por usar um módulo HTTP para solucionar esse problema.
public class FixCassetteContentEncodingOutputCacheBugModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += Context_PostRequestHandlerExecute;
}
private void Context_PostRequestHandlerExecute(object sender, EventArgs e)
{
var httpContext = HttpContext.Current;
if (httpContext == null)
{
return;
}
var request = httpContext.Request;
var response = httpContext.Response;
if (request.HttpMethod != "GET")
{
return;
}
var path = request.Path;
if (!path.StartsWith("/cassette.axd", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
if (response.Headers["Vary"] == "Accept-Encoding")
{
httpContext.Response.Cache.VaryByHeaders.SetHeaders(new[] { "Accept-Encoding" });
}
}
public void Dispose()
{
}
}
Espero que isso ajude alguém 😄!