Различия
Показаны различия между двумя версиями страницы.
Следующая версия | Предыдущая версия | ||
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? | + | |
- | + | ||
- | Оригинал: | + |