Пишем современный маршрутизатор на JavaScript
Простые одностраничные приложения, основанные на React, Vue или чистом JavaScript, окружают нас повсюду. Хороший «одностраничник» предполагает соответствующий механизм маршрутизации.
Такие библиотеки, как «navigo» или «react-router», приносят большую пользу. Но как они работают? Необходимо ли нам импортировать всю библиотеку? Или достаточно какой-то части, скажем, 10%? В действительности, быстрый и полезный маршрутизатор можно легко написать самому, это займет немного времени, а программа будет состоять менее чем из 100 строчек кода.
Требования
Наш маршрутизатор должен быть:
- написан на ES6+
- совместим с историей и хешем
- переиспользуемой библиотекой
- маршрутизаторы (routes): список зарегистрированных маршрутизаторов
- режим (mode): хеш или история
- корневой элемент (root): корневой элемент приложения, если мы находимся в режиме использования истории
- конструктор (constructor): основная функция для создания нового экземпляра маршрутизатора
class Router < routes = [] mode = null root = '/' constructor(options) < this.mode = window.history.pushState ? 'history' : 'hash' if (options.mode) this.mode = options.mode if (options.root) this.root = options.root >> export default Router
Добавление и удаление маршрутизаторов
Добавление и удаление маршрутизаторов осуществляется через добавление и удаление элементов массива:
class Router < routes = [] mode = null root = '/' constructor(options) < this.mode = window.history.pushState ? 'history' : 'hash' if (options.mode) this.mode = options.mode if (options.root) this.root = options.root >add = (path, cb) => < this.routes.push(< path, cb >) return this > remove = path => < for (let i = 0; i < this.routes.length; i += 1) < if (this.routes[i].path === path) < this.routes.slice(i, 1) return this >> return this > flush = () => < this.routes = [] return this >> export default Router
Получение текущего пути
Мы должны знать, где находимся в приложении в определенный момент времени.
Для этого нам потребуется обработка обоих режимов (истории и хеша). В первом случае, нам нужно удалить путь к корневому элементу из window.location, во втором — «#». Нам также необходима функция (clearSlash) для удаления всех маршрутизаторов (строки от начала до конца):
[. ] clearSlashes = path => path .toString() .replace(/\/$/, '') .replace(/^\//, '') getFragment = () => < let fragment = '' if (this.mode === 'history') < fragment = this.clearSlashes(decodeURI(window.location.pathname + window.location.search)) fragment = fragment.replace(/\?(.*)$/, '') fragment = this.root !== '/' ? fragment.replace(this.root, '') : fragment >else < const match = window.location.href.match(/#(.*)$/) fragment = match ? match[1] : '' >return this.clearSlashes(fragment) > > export default Router
Навигация
Ок, у нас имеется API для добавления и удаления URL. Также у нас имеется возможность получать текущий адрес. Следующий шаг — навигация по маршрутизатору. Работаем со свойством «mode»:
[. ] getFragment = () => < let fragment = '' if (this.mode === 'history') < fragment = this.clearSlashes(decodeURI(window.location.pathname + window.location.search)) fragment = fragment.replace(/\?(.*)$/, '') fragment = this.root !== '/' ? fragment.replace(this.root, '') : fragment >else < const match = window.location.href.match(/#(.*)$/) fragment = match ? match[1] : '' >return this.clearSlashes(fragment) > navigate = (path = '') => < if (this.mode === 'history') < window.history.pushState(null, null, this.root + this.clearSlashes(path)) >else < window.location.href = `$#$` > return this > > export default Router
Наблюдаем за изменениями
Теперь нам нужна логика для отслеживания изменений адреса как с помощью ссылки, так и с помощью созданного нами метода «navigate». Также нам необходимо обеспечить рендеринг правильной страницы при первом посещении. Мы могли бы использовать состояние приложения для регистрации изменений, однако в целях изучения сделаем это с помощью setInterval:
class Router < routes = []; mode = null; root = "/"; constructor(options) < this.mode = window.history.pushState ? "history" : "hash"; if (options.mode) this.mode = options.mode; if (options.root) this.root = options.root; this.listen(); >[. ] listen = () => < clearInterval(this.interval) this.interval = setInterval(this.interval, 50) >interval = () => < if (this.current === this.getFragment()) return this.current = this.getFragment() this.routes.some(route =>< const match = this.current.match(route.path) if (match) < match.shift() route.cb.apply(<>, match) return match > return false >) > > export default Router
Заключение
Наша библиотека готова к использованию. Она состоит всего лишь из 84 строчек кода!
Код и пример использования на Github.
Маршруты¶
Идея маршрута (англ. — route) впервые появилась в Ruby on Rails и быстро обрела популярность в других веб-фреймворках. Также концептуально очень близкой системой является URLConf в Django. В последующих разработках наиболее мощной реализацией данной идеи, вероятно, является http://routes.groovie.org/, используемая в Pylons.
Маршруты отвечают за ключевую проблему веб-разработки: сопоставление кода и URL. Например, какой код должен отвечать за обработку запросов по адресу «/2008/01/08» или «/login» ? Во многих фреймворках используется фиксированная система диспетчеризации, например «/A/B/C» означает прочитать файл «C» в каталоге «B» (например /auth/login.php или /cgi-bin/hello.cgi ), или вызвать метод «С» класса «B» в модуле «A».
Это работает прекрасно до тех пор, пока не возникает необходимости в реорганизации кода, и выясняется, что закладки пользователей стали недействительны. Кроме того, если вы хотите переделать адреса (например, создать раздел в подразделе), то нужно изменить уже отлаженную логику по генерации ссылок внутри сайта.
Маршруты предлагают иной подход. Вы определяете шаблоны URL и связываете их со своим кодом. Если вы измените свое решение по поводу конкретного URL , то просто поменяйте шаблон URL — код по-прежнему будет работать отлично, и не понадобится менять какую-либо логику.
Сопоставление с образом¶
- Сопоставление с образом
- Python -> Selector
Регулярные выражения дают огромные возможности для обработки URL-путей, но из-за ограничений, описанных в стандарте RFC 1738, большинство из них не нужны, при этом использование регулярных выражений затрудняет читабельность кода. Более современный подход придуманный Ruby on Rails это использовать технологию сопоставление с образом . Рассмотрим отличия на примере нашего блога:
Исходный код доступен по адресу:
Примеры работают только в Python3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
from views import BlogRead, BlogIndex, BlogCreate, BlogDelete, BlogUpdate from wsgi_basic_auth import BasicAuth # third-party import selector def make_wsgi_app(): passwd = 'admin': '123' > # BasicAuth applications create = BasicAuth(BlogCreate, 'www', passwd) update = BasicAuth(BlogUpdate, 'www', passwd) delete = BasicAuth(BlogDelete, 'www', passwd) # URL dispatching middleware dispatch = selector.Selector() dispatch.add('/', GET=BlogIndex) dispatch.prefix = '/article' dispatch.add('/add', GET=create, POST=create) dispatch.add('/', GET=BlogRead) dispatch.add('//edit', GET=update, POST=update) dispatch.add('//delete', GET=delete) return dispatch if __name__ == '__main__': from paste.httpserver import serve app = make_wsgi_app() serve(app, host='0.0.0.0', port=8000)
Регулярные выражения | Сопоставление с образом |
---|---|
/ | / |
/article/add | /article/add |
^/article/(?Pd+)/$ | /article/ |
^/article/(?Pd+)/edit$ | /article//edit |
^/article/(?Pd+)/delete$ | /article//delete |
Previous: Разделение кода Next: Шаблоны
© Copyright 2020, Кафедра Интеллектуальных Информационных Технологий ИнФО УрФУ. Created using Sphinx 1.7.6.
PHP-роутинг (Routing) для новичков
Роутинг — это маршрутизация: входящий URL разбирается специальным образом и по его результату выполняется определенный код. С роутингом напрямую связано понятие ЧПУ (человекопонятные урлы), которое позволяет исключить в адресах сложные параметры. Например вместо http://сайт/admin/new-page пришлось бы использовать http://сайт/admin.php?action=new-page
Любой входящий URL на сервере разбирается по единому стандарту. Полностью приводить документацию не буду (см. как пример функцию parse_url), важно лишь понять, что в адресе передается параметр path (путь на сервере), которого на сервере реально может не быть. Например в адресе http://сайт/admin каталога admin реально может не существовать.
То есть сервер, получив такой адрес, попытается найти каталог admin , но не найдя его, выдаст 404-страницу (not found).
Чтобы исключить такой вариант, серверу указывается, что для всех несуществующих каталогов и файлов, подключать php-файл (обычно index.php ).
Делается это в файле .htaccess с помощью Apache-модуля mod_rewrite. Вот довольно типовой вариант (MaxSite CMS):
RewriteCond % !-f RewriteCond % !-d RewriteRule ^(.*)$ /index.php/$1 [L,QSA]
Тут главная строчка с RewriteRule — именно она определяет шаблон входящего адреса (в примере это регулярное выражение) и что с ним делать. В данном примере будет подключен index.php с параметрами после слэша.
Строчка RewriteCond % !-f / -d указывает исключить из обработки реально существующие на сервере файлы и каталоги.
Похожий вариант, только чуть короче, от WordPress:
RewriteRule . /index.php [L]
RewriteRule ^(.*)$ /index.php?page=$1 [QSA]
Здесь принудительно добавляется query-параметр page.
Еще один распространенный вариант (пожалуй самый «типовой»):
RewriteRule (.*) index.php?$1 [QSA,L]
Все эти RewriteRule-правила делают простую вещь: как бы «преобразуют» входящий адрес в набор query-параметров. Например адрес http://сайт/admin превратится в http://сайт/index.php?admin
Посмотрите на полный код .htaccess :
RewriteEngine on RewriteBase / RewriteCond % !-f RewriteCond % !-d RewriteRule (.*) /index.php?$1 [QSA,L]
Если это какой-то подкаталог, то он указываетс в RewriteBase и как путь к php-файлу. Например каталог на сервере route :
RewriteEngine on RewriteBase /route/ RewriteCond % !-f RewriteCond % !-d RewriteRule (.*) /route/index.php?$1 [QSA,L]
Теперь, все адреса на сервере будут передаваться в файл index.php , а исходный адрес сохранится в виде query-параметра.
Если в index.php разместить
То мы можем увидеть query-параметры. В PHP за это отвечается суперглобальная переменная $_GET . Например для http://сайт/admin это будет admin , для http://сайт/admin/new-page — admin/new-page .
Таким образом, с помощью .htaccess происходит первая часть роутинга, где мы получаем готовый $_GET .
Кстати, насчет .htaccess WordPress. Он не создает $_GET , поэтому придется использовать $_SERVER[‘REQUEST_URI’] в который включается подкаталог. Работать с таким адресом уже будет сложней.
Второй этап роутинга выполняется полностью на PHP. Получив $_GET нужно решить что с ним делать. Например если адрес admin, подключить файл admin.php .
Существуют несколько принципиально разных подходов в организации роутинга. Наиболее популярный подход — это когда в адресе передаётся «действие», которое описывается через php-класс. Такой подход хорошо описан в CodeIgniter:
example.com/class/function/id/
Например пусть будет класс admin в нём метод edit, принимающий параметр $id.
example.com/admin/edit/23 class admin < function edit($id) < . >>
Это сильно утрированный пример, но он хорошо показывает соответствие адреса и php-класса.
Другой вариант похожий, но используется не классы, а функции.
example.com/admin/edit/23 function admin($params)
То есть функция — это первый сегмент, а остальные выступают уже как парметры. Встречается более «продвинутый» вариант.
example.com/admin function admin($params) < . >example.com/admin/edit/23 function admin_edit($params)
То есть имя функции строится по сегментам URL.
Третий, тоже распространенный вариант — адрес указывает на подключаемый файл.
example.com/admin $fn = 'pages/admin.php'; if (file_exists($fn)) reqiure($fn); else reqiure('pages/404.php');
Здесь все файлы хранятся в каталоге pages и подключаются только если реально существуют. Если файла нет, то подключается предопределенный 404-файл.
Если сайт представляет собой сложный проект, то как правило используют роутинг на основе php-классов. Так его проще поддерживать и развивать. Если же сайт состоит из небольшого количества страниц, то как правило используют подключаемые файлы, где один файл — это одна страница.
Строго говоря, роутинг «на классах» тоже использует «файловое» подключение. Вначале подключается файл с кодом класса, а уже после этого выполняется сам класс.
В задачу роутинга входит не только необходимое «действие», но и валидация входящего адреса и его лексический разбор.
Адреса могут строиться по шаблону. Например какой-то адрес должен содержать только номер, а не текст (например example.com/admin/edit/23 , но не example.com/admin/edit/hello ). Или адрес может быть неизменным, но обработчик будет меняться от вида запроса GET или POST. В одном случае нужно подключить одну функцию/файл, в другом — другой. Бывают и более сложные задачи, все их перечислять нет смысла, поэтому появились разного рода php-библиотеки для организации роутинга.
Свой «велосипед» не изобретал только ленивый, но я отмечу довольно известный FastRoute, который вобрал в себя наиболее типичные решения.
В первую очередь это использование регулярных выражений при задании правил, например:
$r->addRoute('GET', '/user/', 'handler1');
Примерно такой же подход используется и в роутинге CodeIgniter.
products/shirts/123 $route['products/([a-z]+)/(d+)'] = "$1/id_$2";
То есть входящий адрес должен соответствовать шаблону и только в этом случае он «сработает».
В FastRoute реализована поддержка POST и GET-запросов. Такая возможность интересна, хотя на больших проектах такие вещи лучше делать на уровне самого «действия». Но это уже тонкости. Про эту библиотеку я упоминаю в первую очередь из-за того, что она достаточно популярна и уже используется в нескольких интересных проектах: Slim и Lumen.
Для небольших проектов, конечно же, FastRoute будет избыточна, поэтому можно ограничиться вариантом попроще, да и мозги потренировать. 🙂
ТЗ. Пусть роутинг будет обрабатывать адреса вида http://сайт/blog/contact/map , по которому будет подключен файл content/blog/contact/map/index.php , где каталог content — это общее хранилище всех страниц сайта, а index.php — обязательный файл с кодом страницы. Роутинг должен проверять реальный файл и если его нет, то подключать 404-страницу. Если никаких параметров нет, то это home-страница. Пусть они будут предопределены.
Решение. По сути задача сводится к преобразованию входящего адреса в путь на сервере. У нас есть массив $_GET , где первый ключ и есть входящий URL. Дальше формируем путь к файлу и если он есть, то подключаем. Если нет, то подключаем 404-страницу.
Весь код в 2 строчки:
Это простой роутинг для простых проектов. Достаточно создать в content каталог, как он станет доступен по одноименному адресу.
Что такое роутинг или маршрутизация простыми словами для чайников
Маршрутизатор или роутер — это аппаратное средство, которое обрабатывает отправляемые или получаемые потоки данных. В роли маршрутизаторов часто выступают отдельные устройства , что обеспечивает максимально быструю работу с данными , или обычные компьютеры с правильно настроенным специализированным программным обеспечением.
Роутинг — это сложный механизм передачи данных
Роутинг бывает разный, например, различают два основных вида роутинга:
- Прямой роутинг — это когда данные могут передаваться внутри одной сети, минуя IP-маршрутизацию. При таком подходе перед отправкой данных узел отправителя проверяет, находится ли получатель с ним в одной сети. И если это так, тогда отправитель отправляет на адрес получателя необходимый пакет данных. Для «определения адреса» в таком подходе есть даже собственный протокол ARP (Address Resolution Protocol).
- Косвенный роутинг — это когда пакеты с данными передаются между разными IP-сетями. В этом случае при передаче пакетов есть «посредник», он же маршрутизатор, он же роутер. При таком подходе отправитель передает пакет с данными маршрутизатору, а тот уже доставляет данные по нужному адресу.
Что должен выяснить роутер, чтобы отправить данные:
- Как правило, к одному роутеру подключа е т ся несколько различных интерфейсов разных сетей. Поэтому роутеру в первую очередь необходимо определить , в какой интерфейс отправлять пакет данных.
- Следующим шагом роутер должен выяснить, что конкретно нужно сделать с данными. Тут у роутера есть 2 решения: либо он передает пакет данных сразу в сеть, либо он передает данные другому маршрутизатору в этой сети. Когда он передает данные другому роутеру, то ему нужно точно знать , какому именно передать , ч тобы именно к передаваемому роутеру была подключена сеть с получателем.
Иногда так происходит , что на роутер приходит пакет с данными, для которого не ясен маршрут и не ясен получатель. В этом случа е маршрутизатор просто «выбрасывает» пакет с данными в сеть, тем самым «засоряя» сеть «мусорными пакетами».
Таблицы роутинга
- Адрес шлюза — это адрес самого роутера и других роутеров, на которые отправляются пакеты с данными.
- Интерфейс — это физические порты, по которым осуществляется движение пакетов.
- Метрику — числовое значение, определяющее приоритет маршрута.
- Маску подсети — это битовое значение, которое помогает определить по заданному IP-адресу адреса отдельных узлов подсети и адрес самой подсети.
- Сетевой адрес — это ID устройства, подключенного к общей сети.
Как записываются данные в таблицу?
Этот вопрос можно перефразировать так: «Как и кем составляются маршруты при передач е данных?». Маршрут может задаваться 3-мя способами:
- Роутер сам прописывает маршрут передачи и осуществляет записи в таблицу. Такой способ применим по «прямому маршруту», когда передача данных осуществляется внутри одной сети.
- Маршруты можно прописать «вручную». При таком подходе прописывается адрес следующего соседнего роу те ра, которому передаются пакеты данных, а он уже распределяет их по подключенным к нему сетям.
- Маршруты прописываются автоматически, используя протоколы маршрутизации. Данные протоколы самостоятельно отслеживают изменения в компоновке сети и вносят соответствующие коррективы в таблицу маршрутов.
Как рассчитывается маршрут роутинга
Между получателем и отправителем может быть организовано несколько маршрутов передачи пакетов информации. Чтобы решить , по какому маршруту передать информацию, роутер проводит расчет лучшего маршрута, сравнивая их метрики, и вносит в таблицу маршрутов самый оптимальный из доступных.
При расчете метрик маршрута бер у тся во внимание:
- возможности полосы пропускания;
- время, необходимое для перемещения пакета от отправителя к получателю;
- загруженность канала передачи информации в момент времени;
- насколько надежен потенциальный канал передачи, то есть есть ли в нем какие-либо ошибки;
- потенциальное количество переходов между роутерами.
Сравнивая все описанные свойства маршрута, роутер задает этому маршруту определенное числовое значение. Данное числовое значение принадлежит только этому маршруту и вносится в таблицу маршрутов в графу «Метрика». И если для передачи данных буд е т доступно несколько маршрутов, то роутер выберет тот, у которого будет меньшее число метрики.
Заключение
Роутинг — это неосязаемый процесс, который виден , только если его специально просмотреть. Даже сейчас, читая нашу статью, вы просто не замечаете , сколько незримых процессов происходит, а их происходит очень много.
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.