Chúng tôi cũng gặp phải lỗi này nhưng chúng tôi đang sử dụng thư viện quản lý tài sản (Cassette). Sau một cuộc điều tra mở rộng về vấn đề này, chúng tôi đã phát hiện ra rằng nguyên nhân cốt lõi của vấn đề này là do sự kết hợp của ASP.NET, IIS và Cassette. Tôi không chắc đây có phải là vấn đề của bạn không (sử dụng Headers
API chứ không phải Cache
API), nhưng mô hình có vẻ giống nhau.
Lỗi # 1
Cassette đặt Vary: Accept-Encoding
tiêu đề như là một phần của phản hồi của nó đối với một gói vì nó có thể mã hóa nội dung bằng gzip / deflate:
Tuy nhiên, bộ đệm đầu ra ASP.NET sẽ luôn trả về phản hồi đã được lưu trước đó. Ví dụ: nếu yêu cầu đầu tiên có Accept-Encoding: gzip
và Cassette trả về nội dung được nén, bộ đệm đầu ra ASP.NET sẽ lưu URL như Content-Encoding: gzip
. Yêu cầu tiếp theo cho cùng một URL nhưng với mã hóa được chấp nhận khác (ví dụ Accept-Encoding: deflate
) sẽ trả về phản hồi được lưu trong bộ nhớ cache với Content-Encoding: gzip
.
Lỗi này là do Cassette sử dụng HttpResponseBase.Cache
API để đặt cài đặt bộ đệm đầu ra (ví dụ Cache-Control: public
) nhưng sử dụng HttpResponseBase.Headers
API để đặt Vary: Accept-Encoding
tiêu đề. Vấn đề là các ASP.NET OutputCacheModule
là không biết tiêu đề phản ứng; nó chỉ hoạt động thông qua Cache
API. Đó là, nó hy vọng nhà phát triển sẽ sử dụng API kết hợp chặt chẽ vô hình thay vì chỉ HTTP tiêu chuẩn.
Lỗi # 2
Khi sử dụng IIS 7.5 (Windows Server 2008 R2), lỗi # 1 có thể gây ra sự cố riêng với kernel IIS và bộ đệm người dùng. Ví dụ: một khi một gói được lưu thành công với bộ đệm Content-Encoding: gzip
, bạn có thể thấy nó trong bộ đệm kernel IIS netsh http show cachestate
. Nó cho thấy một phản hồi với 200 mã trạng thái và mã hóa nội dung của "gzip". Nếu yêu cầu tiếp theo có một mã hóa khác nhau có thể chấp nhận (ví dụ
Accept-Encoding: deflate
) và một If-None-Match
tiêu đề phù hợp với hash của gói, yêu cầu vào cache hạt nhân và chế độ người dùng IIS sẽ được coi là một bỏ lỡ . Do đó, khiến cho yêu cầu được xử lý bởi Cassette, trả về 304:
Tuy nhiên, khi chế độ người dùng và nhân của IIS xử lý phản hồi, họ sẽ thấy rằng phản hồi cho URL đã thay đổi và bộ đệm nên được cập nhật. Nếu bộ đệm kernel IIS được kiểm tra netsh http show cachestate
lại, phản hồi 200 được lưu trong bộ nhớ cache được thay thế bằng phản hồi 304. Tất cả các yêu cầu tiếp theo đến gói, bất kể Accept-Encoding
và If-None-Match
sẽ trả về phản hồi 304. Chúng tôi đã thấy những tác động tàn phá của lỗi này khi tất cả người dùng được cung cấp 304 cho tập lệnh cốt lõi của chúng tôi do một yêu cầu ngẫu nhiên có một bất ngờ Accept-Encoding
và If-None-Match
.
Vấn đề dường như là bộ nhớ cache của kernel và chế độ người dùng không thể thay đổi dựa trên Accept-Encoding
tiêu đề. Bằng chứng về điều này, bằng cách sử dụng Cache
API với cách giải quyết bên dưới, bộ đệm IIS và bộ đệm chế độ người dùng dường như luôn bị bỏ qua (chỉ sử dụng bộ đệm đầu ra ASP.NET). Điều này có thể được xác nhận bằng cách kiểm tra xem có netsh http show cachestate
trống không với cách giải quyết bên dưới. ASP.NET liên lạc trực tiếp với nhân viên IIS để bật hoặc tắt một cách có chọn lọc kernel IIS và bộ đệm chế độ người dùng theo yêu cầu.
Chúng tôi không thể tạo lại lỗi này trên các phiên bản IIS mới hơn (ví dụ: IIS Express 10). Tuy nhiên, lỗi # 1 vẫn có thể tái tạo.
Cách khắc phục ban đầu của chúng tôi đối với lỗi này là chỉ tắt bộ đệm cache chế độ người dùng / kernel IIS cho các yêu cầu Cassette như những người khác đã đề cập. Bằng cách đó, chúng tôi đã phát hiện ra lỗi số 1 khi triển khai thêm một lớp bộ đệm ẩn trước các máy chủ web của chúng tôi. Lý do mà hack chuỗi truy vấn hoạt động là vì OutputCacheModule
sẽ ghi lại lỗi bộ nhớ cache nếu Cache
API không được sử dụng để thay đổi dựa trên QueryString
và nếu yêu cầu cóQueryString
.
Giải pháp thay thế
Chúng tôi đã có kế hoạch di chuyển khỏi Cassette bằng mọi cách, vì vậy thay vì duy trì ngã ba Cassette của riêng mình (hoặc cố gắng hợp nhất PR), chúng tôi đã chọn sử dụng mô-đun HTTP để khắc phục sự cố này.
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()
{
}
}
Tôi hy vọng điều này sẽ giúp được ai đó!