当RewriteCond匹配时,mod_rewrite不发送Vary:accept-language

Luc*_*ent 5 mod-rewrite apache2 vary

我有一个重写规则,重定向到/如果没有接受语言,有人试图访问?lang=en.它工作正常,除了返回的标头.Vary: accept-language响应中缺少.

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

Apache文档指定:

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

条件肯定是匹配和重定向,所以我不明白为什么Apache不添加语言各不相同.可以看出为什么如果代理要缓存那么这将是一个真正的问题?lang = en总是重定向到/无论发送的接受语言头.

Tim*_*one 10

在窥视Apache的请求处理系统的粗糙腹部之后,事实证明文档有点误导......但在我进入解释之前,我可以告诉你,在这个问题上,Apache会受到Apache的支配.

客户问题

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

它使用名称apr_table_get()查询标题,请求的标题表以及您提供的名称:

const char *val = apr_table_get(ctx->r->headers_in, name);
Run Code Online (Sandbox Code Playgroud)

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

if (val) {
   // Set the structure member ctx->vary_this
}
Run Code Online (Sandbox Code Playgroud)

ctx->vary_this用于RewriteCond累积标题名称,应该汇总到最终的Vary标题*.由于如果没有值,则不会发生任何赋值或附加,因此引用(但未发送)的标题将永远不会出现Vary.文档没有明确说明这一点,因此它可能或可能不是您所期望的.

*另外,NV(无变化)标志和忽略失败功能是通过设置ctx->vary_thisNULL,防止其添加到响应头来实现的.

但是,您可能发送了Accept-Language,但它是空白的.在这种情况下,空字符串将通过上述检查,标题名称将被添加到因人而异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);
}
Run Code Online (Sandbox Code Playgroud)

这可以使用日志语句进行验证,并且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);
}
Run Code Online (Sandbox Code Playgroud)

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

幸运的是,这终究是问题的根源.虽然它看起来像它,但事情并非"ass",这会导致原始标题的破坏.在消息来源中甚至有关于它的评论:

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);
        }
        //...
    }
//...
}
Run Code Online (Sandbox Code Playgroud)

请注意r->headers_out已替换,并清除原始表.该表具有预期会在响应中显示的所有信息,因此现在它已丢失.

结论

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

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

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