Роутинг что это такое
Маршрутизация — что это такое и как работает
Практически каждый пользователь интернета, который читал материалы на тему работы глобальной паутины и ее нюансах в целом слышали про маршрутизацию и, некоторые в какой-то степени даже понимают, что это.
Но, это довольно интересная тема и я решил вынести ее в отдельную статью, давайте узнаем, что это такое, как она работает, зачем она вообще нужна и какие у нее бывают виды. На самом деле это все довольно просто.
Итак, вы уже знаете, что такое UDP протокол и зачем он нужен, сейчас же мы разберем не менее интересную тему о том, как строятся пути для передачи информации в IP сетях.
Что такое маршрутизация
Маршрутизация (Routing) — это процесс по определению/вычислению лучшего маршрута движения для данных в сетях связи. Есть еще второе определение — это передача пакетов данных от отправителя к получателю.
Сами маршруты могут быть статическими — задаются административно, или динамическими, т.е. рассчитываться по специальным алгоритмам-протоколам, которые базируются на данных о топологии и состоянии сети.
Функцию роутинга могут выполнять:
Таблица маршрутизации
Таблица маршрутизации — это файл-электронная таблица или база данных, которая располагается на маршрутизаторе или специально настроенном компьютере. В ней описывается соответствие адресов назначения с интерфейсами, через которые необходимо производить отправку данных до следующего маршрутизатора.
Таблица содержит:
Может заполняться как вручную, так и автоматически.
Протокол маршрутизации
Протокол маршрутизации используется маршрутизаторами для определения возможных и оптимальных маршрутов движения пакетов данных по сети. Текущий протокол позволяет обеспечивать маршрут на автоматическом уровне избегая ручного ввода.
Это в свою очередь снижает возможное количество ошибок, делает взаимодействие всех роутеров в сети согласованным и, конечно же, облегчает работу самому администратору.
Маршрутизация в IP сетях
По итогу, все это нужно для отправки и приема пакетов данных от одного устройства по сетке к другому и это может происходить через разные сети. Сами маршрутизаторы/роутеры отправляют данные практически во все сети, из локальной в глобальную паутину, используют NAT и т.д.
Информация на роутер поступает от других таких-же роутеров или от администратора. Составляется таблица — вручную или динамически. И, соответственно, пакеты данных отправляются.
Статическая маршрутизация
В данном случае маршруты задаются в таблице напрямую администратором во время конфигурации маршрутизатора, без использования протоколов, помогающих построить пути.
При задании такого маршрута назначается:
Плюсы:
Минусы:
Динамическая маршрутизация
В этом случае таблица редактируется на программном уровне и рассчитывается по протоколам. Позволяет маршрутизаторам в реальном времени своевременно менять пути, применяемые для передачи IP пакетов. У каждого протокола есть свой метод определения маршрута движения пакетов: самый быстрый путь, использование именно того маршрута, который рекомендуют другие роутеры и т.д.
Так, если в сети произойдут какие-либо изменения, то протокол динамической маршрутизации оповестит об этом все маршрутизаторы, а вот при статической, все придется делать администратору.
В заключение
Работая в интернете или читая там занимательную литературу, вы даже не замечаете сколько процессов происходит, а происходит там очень много. Если вам и дальше интересны материалы на темы работы интернета, то заходите еще.
Хорошо настроенный роутинг фронтенда
Хочешь проверить свои знания по фронтенду?
Подпишись на наш канал с тестами по HTML/CSS/JS в Telegram!
Перевод статьи Хайме Ямасаки Вукелика «Client-side routing done right».

