Перейти к основному содержимому

Маршрутизация приложений Play в Java

· 10 мин. чтения

1. Обзор

Маршрутизация — это распространенная концепция, которая используется в большинстве сред веб-разработки, включая Spring MVC .

Маршрут — это шаблон URL, который сопоставляется с обработчиком. Обработчик может быть физическим файлом, например загружаемым ресурсом в веб-приложении, или классом, обрабатывающим запрос, например контроллером в приложении MVC.

В этом руководстве мы рассмотрим аспект маршрутизации при разработке веб-приложений с помощью Play Framework .

2. Настройка

Во-первых, нам нужно создать приложение Java Play. Подробная информация о том, как настроить Play Framework на машине, доступна в нашей вводной статье .

К концу установки у нас должно быть работающее приложение Play, к которому мы можем получить доступ из браузера.

3. HTTP-маршрутизация

Так как же Play узнает, к какому контроллеру обращаться каждый раз, когда мы отправляем HTTP-запрос? Ответ на этот вопрос лежит в конфигурационном файле app/conf/routes .

Маршрутизатор Play преобразует HTTP-запросы в вызовы действий. HTTP-запросы считаются событиями в архитектуре MVC , и маршрутизатор реагирует на них, консультируясь с файлом маршрутов , для какого контроллера и какое действие в этом контроллере выполнять.

Каждое из этих событий предоставляет маршрутизатору два параметра: путь запроса со строкой запроса и HTTP-метод запроса.

4. Базовая маршрутизация с помощью Play

Чтобы маршрутизатор выполнял свою работу, в файле conf/routes должны быть определены сопоставления методов HTTP и шаблонов URI с соответствующими действиями контроллера:

