6

我有一个重写规则,/如果没有Accept-Language标题并且有人尝试访问,它会重定向到?lang=en。它工作正常,除了返回的标题。Vary: Accept-Language响应中缺少。

RewriteCond %{HTTP:Accept-Language} ^$  
RewriteCond %{QUERY_STRING}         ^lang=en  
RewriteRule ^$                      http://www.example.com/?     [R=301,L]

Apache 文档指定:

如果在条件中使用 HTTP 标头,则此标头将添加到响应的 Vary 标头中,以防请求的条件评估为真。如果请求的条件评估为 false,则不会添加它。

条件肯定是匹配和重定向的,所以我不明白为什么 Apache 不添加语言变化。?lang=en如果代理要缓存它并始终重定向到/而不管发送的 Accept-Language 标头如何,人们就会明白为什么这将是一个真正的问题。

4

3 回答 3

11

在窥探 Apache 请求处理系统的肮脏底层之后,事实证明该文档有些误导......但在我开始解释之前,据我所知,你在这个问题上受 Apache 的摆布。

客户问题

首先,如果不是客户端发送的,则头名称不会添加到Vary响应头中。这是由于如何在内部mod_rewrite构造该标头的值。

apr_table_get()它使用、请求的标头表和您提供的名称按名称查找标头:

const char *val = apr_table_get(ctx->r->headers_in, name);

如果name不是表中的键,则此函数将返回NULL. 这是一个问题,因为在此之后立即检查val

if (val) {
   // Set the structure member ctx->vary_this
}

ctx->vary_this用于RewriteCond逐个累积应组装到最终Vary标头*中的标头名称。由于如果没有值就不会发生赋值或追加,因此引用(但未发送)的标头将永远不会出现在Vary. 该文档没有明确说明这一点,因此它可能是也可能不是您所期望的。

*顺便说一句,NV(无变化)标志和失败时忽略功能是通过设置ctx->vary_this来实现的,以NULL防止将其添加到响应标头中。

但是,您可能发送了Accept-Language,但它是空白的。在这种情况下,空字符串将通过上述检查,并且标题名称将根据上述内容添加到Vary by mod_rewrite。牢记这一点,我使用以下请求来诊断发生了什么:

用户代理:提琴手
接受:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
接受语言:
接受编码:gzip,deflate
接受字符集:ISO-8859-1,utf-8;q=0.7,*;q=0.7
保活:115
连接:保持活动
主机:129.168.0.123

这也不起作用,但为什么呢?mod_rewrite当规则和条件匹配时肯定会设置标题(ctx->varyctx->vary_this所有检查条件的聚合):

if (ctx->vary) {
    apr_table_merge(r->headers_out, "Vary", ctx->vary);
}

这可以通过日志语句进行验证,并且r->headers_out 生成响应标头时使用的变量。既然肯定出了问题,那么规则执行后肯定有问题。

.htaccess 问题

目前,您似乎正在定义您的规则.htaccess,或<Directory>部分。这意味着它mod_rewrite在 Apache 的修复阶段运行,它在这里实际执行重写的机制非常混乱。让我们暂时假设没有外部重定向,因为即使没有它,您也会遇到问题(稍后我将解决重定向问题)。

执行重写后,模块在请求处理中实际映射到文件为时已晚。相反,它会将自己指定为请求的“内容”处理程序,当请求到达该点时,它会调用ap_internal_redirect(). 这会导致创建一个新的请求对象,该对象不包含headers_out原始表。

假设不会mod_rewrite导致进一步的重定向,则从的请求对象生成响应,该对象永远不会分配适当的(原始)标头。可以通过在每个服务器上下文中工作(在主配置中或在 a 中<VirtualHost>)来解决这个问题,但是......

重定向问题

不幸的是,事实证明这在很大程度上是无关紧要的,因为即使我们确实mod_rewrite在服务器上下文中使用,在重定向事件中响应所采用的路径仍然会导致模块设置的标头被丢弃。

当 Apache 接收到请求时,通过一系列函数调用,它会到达ap_process_request(). 这反过来调用ap_process_request_internal(),其中大部分重要的请求解析步骤发生(包括调用mod_rewrite)。它返回一个整数状态码,在重定向的情况下恰好设置为 301。

大多数请求返回OK(其值为 0),立即导致ap_finalize_request_protocol(). 但是,这里不是这种情况

if (access_status == OK) {
    ap_finalize_request_protocol(r);
}
else {
    r->status = HTTP_OK;
    ap_die(access_status, r);
}