В этой статье я не буду доказывать, что невозможно идеально настроить роутинг фронтенд-приложения, хотя именно такой точки зрения и придерживаюсь. Я просто развлеку вас отрывками кода и, возможно, вы иначе посмотрите на какие-то вещи.
Шаблон, представленный в этой статье, я сформулировал несколько недель назад. Хочу поделиться им с вами, потому что он существенно улучшил наше приложение.
Что такое «роутинг»?
Если вы не знаете, что такое роутинг фронтенда, вам повезло. Не стоит углубляться в эту тему. Но поскольку вы все равно рано или поздно с этим столкнетесь, я все же поясню.
Концепция роутинга существовала в бэкенд-приложениях задолго до того, как проникла во фронтенд. Там она прекрасно справлялась со своими задачами. Если вы когда-либо писали бэкенд-приложения, вы знаете, насколько необходим роутинг.
Словарное определение роутинга это «направление по определенному пути». В этом вся суть. Вы сопоставляете URL-шаблоны с какими-то функциями вашего кода (или с компонентами, классами и т. д.). А когда пользователь обращается к вашему приложению по этому URL-шаблону, вы направляете его к нужному участку вашего кода, который будет обрабатывать запрос. Конечно, было бы классно, если бы все было так просто, но это не так. Во фронтенде вещи не столь прямолинейны, как в бэкенде.
В этой статье я буду использовать некоторые предопределенные шаблоны (а также регулярные выражения), которые выглядят как URL-адреса.
Если вы видите что-то вроде:
Теперь, когда мы побегали, давайте рассмотрим, как решаются проблемы роутинга.
Как насчет сохраненных URL-ссылок?
Когда пользователь нажимает на одну из своих закладок в браузере или вставляет в строку браузера сохраненный URL, приложение должно инициализироваться в определенном состоянии. Например, если пользователь может делиться какой-то информацией в вашем приложении, он может скопировать URL и послать его другу, а друг, пройдя по ссылке, должен увидеть то же самое (более или менее), что видит ваш пользователь.
Возможность делать закладки на определенном состоянии приложения (или его частях) не зависит от того, используете ли вы для роутинга фронтенд-библиотеки. Фактически, это не обязательно. Когда ваше приложение загружается, вам просто нужно посмотреть window.location и понять, каким должно быть начальное состояние (дальше об этом будет подробнее).
Что, если пользователь нажмет клавишу «назад»?
Да, пользователи – они такие. Они обязательно нажмут клавишу «назад». Хотел бы я, чтоб было иначе, но они все равно это сделают. И когда пользователь нажимает эту кнопку, он ожидает, что приложение поведет себя правильным образом.
Это хорошо, но разве библиотеки роутинга не решают эту задачу? Конечно, решают. (А если нет, то их вряд ли можно называть фронтенд-библиотеками роутинга). Но давайте вернемся назад (каламбур) и подумаем над тем, что же они делают на самом деле.
Все, чем они занимаются, это управляют событием popstate. Это довольно простое событие: оно просто дает вам знать, что URL в адресной строке изменился. Затем вы можете обратиться к window.location и получить всю необходимую информацию.
В нашем случае все немного отличается, потому что состояние не инициализируется, а изменяется. Но это касается любого события в вашем приложении. Так почему же с popstate что-то должно быть по-другому, верно?
А теперь о хорошем
Я все время говорю «обратиться к window.location». Если вы считаете, что URL-адреса – это просто сериализованное состояние приложения (ну, не все), вы можете существенно изменить свое понятие об URL-адресах, и осознать, что да, на самом деле роутинг вам не нужен.
Некоторые части состояния вашего приложения важны по двум указанным выше причинам (вероятно, и по другим причинам тоже, просто сейчас не могу о них вспомнить). Какие «некоторые части» при этом имеются в виду, зависит от вашего приложения, поэтому я просто приведу пример с приложением, над которым сейчас работаю.
В нашем случае приложение разделено на модули (нет, я не собираюсь сейчас убеждать вас в необходимости модульности кода, у нас модуль – просто часть бизнес-концепции). Эти модули делятся на секции. Чтобы инициализировать любую комбинацию модуль-секция в нашем приложении, нам нужно знать, о каком модуле и секции идет речь, а также нужную для этого модуля информацию. Например, модуль компании должен знать ID компании.
Для полного описания любого представления в нашем приложении у нас есть объект, который выглядит вот так:
[code] <
module: ‘company’,
moduleArgs: [‘some-id-123’],
section: ‘profile’,
sectionArgs: <>,
>[/code]
Это просто JavaScript-объект. В отличие от URLs, его легко создать и легко им манипулировать с помощью инструментов, которые у вас уже есть и с которыми вы уже знакомы. Так же, как и с любыми данными в вашем приложении.
Теперь мы преобразуем это в URL, придерживаясь следующих правил:
Объект, указанный в примере, преобразуется в такой URL:
Все эти правила произвольны, просто для нашего приложения они подходят. В вашем приложении правила могут быть другими, это зависит от ваших нужд. Например, вы можете сказать, что второй сегмент пути это секция вашего приложения, потому что у вас всегда есть одна секция. Мы решили, что секция не может быть частью пути, потому что некоторые модули состоят лишь из одной секции. В общем, вы поняли.
У нас есть модуль, заботящийся о конверсии между URL и объектом. Это довольно просто, поэтому я не буду утомлять вас подробностями. Давайте рассмотрим некоторые примеры этого:
[code]// We are on ‘/company/some-id-123?view=profile’
> loc.toLocation(window.location)
> loc.toURL( < module: ‘lists’, moduleArgs: [‘id-456’],
… section: undefined, sectionArgs: <> >)
‘/lists/id-456’
> loc.toURL(loc.toLocation( <
… pathname: ‘/lists/id-456’,
… search: »
… >))
‘/lists/id-456′[/code]
Эти две функции реверсивны: если вы направите значение через обе, в конце получите начальное значение. (Что-то вроде. ToLocation() будет использовать объект window.location, так что мы получим строку, представляющую URL этого объекта местоположения). Это очень важно для надежности, и у нас есть отдельные тесты для проверки этого поведения.
Но это еще не все. Чтобы поддерживать синхронность URL и состояния приложения, нам нужно инициализировать состояние приложения, реагировать на изменения в URL, а также реагировать на изменения в состоянии.
Когда приложение инициализируется, мы конвертируем window.location в данные, специфичные для приложения, и устанавливаем начальное состояние. В Vue это может выглядеть так:
[code]@Component
class ModuleSelector extends Vue <
data() <
return <
location: loc.toLocation(window.location)
>
>
Теперь свойство location является реактивным состоянием, содержащим данные, инициализированные из текущего местонахождения.
По мере того как пользователь будет взаимодействовать с приложением, нам понадобится способ направлять пользователя к другим местам. Для этого у нас есть специальный сервис.
Примечание: есть одна хитрость в том, как сервис goToLocation() управляет partial location objects. Благодаря ей нам не нужно все переписывать при изменении чего-то одного.
Этот сервис преобразует объект локации в URL и использует window.history.pushState() для обновления адресной строки. Он также передает событие locationChange, специфичное для данного приложения, что позволяет приложению обновить свое состояние. URL в адресной строке и состояние приложения всегда синхронизируются.
Когда пользователь в ходе сессии кликает на кнопку «Назад», мы выдаем событие locationChange, и состояние опять синхронизируется приблизительно таким же образом.
Компонент Vue, который вы видели раньше, имеет такой инструмент:
[code]@Component
class ModuleSelector extends Vue <
// …
@Listen(‘locationChange’)
setLocation(newLocation) <
this.location = newLocation
>
>[/code]
И последний отрывок касается рендеринга соответствующего модуля:
[code]@Component
class ModuleSelector extends Vue <
render() <
// some JSX magic here
const ModuleComponent = VIEWS[this.location.module]
return
>
// …
>[/code]
Объект VIEWS это лишь сопоставление имен и компонентов, отображающих представление этого модуля. Например:
[code]const VIEWS = <
company: Company,
lists: Lists,
>[/code]
Вот и все. Я уверен, что идею вы поняли. Если вы видите что-то, что вам не нравится, это потому, что это наше приложение, а не ваше. Вы можете сделать все по-другому. Собственно, это даже обязательно, потому что вам нужно сделать так, как будет лучше всего для вашего приложения. Единого рецепта нет.
Погодите, но это может делать роутер!
При более традиционном роутинге технически вы могли бы сделать кое-что из этого. Например, если роутер поддерживает чего-либо после первоначального совпадения (например, /:module/* ). Частично это поможет адресовать URLs к домену приложения. Но как же с обратными ситуациями? Большинство роутеров такого не делают, и чтобы направить браузер по другому адресу, просят вас использовать URL. Адреса заботят нас в той же степени, что и JSON payloads. На самом деле мы заботимся о данных.
Так что нет, роутеры обычно не могут делать то, о чем шла речь выше. Потому что они имеют дело с URL-шаблонами, а не с данными. Они не рассматривают URLs в качестве сосудов для ваших данных. Вместо этого они перетаскивают URLs в ваше приложение.
Немного не в тему, есть также библиотеки роутинга, занимающиеся переходами и еще много чем. Они не только неспособны обеспечить функционал, специфичный для вашего приложения, но и предоставляют функции, которые, вероятно, в вашем коде представления уже есть!
Покажите мне код!
Дело в том, что вам не нужен наш код. Наш код специфичен для нашего приложения. Вам стоит обратиться к MDN, где вы найдете все, что вам нужно знать.
Лишь от вас зависит, что использовать для передачи изменений локации: события, Redux store, Vuex или что-то еще. То, как вы напишете код состояния и его частей, также зависит только от вас. Это часть вашего приложения, так что вы вольны делать все, что хотите, в этом и вся прелесть. А также, как вы могли заметить (надеюсь), это достаточно просто.
Давайте быстро повторим необходимые шаги. Вам нужно:
Любой код помимо APIs браузера принадлежит к вашему приложению.
Я надеюсь, что эта статья дала вам понять, что для современных приложений, ориентирующихся на состояние приложения, классический роутинг это нечто ненужное. Он может делать что-то из описанного здесь, но это как вырезать ложки бензопилой. Не существует правильного подхода к такому вырезанию.
Роутинг
Маршрутизация (англ. Routing ) — процесс определения маршрута следования информации в сетях связи. В русском языке часто используется слово «роутинг». Надо заметить, что правильное произношение этого слова — «рутинг». (В США произносится «раутинг», соответственно маршрутизатор — «раутер»)
Маршруты могут задаваться административно (статические маршруты), либо вычисляться с помощью алгоритмов маршрутизации, базируясь на информации о топологии и состоянии сети, полученной с помощью протоколов маршрутизации (динамические маршруты).
Статическими маршрутами могут быть:
Процесс маршрутизации в компьютерных сетях выполняется специальными программно-аппаратными средствами — маршрутизаторами. Название идёт от самого процесса (основной функции) — маршрутизации. В дополнение к маршрутизации, маршрутизаторы осуществляют и коммутацию каналов/сообщений/пакетов/ячеек, так же, как и коммутатор компьютерной сети выполняет маршрутизацию (определение на какой порт отправить пакет на основании таблицы MAC адресов), а называется в честь основной его функции — коммутации.
Содержание
Маршрутизируемые протоколы
Протокол маршрутизации может работать только с пакетами, принадлежащими к одному из маршрутизируемых протоколов, например, IPX или Xerox Network System. Маршрутизируемые протоколы определяют формат пакетов (заголовков), важнейшей информацией из которых для маршрутизации является адрес назначения. Протоколы, не поддерживающие маршрутизацию, могут передаваться между сетями с помощью туннелей. Подобные возможности обычно предоставляют программные маршрутизаторы и некоторые модели аппаратных маршрутизаторов. В сети желательно использовать какой-либо один маршрутизируемый протокол, так как некоторые маршрутизаторы допускают совмещение разных протоколов и это всегда снижает производительность сети.
Программная и аппаратная маршрутизация
В современных аппаратных маршрутизаторах для построения таблиц маршрутизации используется специализированное ПО («прошивка»), для обработки же IP-пакетов используется коммутационная матрица (или другая технология аппаратной коммутации), расширенная фильтрами адресов в заголовке IP-пакета.
Аппаратная маршрутизация [1]
Выделяют два типа аппаратной маршрутизации: со статическими шаблонами потоков и с динамически адаптируемыми таблицами.
Статические шаблоны потоков подразумевают разделение всех входящих в маршрутизатор IP-пакетов на виртуальные потоки; каждый поток характеризуется набором признаков для пакета: IP-адресами отправителя/получателя, TCP/UDP-порт отправителя/получателя (в случае поддержки маршрутизации на основании информации 4 уровня), порт, через который пришёл пакет. Оптимизация маршрутизации при этом строится на идее, что все пакеты с одинаковыми признаками должны обрабатываться одинаково (по одинаковым правилам), при этом правила проверяются только для первого пакета в потоке (при появлении пакета с набором признаков, не укладывающимся в существующие потоки, создаётся новый поток), по результатам анализа этого пакета формируется статический шаблон, который и используется для определения правил коммутации приходящих пакетов (внутри потока). Обычно время хранения неиспользующегося шаблона ограничено (для освобождения ресурсов маршрутизатора). Ключевым недостатком подобной схемы является инерциональность по отношению к изменению таблицы маршрутизации (в случае существующего потока изменение правил маршрутизации пакетов не будет «замечено» до момента удаления шаблона).
Динамически адаптированные таблицы используют правила маршрутизации «напрямую», используя маску и номер сети из таблицы маршрутизации для проверки пакета и определения порта, на который нужно передать пакет. При этом изменения в таблице маршрутизации (в результате работы, например, протоколов маршрутизации/резервирования) сразу же влияют на обработку всех новопришедших пакетов. Динамически адаптированные таблицы также позволяют легко реализовывать быструю (аппаратную) проверку списков доступа.
Программная маршрутизация
Программная маршрутизация выполняется либо специализированным ПО маршрутизаторов (в случае, когда аппаратные методы не могут быть использованы, например, в случае организации туннелей), либо программным обеспечением на компьютере. В общем случае, любой компьютер осуществляет маршрутизацию своих собственных исходящих пакетов (как минимум, для разделения пакетов, отправляемых на шлюз по умолчанию и пакетов, предназначенных узлам в локальном сегменте сети). Для маршрутизации чужих IP-пакетов, а также построения таблиц маршрутизации используется различное ПО:
Pilot: многофункциональный JavaScript роутер
С каждым днем сайты становятся все сложнее и динамичнее. Уже недостаточно просто «оживить» интерфейс — все чаще требуется создать полноценное одностраничное приложение. Ярким примером такого приложения является любая web-почта (например, Mail.Ru), где переходы по ссылкам приводят не к перезагрузке страницы, а только к смене представления. А это значит, что задача получения данных и их отображения в зависимости от маршрута, которая всегда была прерогативой сервера, ложится на клиент. Обычно эту проблему решают с помощью простенького роутера, на основе регулярных выражений, и дальше не развивают, в то время как на back-end этой теме уделяют гораздо больше внимания. В этой статье я постараюсь восполнить этот пробел.
Что такое роутинг?
Это, наверное, самая недооцененная часть JavaScript-приложения :]
На сервере роутинг — это процесс определения маршрута внутри приложения в зависимости от запроса. Проще говоря, это поиск контроллера по запрошенному URL и выполнение соответствующих действий.
Схематично приложение будет выглядеть следующим образом:
Каждому экрану будет соответствовать свой URL, и роутер, их описывающий, может выглядеть, например, так:
В объекте `routes` непосредственно задаются маршруты: ключ — шаблон пути, а значение — название функции-контроллера.
Далее нужно преобразовать ключи объекта `Router.routes` в регулярные выражения. Для этого определим метод `Router.init`:
Осталось описать метод навигации, который будет осуществлять поиск маршрута и вызов контроллера:
Когда всё готово, инициализируем роутер и выставляем начальную точку навигации. Важно не забыть перехватить событие `click` со всех ссылок и перенаправить на маршрутизатор.
Как видите, ничего сложного; думаю, многим знаком подобный подход. Обычно все отличия в реализациях сводятся к формату записи маршрута и его трансформации в регулярное выражение.
Вернемся к нашему примеру. Единственное, что в нем отсутствует — это реализация функций, отвечающих за обработку маршрута. Обычно в них идет сбор данных и отрисовка, например, так:
На первый взгляд, выглядит неплохо, но есть и подводные камни. Получение данных происходит асинхронно, и при быстром перемещении между маршрутами можно получить совсем не то, что ожидалось. Например, рассмотрим такую ситуацию: пользователь кликнул на ссылку второй страницы галереи, но в процессе загрузки заинтересовался произведением с первой страницы и решил посмотреть его подробно. В итоге ушло два запроса. Отработать они могут в произвольном порядке, и пользователь вместо картины получит вторую страницу галереи.
Эту проблему можно решить разными способами; каждый выбирает свой путь. Например, можно вызвать `abort` для предыдущего запроса, или перенести логику в `ManagerView.set`.
Что же делает `ManagerView`? Метод `set(name, data)` принимает два параметра: название «экрана» и «данные» для его построения. В нашем случае задача сильно упрощена, и метод `set` отображает нужный элемент по id. Он использует название вида как постфикс `«app-»+name`, а данные — для построения html. Также `ManagerView` должен запоминать название предыдущего экрана и определять, когда начался/изменился/закончился маршрут, чтобы корректно манипулировать внешним видом.
Вот мы и создали одностраничное приложение, со своим `Router` и `ManagerView`, но пройдет время, и нужно будет добавить новый функционал. Например, раздел «Статьи», где будут описания «работ» и ссылки на них. При переходе на просмотр «работы» нужно построить ссылку «Назад к статье» или «Назад в галерею», в зависимости от того, откуда пришел пользователь. Но как это сделать? Ни `ManagerView`, ни `Router` не обладают подобными данными.
Также остался ещё один важный момент — это ссылки. Постраничная навигация, ссылки на разделы и т.п., как их «строить»? «Зашить» прямо в код? Создать функцию, которая будет возвращать URL по мнемонике и параметрам? Первый вариант совсем плохой, второй лучше, но не идеален. С моей точки зрения, наилучший вариант — это возможность задать `id` маршрута и метод, который позволяет получать URL по ID и параметрам. Это хорошо тем, что маршрут и правило для формирования URL есть одно и то же, к тому же этот вариант не приводит к дублированию логики получения URL.
Pilot
Определим контроллеры, отвечающие за маршруты. HTML-структура приложения остается той же, что и в примере в первой части статьи.
Переключение между маршрутами влечет за собой смену экранов, поэтому в примере я использую Pilot.View. Помимо работы с DOM-элементами, экземпляр его класса изначально подписан на события routestart и routeend. При помощи этих событий Pilot.View контролирует отображение связанного с ним DOM-элемента, выставляя ему `display: none` или убирая его. Сам узел назначается через свойство `el`.
Существует три типа событий: routestart, routechange и routeend. Их вызывает роутер на котроллер(ы). Схематично это выглядит так:
Есть три маршрута и их контроллеры:
Каждому маршруту может соответствовать несколько URL. Если новый URL соответствует текущему маршруту, то роутер генерит событие routechage. Если маршрут изменился, то его контроллер получает событие routeend, а контроллер нового — событие routestart.
Помимо изменения видимости контейнера (`this.el`), как правило, нужно обновлять его содержимое. Для этого у Pilot.View есть следующие методы, которые нужно переопределить в зависимости от задачи:
template(data) — метод шаблонизации, внутри которого формируется HTML. В примере используются данные, полученные в loadData.
onRoute(evt, req) — вспомогательное событие. Вызывается после routestart или routechange. В примере используется для обновления содержимого контейнера с помощью вызова метода render.
render() — метод для обновления HTML контейнера (`this.el`). Вызывает this.template(this.getData()).
Теперь осталось собрать приложение. Для этого нам понадобится роутер:
Первым делом мы создаем роутер и в методе `init` определяем маршруты. Маршрут задается методом `route`. Он принимает три аргумента: id маршрута, паттерн и контролер.
Синтаксис маршрута, лукавить не буду, позаимствован у Express. Он подошел по всем пунктам, и тем, кто уже работал с Express, будет проще. Единственное — добавил группы; они позволяют гибче настраивать паттерн маршрута и помогают при навигации по id.
Рассмотрим маршрут, отвечающий за галерею:
Получилось очень удобно: маршрут и URL есть одно и то же. Это позволяет избежать явных URL в коде и необходимости создавать дополнительные методы для формировал URL. Для навигации на нужный маршрут, используется Guide.go(id, params).
Последним действием создается инстанс GuideRouter с опциями перехвата ссылок и использования History API. По умолчанию Pilot работает с location.hash, но есть возможность использовать history.pushState. Для этого нужно установить Pilot.pushState = true. Но, если браузер не поддерживает location.hash или history.pushState, то для полноценной поддержки History API нужно использовать полифил, либо любую другую подходящую библиотеку. При реализации придется переопределить два метода — Pilot.getLocation() и Pilot.setLocation(req).
Вот в целом и всё. Остальные возможности можно узнать из документации.
Жду ваших вопросов, issue и любой другой отдачи :]