GET     /     controllers.HomeController.index
GET / assets/*file controllers.Assets.versioned(path="/public", file: Asset)

Все файлы маршрутов также должны сопоставлять статические ресурсы в папке play-routing/public , доступной клиенту в конечной точке /assets .

Обратите внимание на синтаксис определения маршрутов HTTP и действие контроллера пространства шаблонов URI пространства методов HTTP. ``

5. Шаблоны URI

В этом разделе мы немного расскажем о шаблонах URI.

5.1. Статические шаблоны URI

Первые три приведенных выше шаблона URI являются статическими. Это означает, что сопоставление URL-адресов с ресурсами происходит без какой-либо дальнейшей обработки в действиях контроллера.

Пока вызывается метод контроллера, он возвращает статический ресурс, содержимое которого определяется до запроса.

5.2. Динамические шаблоны URI

Последний приведенный выше шаблон URI является динамическим. Это означает, что действию контроллера, обслуживающему запрос на эти URI, требуется некоторая информация из запроса для определения ответа. В приведенном выше случае он ожидает имя файла.

Обычная последовательность событий такова, что маршрутизатор получает событие, выбирает путь из URL-адреса, декодирует его сегменты и передает их контроллеру.

Затем параметры пути и запроса вводятся в действие контроллера в качестве параметров. Мы продемонстрируем это на примере в следующих разделах.

6. Расширенная маршрутизация с помощью Play

В этом разделе мы подробно обсудим дополнительные параметры маршрутизации с использованием динамических шаблонов URI.

6.1. Параметры простого пути

Простые параметры пути — это безымянные параметры в URL-адресе запроса, которые появляются после хоста и порта и анализируются в порядке появления.

Внутри play-routing/app/HomeController.java создадим новое действие:

public Result greet(String name) {
return ok("Hello " + name);
}

Мы хотим иметь возможность выбрать параметр пути из URL-адреса запроса и сопоставить его с именем переменной.

Маршрутизатор получит эти значения из конфигурации маршрута.

Итак, давайте откроем play-routing/conf/routes и создадим сопоставление для этого нового действия:

GET     /greet/:name     controllers.HomeController.greet(name: String)

Обратите внимание, как мы сообщаем маршрутизатору, что имя является сегментом динамического пути с синтаксисом двоеточия, а затем передаем его в качестве параметра вызову действия приветствия.

Теперь давайте загрузим http://locahost:9000/greet/john в браузере, и нас будут приветствовать по имени:

Hello john

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

Давайте оживим нашу конечную точку /greet информацией о возрасте.

Вернемся к действию приветствия HomeController , мы изменим его на:

public Result greet(String name, int age) {
return ok("Hello " + name + ", you are " + age + " years old");
}

И маршрут до:

GET     /greet/:name/:age               controllers.HomeController.greet(name: String, age: Integer)

Обратите также внимание на синтаксис Scala для объявления переменной age: Integer . В Java мы будем использовать синтаксис Integer age . Play Framework построен на Scala. Следовательно, существует много синтаксиса scala.

Давайте загрузим http://localhost:9000/greet/john/26 :

Hello john, you are 26 years old

6.2. Подстановочные знаки в параметрах пути

В нашем файле конфигурации маршрутов последнее сопоставление:

GET     /assets/*file  controllers.Assets.versioned(path="/public", file: Asset)

Мы используем подстановочный знак в динамической части пути. Мы говорим Play, что любое значение, заменяющее *file в фактическом запросе, должно анализироваться целиком, а не декодироваться, как в других случаях параметров пути.

В этом примере контроллер встроенный, Assets , который позволяет клиенту загружать файлы из папки play-routing/public . Когда мы загружаем http://localhost:9000/assets/images/favicon.png , мы должны увидеть изображение значка Play в браузере, так как он находится в папке /public/images .

Давайте создадим наш собственный пример действия в HomeController.java :

public Result introduceMe(String data) {
String[] clientData = data.split(",");
return ok("Your name is " + clientData[0] + ", you are " + clientData[1] + " years old");
}

Обратите внимание, что в этом действии мы получаем один строковый параметр и применяем нашу логику для его декодирования. В этом случае логика состоит в том, чтобы разбить строку с разделителями-запятыми на массив. Раньше мы зависели от маршрутизатора, который расшифровывал эти данные.

С подстановочными знаками мы предоставлены сами себе. Мы надеемся, что клиент правильно понимает наш синтаксис при передаче этих данных. В идеале мы должны проверить входящую строку перед ее использованием .

Создадим маршрут к этому действию:

GET   /*data   controllers.HomeController.introduceMe(data)

Теперь загрузите URL-адрес http://localhost:9000/john,26 . Это напечатает:

Your name is john, you are 26 years old

6.3. Регулярное выражение в параметрах пути

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

public Result squareMe(Long num) {
return ok(num + " Squared is " + (num * num));
}

Теперь мы добавим его маршрут:

GET   /square/$num<[0-9]+>   controllers.HomeController.squareMe(num:Long)

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

Теперь, если мы разместили маршрут, как указано в предыдущем абзаце, и загрузили http://localhost:9000/square/2 , нас должно приветствовать ArrayIndexOutOfBoundsException :

./9f22d3d7988f8e1f559a1095d4df7fb4.png

Если мы проверим журналы ошибок в консоли сервера, мы поймем, что вызов действия фактически был выполнен для действия IntroductionMe , а не для действия SquareMe . Как было сказано ранее о подстановочных знаках, мы сами по себе и не проверяли входящие данные. ``

Вместо строки, разделенной запятыми, метод IntroductionMe вызывался со строкой « квадрат/2 ». Следовательно, после его разбиения мы получили массив единичного размера. Попытка достичь индекса 1 вызвала исключение.

Естественно, мы ожидаем, что вызов будет направлен методу SquareMe . Почему он был направлен, чтобы представить меня ? Причина в функции Play, которую мы рассмотрим далее, под названием « Приоритет маршрутизации».

7. Приоритет маршрутизации

Если есть конфликт между маршрутами, как между SquareMe и IntroductionMe , то Play выбирает первый маршрут в порядке объявления .

Почему возникает конфликт? Из-за пути контекста подстановочного знака /*data соответствует любому URL-адресу запроса, кроме базового пути / . Таким образом , каждый маршрут, шаблон URI которого использует подстановочные знаки, должен стоять последним по порядку .

Теперь давайте изменим порядок объявления маршрутов таким образом, чтобы маршрут IntroductionMe шел после SquareMe и перезагрузил: ``

2 Squared is 4

Чтобы проверить силу регулярных выражений в маршруте, попробуйте загрузить http://locahost:9000/square/-1 , маршрутизатор не сможет сопоставить маршрут SquareMe . Вместо этого он будет соответствовать вводитьMe, и мы снова получим исключение ArrayIndexOutOfBoundsException .

Это связано с тем, что -1 не соответствует предоставленному регулярному выражению, как и ни одному алфавитному символу.

8. Параметры

До этого момента мы рассмотрели синтаксис объявления типов параметров в файле маршрутов.

В этом разделе мы рассмотрим дополнительные параметры, доступные нам при работе с параметрами в маршрутах.

8.1. Параметры с фиксированными значениями

Иногда мы хотим использовать фиксированное значение для параметра. Это наш способ сообщить Play использовать предоставленный параметр пути или, если контекстом запроса является путь / , использовать определенное фиксированное значение.

Другой способ взглянуть на это — иметь две конечные точки или контекстные пути, ведущие к одному и тому же действию контроллера — с одной конечной точкой, требующей параметр из URL-адреса запроса, и по умолчанию для другой, если указанный параметр отсутствует.

Чтобы продемонстрировать это, давайте добавим действие write() в HomeController :

public Result writer() {
return ok("Routing in Play by ForEach");
}

Предполагая, что мы не всегда хотим, чтобы наш API возвращал String :

Routing in Play by ForEach

Мы хотим контролировать это, отправляя имя автора статьи вместе с запросом, по умолчанию фиксированное значение ForEach , только если запрос не имеет параметра автора .

Итак, давайте еще изменим действие записи, добавив параметр :

public Result writer(String author) {
return ok("REST API with Play by " + author);
}

Давайте также посмотрим, как добавить в маршрут параметр с фиксированным значением:

GET     /writer           controllers.HomeController.writer(author = "ForEach")
GET /writer/:author controllers.HomeController.writer(author: String)

Обратите внимание, что теперь у нас есть два отдельных маршрута, каждый из которых ведет к действию HomeController.index вместо одного.

Теперь, когда мы загружаем http://localhost:9000/writer из браузера, мы получаем:

Routing in Play by ForEach

И когда мы загружаем http://localhost:9000/writer/john , мы получаем:

Routing in Play by john

8.2. Параметры со значениями по умолчанию

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

Разница между ними заключается в том, что фиксированные значения используются в качестве резерва для параметров пути, а значения по умолчанию используются в качестве резерва для параметров запроса .

Параметры пути имеют вид http://localhost:9000/param1/param2 , а параметры запроса имеют вид http://localhost:9000/?param1=value1¶m2=value2 .

Второе отличие заключается в синтаксисе объявления двух в маршруте. Параметры фиксированного значения используют оператор присваивания, как в:

author = "ForEach"

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

author ?= "ForEach"

Мы используем оператор ?= , который условно присваивает ForEach автору в случае , если автор не содержит значения. ``

Для полной демонстрации давайте создадим действие HomeController.writer . Скажем, помимо имени автора, которое является параметром пути, мы также хотим передать идентификатор автора в качестве параметра запроса, который по умолчанию должен быть равен 1 , если он не передается в запросе.

Мы изменим действие записи на :

public Result writer(String author, int id) {
return ok("Routing in Play by: " + author + " ID: " + id);
}

и писатель направляется к:

GET     /writer           controllers.HomeController.writer(author="ForEach", id: Int ?= 1)
GET /writer/:author controllers.HomeController.writer(author: String, id: Int ?= 1)

Теперь при загрузке http://localhost:9000/writer мы видим:

Routing in Play by: ForEach ID: 1

Нажатие http://localhost:9000/writer?id=10 дает нам:

Routing in Play by: ForEach ID: 10

А как насчет http://localhost:9000/writer/john ?

Routing in Play by: john ID: 1

И, наконец, http://localhost:9000/writer/john?id=5 возвращает:

Routing in Play by: john ID: 5

9. Заключение

В этой статье мы рассмотрели понятие маршрутизации в приложениях Play. У нас также есть статья о создании RESTful API с Play Framework , где концепции маршрутизации, описанные в этом руководстве, применяются на практическом примере.

Как обычно, исходный код этого руководства доступен на GitHub .