Я работаю на веб-сайте, работающем на общем сервере 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, выполняются в порядке module , определенном в основной конфигурации Apache (или, как я понимаю, в любом случае ...).В этом случае (и, что особенно важно для успеха этого решения), mod_headers был настроен на выполнение после mod_rewrite, поэтому директива RequestHeader выполняется после правил перезаписи.Это означает, что заголовок SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj
добавляется к HTTP-запросу, если RewriteRule с [E = END: 1] в его списке флагов совпадает.При следующем проходе (после повторной отправки запроса на сервер) первый RewriteRule обнаруживает этот заголовок и прерывает дальнейшую перезапись.
Некоторые замечания, касающиеся этого решения:
Не будет работать, если Apache настроен для запуска mod_headers до mod_rewrite.(Я не уверен, что это вообще возможно, или если да, то как необычно это будет).
Если внешний пользователь включает заголовок SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj
в свой HTTP-запроссервер отключит все правила перезаписи URL, и этот пользователь увидит структуру каталогов файловой системы «как есть».Вот причина случайной строки символов ascii в конце имени заголовка - это затрудняет угадывание заголовка.Является ли это функцией или уязвимостью безопасности, зависит от вашей точки зрения!
Идея здесь заключалась в том, чтобы обойти использование флага [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, and /Bob to /Alice
RewriteRule ^Alice(/.*)?$ Bob$1 [L]
RewriteRule ^Bob(/.*)?$ Alice$1 [L]
Или дажеболее того, это (хотя переменные REDIRECT_ * плохо документированы в документации Apache v2.2 - кажется, они упоминаются только здесь ) - поэтому я не могу гарантировать, что это будет работать на всех версиях Apache):
RewriteCond %{ENV:REDIRECT_STATUS} !^$
RewriteRule .* - [L].
# Map /Alice to /Bob, and /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]
Идея здесь заключалась в том, чтобы обойти использование флага [END] в версиях Apache, у которых его еще нет,Но на самом деле вы можете использовать этот общий подход для хранения не только одного флага - вы можете хранить произвольные строки или числа, которые будут сохраняться при перенаправлении на внутренний сервер, и разрабатывать правила перезаписи, чтобы они зависели от них на основе любого из условий тестирования.RuleCond предоставляет.(Я не могу, из головы в голову, подумать о причине , почему вы хотели бы сделать это ... но, эй, чем больше у вас гибкости и контроля, тем лучше, верно?)
Полагаю, любой, кто читал это далеко, понял, что я не задаю здесь вопрос.Это больше связано с тем, что я нашел свое собственное решение проблемы, с которой столкнулся, и хотел опубликовать ее здесь для справки на случай, если кто-то еще столкнется с той же проблемой.Это большая часть того, для чего предназначен этот веб-сайт, верно?
...
Но так как этот должен быть вопросом и ответомфорум, я спрошу:
- Может кто-нибудь увидеть какие-либо потенциальные проблемы с этим решением (кроме тех, которые я уже упоминал)?
- Или у кого-нибудь есть лучший способдостижения того же самого?