我有一个重写规则,该规则将重定向到 /如果没有接受语言,有人试图访问 ?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_thisNULL, ,防止其添加到响应标头中。

但是,您可能会发送 接受语言, ,但它是空白的。在这种情况下,空字符串将通过上面的检查,并将标题名称添加到 各不相同 经过 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->varyctx->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>
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top