Как заблокировать несколько проходов mod_rewrite (или бесконечных циклов) в контексте .htaccess

Я работаю над веб-сайтом, работающим на общем сервере Apache v2.2, поэтому вся конфигурация осуществляется через файлы .htaccess и Я хотел использовать mod_rewrite для сопоставления URL-адресов с файловой системой менее чем полностью простым способом. Для примера скажем, что я хотел сделать следующее:

  • Сопоставить URL-адрес www.mysite.com/Alice с папкой файловой системы / public_html / Bob
  • Сопоставить URL-адрес www.mysite.com/Bob в папку файловой системы / public_html / Alice

Теперь, после нескольких часов тщательной разработки набора правил (настоящего, а не Алисы / Боба!), Я положил все мои тщательно созданные правила перезаписи в файле .htaccess в / public_html и протестированы ... только для того, чтобы получить ошибку сервера 500!

Меня поймала хорошо задокументированная "ошибка!" в Apache: когда правила mod_rewrite используются внутри файла .htaccess, перезаписанный URL повторно отправляется для следующего цикла обработки (как если бы это был внешний запрос). Это происходит так, что могут применяться любые правила в целевой папке перезаписанного запроса, но это может привести к некоторому очень противоречивому поведению веб-сервера!

В приведенном выше примере это означает, что запрос для www.mysite.com/Alice/foo.html переписывается на / Bob / foo.html , а затем повторно отправил (внутренне) на сервер как запрос для www.mysite.com/Bob/foo.html . Затем он перезаписывается обратно в /Alice/foo.html и повторно отправляется, что приводит к его повторной перезаписи в /Bob/foo.html и т. Д. ; следует бесконечный цикл ... прерванный только ошибкой тайм-аута сервера.


Вопрос в том, как гарантировать, что набор правил .htaccess mod_rewrite применяется только ОДИН РАЗ?


Флаг [L] в RewriteRule останавливает все дальнейшие перезаписи во время одного прохода по набору правил , но не останавливает повторное применение всего набора правил после повторной отправки перезаписанного URL на сервер. Согласно документации, Apache v2.3.9 + (в настоящее время находится в бета-версии) содержит флаг [END], который обеспечивает именно эту функциональность. К сожалению, веб-хостинг все еще использует Apache 2.2, и они отклонили мою вежливую просьбу о переходе на бета-версию!

Что необходимо, так это обходной путь, обеспечивающий аналогичные функции флага [END]. Моя первая мысль заключалась в том, что я мог бы использовать переменную среды: установить флаг во время первого прохода перезаписи, который сообщал бы последующим проходам не выполнять дальнейшую перезапись. Если бы я назвал свою флаговую переменную «END», код мог бы выглядеть следующим образом:

#  Prevent further rewriting if 'END' is flagged
RewriteCond %{ENV:END} =1
RewriteRule .* - [L]

#  Map /Alice to /Bob, and /Bob to /Alice, and flag 'END' when done
RewriteRule ^Alice(/.*)?$ Bob$1 [L,E=END:1]
RewriteRule ^Bob(/.*)?$ Alice$1 [L,E=END:1]

К сожалению, этот код не работает: после небольшого экспериментирования я обнаружил, что переменные среды не выживают в процессе повторной отправки переписал URL на сервер.Последняя строка на этой странице документации Apache предполагает, что переменные среды должны выдерживать внутренние перенаправления, но я обнаружил, что это не так.

[ РЕДАКТИРОВАТЬ: На некоторых серверах работает . Если да, то это лучшее решение, чем то, что следует ниже. Вам придется попробовать это на собственном сервере, чтобы убедиться в этом.]

Тем не менее, общую идею можно спасти. После многих часов возни и советов коллеги я понял, что заголовки HTTP-запросов сохраняются при внутренних перенаправлениях, поэтому, если бы я мог сохранить свой флаг в одном из них, это могло бы сработать!


Вот мое решение:


# This header flags that there's no more rewriting to be done.
# It's a kludge until use of the END flag becomes possible in Apache v2.3.9+
# ######## REMOVE this directive for Apache 2.3.9+, and change all [...,L,E=END:1]
# ######## to just [...,END] in all the rules below!

RequestHeader set SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj 1 env=END


# If our special end-of-rewriting header is set this rule blocks all further rewrites.
# ######## REMOVE this directive for Apache 2.3.9+, and change all [...,L,E=END:1]
# ######## to just [...,END] in all the rules below!

RewriteCond %{HTTP:SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj} =1 [NV]
RewriteRule .* - [L]


#  Map /Alice to /Bob, and /Bob to /Alice, and flag 'END' when done

RewriteRule ^Alice(/.*)?$ Bob$1 [L,E=END:1]
RewriteRule ^Bob(/.*)?$ Alice$1 [L,E=END:1]

