Я работаю над веб-сайтом, работающим на общем сервере Apache v2.2, поэтому вся конфигурация осуществляется через файлы .htaccess и Я хотел использовать mod_rewrite для сопоставления URL-адресов с файловой системой менее чем полностью простым способом. Для примера скажем, что я хотел сделать следующее:
www.mysite.com/Alice
с папкой файловой системы / public_html / Bob
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
и т. Д. ; следует бесконечный цикл ... прерванный только ошибкой тайм-аута сервера.
Флаг [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 обнаруживает этот заголовок и прерывает любую дальнейшую перезапись.
Некоторые моменты, которые следует отметить по поводу этого решения:
Оно не будет работать, если 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 и / 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]
Идея заключалась в обходном пути чтобы заменить использование флага [END] в версиях Apache, в которых его еще нет. Но на самом деле вы можете использовать этот общий подход для хранения большего, чем просто одного флага - вы можете хранить произвольные строки или числа, которые будут сохраняться при перенаправлении внутреннего сервера, и разработать свои правила перезаписи в зависимости от них на основе любого из условий тестирования. RuleCond предоставляет. (Я не могу придумать причину , почему вы захотите это сделать ... но эй, чем больше у вас гибкости и контроля, тем лучше, верно? )
Думаю, любой, кто дочитал до этого места, понял, что я не задаю здесь вопросов. Это больше связано с тем, что я нашел собственное решение возникшей у меня проблемы и хочу опубликовать его здесь для справки на случай, если кто-то еще столкнется с той же проблемой. Это большая часть того, для чего предназначен этот веб-сайт, верно?
...
Но поскольку этот является форумом вопросов и ответов, я спрошу: