OutputCache отправляет неверный заголовок Vary, когда вызов попадает в кеш
У меня есть метод действий, который я хочу кэшировать:
[OutputCache(Duration=60*5, Location=OutputCacheLocation.Any, VaryByCustom="index")]
public ActionResult Index()
{
return View();
}
При таком подходе:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
context.Response.Cache.SetOmitVaryStar(true);
context.Response.Cache.VaryByHeaders["Cookie"] = true;
if (User.Identity.IsAuthenticated)
{
Debug.Print("Authenticated");
context.Response.Cache.SetNoServerCaching();
context.Response.Cache.SetCacheability(HttpCacheability.Private);
return null;
}
else
{
Debug.Print("Non authenticated");
return custom;
}
}
Идея заключалась в сохранении кешированной версии страницы для пользователей, не прошедших аутентификацию, но избегающей кэширования для аутентифицированных.
Я думал, что он всегда будет возвращать HTTP-заголовок Vary:Cookie
, но это не так.
Выполняя тест с Fiddler и выдавая дважды тот же запрос, в первом HTTP-вызове он идет хорошо:
HTTP/1.1 200 OK
Cache-Control: public, max-age=300
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 10:53:36 GMT
Last-Modified: Thu, 09 Feb 2012 10:48:36 GMT
Vary: Cookie
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 10:48:37 GMT
Content-Length: 441
Но во втором он перезаписывает заголовок:
HTTP/1.1 200 OK
Cache-Control: public, max-age=297
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 10:53:36 GMT
Last-Modified: Thu, 09 Feb 2012 10:48:36 GMT
Vary: *
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 10:48:39 GMT
Content-Length: 441
Итак, насколько я знаю, браузеры не будут кэшировать запрос, даже если он является общедоступным, так как Vary:*
означает, что запрос был сгенерирован с параметрами, которые не указаны в URL-адресе или в заголовках HTTP. Есть ли способ исправить это?
С уважением.
UPDATE:
Аналогичным образом, когда я отправляю два идентичных аутентифицированных запроса, первый вызов получает модификатор private
, но не заголовок Vary
:
HTTP/1.1 200 OK
Cache-Control: private, max-age=300
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 12:43:14 GMT
Last-Modified: Thu, 09 Feb 2012 12:38:14 GMT
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 12:38:14 GMT
Content-Length: 443
Но второй получает тот же ответ, что и не аутентифицированный запрос:
HTTP/1.1 200 OK
Cache-Control: public, max-age=298
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 12:44:32 GMT
Last-Modified: Thu, 09 Feb 2012 12:39:32 GMT
Vary: *
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 12:39:33 GMT
Content-Length: 443
Я загрузил проект показывающий проблему, поэтому, возможно, вы хотите попробовать.
Имейте в виду, что существует IHttpModule
, который устанавливает запрос как аутентифицированный или нет, в зависимости от того, имеет ли запрос файл cookie или нет, это не подход "реальной жизни", он просто предназначен для тестирования.
Проект содержит только веб-страницу со ссылкой на себя, ссылку, в которую вы входите, и другую ссылку, в которой вы выходите:
- LogIn: снова перенаправляет файл cookie в перенаправление
HTTP 302
на домашнюю страницу.
- LogOut: снова отправляет файл cookie с истекшим сроком в
HTTP 302
на главную страницу.
ожидаемое/идеальное поведение:
- Индекс доступа пользователей и получить страницу с сервера. Дата показа страницы "A" .
- Индекс доступа пользователей снова, и браузер показывает кешированную версию. На странице отображается дата "A" .
- Очистить кеш браузера.
- Индекс доступа пользователей снова, и браузер отображает версию кеширования сервера. Дата показа страницы "A" .
- Пользователь нажимает логин, а broswer получает новую страницу, отображающую дату "B" .
- Пользователь нажимает кнопку выхода из системы, и браузер получает страницу кэширования сервера. На странице появится дата "A" .
Но это поведение до сих пор:
- Индекс доступа пользователей и получить страницу с сервера. Дата показа страницы "A" .
- Индекс доступа пользователей снова, и браузер показывает кешированную версию. На странице отображается дата "A" .
- Очистить кеш браузера.
- Индекс доступа пользователей снова, и браузер отображает версию кеширования сервера. Дата показа страницы "A" .
- Пользователь нажимает логин, а broswer получает новую страницу, отображающую дату "B" .
- Пользователь нажимает кнопку выхода из системы, а браузер должен получать серверную кешированную страницу, но не. Страница show date "B" снова из кеша браузера. Это связано с отсутствием заголовка
Vary
в аутентифицированном ответе.
Я не знаю, что-то не так в кэшировании, просто не хватает деталей или OutputCache
работает не очень хорошо, но я был бы признателен за любые рекомендации.
Приветствия.
ОБНОВЛЕНИЕ 2:
Я намерен использовать семантику кеша HTTP для:
- Разрешить браузерам и прокси кэшировать "общедоступную" версию страницы.
- Разрешить браузерам кэшировать "аутентифицированную" версию страницы для своего пользователя.
Если я изменил объявление OutputCache, чтобы сделать кеширование только на сервере и предотвратить кэширование ниже по течению и клиенту:
[OutputCache(Duration=60*5, Location=OutputCacheLocation.Server, VaryByCustom="index")]
он ведет себя так, как ожидалось, но кеш-сервер и клиентский кеш предотвращаются, и это не то, что я хочу.
Ответы
Ответ 1
Я не думаю, что атрибут [OutputCache]
- это то, что вы хотите, метод VaryByCustom
в основном говорит, что я хочу кэшировать разные версии на основе этих параметров, на самом деле у него нет опции для Do not Cache, а большая часть кода в атрибуте построена на кешировании на основе сервера.
При этом документация на MSDN для пользовательского кэширования, по-видимому, указывает, что вам нужно вернуть строку, которая будет меняться в зависимости от аутентификации состояние:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if(custom == "user") return "User:" + context.Request.User.Identity.Name;
return base.GetVaryByCustomString(context, custom);
}
И затем используйте пользовательский литерал в VaryByCustom
:
[OutputCache(Duration=60*5, Location=OutputCacheLocation.Any, VaryByCustom="user")]
public ActionResult Index()
{
return View();
}
Таким образом, в основном это приведет к созданию кеша для анонимности (при условии, что анонимная идентификация - пустая строка или что-то еще), и каждый пользователь на сервере, и Vary: *
, отправленный клиенту, я считаю. Очевидно, что это не идеальное то, что вы ищете.
Если вы действительно хотите кэшировать не прошедшую проверку версию с использованием кеширования HTTP, я бы рекомендовал не использовать OutputCacheAttribute
и использовать что-то более обычное.
Вы можете просто написать свой собственный собственный атрибут, что-то вроде того, что у вас есть для вашей реализации GetVaryByCustomString
(это всего лишь некоторый псевдокод, потребуется больше, чем это):
public class HttpCacheUnauthenticatedAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if(!filterContext.HttpContext.Request.IsAuthenticated) {
//TODO: set unauthenticated caching values and vary here
}
}
}
И затем пометьте свой метод действий с ним:
[HttpCacheUnauthenticated]
public ActionResult Index()
{
return View();
}
Ответ 2
Вид борьбы с чем-то подобным. Пробовали ли вы в web.config настройку omitVaryStar=true
https://msdn.microsoft.com/en-us/library/ms228124(v=vs.100).aspx
Ответ 3
Я использую собственный поставщик кеша, и в этом случае для этого есть простое решение.
В BeginRequest, на основе состояния проверки подлинности пользователя, мы устанавливаем контекстную информацию, чтобы не запускать кеш:
HttpContext.Current.Items["NoCache"] = "1";
И затем в нашем методе GetVaryBy мы возвращаем значение null, если эта информация задана:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (HttpContext.Current.Items["NoCache"] != null)
return null;
// remaining code here
}
И затем в методах кеша мы можем проверить то же самое. Например:
public override object Add(string key, object entry, DateTime utcExpiry)
{
if (HttpContext.Current.Items["NoCache"] != null)
return null;
// remaining code here
}