在窥探 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->vary
是ctx->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。相关代码部分中的版本之间似乎没有任何显着差异。