ap_die()做一些额外的操作(比如将响应代码返回到 301),在这种特殊情况下,以调用ap_send_error_response().

幸运的是,这最终是问题的根源。尽管看起来像这样,但事情并不是“倒退”,这会导致原始标头的破坏。源代码中甚至对此有评论:

if (!r->assbackwards) {
    apr_table_t *tmp = r->headers_out;

    /* For all HTTP/1.x responses for which we generate the message,
     * we need to avoid inheriting the "normal status" header fields
     * that may have been set by the request handler before the
     * error or redirect, except for Location on external redirects.
     */
    r->headers_out = r->err_headers_out;
    r->err_headers_out = tmp;
    apr_table_clear(r->err_headers_out);

    if (ap_is_HTTP_REDIRECT(status) || (status == HTTP_CREATED)) {
        if ((location != NULL) && *location) {
            apr_table_setn(r->headers_out, "Location", location);
        }
        //...
    }
//...
}

注意r->headers_out被替换了,原来的表被清空了。该表包含预期会在响应中显示的所有信息,所以现在它丢失了。

结论

如果您不重定向并且在每个服务器上下文中定义规则,那么一切似乎都可以正常工作。然而,这不是你想要的。我可以看到一个潜在的解决方法,但我不确定它是否可以接受,更不用说重新编译服务器的需要了。

至于Vary: Accept-Encoding,我只能假设它来自一个不同的模块,该模块的行为方式允许标头潜入。我也不确定为什么Gumbo在尝试时没有问题。

作为参考,我正在查看 2.2.14 和2.2 主干源代码,并且正在修改和运行 Apache 2.2.15。相关代码部分中的版本之间似乎没有任何显着差异。

于 2010-09-14T16:55:13.377 回答
1

您可能想尝试以下方法作为解决方法:

<LocationMatch "^.*lang\=">
    Header onsuccess merge Vary "Accept-Language"
</LocationMatch>
于 2012-02-06T10:37:43.760 回答
0

要仅在重定向响应上专门设置Vary: Accept-LanguageHTTP 响应标头(这是此处所期望的),您需要设置一个环境变量(例如)作为重定向规则的一部分,并使用它来有条件地使用指令设置标头。VARY_ACCEPT_LANGUAGEHeader

您还需要在指令中使用always 条件(而不是默认值onsuccess),Header以便在 3xx 响应(即非 200 响应)上设置它。

例如:

# Redirect requests that have an empty Accept-Language header and "lang=en" is present
RewriteCond %{HTTP:Accept-Language} ^$  
RewriteCond %{QUERY_STRING} ^lang=en  
RewriteRule ^$ /? [E=VARY_ACCEPT_LANGUAGE:1,R=301,L]

# Set/Merge "Vary" header on Accept-Language redirect
Header always merge Vary "Accept-Language" env=VARY_ACCEPT_LANGUAGE

但是,Vary不应该只在重定向响应上设置标头(当Accept-Language标头为空时),它需要在对请求的所有响应中设置/?lang=en,无论Accept-LanguageHTTP 请求标头实际设置为什么。因此,依靠 Apache 仅使用重定向来设置此标头是不够的(即使它确实按照最初的预期在响应上设置了标头)。

为了在Vary对请求的所有响应/?lang=en(包括重定向)上设置适当的标头,请执行以下操作:

# Set env var if "/?lang=en" is requested
RewriteCond %{QUERY_STRING} ^lang=en  
RewriteRule ^$ - [E=VARY_ACCEPT_LANGUAGE:1]

# Redirect requests that have an empty Accept-Language header and "lang=en" is present
RewriteCond %{HTTP:Accept-Language} ^$  
RewriteCond %{QUERY_STRING} ^lang=en  
RewriteRule ^$ /? [R=301,L]

# Set/Merge "Vary" header on all responses from "/?lang=en"
Header always merge Vary "Accept-Language" env=VARY_ACCEPT_LANGUAGE

但是请注意,如果您有额外的内部重写指令导致重写引擎重新启动,则 env varVARY_ACCEPT_LANGUAGE被重命名为REDIRECT_VARY_ACCEPT_LANGUAGE,上述Header指令将不会成功。你可能需要一个额外的指令来处理这个。例如:

Header always merge Vary "Accept-Language" env=REDIRECT_VARY_ACCEPT_LANGUAGE
于 2021-02-21T01:00:27.983 回答