【问题标题】:Unable to append 'Vary' header to response无法将“Vary”标头附加到响应
【发布时间】:2011-12-12 13:25:07
【问题描述】:

我正在尝试将Vary: Accept-Encoding 标头添加到对我压缩的文件的响应中,as advised earlier

但是,由于某种原因,这是不可能的 - 无论是从 Visual Studio 测试服务器还是 IIS 服务器。

我有以下代码:

if (url.Contains(".js") || url.Contains(".aspx") || url.Contains(".css"))
{
    app.Response.AppendHeader("Vary", "Accept-Encoding");
    app.Response.AppendHeader("Varye", "Accept-Encoding"); // for testing
    app.Response.AppendHeader("Varye", "Accept-Things");   // for testing
    app.Response.AppendHeader("Vary", "Accept-Stuff");     // for testing
    app.Response.AppendHeader("Var", "Accept-Items");      // for testing

    encodings = encodings.ToLower();

    if (encodings.Contains("gzip") || encodings == "*")
    {
        app.Response.Filter = new GZipStream(baseStream, CompressionMode.Compress);
        app.Response.AppendHeader("Content-Encoding", "gzip");

    }
}

这会产生以下响应标头:

Status=OK - 200
Server=ASP.NET Development Server/10.0.0.0
Date=Fri, 21 Oct 2011 12:24:11 GMT
X-AspNet-Version=4.0.30319
Varye=Accept-Encoding, Accept-Things
Var=Accept-Items
Content-Encoding=gzip
Cache-Control=public
Etag="1CC8F2E9D772300"
Content-Type=text/css
Content-Length=16200
Connection=Close

如您所见,Vary 标头不存在。存在具有类似语法的无意义标头,因此在发送 Vary 标头之前,一定有什么东西在某处取出。

我不知道它是否相关,但这是我在 web.config 中定义我的压缩模块的地方:

<httpModules>
    <add name="CompressionModule" type="Utility.HttpCompressionModule"/>
</httpModules>

(其中 Utility.HttpCompressionModule 是我上面提供的代码摘录所属的类。)

为什么我无法添加Vary 标头?

编辑: Eric C 的解决方案给我留下了这样的代码:

