当调用命中缓存时,OutputCache 发送错误的 Vary 标头

发布于 2025-01-03 19:03:47 字数 4239 浏览 4 评论 0原文

我有一个要缓存的操作方法:

[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;
    }
}

想法是 为未经身份验证的用户保留页面的缓存版本,但避免为经过身份验证的用户进行缓存

我以为它总是会返回 Vary:Cookie HTTP 标头,但事实并非如此。 使用 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 标头中的参数生成的。有办法解决这个问题吗?

问候。

更新:

以类似的方式,当我发送两个相同的经过身份验证的请求时,第一个调用会获取 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 :再次通过 HTTP 302 重定向发送一个 cookie 到主页。
  • LogOut:再次通过 HTTP 302 重定向将过期的 cookie 发送到主页。

预期/理想行为是:

  1. 用户访问索引,并从服务器获取页面。页面显示日期“A”。
  2. 用户再次访问Index,浏览器显示缓存版本。页面显示日期“A”。
  3. 清理浏览器缓存。
  4. 用户再次访问Index,浏览器显示服务器缓存的版本。页面显示日期“A”。
  5. 用户单击登录,浏览器会出现一个新页面,显示日期“B”。
  6. 用户点击注销,浏览器获取服务器缓存的页面。页面再次显示日期“A”。

但这是迄今为止的行为:

  1. 用户访问索引,并从服务器获取页面。页面显示日期“A”。
  2. 用户再次访问Index,浏览器显示缓存的版本。页面显示日期“A”。
  3. 清理浏览器缓存。
  4. 用户再次访问Index,浏览器显示服务器缓存的版本。页面显示日期“A”。
  5. 用户单击登录,浏览器会出现一个新页面,显示日期“B”。
  6. 用户单击注销,浏览器应该获取服务器缓存的页面,但事实并非如此。页面再次显示浏览器缓存中的日期“B”。这是因为经过身份验证的响应中缺少 Vary 标头。

我不知道我是否在缓存方面遇到了问题,只是缺少一些细节,或者 OutputCache 不能很好地工作,但我将不胜感激任何指导。

干杯。

更新 2:

我的目的是使用 HTTP 缓存语义来:

  1. 允许浏览器和代理缓存页面的“公共”版本。
  2. 允许浏览器为其用户缓存页面的“经过身份验证”的版本。

如果我更改 OutputCache 声明以仅在服务器上进行缓存并阻止下游和客户端缓存:

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

它的行为符合预期,但下游和客户端缓存被阻止,这不是我想要的。

I have an action method that I want to cache:

[OutputCache(Duration=60*5, Location=OutputCacheLocation.Any, VaryByCustom="index")]
public ActionResult Index()
{
    return View();
}

With this approach:

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

The idea was to keep a cached version of the page for non-authenticated users, but avoid caching for authenticated ones.

I thought it will always return a Vary:Cookie HTTP header, but it is not.
Doing a test with Fiddler and issuing twice the same request, in the first HTTP call it goes good:

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

But in the second one, it overwrites the header:

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

So, as far as I know, browsers won't cache the request even if it is public, since Vary:* means that the request has been generated with parameters that are not in the URL nor in the HTTP headers. Is there a way to fix this?

Regards.

UPDATE:

In a similar way, when I send two identical authenticated requests, the first call gets the private modifier, but not the Vary header:

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

But the second one gets the same response that a non-authenticated request:

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

I have uploaded a test project showing the issue so may be you want to give it a try.

Please be aware that there is an IHttpModule that sets a request as authenticated or not depending on if the request has a cookie or not, this is not a "real life" approach, it is just for testing purposes.

The project contains only a web page with a link to itself, a link that logs you in, and another link that logs you out:

  • LogIn : Sends a cookie in a HTTP 302 redirection to the home page again.
  • LogOut: Sends a expired cookie in a HTTP 302 recirection to the home page again.