... и оно сработало! Вот почему: внутри файла .htaccess директивы, связанные с различными модулями apache, выполняются в порядке модулей , определенном в основной конфигурации Apache (или, как я понимаю, во всяком случае ...). В этом случае (и критически важно для успеха этого решения) mod_headers был установлен для выполнения после mod_rewrite, поэтому директива RequestHeader выполняется после правил перезаписи. Это означает, что заголовок SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj добавляется к HTTP-запросу, если найдено соответствие RewriteRule с [E = END: 1] в его списке флагов. На следующем проходе (после повторной отправки перезаписанного запроса на сервер) первый RewriteRule обнаруживает этот заголовок и прерывает любую дальнейшую перезапись.

Некоторые моменты, которые следует отметить по поводу этого решения:

  1. Оно не будет работать, если Apache настроен на запуск mod_headers до mod_rewrite. (Я не уверен, возможно ли это вообще, и если да, то насколько это было бы необычно).

  2. Если внешний пользователь включает заголовок SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj в свой HTTP-запрос к серверу, он отключит все правила перезаписи URL ,и этот пользователь увидит структуру каталогов файловой системы «как есть». Это причина случайной строки символов ascii в конце имени заголовка - из-за этого заголовок трудно угадать. Является ли это функцией или уязвимостью безопасности, зависит от вашей точки зрения!

  3. Идея заключалась в обходном пути, имитирующем использование флага [END] в версиях Apache, в которых его еще нет. Если все, что вам нужно, - это убедиться, что ваш набор правил запускается только один раз, независимо от того, какие правила срабатывают, то вы, вероятно, могли бы отказаться от использования переменной среды END и просто сделать следующее:

     RewriteCond% {HTTP: SPECIAL-HEADER -STOP-FURTHER-REWRITES-kjhsdf87653vasj} = 1 [NV] 
    RewriteRule. * - [L] 
     
    RequestHeader set SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj 1 
     
     # Map / Alice to / Bob и / Bob to / Alice 
    RewriteRule ^ Alice (/.*)?$ Bob $ 1 [L] 
    RewriteRule ^ Боб (/.*)?$ Алиса $ 1 [L] 
     

    Или даже лучше, это (хотя переменные REDIRECT_ * плохо задокументированы в документации Apache v2.2 - кажется, они только упоминаются здесь ) - поэтому я не могу гарантировать, что он будет работать на всех версиях Apache):

     RewriteCond% {ENV: REDIRECT_STATUS}! ^ $ 
    RewriteRule. * - [L ]. 
     
     # Map / Alice to / Bob и / Bob to / Alice 
    RewriteRule ^ Alice (/.*)?$ Bob $ 1 [L] 
    RewriteRule ^ Bob (/.*)?$ Alice $ 1 [L] 
     

    Однако, если вы запустите Apache v2.3.9 +, я ожидаю, что использование флага [END] будет более эффективным, чем вышеуказанное решение, потому что (предположительно) оно полностью позволяет избежать повторной отправки перезаписанного URL-адреса на сервер для другого прохода перезаписи.

    Обратите внимание, что вы также можете заблокировать перезапись подзапросов, и в этом случае вы можете добавить RewriteCond к правилу перезаписи «больше не делать», например:

     RewriteCond % {ENV: REDIRECT_STATUS}! ^ $ [OR] 
    RewriteCond% {IS_SUBREQ} = true 
    RewriteRule. * - [L] 
     
  4. Идея заключалась в обходном пути чтобы заменить использование флага [END] в версиях Apache, в которых его еще нет. Но на самом деле вы можете использовать этот общий подход для хранения большего, чем просто одного флага - вы можете хранить произвольные строки или числа, которые будут сохраняться при перенаправлении внутреннего сервера, и разработать свои правила перезаписи в зависимости от них на основе любого из условий тестирования. RuleCond предоставляет. (Я не могу придумать причину , почему вы захотите это сделать ... но эй, чем больше у вас гибкости и контроля, тем лучше, верно? )


Думаю, любой, кто дочитал до этого места, понял, что я не задаю здесь вопросов. Это больше связано с тем, что я нашел собственное решение возникшей у меня проблемы и хочу опубликовать его здесь для справки на случай, если кто-то еще столкнется с той же проблемой. Это большая часть того, для чего предназначен этот веб-сайт, верно?

...

Но поскольку этот является форумом вопросов и ответов, я спрошу:

  • Может ли кто-нибудь увидеть какие-либо потенциальные проблемы с этим решением (кроме тех, о которых я уже упоминал)?
  • Или есть ли у кого-нибудь лучший способ добиться того же?

16
задан Doin 23 March 2014 в 11:39
поделиться