mod_rewrite不发送不同:重新匹配时接受语言
-
02-10-2019 - |
题
我有一个重写规则,该规则将重定向到 /如果没有接受语言,有人试图访问 ?lang=en
. 。它可以正常工作,除了返回的标头。 Vary: accept-language
响应中缺少。
RewriteCond %{HTTP:Accept-Language} ^$
RewriteCond %{QUERY_STRING} ^lang=en
RewriteRule ^$ http://www.example.com/? [R=301,L]
Apache文档指定:
如果在条件下使用HTTP标头,则将标头添加到响应的变化标头中,以防条件评估为true的请求。如果条件评估为false的请求,则不会添加。
条件肯定是匹配和重定向的,所以我不明白为什么Apache不添加语言会有所不同。如果代理人缓存?
解决方案
在窥视了Apache请求处理系统的肮脏腹部后,事实证明,该文档有些误导……但是在我开始解释之前,我可以告诉您您对此掌握了Apache的怜悯。
客户问题
首先,标题名称不会添加到 各不相同 响应标头如果客户未发送。这是由于如何 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
. 。该文档并未明确说明这一点,因此它可能是您预期的。
*旁边 NV
(没有变化)通过设置实现标志和忽略失败功能 ctx->vary_this
到 NULL
, ,防止其添加到响应标头中。
但是,您可能会发送 接受语言, ,但它是空白的。在这种情况下,空字符串将通过上面的检查,并将标题名称添加到 各不相同 经过 mod_rewrite
从上面描述的内容。牢记这一点,我使用以下请求来诊断发生的事情:
User-Agent: Fiddler Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Host: 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的Fixup阶段运行,并且在此处实际执行重写的机制非常混乱。让我们假设一秒钟没有外部重定向,因为即使没有问题,您就会有问题(并且稍后我会解决重定向的问题)。
执行重写后,在请求处理中,该模块实际映射到文件为时已晚。相反 ap_internal_redirect()
. 。这导致创建一个新请求对象,一个对象不包含 headers_out
原始桌子。
假如说 mod_rewrite
没有导致进一步的重定向,从 新的 请求对象,它永远不会有适当的(原始)标题分配给其。可以通过在每个服务器上下文中(在主配置或在 <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
, ,我只能假设它来自一个不同的模块,该模块的行为能够使标头横渡。我也不确定为什么 秋葵 尝试时没有问题。
作为参考,我正在研究2.2.14,并且 2.2中继源代码, ,我正在修改和运行Apache 2.2.15。相关代码部分中的版本之间似乎没有任何显着差异。
其他提示
您可能想尝试以下操作方法:
<LocationMatch "^.*lang\=">
Header onsuccess merge Vary "Accept-Language"
</LocationMatch>