mod_rewrite не отправляет варьироваться: accept-language при перезаписи матчи

StackOverflow https://stackoverflow.com/questions/3698363

  •  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 используется в условии, этот заголовок добавляется в заголовок VARY из ответа в случае, если условие оценивается к true для запроса. Он не добавляется, если условие оценивает false для запроса.

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

Это было полезно?

Решение

После того, как заглянул в семейное лечение системы обработки запросов 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 Принять: Text / HTML, приложение / XHTML + XML, приложение / XML; q = 0,9, * / *; q = 0,8 Accept-language: Accept-encoding: gzip, Deflate Accemard-charset: ISO-8859 -1, UTF-8; q = 0,7, *; q = 0,7 aive-alive: 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 Работает на фазе Fixup 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().

К счастью, это, наконец, корень проблемы. Хотя это может показаться, что все не «Assackardwards», и это вызывает разрушение оригинальных заголовков. Есть даже комментарий об этом в источнике:

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