if (url.Contains(".js") || url.Contains(".aspx") || url.Contains(".css"))
{
    app.Response.Cache.SetVaryByCustom("Accept-Encoding");

    encodings = encodings.ToLower();

    if (encodings.Contains("gzip") || encodings == "*")
    {
        app.Response.Filter = new GZipStream(baseStream, CompressionMode.Compress);
        app.Response.AppendHeader("Content-Encoding", "gzip");

    }

但是,标题看起来像这样:

Status=OK - 200
Server=ASP.NET Development Server/10.0.0.0
Date=Mon, 24 Oct 2011 09:26:37 GMT
Content-Encoding=gzip
Cache-Control=public
Etag="1CC7A09FDE77300"
Vary=*
Content-Type=application/x-javascript
Content-Length=44447
Connection=Close

(不知道为什么这是application/x-javascript,因为它在HTML中设置为text/javascript,但这无关紧要。)

如您所见,我现在有一个可变标头,但它设置为Vary=* 而不是Vary=Accept-Encoding,正如您在压缩模块中的代码中所期望的那样。

这里发生了什么?如何正确设置 Vary 标头?

第二次编辑: 我将粘贴整个班级的源代码。没有比我已经发布的更多内容了,但它可能有助于准确了解我在做什么:

public class HttpCompressionModule : IHttpModule
{
    /// <summary>
    /// Initializes a new instance of the <see cref="AjaxHttpCompressionModule"/> class.
    /// </summary>
    public HttpCompressionModule()
    {
    }

    #region IHttpModule Members

    /// <summary>
    /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
    /// </summary>
    void IHttpModule.Dispose()
    {

    }

    /// <summary>
    /// Initializes a module and prepares it to handle requests.
    /// </summary>
    /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application</param>
    void IHttpModule.Init(HttpApplication context)
    {
        context.BeginRequest += (new EventHandler(this.context_BeginRequest));
    }

    #endregion

    /// <summary>
    /// Handles the BeginRequest event of the context control.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    void context_BeginRequest(object sender, EventArgs e)
    {            
        HttpApplication app = (HttpApplication)sender;
        string encodings = app.Request.Headers.Get("Accept-Encoding");
        Stream baseStream = app.Response.Filter;


        if (string.IsNullOrEmpty(encodings))
            return;


        string url = app.Request.RawUrl.ToLower();

        if (url.Contains(".js") || url.Contains(".css") || url.Contains("ajax.ashx"))
        {
            app.Response.Cache.SetVaryByCustom("Accept-Encoding");

            encodings = encodings.ToLower();

            if (encodings.Contains("gzip") || encodings == "*")
            {
                app.Response.Filter = new GZipStream(baseStream, CompressionMode.Compress);
                app.Response.AppendHeader("Content-Encoding", "gzip");

            }
            else if (encodings.Contains("deflate"))
            {
                app.Response.Filter = new DeflateStream(baseStream, CompressionMode.Compress);
                app.Response.AppendHeader("Content-Encoding", "deflate");
            }
        }
    }
}

此外,这是我的 web.config 文件的 System.Web 部分:

<system.web>
    <!--<compilation debug="true"></compilation>-->
    <trace enabled="true" traceMode="SortByTime"/>
    <httpRuntime executionTimeout="180"/>
    <globalization culture="en-GB" uiCulture="en-GB"/>
    <!-- custom errors-->
    <customErrors mode="Off">
    </customErrors>
    <!-- Membership -->
    <membership defaultProvider="SqlProvider" userIsOnlineTimeWindow="15">
        <providers>
            <clear/>
            <add name="SqlProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SQLServerAuth" applicationName="mycompany" minRequiredPasswordLength="4" minRequiredNonalphanumericCharacters="0" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" passwordFormat="Hashed" maxInvalidPasswordAttempts="1024"/>
        </providers>
    </membership>
    <!-- Roles -->
    <roleManager enabled="true" cacheRolesInCookie="true" defaultProvider="SqlProvider">
        <providers>
            <clear/>
            <add connectionStringName="SQLServerAuth" applicationName="mycompany" name="SqlProvider" type="System.Web.Security.SqlRoleProvider"/>
        </providers>
    </roleManager>
    <!-- Authentication -->
    <anonymousIdentification enabled="false"/>
    <authentication mode="Forms">
        <forms name=".AUTH" protection="All" timeout="2" path="/">
        </forms>
    </authentication>
    <httpModules>
        <add name="CompressionModule" type="Utility.HttpCompressionModule"/>
    </httpModules>
</system.web>

没什么好说的了。据我所知,我们在网站上没有其他非标准的东西。有什么想法吗?

【问题讨论】:

  • 我通过Response.AppendHeaderResponse.AddHeader 对其进行了测试,两者都可以正常工作!你能解释一下你的源代码吗?
  • 我再次测试它并且工作!通过AppendAdd 两者!您仅将模块限制为某些扩展:if (url.Contains(".js") || url.Contains(".css") || url.Contains("ajax.ashx")) 您是否使用此扩展的标头进行测试?在if 中添加.aspx 扩展并进行测试。代码没有任何问题,可以正常工作。
  • 这对我不起作用真的很奇怪。但是,通过调试器,我已经确认 Content-Encoding 逻辑正在用于我要压缩的文件,目前我的 .aspx 文件都设置为 No-CachePrivate,我最感兴趣的是看到我的静态内容文件(相当大)是否被正确缓存。

标签: c# asp.net http-headers


【解决方案1】:

从 IIS 7.5 开始,Vary 标头被 gzip IIS 过滤器 (gzip.dll) 覆盖,该过滤器实现了 DyanamicCompressionModule。过滤器始终将标头设置为“Vary: Accept-Encoding”,而不管在 ASP.NET 代码中所做的更改。到目前为止,唯一的解决方法是禁用动态内容的压缩,然后在代码中实现它。方法如下:

  1. 从 Web.config 中删除以下行:

    &lt;add name="CompressionModule" type="Utility.HttpCompressionModule"/&gt;

  2. 然后转到 IIS 管理控制台并确保没有为动态内容启用压缩。

  3. Global.asax.cs中手动实现压缩,方法HttpApplication.Application_BeginRequest

protected void Application_BeginRequest(对象发送者,EventArgs e) { HttpContext 上下文 = HttpContext.Current; 上下文.响应.过滤器 = 新 GZipStream(context.Response.Filter, CompressionMode.Compress); context.Response.AppendHeader("内容编码", "gzip"); context.Response.Cache.VaryByHeaders["Accept-Encoding"] = true; // 我们现在可以设置额外的 Vary 标头... context.Response.Cache.VaryByHeaders.UserAgent = true; context.Response.Cache.VaryByHeaders["X-Requested-With"] = true; }

这是最近reported to Microsoft的问题。

【讨论】:

  • 感谢您的回复。我最近也回到了这个项目,所以这是个好时机。我会尽量记住我在哪里,并在星期一尝试您的解决方案。
  • 替换 AppendHeader("Vary", "Accept-Encoding");通过 Response.Cache.VaryByHeaders["Accept-Encoding"] = true;给了我想要的结果。较早的方式,如果一旦未压缩的页面被缓存,那么请求压缩将只给出未压缩的缓存页面。
【解决方案2】:

试试:

Response.Cache.SetVaryByCustom("Accept-Encoding");
Response.Cache.SetOmitVaryStar(true);

因为在上面似乎有些东西已经让代码相信 * 在这里是合适的,而你确定它不是。

请注意,对于 IE,缓存机制存在缺陷(部分修复,IIRC 与 IE9 但之前没有),它需要这样一个不同的标头来指示完全无法缓存(这是有效的,但不是最佳行为)。出于这个原因,有些人更喜欢根据 User-Agent 进行更改,因为虽然 Accept-Encoding 在技术上是正确的发送值:

  1. IE 会正确缓存它。
  2. 可能的用户代理在其发送的 Accept-Encoding 标头中有所不同,但不太可能在字段中遇到。

注意:您还必须确保发送具有不同版本(g 压缩、压缩和未压缩)的不同电子标签作为电子标签标签实体(发送的字节串,包括任何 Content-Encoding 但不包括任何 Transport-Encoding)而不是资源,并且不改变 E-Tag 可能会导致您设置 Vary: Accept-Encoding 以避免的 SNAFU。

如果你不能让它改变 E-Tag,那么你最好省略它并单独使用 last-modified 进行条件 GET。


顺便说一句,对于为什么设置 application/x-javascript 的疑惑,答案是服务器与“.js”相关联的内容类型。 HTML 中的提示不会影响这一点,因为它不是服务器看到此请求的内容,但它可以 用于覆盖内容类型,如果有一些像 text/plain 这样的虚假内容由服务器发送(标准不明确,用户代理行为可能会有所不同,同时保持在标准的范围内)。由于浏览器通常都认为 application/javascript application/x-javascript 和 text/javascript 表示 javascript,所以这里无关紧要。

【讨论】:

  • 感谢您的帮助。如果我设法让它工作,我将使用User-Agent。我尝试了SetOmitVaryStar(true),这导致响应中没有Vary 标头。如果我不使用SetOmitVaryStar() 方法,则Vary 标头的值为*。但是,如果我不使用SetOmitVaryStar() 方法并且 不使用SetVaryByCustom() 方法,则Vary 标头不存在。这真的很奇怪。
  • @Oliver 如果你使用 SetOmitVaryStar 并使用 SetVaryByCustom 怎么样?
  • 这就是我首先尝试的 - 响应中不存在 Vary 字段。我不知道 asp.net 正在研究什么样的内部逻辑来做到这一点。似乎 Vary 标头默认不存在,尝试使用 SetVaryByCustom() 使标头存在,但将其值设置为 *,但这可以通过同时使用 SetVaryByCustom()SetOmitVaryStar() 来抑制,导致标题不再出现。我可能暂时关闭 HttpCompression 并将其作为项目留到另一天...
  • @Oliver 剩下的两个想法。首先,切换 SetVaryByCustom 和 SetOmitVaryStar 的顺序。其次是不使用缓存对象模型,而是自己处理 - 当然我发现在这些情况下还有其他优势(对于一件事来说,强制电子标签针对不同的压缩类型进行不同的设置更容易) ,但如果你一直使用它,这并不是一个简单的改变:(
  • 嘿,我也突然想到,在查看其他内容时,您可以编写一个自定义 HttpEncoder 来重写 Vary。不过那太邪恶了。
【解决方案3】:

试试这个:

app.Response.Cache.SetVaryByCustom("Accept-Encoding");

【讨论】:

    【解决方案4】:

    需要手动添加header值才能看到Vary: Accept-Encoding

    response.Cache.SetOmitVaryStar(true);
    response.AddHeader("Vary", "Accept-Encoding");
    

    【讨论】:

      【解决方案5】:

      正如其他人所提到的,这是一个known issue,IIS 压缩模块专门覆盖了Vary 标头,并已由hotfix 解决。

      如果您无法确保已安装修补程序,则另一种解决方法是在 Web.config 中使用 IIS URL Rewrite 附加标头:

      <configuration>
        <system.webServer>
          <rewrite>
            <outboundRules>
              <rule name="Append 'Vary: X-Requested-With' header" patternSyntax="ECMAScript">
                <match serverVariable="RESPONSE_VARY" pattern=".+" />
                <action type="Rewrite" value="{R:0}, X-Requested-With" replace="true" />
              </rule>
              <rule name="Set 'Vary: X-Requested-With' header if no others" patternSyntax="ECMAScript">
                <match serverVariable="RESPONSE_VARY" pattern=".+" negate="true" />
                <action type="Rewrite" value="X-Requested-With" />
              </rule>
            </outboundRules>
          </rewrite>
        </system.webServer>
      </configuration>
      

      【讨论】:

        【解决方案6】:

        我也试过这些

        Response.Cache.SetOmitVaryStar(true);
        Response.AddHeader("Vary", "Accept-Encoding");
        

        ,但不起作用.. 但是,这确实起作用了:

        Response.Cache.VaryByHeaders["Accept-Encoding"] = true;
        

        【讨论】:

        • 终于!这是唯一有效的建议。与 M$ 产品的典型斗争......没有什么能像预期的那样工作。在 PHP 中,这些命令实际上做了它们应该做的事情。
        猜你喜欢
        • 1970-01-01
        • 2018-04-23
        • 1970-01-01
        • 2018-02-26
        • 2018-03-24
        • 2020-08-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多