Глядя на журнал изменений …
Журналы изменений для Apache 2.0.45 и 1.3.28 содержат следующие примечания:
Apache 2.0.45:
Предотвратить бесконечные циклы внутренних перенаправлений в mod_rewrite
прерывание после превышения лимита внутренних перенаправлений.
ограничение по умолчанию 10 и может быть изменено с помощью RewriteOptions
директивы. PR 17462. [Андре Мало]
Apache 1.3.28:
backport из серии 2.x: предотвращение бесконечных циклов внутренних перенаправлений
в mod_rewrite путем прерывания после превышения лимита внутренних перенаправлений.
Предел по умолчанию равен 10 и может быть изменен с помощью RewriteOptions
директивы. PR 17462. [Андре Мало]
Эти два выпуска популярного веб-сервера представляют новую опцию для директивы RewriteOptions: MaxRedirects.
mod_rewrite, для тех, кто не знает, это мощный модуль, который позволяет обрабатывать входящие запросы URI и многое другое.
Эта статья объяснит использование и преимущества опции MaxRedirects. Компании веб-хостинга, которые предлагают модуль mod_rewrite, должны принять во внимание эти причины для обновления до последней версии Apache. Поддержание вашего веб-сервера в актуальном состоянии является обязательным. Хостинг-компаниям, которые не предлагают модуль mod_rewrite, рекомендуется включить его.
Прежде чем мы продолжим, вам понадобится работающая установка Apache с установленным mod_rewrite. Пожалуйста, смотрите мой FAQ для установки помощи.
Типичный случай
Давайте начнем с примера.
Мы хотим перенаправить все URI в скрипт PHP. Перенаправление будет внутренним, поэтому клиент, например браузер, не увидит, что перенаправление произошло. Для этого мы создаем в корне документа файл .htaccess, содержащий следующие правила:
# Enable rewrite engine
Options +FollowSymLinks
RewriteEngine On
# Redirect internally all URIs to /index.php
RewriteRule .* /index.php [L]
Однако это не сработает — это замораживает ваш веб-сервер Apache. Почему?
Проблема в этой строке:
RewriteRule .* /index.php [L]
Проще говоря, это правило «говорит»: «Перенаправляйте все запросы в /index.php.». Это может звучать так, как мы изначально стремились достичь, но на практике мы не получим ожидаемых результатов.
Вот что происходит с запросом на www.example.net/path/to/:
Фаза А
- URI это / путь / к /
- RewriteRule перезапишет URI из пути / в / в /index.php
- Внутреннее перенаправление будет сделано для /index.php
- Внутреннее перенаправление будет рассматриваться как новый запрос, поэтому он будет проанализирован снова (см. Этап b).
Фаза Б
- Предыдущее внутреннее перенаправление, /index.php, будет снова обработано механизмом перезаписи
- RewriteRule перепишет index.php в /index.php
- Внутреннее перенаправление будет сделано для /index.php
В этот момент механизм перезаписи будет работать на этапе B непрерывно, а веб-сервер Apache будет зависать.
Если это звучит сложно, не волнуйтесь! Вот упрощенный файл журнала, который показывает процесс:
# phase a)
[] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/path/to/ -> path/to/
[] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'path/to/'
[] (2) [per-dir /www/htdocs/] rewrite path/to/ -> /index.php
[] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
# phase b)
[redir#1] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#1] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#1] (2) [per-dir /www/htdocs/] rewrite index.php -> /index.php
[redir#1] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
# phase b)
[redir#2] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#2] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#2] (2) [per-dir /www/htdocs/] rewrite index.php -> /index.php
[redir#2] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
# phase b)
[redir#3] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#3] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#3] (2) [per-dir /www/htdocs/] rewrite index.php -> /index.php
[redir#3] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
....ad infinitum...
# phase b)
[redir#N] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#N] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#N] (2) [per-dir /www/htdocs/] rewrite index.php -> /index.php
[redir#N] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
Это то, что происходит с версиями Apache до версий 2.0.45 и 1.3.28.
Maxredirects
Следующий упрощенный файл журнала показывает, что произойдет, если наш пример будет работать под Apache 2.0.45 или 1.3.28:
# phase a)
[] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/path/to/ -> path/to/
[] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'path/to/'
[] (2) [per-dir /www/htdocs/] rewrite path/to/ -> /index.php
[] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
# phase b)
[redir#1] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#1] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#1] (2) [per-dir /www/htdocs/] rewrite index.php -> /index.php
[redir#1] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
# phase b)
[redir#2] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#2] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#2] (2) [per-dir /www/htdocs/] rewrite index.php -> /index.php
[redir#2] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
...last...
# phase b)
[redir#10] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#10] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#10] (2) [per-dir /www/htdocs/] rewrite index.php -> /index.php
[redir#10] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
Хотя журнал перезаписи может показаться таким же, как и раньше, это не так. Внутренние перенаправления не являются непрерывными, так как они прекращаются после 10 попыток. Веб-сервер не будет зависать, и мы получим 500 HTTP-ошибку состояния (Internal Server Error).
Наш error_log будет выглядеть так:
[Mon Jul 21 15:24:27 2003] [error] [client 127.0.0.1] mod_rewrite: maximum number of internal redirects reached. Assuming configuration error. Use 'RewriteOptions MaxRedirects' to increase the limit if neccessary.
Этот подробный журнал ошибок поможет нам быстро и точно отладить наши правила.
Несмотря на то, что мы используем те же правила, что и раньше, наш веб-сервер Apache больше не будет зависать. Разве это не волшебство?
RewriteOptions
Таким образом, наш веб-сервер больше не будет зависать … но почему внутренние перенаправления прекратились после 10 перенаправлений? Здесь вступают в действие директива RewriteOptions и опция maxredirects.
По умолчанию (то есть, если вы не объявляете это), maxredirects имеет значение:
RewriteOptions maxredirect=10
Эта директива заставит механизм перезаписи останавливаться после 10 внутренних перенаправлений, поскольку после этого времени мы, вероятно, в бесконечном цикле.
Если число 10 перенаправлений слишком велико, вы можете изменить его на более низкое значение. Например:
RewriteOptions maxredirect=3
Наш файл журнала будет выглядеть так:
# phase a)
[] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/path/to/ -> path/to/
[] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'path/to/'
[] (2) [per-dir /www/htdocs/] rewrite path/to/ -> /index.php
[] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
# phase b)
[redir#1] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#1] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#1] (2) [per-dir /www/htdocs/] rewrite index.php -> /index.php
[redir#1] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
# phase b)
[redir#3] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#3] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#3] (2) [per-dir /www/htdocs/] rewrite index.php -> /index.php
[redir#3] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
...stop
Помните, что опция maxredirect помогает контролировать преднамеренную ошибку в созданных нами правилах. Это не исправит ошибку, но остановит замораживание нашего веб-сервера.
Это хорошо как для хостинговых компаний, так и для пользователей mod_rewrite.
Выводы
И последнее, но не менее важное: что мы можем сделать, чтобы исправить правила, которые мы использовали в примере? Нам нужно использовать это правило вместо:
RewriteRule .* index.php [L]
Разница в том, что в исправленном правиле index.php не начинается с косой черты. В последнем случае mod_rewrite достаточно умен, чтобы предотвратить бесконечные циклы, поскольку он видит, что исходный запрос index.php такой же, как внутреннее перенаправление на index.php, поэтому он не будет выполнять перенаправление. Это можно увидеть в следующем файле журнала:
# phase a)
[] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/path/to/ -> path/to/
[] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'path/to/'
[] (2) [per-dir /www/htdocs/] rewrite path/to/ -> index.php
[] (3) [per-dir /www/htdocs/] add per-dir prefix: index.php -> /www/htdocs/index.php
[] (2) [per-dir /www/htdocs/] strip document_root prefix: /www/htdocs/index.php -> /index.php
[] (1) [per-dir /www/htdocs/] internal redirect with /index.php [INTERNAL REDIRECT]
# phase b)
[redir#1] (3) [per-dir /www/htdocs/] strip per-dir prefix: /www/htdocs/index.php -> index.php
[redir#1] (3) [per-dir /www/htdocs/] applying pattern '(.*)' to uri 'index.php'
[redir#1] (2) [per-dir /www/htdocs/] rewrite index.php -> index.php
[redir#1] (3) [per-dir /www/htdocs/] add per-dir prefix: index.php -> /www/htdocs/index.php
# Smart check that will avoid the endless loops
[redir#1] (1) [per-dir /www/htdocs/] initial URL equal rewritten URL: /www/htdocs/index.php [IGNORING REWRITE]
Интересно, не правда ли?
Если у вас есть какие-либо вопросы, касающиеся Apache и / или mod_rewrite, посетите форум Apache SitePoint .