Различия
Показаны различия между двумя версиями страницы.
| Следующая версия | Предыдущая версия | ||
| system:apache [2014/05/30 11:51] – внешнее изменение 127.0.0.1 | system:apache [2016/03/16 15:57] (текущий) – mirocow | ||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| ====== Apache ====== | ====== Apache ====== | ||
| - | ===== mod_rewrite ===== | + | * [[system: |
| - | + | * [[system:apache:fastcgi]] | |
| - | Эта статья выросла из идеи продвинутого обучения наших сотрудников технической поддержки работе с mod_rewrite. Практика показала, | + | * [[apache:apache]] |
| - | + | * [[apache:apache:ab]] | |
| - | В статье изложен механизм работы mod_rewrite. Понимание принципов его работы позволяет четко осознавать действие каждой директивы и ясно представлять себе, что происходит в тот или иной момент внутри mod_rewrite при обработке директив. | + | |
| - | + | ||
| - | Я предполагаю, | + | |
| - | + | ||
| - | Итак, вы изучили mod_rewrite, | + | |
| - | + | ||
| - | ==== С чем работает RewriteRule ==== | + | |
| - | + | ||
| - | + | ||
| - | Первому RewriteRule передается путь от того места, где находится .htaccess, до запрошенного файла. Эта строка никогда не начинается со "/" | + | |
| - | + | ||
| - | Чтобы досконально понять, | + | |
| - | + | ||
| - | Когда только начинаешь работать с mod_rewrite, | + | |
| - | + | ||
| - | Из-за внутренней архитектуры Apache в тот момент, | + | |
| - | + | ||
| - | Поэтому в mod_rewrite передается абсолютный путь до файла, который должен быть обработан. Также mod_rewrite знает путь до .htaccess, в котором размещены правила RewriteRule. Чтобы сделать из пути до файла что-то похожее на ссылку, | + | |
| - | + | ||
| - | Так вот, именно этот путь, от которого отрезан путь до .htaccess, передается в первый RewriteRule. Например: | + | |
| - | | + | |
| - | DocumentRoot: | + | |
| - | Путь до файла: / | + | |
| - | .htaccess находится в: / | + | |
| - | В первый RewriteRule будет передано: | + | |
| - | Обратите внимание: | + | |
| - | как работает RewriteRule | + | |
| - | Путь до .htaccess отрезается вместе со слешем. Из этого есть следствие: | + | |
| - | + | ||
| - | Важно запомнить, | + | |
| - | + | ||
| - | # работать не будет - правило начинается со / | + | |
| - | RewriteRule ^/ | + | |
| - | + | ||
| - | # работать не будет - название сайта не анализируется RewriteRule | + | |
| - | RewriteRule ^example.com/ | + | |
| - | + | ||
| - | # работать не будет - аргументы ссылки не попадают в RewriteRule | + | |
| - | RewriteRule index.php\? | + | |
| - | + | ||
| - | # Будет работать только если .htaccess находится там же, где находится папка templates, | + | |
| - | # например, | + | |
| - | # работать НЕ БУДЕТ, потому что mod_rewrite отрежет путь до .htaccess и на вход RewriteRule | + | |
| - | # строка попадет уже без " | + | |
| - | RewriteRule ^templates/ | + | |
| - | + | ||
| - | В начале использования mod_rewrite я рекомендую работать с ним только в .htaccess в корне сайта. Это несколько упростит контроль за его работой. | + | |
| - | + | ||
| - | С чем работает RewriteRule, | + | |
| - | + | ||
| - | ==== Как работает RewriteRule ==== | + | |
| - | + | ||
| - | + | ||
| - | RewriteRule просто преобразовывает строку в соответствии с регулярными выражениями, | + | |
| - | + | ||
| - | Как мы выяснили выше, на вход RewriteRule попадает путь от .htaccess до запрошенного файла. Удобнее всего теперь абстрагироваться от путей и ссылок и рассматривать то, с чем работает RewriteRule, | + | |
| - | + | ||
| - | В общем виде, если исключить сложности с использованием флагов (некоторые из которых мы рассмотрим ниже) и сложности с составлением регулярных выражений (которых мы почти не будем касаться в этой статье), | + | |
| - | Взяли строку. | + | |
| - | Сравнили с регулярным выражением в первом аргументе. | + | |
| - | Если есть совпадение — заменили всю строку на значение второго аргумента. | + | |
| - | Передали строку следующему RewriteRule. | + | |
| - | Вот, в общем, и все. Чтобы наглядно проиллюстрировать, | + | |
| - | + | ||
| - | # Запрос: | + | |
| - | # В первый RewriteRule попадет " | + | |
| - | + | ||
| - | # Преобразовываем запрос в произвольную строку. | + | |
| - | RewriteRule ^info.html$ "I saw a turtle in the hole. And it was dancing rock-n-roll. And it was smiling. All in all, it was a very funny doll." | + | |
| - | + | ||
| - | # " | + | |
| - | + | ||
| - | # Заменяем эту строку на внешнюю ссылку. | + | |
| - | RewriteRule turtle https:// | + | |
| - | + | ||
| - | # "I saw a turtle..." | + | |
| - | + | ||
| - | # Заменяем имя сайта! | + | |
| - | RewriteRule ^(.*)example.com(.*)$ $1example.org$2 | + | |
| - | + | ||
| - | # " | + | |
| - | + | ||
| - | # Заменяем протокол! | + | |
| - | RewriteRule ^https: | + | |
| - | + | ||
| - | # " | + | |
| - | + | ||
| - | # Заменяем конечную ссылку. | + | |
| - | RewriteRule ^(.*)/ | + | |
| - | + | ||
| - | # " | + | |
| - | + | ||
| - | + | ||
| - | Как видите, | + | |
| - | + | ||
| - | Здесь нужно сделать замечание: | + | |
| - | + | ||
| - | После того как все преобразования произведены и выполнено последнее RewriteRule, | + | |
| - | + | ||
| - | ==== Для чего нужен RewriteBase ==== | + | |
| - | + | ||
| - | + | ||
| - | Если получившийся после преобразований запрос является относительным и отличается от исходного, | + | |
| - | RewriteBase выполняется только после всех RewriteRule, | + | |
| - | + | ||
| - | Мы уже говорили выше о том, что в mod_rewrite, | + | |
| - | + | ||
| - | RewriteBase выполняется после всех преобразований. Это значит, | + | |
| - | + | ||
| - | После всех преобразований RewriteBase смотрит, | + | |
| - | images/ | + | |
| - | / | + | |
| - | http:// | + | |
| - | Если путь абсолютный, | + | |
| - | + | ||
| - | # .htaccess находится в /images/ | + | |
| - | # RewriteBase указан /images/ | + | |
| - | RewriteBase /images/ | + | |
| - | + | ||
| - | # Запрос http:// | + | |
| - | # На вход RewriteRule попадает " | + | |
| - | RewriteRule ^logo.gif$ logo-orange.gif | + | |
| - | # После RewriteRule: | + | |
| - | # После RewriteBase: | + | |
| - | + | ||
| - | # Запрос http:// | + | |
| - | # На вход RewriteRule попадает " | + | |
| - | RewriteRule ^header.png$ / | + | |
| - | # После RewriteRule: | + | |
| - | # После RewriteBase: | + | |
| - | + | ||
| - | # Запрос http:// | + | |
| - | # На вход RewriteRule попадает " | + | |
| - | # Используем внешний относительный редирект | + | |
| - | RewriteRule ^director.tiff$ staff/ | + | |
| - | # После RewriteRule: | + | |
| - | # + mod_rewrite запомнил, | + | |
| - | # После RewriteBase: | + | |
| - | # mod_rewrite вспомнил про внешний редирект: | + | |
| - | # "/ | + | |
| - | + | ||
| - | Обычно после некоторого знакомства с mod_rewrite складывается следующая привычка: | + | |
| - | + | ||
| - | - в каждый .htaccess добавлять «RewriteBase /» | + | |
| - | - все перенаправления начинать со слеша: «RewriteRule news.php / | + | |
| - | RewriteBase должен совпадать с путем от корня сайта до .htaccess. | + | |
| - | Начинать перенаправления со "/" | + | |
| - | + | ||
| - | {{:system:rewritebase.png? | + | |
| - | + | ||
| - | Что будет, если не указать RewriteBase? | + | |
| - | + | ||
| - | # Запрос http:// | + | |
| - | # DocumentRoot: | + | |
| - | # .htaccess находится в корне сайта, и в нем НЕ УКАЗАН RewriteBase. | + | |
| - | # Поэтому по умолчанию RewriteBase равен абсолютному пути до .htaccess: / | + | |
| - | + | ||
| - | # На входе RewriteRule - " | + | |
| - | RewriteRule ^index.php main.php [R] | + | |
| - | # На выходе: | + | |
| - | # mod_rewrite запомнил, | + | |
| - | + | ||
| - | # Закончились RewriteRule | + | |
| - | # mod_rewrite все равно выполняет RewriteBase, | + | |
| - | # Получается: | + | |
| - | + | ||
| - | # Здесь mod_rewrite вспоминает, | + | |
| - | # "/ | + | |
| - | + | ||
| - | # Получилось совсем не то, что имели в виду. | + | |
| - | + | ||
| - | Итак, запрос прошел через все RewriteRule, | + | |
| - | + | ||
| - | === Как работает mod_rewrite. Флаг [L] === | + | |
| - | + | ||
| - | + | ||
| - | mod_rewrite запускает обработку запроса снова и снова, до тех пор, пока он не перестанет меняться. И флаг **[L]** не может это остановить. | + | |
| - | + | ||
| - | При составлении более-менее сложных конфигураций mod_rewrite важно понимать, | + | |
| - | + | ||
| - | Apache поступает так, потому что в процессе изменения запроса он мог быть перенаправлен в другую директорию. В ней может быть собственный .htaccess, который не участвовал в предыдущей обработке запроса. В этом же новом .htaccess могут быть правила, | + | |
| - | + | ||
| - | — Постойте, | + | |
| - | + | ||
| - | Не совсем так. Флаг **[L]** останавливает текущую итерацию обработки запроса. Однако если запрос был изменен теми RewriteRule, | + | |
| - | + | ||
| - | | + | |
| - | + | ||
| - | RewriteBase / | + | |
| - | + | ||
| - | RewriteRule ^a.html$ b.html [L] | + | |
| - | RewriteRule ^b.html$ a.html [L] | + | |
| - | + | ||
| - | Пример выше приведет к бесконечному циклу перенаправлений и к «Internal Server Error» в итоге. В этом примере бесконечный цикл очевиден, | + | |
| - | + | ||
| - | Чтобы избежать подобных ситуаций, | + | |
| - | Когда используется внешний редирект — [L,R=301] или [L,R=302]. В случае внешнего редиректа дальнейшая обработка запроса нежелательна (см. ниже про флаг **[R]**), и ее лучше остановить. | + | |
| - | Когда в .htaccess есть зацикливание, | + | |
| - | + | ||
| - | А вот приведенный ниже пример зацикливаться не будет. Попробуйте определить, | + | |
| - | + | ||
| - | # Запрос: | + | |
| - | # Начало .htaccess | + | |
| - | + | ||
| - | RewriteBase / | + | |
| - | RewriteRule ^a.html$ b.html | + | |
| - | RewriteRule ^b.html$ a.html | + | |
| - | + | ||
| - | # Конец .htaccess | + | |
| - | + | ||
| - | Отгадка: | + | |
| - | + | ||
| - | === Как работает mod_rewrite. Флаг | + | |
| - | + | ||
| - | * Флаг **[R]** не останавливает обработку запроса, | + | |
| - | * Флаг **[R]** сообщает Apache, что нужно выполнить не внутренний, | + | |
| - | + | ||
| - | Казалось бы, при обработке флага **[R]** Apache должен сразу прекратить обработку RewriteRule и вернуть пользователю внешний редирект. Однако давайте вспомним фантастический пример из раздела «Как работает RewriteRule». В нем мы сначала указали флаг **[R]**, обозначив необходимость внешнего редиректа, | + | |
| - | + | ||
| - | Именно так и работает Apache при указании внешнего редиректа. Он просто «помечает» себе, что после выполнения всех правил необходимо вернуть статус 302 (по умолчанию), | + | |
| - | + | ||
| - | Тем не менее, вряд ли вы хотите после отдачи внешнего редиректа каким-либо образом изменять его. Поэтому рекомендуется при употреблении флага **[R]** указывать его совместно с **[L]**: | + | |
| - | + | ||
| - | # BlackJack переехал на красивое имя | + | |
| - | RewriteRule ^bj/(.*) blackjack/ | + | |
| - | + | ||
| - | # Можно использовать просто внешнюю ссылку | + | |
| - | RewriteRule ^bj/(.*) http:// | + | |
| - | + | ||
| - | Вместо использования флага **[R]** можно указывать просто внешнюю ссылку. В этом случае Apache сам догадается, | + | |
| - | Если внешний редирект ведет на тот же сайт, лучше использовать флаг **[R]** без указания полной ссылки (иными словами, | + | |
| - | Если же внешний редирект ведет на другой сайт, иначе, как указав полную внешнюю ссылку, | + | |
| - | + | ||
| - | === Как работает mod_rewrite. Указание параметров запроса и флаг [QSA] === | + | |
| - | + | ||
| - | Изменение параметров запроса в RewriteRule не изменяет строку, | + | |
| - | + | ||
| - | Используемая терминология: | + | |
| - | + | ||
| - | С помощью RewriteRule можно изменять не только путь до файла, который будет обрабатываться, | + | |
| - | RewriteBase / | + | |
| - | + | ||
| - | | + | |
| - | # На входе: " | + | |
| - | RewriteRule ^news/(.*)$ index.php? | + | |
| - | # После RewriteRule: | + | |
| - | # %{QUERY_STRING}: | + | |
| - | + | ||
| - | В момент, | + | |
| - | RewriteRule заменяет строку, | + | |
| - | Часть второго аргумента после вопросительного знака попадает в переменную %{QUERY_STRING}. Если был указан флаг **[QSA]**, параметры запроса будут добавлены в начало %{QUERY_STRING}. Если флаг указан не был, %{QUERY_STRING} полностью заменится параметрами запроса из RewriteRule. | + | |
| - | Еще пара примеров: | + | |
| - | + | ||
| - | RewriteBase / | + | |
| - | + | ||
| - | # Запрос: | + | |
| - | # На входе RewriteRule: | + | |
| - | RewriteRule ^news/(.*)$ index.php? | + | |
| - | # После преобразования: | + | |
| - | # Значение %{QUERY_STRING}: | + | |
| - | + | ||
| - | Скорее всего, правило выше работает неправильно, | + | |
| - | RewriteBase / | + | |
| - | + | ||
| - | # Запрос: | + | |
| - | # На входе RewriteRule: | + | |
| - | RewriteRule ^news/(.*)$ index.php? | + | |
| - | # После преобразования: " | + | |
| - | # Значение %{QUERY_STRING}: " | + | |
| - | + | ||
| - | Мы добавили только флаг **[QSA]**, и правило стало работать корректно. | + | |
| - | + | ||
| - | Важно понимать, | + | |
| - | + | ||
| - | — Конечно, | + | |
| - | + | ||
| - | Нет, %{QUERY_STRING} изменяется сразу же. Доказательство приводить не буду — про параметры и так уже написано больше, | + | |
| - | + | ||
| - | Что же делать, | + | |
| - | + | ||
| - | ==== RewriteCond и производительность ==== | + | |
| - | + | ||
| - | + | ||
| - | Сначала проверяется совпадение запроса с RewriteRule, | + | |
| - | + | ||
| - | Пару слов стоит сказать о том, в каком порядке mod_rewrite выполняет директивы. Так как в .htaccess сначала идут RewriteCond, | + | |
| - | + | ||
| - | На самом деле все происходит наоборот. Сначала mod_rewrite проверяет, | + | |
| - | + | ||
| - | Так что если у вас в RewriteRule регулярное выражение на две страницы и вы, задумавшись о производительности, | + | |
| - | + | ||
| - | ==== Переменные и флаги RewriteCond, | + | |
| - | + | ||
| - | + | ||
| - | Читайте документацию. | + | |
| - | + | ||
| - | Мы познакомились с принципами работы RewriteRule, | + | |
| - | + | ||
| - | К счастью, | + | |
| - | + | ||
| - | ==== Разница в работе mod_rewrite в контексте .htaccess и в контексте VirtualHost ==== | + | |
| - | + | ||
| - | + | ||
| - | В контексте < | + | |
| - | + | ||
| - | Как я говорил в начале статьи, | + | |
| - | В < | + | |
| - | Второй аргумент RewriteRule также необходимо начинать со /, иначе будет «Bad Request». | + | |
| - | RewriteBase не имеет смысла. | + | |
| - | Проход правил происходит только один раз. Флаг **[L]** действительно заканчивает обработку всех правил, | + | |
| - | + | ||
| - | ==== Советы и решения ==== | + | |
| - | + | ||
| - | + | ||
| - | Здесь собраны советы, | + | |
| - | + | ||
| - | === Составление регулярных выражений === | + | |
| - | + | ||
| - | + | ||
| - | Старайтесь составлять регулярные выражения так, чтобы они наиболее узко определяли именно те запросы, | + | |
| - | + | ||
| - | # Начинайте все регулярные выражения с " | + | |
| - | # и заканчивайте " | + | |
| - | RewriteRule ^news.php$ index.php | + | |
| - | # Даже если в этом нет необходимости - для универсальности и лучшего понимания конфигурации: | + | |
| - | RewriteRule ^news/(.*)$ index.php | + | |
| - | + | ||
| - | # Если под маску должны попадать только цифры - укажите это явно. | + | |
| - | # Если какие-то цифры постоянны, | + | |
| - | # Если в оставшейся части запроса не могут присутствовать слеши, ограничьте их присутствие. | + | |
| - | # Не забывайте экранировать " | + | |
| - | # Следующее правило нацелено на запросы вида http:// | + | |
| - | RewriteRule ^news/ | + | |
| - | + | ||
| - | Впрочем, | + | |
| - | + | ||
| - | === Изменение внешних редиректов === | + | |
| - | + | ||
| - | + | ||
| - | Несмотря на то, что mod_rewrite позволяет изменять с помощью RewriteRule даже внешние редиректы, | + | |
| - | + | ||
| - | Не думаю, что разработчики mod_rewrite предполагали, | + | |
| - | + | ||
| - | === Как остановить бесконечный цикл === | + | |
| - | + | ||
| - | + | ||
| - | Иногда логика перенаправлений на сайте такова, | + | |
| - | + | ||
| - | На сайте была страница /info.html. Специалист по SEO решил, что поисковые системы будут лучше индексировать эту страницу, | + | |
| - | # сделать внешний редирект | + | |
| - | RewriteRule ^info.html information.html [R,L] | + | |
| - | # но по запросу / | + | |
| - | RewriteRule ^information.html info.html | + | |
| - | + | ||
| - | … и сталкивается с бесконечным циклом. Каждый запрос / | + | |
| - | + | ||
| - | Решить эту проблему можно как минимум двумя способами. На Хабре был уже описан один из них — нужно установить переменную окружения и на основании ее значения прекращать перенаправления. Код будет выглядеть следующим образом: | + | |
| - | + | ||
| - | RewriteCond %{ENV: | + | |
| - | RewriteRule ^ - [L] | + | |
| - | + | ||
| - | RewriteRule ^info.html$ information.html [R,L] | + | |
| - | RewriteRule ^information.html$ info.html [E=FINISH: | + | |
| - | + | ||
| - | Обратите внимание, | + | |
| - | + | ||
| - | Второй способ — проверить в THE_REQUEST, | + | |
| - | + | ||
| - | # Внешний редирект происходит только если пользователь запросил info.html. | + | |
| - | # Если же info.html - это результат внутреннего перенаправления, | + | |
| - | RewriteCond %{THE_REQUEST} " | + | |
| - | RewriteRule ^info.html$ information.html [R,L] | + | |
| - | + | ||
| - | RewriteRule ^information.html$ info.html | + | |
| - | + | ||
| - | + | ||
| - | ==== Анализ исходного запроса пользователя — борьба с раскрытием ссылок Apache ==== | + | |
| - | + | ||
| - | При обработке запроса Apache раскрывает закодированные (URL-encoded) символы из первоначального запроса. В некоторых случаях это может быть нежелательно — разработчик хочет проверять именно первоначальный, | + | |
| - | + | ||
| - | RewriteCond %{THE_REQUEST} ^GET[\ ]+/ | + | |
| - | RewriteRule ^(.*)$ index.php? | + | |
| - | + | ||
| - | Оригинал: | + | |