Вопрос или проблема
У меня есть следующая структура моего проекта:
.htaccess
RewriteEngine On
RewriteRule ^file/(.*)$ files.php?get=$1 [QSA,L]
RewriteRule ^api/(.*)$ api.php?get=$1 [QSA,L]
RewriteRule ^(.*)$ pages.php?get=$1 [QSA,L]
files.php
<?php
echo 'files:<br>';
var_dump($_GET);
// TODO: реализация генерации файлов
?>
api.php
<?php
echo 'api:<br>';
var_dump($_GET);
// TODO: реализация команд API
?>
pages.php
<?php
echo 'pages:<br>';
var_dump($_GET);
// TODO: маршрутизация и генерация html страниц. адреса страниц могут иметь разное вложение
?>
Моя проблема заключается в том, что для любого запроса выполняется код из файла pages.php
.
ВОЗМОЖНО, решение моей проблемы заключается в:
.htaccess
RewriteEngine On
RewriteRule .* index.php [L]
index.php
<?php
switch (explode("https://webmasters.stackexchange.com/", $_SERVER['REQUEST_URI'])[1])
{
case 'file':
require 'files.php';
break;
case 'api':
require 'api.php';
break;
default:
require 'pages.php';
break;
}
?>
НО я столкнулся с некоторым странным поведением mod_rewrite при изменении регулярных выражений. И мне хотелось бы разобраться в этой ситуации.
Если у меня есть следующие регулярные выражения:
RewriteRule ^file/(.*)$ files.php?get=$1 [QSA,L]
RewriteRule ^api/(.*)$ api.php?get=$1 [QSA,L]
RewriteRule ^(.*)$ pages.php?get=$1 [QSA,L]
Для URL http://localhost/any_text_1/any_text_2/any_text_3
у меня правильный результат:
pages
array(1) { ["get"]=> string(32) "any_text_1/any_text_2/any_text_3" }
Но для URL http://localhost/api/cmd1/param1
у меня неправильный результат (управление должно было перейти к файлу api.php
, а не pages.php
):
pages (ПРИМЕЧАНИЕ: здесь я ожидал получить "api")
array(1) { ["get"]=> string(11) "cmd1/param1" }
НО если я изменю регулярные выражения (уберу символ “/”):
RewriteRule ^file(.*)$ files.php?get=$1 [QSA,L]
RewriteRule ^api(.*)$ api.php?get=$1 [QSA,L]
RewriteRule ^(.*)$ pages.php?get=$1 [QSA,L]
Тогда я получу нужный результат (за исключением одной проблемы):
Для URL http://localhost/any_text_1/any_text_2/any_text_3
у меня правильный результат (за исключением лишнего символа “/” в параметре):
pages
array(1) { ["get"]=> string(32) "/any_text_1/any_text_2/any_text_3" }
А для URL http://localhost/api/cmd1/param1
у меня правильный результат (за исключением лишнего символа “/” в параметре):
api
array(1) { ["get"]=> string(12) "/cmd1/param1" }
НО проблема, о которой я говорил ранее, заключается в следующем:
Для URL http://localhost/api11111/cm1/param1
я получаю результат:
api
array(1) { ["get"]=> string(16) "11111/cm1/param1" }
Но этот адрес должен был обрабатывать файл pages.php
, а не api.php
.
Получается, что регулярные выражения с символом “/” не выполняют маршрутизацию. А регулярные выражения без символа “/” выполняют маршрутизацию, но делают это не совсем корректно.
Почему? Это ошибка в моих регулярных выражениях или в mod_rewrite?
П.С.: Извините, если мой вопрос слишком длинный или плохо сформулирован. Честно, я старался написать лучше.
URL вроде
http://localhost/file/1
соответствует правилу^file/(.*)$
и код из файлаfiles.php
должен быть выполнен. Но выполняется код из файлаpages.php
. … (В коде из файла pages.php я получаю правильное значение параметра get).
В каталоге/.htaccess
флага L
просто завершает текущий проход через движок переписывания. Он не завершает все выполнение. Затем происходит второй проход, который передает обратно вновь переписанный URL (например, files.php?get=1
в этом примере).
Поскольку последнее правило соответствует всему, оно захватывает вновь переписанный URL во время второго прохода через движок переписывания и в конечном итоге переписывает URL в pages.php?get=files.php&get=1
(предыдущая переписанная строка запроса присоединяется из-за флага QSA
). Что касается PHP, параметр get
кажется правильным, потому что второй экземпляр перезаписывает первый (в суперглобальной $_GET
).
Ваше регулярное выражение должно быть более специфичным. Вам действительно нужно соответствовать всему в последнем правиле (и в первых двух правилах для этого вопроса)? Или оно предназначено только для соответствия отдельным сегментам пути (и без точек), как в вашем примере “news
“?
Если да, то сделайте регулярное выражение более строгим. Например:
:
RewriteRule ^([^/.]*)$ pages.php?get=$1 [QSA,L]
Регулярное выражение ^([^/.]*)$
соответствует news
, но не соответствует news/something
или news.php
.
Или/И на Apache 2.4 вы можете предотвратить второй проход через движок переписывания, используя флаг END
вместо L
в ваших правилах.
Однако, если вы не сделаете регулярное выражение более специфичным (или не добавите дополнительные условия), то вы потенциально также перепишите свои статические активы (CSS, JS, изображения и т.д.), что обычно нежелательно.
Непосредственно:
RewriteRule ^api/(.*)$ api.php?get=$1 [QSA,L]
Для того чтобы это правило сработало, вам нужно убедиться, что MultiViews
отключен (в противном случае mod_negotiation “переписывает” запрос в api.php
до обработки этого правила, и параметр get
не передается). MultiViews
отключен по умолчанию в Apache, однако некоторые серверы/дистрибутивы переопределяют это и включают его по некоторым причинам.
В основном, MultiViews позволяет URL файлов без расширения просто “работать”. Но это относится ко всему, потенциально приводит к проблемам с дублированием контента и может конфликтовать с вашими правилами mod_rewrite!
Например, в верхней части вашего .htaccess
файла:
# Отключить MultiViews
Options -MultiViews
Ответ или решение
Конечно, давайте подробно рассмотрим вашу проблему с правилами переписывания (RewriteRule) в файле .htaccess
, проанализируем возможные ошибки и дадим рекомендации по их устранению.
Проблема
Ваши правила переписывания выглядят так:
RewriteEngine On
RewriteRule ^file/(.*)$ files.php?get=$1 [QSA,L]
RewriteRule ^api/(.*)$ api.php?get=$1 [QSA,L]
RewriteRule ^(.*)$ pages.php?get=$1 [QSA,L]
Как вы заметили, для запросов, которые должны обрабатываться файлами files.php
и api.php
, вызывается pages.php
. Это поведение может быть связано с тем, как работает модуль mod_rewrite
, и спецификой правил, установленных в вашей конфигурации.
Анализ причин
-
Порядок правил и флаг L: Флаг
[L]
прекращает дальнейшую обработку правил, только если правило успешно сработало. Однако, если переопределение срабатывает, тогда происходит повторное применение правил для нового URL. В вашем случае, еслиfiles.php?get=...
обрабатывается, следующая строка (т.е. правило дляpages.php
) может перекрыть его. Таким образом, срабатываетpages.php
. -
Неявное совпадение с правилами: Поскольку ваше третье правило
^(.*)$
срабатывает для всех URL, оно может перехватывать запросы, которые должны обрабатываться первыми двумя правилами. Это происходит потому, что выражение^(.*)$
действительно является универсальным и подходит под любые строки. -
Проблемы с регулярными выражениями: Регулярные выражения, которые вы используете, могут быть недостаточно строгими. Например, если у вас есть URL
http://localhost/api11111/cm1/param1
, то срабатывает правило^api/(.*)$
, потому чтоapi11111
начинается сapi
. Это не то, что вам нужно, для обработки API требуется строгое соответствие.
Рекомендации по исправлению
- Уточнение регулярных выражений: Измените регулярные выражения, чтобы они более явно проверяли свои условия. Например:
RewriteRule ^file/(.+)$ files.php?get=$1 [QSA,L]
RewriteRule ^api/(.+)$ api.php?get=$1 [QSA,L]
RewriteRule ^([^/]+)$ pages.php?get=$1 [QSA,L]
В этом случае (.+)
требует хотя бы один символ после file/
или api/
, что предотвращает захват пустой строки. Также ^([^/]+)$
разрешает только одиночные сегменты, что исключает захват подстрок.
- Использование флага END: Если у вас версия Apache 2.4 или новее, подумайте о замене флага
[L]
на[END]
. Это останавливает процесс переписывания сразу после выполнения текущего правила.
RewriteRule ^file/(.+)$ files.php?get=$1 [QSA,END]
RewriteRule ^api/(.+)$ api.php?get=$1 [QSA,END]
RewriteRule ^([^/]+)$ pages.php?get=$1 [QSA,END]
- Отключение MultiViews: Если у вас включен режим
MultiViews
, он может конфликтовать с вашими правилами. Предположите добавление следующей строки в начале файла.htaccess
:
Options -MultiViews
Заключение
Проблема, с которой вы столкнулись, скорее всего, не является багом в mod_rewrite
, а результатом не слишком строгих регулярных выражений и порядка обработки. Уточнение ваших правил поможет достичь того поведения, которого вы ожидаете. Использование флага [END]
и отключение MultiViews
сделает ваши правила еще более предсказуемыми и надежными.
Если у вас остались дополнительные вопросы или потребуются дальнейшие разъяснения, пожалуйста, не стесняйтесь задавать их.