The expected/ideal behaviour would be:

  1. User access Index, and get the page from the server. The page show date "A".
  2. User access Index again, and the browser shows the cached version.The page show date "A".
  3. Clean browser cache.
  4. User access Index again, and the browser shows the server cached version. The page show date "A".
  5. User clicks login, and the broswer gets a new page, that show date "B".
  6. User clicks logout, and the browser gets the server cached page. The page show date "A" again.

But this is the behaviour so far:

  1. User access Index, and get the page from the server. The page show date "A".
  2. User access Index again, and the browser shows the cached version.The page show date "A".
  3. Clean browser cache.
  4. User access Index again, and the browser shows the server cached version. The page show date "A".
  5. User clicks login, and the broswer gets a new page, that show date "B".
  6. User clicks logout, and the browser should get the server cached page, but it does not. The page show date "B" again from the browser cache. This is because the lack of the Vary header in the authenticated response.

I don't know if I get something wrong about caching, just missing some detail or the OutputCache does not work very well, but I would appreciate any guidance.

Cheers.

UPDATE 2:

My intention is to use the HTTP cache semantics to:

  1. Allow browsers and proxys to cache the "public" version of the page.
  2. Allow browsers to cache the "authenticated" version of the page for its user.

If I change the OutputCache declaration to do the caching only on the server and prevent the downstream and client caching:

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

it behaves as expected, but the downstream and client cache is prevented, and that is not what I want.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

好倦 2025-01-10 19:03:47

我不认为 [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);
}

然后在 VaryByCustom 中使用 user 文字:

[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();
}

I don't think the [OutputCache] attribute is what you want, the VaryByCustom method is basically saying that I want to cache different versions based on these parameters, it doesn't really have an option for Do Not Cache and the majority of the code in the attribute is built around server based caching.

That being said the documentation on MSDN for custom caching seems to indicate you need to return a string to vary on based on the authentication state:

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

    return base.GetVaryByCustomString(context, custom);
}

And then use the user literal in the VaryByCustom:

[OutputCache(Duration=60*5, Location=OutputCacheLocation.Any, VaryByCustom="user")]
public ActionResult Index()
{
    return View();
}

So basically this would result in a cache being built for anonymous (assuming the anonymous identity is empty string or something) and every user on the server, and a Vary: * sent to the client I believe. Obviously not ideal what you are looking for.

If you really just want to cache the unauthenticated version using HTTP caching I would recommend not using the OutputCacheAttribute and using something more custom.

You could easily just write in your own custom attribute something like what you have for your GetVaryByCustomString implementation (this is just some pseudo code, would need more than this):

public class HttpCacheUnauthenticatedAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if(!filterContext.HttpContext.Request.IsAuthenticated) {
            //TODO: set unauthenticated caching values and vary here
        }
    }
}

And then tag your action method with it:

[HttpCacheUnauthenticated]
public ActionResult Index()
{
    return View();
}
行至春深 2025-01-10 19:03:47

有点像我自己在与类似的东西摔跤。您是否尝试过在 web.config 中设置 omitVaryStar=true

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

Sort of wrestling with something similar myself. Have you tried in the web.config to the setting omitVaryStar=true

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

回心转意 2025-01-10 19:03:47

我正在使用自定义缓存提供程序,在这种情况下,有一个简单的解决方案。
在 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
}

I am using a custom cache provider and in this case there is a simple solution for this.
On the BeginRequest, based on the user authentication status, we set a context information to not run cache:

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

And then on our GetVaryBy method we return null if this information is set:

public override string GetVaryByCustomString(HttpContext context, string custom)
{
    if (HttpContext.Current.Items["NoCache"] != null)
        return null;

    // remaining code here
}

And then on the cache methods, we can test the same. For instance:

public override object Add(string key, object entry, DateTime utcExpiry)
{
    if (HttpContext.Current.Items["NoCache"] != null)
        return null;

    // remaining code here
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文