OutputCache отправляет неверный заголовок Vary, когда вызов попадает в кеш - PullRequest
11 голосов
/ 09 февраля 2012

У меня есть метод действия, который я хочу кэшировать:

[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 в перенаправлении HTTP 302 на домашнюю страницу.
  • LogOut: снова отправляет файл cookie с истекшим сроком действия HTTP 302 на домашнюю страницу.

ожидаемое / идеальное поведение будет:

  1. Пользователь получает доступ к индексу и получает страницу с сервера. На странице показывается дата "А".
  2. Пользователь снова получает доступ к индексу, и браузер показывает кешированную версию. На странице отображается дата «A».
  3. Очистить кеш браузера.
  4. Пользователь снова получает доступ к индексу, и браузер показывает кешированную версию сервера. На странице показывается дата «А».
  5. Пользователь нажимает кнопку входа, и брат получает новую страницу, на которой отображается дата "B".
  6. Пользователь нажимает кнопку выхода, и браузер получает страницу с кэшированным сервером. На странице снова появится дата «А».

Но это поведение до сих пор:

  1. Пользователь получает доступ к индексу и получает страницу с сервера. На странице показывается дата "А".
  2. Пользователь снова получает доступ к индексу, и браузер показывает кэшированную версию. На странице отображается дата «A».
  3. Очистить кеш браузера.
  4. Пользователь снова получает доступ к индексу, и браузер показывает кешированную версию сервера. На странице показывается дата «А».
  5. Пользователь нажимает кнопку входа, и брат получает новую страницу, на которой отображается дата «B».
  6. Пользователь нажимает кнопку выхода из системы, и браузер должен получить кэшированную страницу сервера, но не . На странице снова отображается дата «B» из кеша браузера. Это связано с тем, что в аутентифицированном ответе отсутствует заголовок Vary.

Я не знаю, если я ошибаюсь в кешировании, просто пропускаю некоторые детали или OutputCache работает не очень хорошо, но я был бы признателен за любые рекомендации.

Приветствие.

ОБНОВЛЕНИЕ 2:

Мое намерение состоит в том, чтобы использовать семантику кэша HTTP для:

  1. Разрешить браузерам и прокси-серверам кэшировать "публичную" версию страницы.
  2. Разрешить браузерам кэшировать "аутентифицированную" версию страницы для своего пользователя.

Если я изменю объявление OutputCache для выполнения кэширования только на сервере и предотвращения кэширования в нисходящем и клиентском режимах:

[OutputCache(Duration=60*5, Location=OutputCacheLocation.Server, VaryByCustom="index")]

он ведет себя так, как ожидалось, но нисходящий и клиентский кеш запрещен, и это не то, что я хочу.

Ответы [ 3 ]

4 голосов
/ 17 февраля 2012

Я не думаю, что атрибут [OutputCache] - это то, что вам нужно, метод VaryByCustom в основном говорит, что я хочу кэшировать разные версии на основе этих параметров, у него нет опции для Не кэшировать , и большая часть кода в атрибуте построена вокруг кэширования на сервере.

При этом документация по MSDN для пользовательского кэширования, похоже, указывает на то, что вам нужно возвращать строку, в зависимости от состояния аутентификации:

public override string GetVaryByCustomString(HttpContext context, string custom)
{
    if(custom == "user") return "User:" + context.Request.User.Identity.Name;

    return base.GetVaryByCustomString(context, custom);
}

А затем используйте user литерал в 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();
}
1 голос
/ 13 апреля 2012

Я борюсь с чем-то похожим.Вы пробовали в web.config настройку omitVaryStar=true

https://msdn.microsoft.com/en-us/library/ms228124(v=vs.100).aspx

0 голосов
/ 27 ноября 2012

Я использую пользовательский поставщик кеша, и в этом случае есть простое решение для этого. В BeginRequest, основываясь на статусе аутентификации пользователя, мы устанавливаем контекстную информацию, чтобы не запускать кеш:

HttpContext.Current.Items["NoCache"] = "1";

И затем в нашем методе GetVaryBy мы возвращаем нуль, если эта информация установлена:

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
}
...