Как сделать единую точку входа с ЧПУ?

В сайтостроении единой точкой входа (в противоположность множественным точкам входа) называется файл-обработчик, принимающий все входящие запросы на программную обработку данных и вывод страниц сайта. Различать запросы исключительно по строке параметров не очень удобно, поэтому совместно с единой точкой входа практически всегда используют ЧПУ.

Первоочередной задачей при создании единой точки входа является реализация перенаправления всех входящих запросов в файл-обработчик, которая осуществляется средствами Web-сервера. Например, в Web-сервере Apache для этой цели можно создать в корневом каталоге сайта файл .htaccess следующего содержания:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^.* /index.php

Условие %{REQUEST_FILENAME} !-f препятствует перенаправлению запросов к существующим файлам в единую точку входа и зацикливанию при обработке правила ^.* /index.php (обработка правил многопроходная; Web-сервер фиксирует ошибку, когда при выполнении большого количества проходов не появляется признак завершения обработки). В данном примере файлом-обработчиком является index.php, расположенный в корневом каталоге сайта.

Следующей задачей является обработка входящих запросов внутри единой точки входа. Web-сервер предоставляет полный адрес из поступившего запроса, включая строку параметров. Этот адрес доступен внутри единой точки входа в $_SERVER['REQUEST_URI']. Например, можно написать в файле index.php такой код:

<!-- HTML-код -->
<pre><?= $_SERVER['REQUEST_URI'] ?></pre>
<!-- HTML-код -->

Демонстрация: /gency-demo/single-entry-point?p=1 (в адресной строке вместо single-entry-point можно вписать любые допустимые для пути символы, а вместо 1 – любое натуральное число до 9999999999 включительно).

С полученным адресом можно делать все, что необходимо разработчику. Адрес можно сравнивать с определенными образцовыми адресами или адресными масками, чтобы при совпадении с образцом выполнять закрепленное за этим образцом действие. Адрес можно разделять на части, выделяя компоненты пути и значения параметров, как это сделано в G-Drive/Gency, чтобы уже на основе этих данных выполнять определенное действие.

В качестве примера обработки адреса можно рассмотреть, как в G-Drive/Gency происходит отделение пути от строки параметров1 и выделение компонентов пути2 (фактически выделяются первый компонент пути, т.н. категория, и целиком оставшаяся часть пути, начиная со второго компонента, т.н. объект):

if (strcmp($_SERVER['REQUEST_URI'], $p = strtolower(preg_replace('#//+#', '/', $_SERVER['REQUEST_URI']))))
{
  header('Location: http'.(empty($_SERVER['HTTPS']) ? '://' : 's://').$_SERVER['HTTP_HOST'].$p, true, 301);
  exit;
}

// 1
list($p) = explode('?', $p, 2);

// 2
if (count($pa = explode('/', $px = trim($p, '/'), 2)) < 2) $pa[] = '';

Первоначально в переменную $p помещается «нормализованный» адрес без множественных слешей и латинских букв в верхнем регистре, полученный в процессе работы соответствующего фильтра, который допускает дальнейшее выполнение кода только при совпадении значений $_SERVER['REQUEST_URI'] и $p. Затем в переменной $p происходит отделение пути от строки параметров и, таким образом, в ней остается «нормализованный» путь. Далее в переменную $px помещается «нормализованный» путь без первого и последнего слешей. В заключение в массив $pa помещаются выделенные компоненты пути.

Демонстрация: /gency-demo-2/single-entry-point?p=1 (в адресной строке вместо single-entry-point можно вписать любые допустимые для пути символы, а вместо 1 – любое натуральное число до 9999999999 включительно).

Комментарии: 8

  1. MrVik

    Спасибо, теперь все понятно!

  2. Михаил

    Если файл-обработчик должен перехватывать все входящие запросы, включая обращения к существующим файлам, в файле .htaccess нужно убрать условие %{REQUEST_FILENAME} !-f. Для предотвращения зацикливания в этом случае следует использовать директиву RewriteBase / совместно с относительным именем файла-обработчика в правиле:

    RewriteEngine On
    
    RewriteBase /
    
    RewriteRule ^.* index.php
    

    Не нужно забывать, что в качестве front-end в связке с Apache может использоваться другой Web-сервер, настроенный таким образом, чтобы обрабатывать обращения к существующим файлам самостоятельно.

  3. Михаил

    Код в статье и фронтальный движок на демосайте обновлены до версии 3.07. Параметр p в адресе теперь может иметь значения до 9999999999 включительно.

  4. Menshakovich

    Спасибо за такую краткую и информативную статью!

  5. AlexandrS

    Получается в варианте с RewriteBase все запросы будут проходить через index.php? И тут у меня возник вопрос, а как будут вести себя изображения, файлы стилей и т.п.

    Ведь в данном случае возрастает нагрузка: либо отдать статику через прямой запрос, либо все пустить через обработку.

  6. Михаил

    Получается в варианте с RewriteBase все запросы будут проходить через index.php? И тут у меня возник вопрос, а как будут вести себя изображения, файлы стилей и т.п.

    В этом случае либо index.php должен выдавать в том числе и статик, либо нужно размещать статик на другом хосте (за исключением фэйвайкона, роботса и т.п.). И RewriteBase тут играет важную, но не определяющую роль. Эту директиву можно было использовать и в варианте с исключением статика совместно с той же RewriteCond.

    Ведь в данном случае возрастает нагрузка: либо отдать статику через прямой запрос, либо все пустить через обработку.

    Есть ситуации, когда это необходимо, например чтобы отдавать статик только авторизованным пользователям (для оптимизации выдачи статика тут используются специальные методы). Если же такой необходимости нет, вы можете размещать статик на другом хосте, что само по себе является хорошей оптимизацией: не нужно при обработке каждого запроса делать тест на наличие статика.

  7. AlexandrS

    Такое бывает, например, на сайтах знакомств и т.п., где обычным пользователям представлены только миниатюры, а авторизированным уже полноразмерные изображения – в таком варианте уже логично использовать фронт, но если речь идет об элементах дизайна, то тут нерационально прогонять каждый запрос через фронт.

  8. Михаил

    Согласен. Но и тут возможны исключения. Если это какая-то закрытая панель, то, к примеру, для противодействия «детекту» можно и упомянутый статик закрыть. Или опять-таки подтягивать его с совершенно другого хоста. Также можно использовать комбинированный вариант.

Отправить комментарий

Ваш адрес E-mail не будет опубликован.