mod_rewrite не отправляет Vary: accept-language, когда RewriteCond совпадает - PullRequest
5 голосов
/ 13 сентября 2010

У меня есть правило перезаписи, которое перенаправляет на /, если нет языка подтверждения, и кто-то пытается посетить ?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ответ, если условие оценивается как истинное для запроса.Он не добавляется, если условие оценивается как ложное для запроса.

Условия определенно совпадают и перенаправляют, поэтому я не понимаю, почему Apache не добавляет изменение языка.Можно понять, почему это было бы настоящей проблемой, если бы прокси-сервер кешировал, который? Lang = en всегда перенаправляет на / независимо от отправляемого заголовка accept-language.

Ответы [ 2 ]

10 голосов
/ 14 сентября 2010

После того, как заглянул в убогий живот системы обработки запросов Apache, оказалось, что документация несколько вводит в заблуждение ... Но прежде чем я углублюсь в объяснение, из того, что я могу сказать, вы зависите от Apache в этомone.

Проблема клиента

Во-первых, имя заголовка не будет добавлено в заголовок ответа 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.В документации явно не говорится об этом, поэтому это может быть, а может и не быть тем, что вы ожидали.

* Кроме того, флаг NV (без изменений) и функция игнорирования при сбое реализованы путем установки ctx->vary_this в NULL, предотвращая его добавление в заголовок ответа.

Однако, возможно, вы отправили Accept-Language , но оно было пустым.В этом случае пустая строка пройдет вышеупомянутую проверку, и имя заголовка будет добавлено к Vary на 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, и механизм, который он использует, чтобы фактически выполнить переписывания здесь, очень запутан.Давайте на секунду предположим, что внешнего перенаправления не существует, поскольку у вас возникла проблема даже без него (и я перейду к проблеме с перенаправлением позже).

После того, как вы выполните перезапись, уже слишком позднообработка запроса для модуля, чтобы фактически отобразить в файл.Вместо этого он назначает себя обработчиком «содержимого» запроса, и когда запрос достигает этой точки, он выполняет вызов 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, я могу только предположить, что он исходит отдругой модуль, который ведет себя так, что позволяет заголовку пробираться.Я также не уверен, почему у Gumbo не было проблем при его попытке.

Для справки я искал исходный код магистрали 2.2.14 и 2.2 , и я модифицировал и запустил Apache 2.2.15.Похоже, что нет никаких существенных различий между версиями в соответствующих разделах кода.

1 голос
/ 06 февраля 2012

В качестве обходного пути вы можете попробовать что-то вроде следующего:

<LocationMatch "^.*lang\=">
    Header onsuccess merge Vary "Accept-Language"
</LocationMatch>
...