Play! и Lift, — эти два фреймворка являются олицетворением того, куда движется основной поток Scala веб-разработчиков. Воистину, попробуйте поискать на Stack Overflow фреймворки для Scala и вы поймете что я прав. Я верю, что процент здравомыслящих людей, которым надоели сложные комбайны, велик, поэтому расскажу про «другой» фреймворк Xitrum.
Xitrum совершенно противоположен им по философии, это — минималистичный фреймворк, целью которого является непосредственно отдача контента. В нем нет магии и ни какого программирования по соглашению. Своим минимализмом он близок к Scalatra, но в отличие от него полностью асинхронен, т.к. построен на основе Netty (v4) и Akka (вот уже более года слежу за Scalatra и до сих пор поддержка Netty не заявлена). Но не пугайтесь, порог вхождения экстремально низок — акторы лишь опциональны, хотя и являются весомым плюсом в пользу фреймворка.
Сразу о производительности. В минимальной конфигурации xitrum запускается и работает с ограничением по памяти в 64Mb. Расходы по процессорному времени не значительны, т.е. сам фреймворк нагрузку на процессор не дает. Все остальное зависит от вас.
Проект родом из Японии и имеет обстоятельную документацию. Разработчики проекта оставляют очень приятное впечатление, всегда прислушиваются к тому, что пишут в официальной группе и очень быстро закрывают баги. Вот уже почти год как я не получил ни одного отказа или зависшей задачи в трекере.
Итак, xitrum:
Xitrum является controller-first фреймворком. Очень легко динамически менять представления контроллера во время выполнения, что является не тривиальным для некоторых Scala/Java фреймворков. На моей памяти это вообще единственный фреймворк из мира Java который позволил без каких либо костылей написать CMS с динамической шаблонизацией, so sad.
Я думаю что уместно будет познакомить читателя с основами фреймворка, более сложные вещи опишу в другой раз если будет интерес.
В xitrum каждый запрос может быть обработан только наследником от Action. Т. е. на каждый самостоятельный маршрут обрабатываемый нашим сервером мы должны объявить отдельный класс контроллер.
Каждый новый запрос поступающий на сервер будет обрабатываться новым экземпляром класса, т. е. хранить состояние в этих классах не имеет смысла. Очень важно понять тот факт, что обработка запросов выполняется асинхронно. Пока вы не вызовете метод respond*(), соединение с клиентом не будет закрыто и клиент будет ждать вашего ответа, возможно вечность. Метод execute выполняется на Netty потоке, поэтому не следует помещать в него длительные операции, например:
При такой реализации контроллера ваш сервер вряд ли сможет обслужить более 1 подключения в секунду. Что бы решить эту проблему нужно использовать либо FutureAction, либо ActorAction.
Xitrum поддерживает все виды HTTP запросов с помощью аннотаций GET, POST и прочих. Любой контроллер может обрабатывать не ограниченно количество маршрутов. Можно определить порядок контроллеров с помощью аннотаций First и Last. Контроллер по умолчанию определяется как METHOD(":*")
Для получения ссылки на контроллер в Action предусмотрен метод url, который генерирует GET ссылку с параметрами.
Ссылку на статические ресурсы из директории public или classpath можно получить с помощью методов publicUrl и resourceUrl соответственно. Поддерживаются классические перенаправления вроде forwardTo и redirectTo.
Xitrum позволяет прозрачно работать с тремя видами параметров:
Доступ к параметрам осуществляется очень просто:
pathParams задаются по аналогии с Rails с помощью символа ':' (:id, :article, :etc), дополнительно значения параметров можно ограничить с помощью регулярных выражений заключенных в '<>' (например, :id<[0-9]+>).
Иногда возникает необходимость считать бинарные данные тела POST запроса, делается это так:
Сам по себе xitrum не имеет встроенного механизма шаблонизации, без шаблонизатора возможно генерировать следующие типы ответа:
Для шаблонизации вы можете использовать Scalate, он подключен в шаблонном проекте. Шаблонизатор поддерживает несколько разных синтаксисов: mustache, scaml, jade и ssp. Я предпочитаю использовать ssp потому что он наиболее близок к html. В шаблонном проекте настроен jade, что бы сменить тип синтаксиса нужно в конфигурации xitrum.conf заменить строчку defaultType = jade на defaultType = ssp.
При использовании Scalate для каждого контроллера можно определить свое представление, по правилам Scalate путь до шаблона должен соответствовать пакету контроллера.
Как видите, что бы отобразить шаблон SiteIndex.ssp, достаточно вызвать respondView(). Предусмотрено понятие фрагмента, с помощью него можно менять представление контроллера.
Xitrum не накладывает ограничений на строгое соответствие представления и контроллера, поэтому одно и то же представление может быть использовано в разных контролерах. Как следствие по умолчанию в шаблонах есть возможность пользоваться только методами из базового трейта Action. Передачу данных в шаблон можно осуществлять с помощью метода at.
Если вы самостоятельно примите решение ограничиться одним шаблоном на контроллер, то очень полезным будет паттерн позволяющий импортировать текущий контроллер в шаблон. При его использовании методы контроллера могут быть прозрачно вызваны из шаблона.
Некоторый интерес представляет метод atJson, — он выполняет автоматическое преобразование моделей в Json, это оказывается очень полезным при передаче данных непосредственно в JavaScript.
Внутри контроллера для доступа к куки нужно использовать переменную requestCookies, а для установки новой куки соответственно responseCookies.
Xitrum автоматически обеспечивает сохранение, восстановление и шифрование сессии в куки. Работа с сессией осуществляется через переменную session.
Обработкой запроса можно дополнительно управлять с помощью фильтров, всего их предусмотрено три: beforeFilter, afterFilter и aroundFilter. beforeFilter выполняется перед всякой обработкой запроса, если он возвращает false, то никакая дальнейшая обработка запроса данным контроллером выполнятся не будет. Напротив afterFilter выполняются последними.
Пример, определение языка интернационализации до обработки запроса.
Итак, обработка запросов упрощенно выполняется следующим образом: (1) request -> (2) before фильтры -> (3) execute метод контроллера -> (4) after фильтры -> (5) response. Xitrum имеет встроенные возможности для кэширования всей цепочки обработки запроса (2 — 3 — 4 — 5) с помощью аннотации CachePageMinute и непосредственно метода execute (3), — аннотация CacheActionMinute. Время жизни кэша указывается в минутах. В кэш попадают только ответы со статусом 200 Ok.
По умолчанию фреймворк использует для кэширования свою реализацию Lru кэша. Однако, реализация механизма кэширования может быть легко изменена в конфигурации. Для кластеризованного кэша наиболее всего подойдет Hazelcast, способы подключения.
Кроме аннотаций, xitrum предоставляет доступ к объекту Cache. Его можно использовать для кэширования своих данных.
Методы предоставляемые объектом Cache
Благодаря понятной маршрутизации реализация RESTful API тривиальна. Из коробки поддерживается документирование API с помощью Swagger
Во время выполнения xitrum сгенерирует swagger.json который может быть использован в Swagger UI для удобного просмотра документации.
Важно: для всех POST запросов предусмотрена защита от CSRF атак, поэтому вы должны передавать csrf-token с любым POST запросом, либо, явно отключить эту защиту с помощью наследования от трейта SkipCsrfCheck. Подробнее про использование csrf-token.
Если вы прочли эту статью до конца, то полагаю теперь вы знает порядка 70% функционала фреймворка. И мне кажется, это подтверждает мою мысль о том, что порог вхождения очень низок. Поэтому, рекомендую пробовать и задавать вопросы.
Оставшуюся часть материала вы всегда можете посмотреть на официальном сайте в учебнике. Вопросы которые я дополнительно хотел бы рассказать:
Источники
Xitrum совершенно противоположен им по философии, это — минималистичный фреймворк, целью которого является непосредственно отдача контента. В нем нет магии и ни какого программирования по соглашению. Своим минимализмом он близок к Scalatra, но в отличие от него полностью асинхронен, т.к. построен на основе Netty (v4) и Akka (вот уже более года слежу за Scalatra и до сих пор поддержка Netty не заявлена). Но не пугайтесь, порог вхождения экстремально низок — акторы лишь опциональны, хотя и являются весомым плюсом в пользу фреймворка.
Сразу о производительности. В минимальной конфигурации xitrum запускается и работает с ограничением по памяти в 64Mb. Расходы по процессорному времени не значительны, т.е. сам фреймворк нагрузку на процессор не дает. Все остальное зависит от вас.
Отзывы пользователей
С официального сайта:
Мое мнение:
Wow, this is a really impressive body of work, arguably the most complete Scala framework outside of Lift (but much easier to use).
Xitrum is truly a full stack web framework, all the bases are covered, including wtf-am-I-on-the-moon extras like ETags, static file cache identifiers & auto-gzip compression. Tack on built-in JSON converter, before/around/after interceptors, request/session/cookie/flash scopes, integrated validation (server & client-side, nice), built-in cache layer (Hazelcast), i18n a la GNU gettext, Netty (with Nginx, hello blazing fast), etc. and you have, wow.
Мое мнение:
Лучший фреймворк который я когда-либо видел для Scala/Java. Xitrum меня действительно цепляет, это как смесь Dancer+Rails со статической типизацией, восхитительно!
Проект родом из Японии и имеет обстоятельную документацию. Разработчики проекта оставляют очень приятное впечатление, всегда прислушиваются к тому, что пишут в официальной группе и очень быстро закрывают баги. Вот уже почти год как я не получил ни одного отказа или зависшей задачи в трекере.
Ngoc Dao о своем проекте (из переписки)
С течением времени многие люди поучаствовали в разработки фреймворка. На данный момент команда, разрабатывающая ядро Xitrum состоит из двух человек: Oshida и Ngoc.
Я начал разрабатывать Xitrum летом 2010 года, для использования в реальных проектах компании Mobilus. В то время, Play поддерживал только Java, а Lift был единственным полноценным фреймворком для Scala. Мы пытались его использовать несколько месяцев, но оказалось, что он не так прост, по крайней мере для нас знакомых с разработкой на Rails. Поэтому, как технический руководитель, я принял решение создать быстрый и масштабируемый веб-фреймворк на Scala для моей команды, настолько же простой в использовании, как и Rails. На самом деле, результат оказался больше похоже на Merb, нежели чем на Rails (в xitrum отсутствует слой доступа к данным).
С течением времени многие люди поучаствовали в разработки фреймворка. На данный момент команда, разрабатывающая ядро Xitrum состоит из двух человек: Oshida и Ngoc.
Итак, xitrum:
- Типо безопасный (typesafe) во всех отношениях где это возможно
- Полностью асинхронный. Необязательно слать ответ на запрос немедленно, можно запустить сложные вычисления и дать ответ, когда он будет готов. Очень легко реализуются такие штуки как Long polling, chunked response, WebSockets, SockJs, EventStream
- Очень производительный, отдача статики сравнима по производительности с Nginx
- Автоматическая сборка маршрутов (routes) приложения, нет нужды заводить какие-либо xml и прочее
- Простая обработка параметров запроса, сессии и куки
- Пре и пост фильтры
- Встроенная поддержка кэширования ответов (в стиле Rails), поддержка ETag
- Прекрасно подходит для разработки RESTful API, встроенная поддержка документирования на основе Swagger Doc
- I18N на основе GNU gettext с динамической перезагрузки файлов перевода в случае их изменения. Автоматический генератор pot файлов из исходников
- Модульность — xitrum автоматически объединяет маршруты из всех jar зависимостей
- Подключаемый по требованию типо безопасный шаблонизатор Scalate или любой другой по вашему желанию
Xitrum является controller-first фреймворком. Очень легко динамически менять представления контроллера во время выполнения, что является не тривиальным для некоторых Scala/Java фреймворков. На моей памяти это вообще единственный фреймворк из мира Java который позволил без каких либо костылей написать CMS с динамической шаблонизацией, so sad.
Я думаю что уместно будет познакомить читателя с основами фреймворка, более сложные вещи опишу в другой раз если будет интерес.
Создание пустого проекта и структура папок
Новый проект проще всего создать так:
По умолчанию сервер запустится на порту 8000. В проекте по умолчанию подключен шаблонизатор Scalate. Это идеальный проект для старта, в нем нет ничего лишнего, кроме стандартного контроллера и пары представлений которые можно удалить.
Что бы импортировать проект в eclipse используем sbt/sbt eclipse, в idea sbt/sbt gen-idea.
Важно: в eclipse нужно руками добавить папку config в classpath, иначе проект не будет запускаться из eclipse (баг sbt-eclipse#182).
Структура директории проекта:
git clone https://github.com/ngocdaothanh/xitrum-new my-app
cd my-app
sbt/sbt run
По умолчанию сервер запустится на порту 8000. В проекте по умолчанию подключен шаблонизатор Scalate. Это идеальный проект для старта, в нем нет ничего лишнего, кроме стандартного контроллера и пары представлений которые можно удалить.
Что бы импортировать проект в eclipse используем sbt/sbt eclipse, в idea sbt/sbt gen-idea.
Важно: в eclipse нужно руками добавить папку config в classpath, иначе проект не будет запускаться из eclipse (баг sbt-eclipse#182).
Структура директории проекта:
./script # скрипты используемые при разворачивании в production
./config # папка конфигурации (akka, logback, xitrum)
./public # папка со статикой (css, js, прочее)
./project # sbt
./src # src
./src/main/scalate # папка с шаблонами
./src/main/scala # scala код
./src/main/scala/quickstart/Boot.scala # точка входа в приложение
Простой контроллер
В xitrum каждый запрос может быть обработан только наследником от Action. Т. е. на каждый самостоятельный маршрут обрабатываемый нашим сервером мы должны объявить отдельный класс контроллер.
import xitrum.Action
import xitrum.annotation.GET
@GET("url/to/HelloAction")
class HelloAction extends Action {
def execute() {
respondHtml(
<xml:group>
<p>Hello world!</p>
</xml:group>
)
}
}
Каждый новый запрос поступающий на сервер будет обрабатываться новым экземпляром класса, т. е. хранить состояние в этих классах не имеет смысла. Очень важно понять тот факт, что обработка запросов выполняется асинхронно. Пока вы не вызовете метод respond*(), соединение с клиентом не будет закрыто и клиент будет ждать вашего ответа, возможно вечность. Метод execute выполняется на Netty потоке, поэтому не следует помещать в него длительные операции, например:
@GET("url/to/HelloAction")
class HelloAction extends Action {
def execute() {
Thread.sleep(1000) // ОШИБКА: блокирующая операция в Netty потоке
respond()
}
}
При такой реализации контроллера ваш сервер вряд ли сможет обслужить более 1 подключения в секунду. Что бы решить эту проблему нужно использовать либо FutureAction, либо ActorAction.
- Action — метод exectue будет выполнен непосредственно в потоке Netty
- FutureAction — метод execute будет выполнен в отдельном потоке (Akka system dispatcher)
- ActorAction — в роли контроллера выступает обычный актор
Маршрутизация
Xitrum поддерживает все виды HTTP запросов с помощью аннотаций GET, POST и прочих. Любой контроллер может обрабатывать не ограниченно количество маршрутов. Можно определить порядок контроллеров с помощью аннотаций First и Last. Контроллер по умолчанию определяется как METHOD(":*")
@GET("url1")
@First
class A extends Action { ... }
@GET("url1", "url2", "...")
@POST("url1", ...)
class B extends Action { ... }
@GET(":*")
@Last
class Default extends Action { ... }
Для получения ссылки на контроллер в Action предусмотрен метод url, который генерирует GET ссылку с параметрами.
url[HelloAction]("name" -> "caiiiycuk") // url/to/HelloAction?name=caiiiycuk
Ссылку на статические ресурсы из директории public или classpath можно получить с помощью методов publicUrl и resourceUrl соответственно. Поддерживаются классические перенаправления вроде forwardTo и redirectTo.
Разбор параметров
Xitrum позволяет прозрачно работать с тремя видами параметров:
- uriParams — параметры после '?' (например: example.com/blah?x=1&y=2)
- bodyParams — параметры переданные в теле POST запроса
- pathParams — параметры закодированные в url (например: example.com/article/:id)
Доступ к параметрам осуществляется очень просто:
param("X") // считать параметр X как String, бросить исключение если параметра нет
params("X") // считать параметр X как List[String], бросить исключение если параметра нет
paramo("X") // считать параметр X как Option[String]
paramso("X") // считать параметр X как Option[List[String]]
param[Type]("X") // считать параметр X как [Type], бросить исключение если параметра нет
params[Type]("X") // считать параметр X как List[[Type]], бросить исключение если параметра нет
paramo[Type]("X") // считать параметр X как Option[[Type]]
paramso[Type]("X") // считать параметр X как Option[List[[Type]]]
pathParams задаются по аналогии с Rails с помощью символа ':' (:id, :article, :etc), дополнительно значения параметров можно ограничить с помощью регулярных выражений заключенных в '<>' (например, :id<[0-9]+>).
@GET("articles/:id<[0-9]+>", "articles/:id<[0-9]+>.:format")
class ArticlesShow extends Action {
def execute() {
val id = param[Int]("id")
val format = paramo("format").getOrElse("json")
...
}
}
Иногда возникает необходимость считать бинарные данные тела POST запроса, делается это так:
val body = requestContentString // результат String
val bodyMap = requestContentJson[Type] // считать Json, результат Type
val raw = request.getContent // результат ByteBuf
Шаблонизация
Сам по себе xitrum не имеет встроенного механизма шаблонизации, без шаблонизатора возможно генерировать следующие типы ответа:
- respondText — ответить строкой «plain/text»
- respondHtml — ответить строкой «text/html»
- respondJson — преобразовать Scala объект в Json строку
- respondBinary — бинарные данные
- respondFile — отправить файл используя zero-copy (send-file)
- Менее важные — respondJs, respondJsonP, respondJsonText, respondJsonPText, respondEventSource
Поддержка chunked response
Случается такая ситуация, когда ответ на запрос не помещается в памяти сервера. Например, наш сервер генерирует годовой отчет в CSV формате. Естественно в этой ситуации мы не можем сохранить весь отчет в памяти и отправить клиенту одним ответом. Жизненный цикл chunked response:
При использовании chunked response совместно с ActorAction можно очень просто реализовать Facebook BigPipe.
- Вызвать метод setChunked
- Вызвать respond*() столько раз, сколько необходимо
- Вызвать respondLastChunk когда все данные отправлены
val generator = new MyCsvGenerator
setChunked()
respondText(header, "text/csv")
while (generator.hasNextLine) {
val line = generator.nextLine
respondText(line)
}
respondLastChunk()
При использовании chunked response совместно с ActorAction можно очень просто реализовать Facebook BigPipe.
Для шаблонизации вы можете использовать Scalate, он подключен в шаблонном проекте. Шаблонизатор поддерживает несколько разных синтаксисов: mustache, scaml, jade и ssp. Я предпочитаю использовать ssp потому что он наиболее близок к html. В шаблонном проекте настроен jade, что бы сменить тип синтаксиса нужно в конфигурации xitrum.conf заменить строчку defaultType = jade на defaultType = ssp.
Возможности Scalate
- HTML совместимый синтаксис (ssp)
- HAML подобный синтаксис (jade)
- Загрузка шаблонов на лету (во время выполнения)
- Компилируемые шаблоны (проверка ошибок на этапе компиляции)
- Включение шаблона в шаблон
- Наследование шаблонов (возможность переопределения блоков)
- Автоматическое экранирование тэгов
- Использование Scala кода непосредственно в шаблоне
При использовании Scalate для каждого контроллера можно определить свое представление, по правилам Scalate путь до шаблона должен соответствовать пакету контроллера.
src/main/scala/quickstart/action/SiteIndex.scala # класс контроллера
src/main/scalate/quickstart/action/SiteIndex.ssp # шаблон контроллера
src/main/scalate/quickstart/action/SiteIndex/ # папка для фрагментов
package quickstart.action
import xitrum.annotation.GET
@GET("")
class SiteIndex extends DefaultLayout {
def execute() {
respondView()
}
}
Как видите, что бы отобразить шаблон SiteIndex.ssp, достаточно вызвать respondView(). Предусмотрено понятие фрагмента, с помощью него можно менять представление контроллера.
@GET("")
class SiteIndex extends DefaultLayout {
def execute() {
respondHtml(renderFragment("some")) # из папки фрагментов этого контроллера
}
}
Xitrum не накладывает ограничений на строгое соответствие представления и контроллера, поэтому одно и то же представление может быть использовано в разных контролерах. Как следствие по умолчанию в шаблонах есть возможность пользоваться только методами из базового трейта Action. Передачу данных в шаблон можно осуществлять с помощью метода at.
Контроллер | Шаблон |
---|---|
|
|
Если вы самостоятельно примите решение ограничиться одним шаблоном на контроллер, то очень полезным будет паттерн позволяющий импортировать текущий контроллер в шаблон. При его использовании методы контроллера могут быть прозрачно вызваны из шаблона.
Контроллер | Шаблон |
---|---|
|
|
Некоторый интерес представляет метод atJson, — он выполняет автоматическое преобразование моделей в Json, это оказывается очень полезным при передаче данных непосредственно в JavaScript.
Контроллер | Шаблон |
---|---|
|
|
Сессия и куки
Внутри контроллера для доступа к куки нужно использовать переменную requestCookies, а для установки новой куки соответственно responseCookies.
// Чтение
requestCookies.get("myCookie") match {
case None => ...
case Some(string) => ...
}
// Установка
responseCookies.append(new DefaultCookie("name", "value"))
Xitrum автоматически обеспечивает сохранение, восстановление и шифрование сессии в куки. Работа с сессией осуществляется через переменную session.
session.clear // очистить сессию
session("userId") = 1 // установить значение
session.isDefinedAt("userId") // проверить существование
session("userId") // считать из сессии
Фильтры
Обработкой запроса можно дополнительно управлять с помощью фильтров, всего их предусмотрено три: beforeFilter, afterFilter и aroundFilter. beforeFilter выполняется перед всякой обработкой запроса, если он возвращает false, то никакая дальнейшая обработка запроса данным контроллером выполнятся не будет. Напротив afterFilter выполняются последними.
before1 -true-> before2 -true-> +--------------------+ --> after1 --> after2
| around1 (1 of 2) |
| around2 (1 of 2) |
| action |
| around2 (2 of 2) |
| around1 (2 of 2) |
+--------------------+
Пример, определение языка интернационализации до обработки запроса.
beforeFilter {
val lango: Option[String] = yourMethodToGetUserPreferenceLanguageInSession()
lango match {
case None => autosetLanguage("ru", "en")
case Some(lang) => setLanguage(lang)
}
true
}
def execute() { ... }
Кэширование
Итак, обработка запросов упрощенно выполняется следующим образом: (1) request -> (2) before фильтры -> (3) execute метод контроллера -> (4) after фильтры -> (5) response. Xitrum имеет встроенные возможности для кэширования всей цепочки обработки запроса (2 — 3 — 4 — 5) с помощью аннотации CachePageMinute и непосредственно метода execute (3), — аннотация CacheActionMinute. Время жизни кэша указывается в минутах. В кэш попадают только ответы со статусом 200 Ok.
import xitrum.Action
import xitrum.annotation.{GET, CacheActionMinute, CachePageMinute}
@GET("articles")
@CachePageMinute(1)
class ArticlesIndex extends Action {
def execute() { ... }
}
@GET("articles/:id")
@CacheActionMinute(10)
class ArticlesShow extends Action {
def execute() { ... }
}
По умолчанию фреймворк использует для кэширования свою реализацию Lru кэша. Однако, реализация механизма кэширования может быть легко изменена в конфигурации. Для кластеризованного кэша наиболее всего подойдет Hazelcast, способы подключения.
Кроме аннотаций, xitrum предоставляет доступ к объекту Cache. Его можно использовать для кэширования своих данных.
import xitrum.Config.xitrum.cache
// Cache with a prefix
val prefix = "articles/" + article.id
cache.put(prefix + "/likes", likes)
cache.put(prefix + "/comments", comments)
// Later, when something happens and you want to remove all cache related to the article
cache.remove(prefix)
Методы предоставляемые объектом Cache
- put(key, value) — бессрочно поместить пару «ключ, значение» в кэш
- putSecond, putMinute, putHour, putDay(key, value, interval) — значение будет удалено из кэша через указанный промежуток времени
- putIfAbsent, putIfAbsentSecond, putIfAbsentMinute, putIfAbsentHour, putIfAbsentDay — тоже самое только значение в кэше не будет обновленно, если оно уже в нем содержится
RESTful API
Благодаря понятной маршрутизации реализация RESTful API тривиальна. Из коробки поддерживается документирование API с помощью Swagger
import xitrum.{Action, SkipCsrfCheck}
import xitrum.annotation.{GET, Swagger}
@Swagger(
Swagger.Note("Dimensions should not be bigger than 2000 x 2000")
Swagger.OptStringQuery("text", "Text to render on the image, default: Placeholder"),
Swagger.Response(200, "PNG image"),
Swagger.Response(400, "Width or height is invalid or too big")
)
trait ImageApi extends Action with SkipCsrfCheck {
lazy val text = paramo("text").getOrElse("Placeholder")
}
@GET("image/:width/:height")
@Swagger( // <-- Наследуется от ImageApi
Swagger.Summary("Generate rectangle image"),
Swagger.IntPath("width"),
Swagger.IntPath("height")
)
class RectImageApi extends Api {
def execute {
val width = param[Int]("width")
val height = param[Int]("height")
// ...
}
}
@GET("image/:width")
@Swagger( // <-- Наследуется от ImageApi
Swagger.Summary("Generate square image"),
Swagger.IntPath("width")
)
class SquareImageApi extends Api {
def execute {
val width = param[Int]("width")
// ...
}
}
Во время выполнения xitrum сгенерирует swagger.json который может быть использован в Swagger UI для удобного просмотра документации.
Важно: для всех POST запросов предусмотрена защита от CSRF атак, поэтому вы должны передавать csrf-token с любым POST запросом, либо, явно отключить эту защиту с помощью наследования от трейта SkipCsrfCheck. Подробнее про использование csrf-token.
Интернационализация
Интернационализация выполняется с помощью GNU gettext. У контроллера предусмотрен метод t для выполнения интернационализации.
Текущий язык перевода выбирается с помощью метода setLanguage, помимо этого можно использовать метод autosetLanguage для автоматического выбора языка в соответствии с Accept-Language браузера. Что бы получить шаблон pot, нужно выполнить sbt compile. Файлы с переводами нужно положить в classpath проекта (обычно в config/i18n). Если файл с переводом был изменен во время работы сервера, он будет перечитан и перевод применится без перезапуска сервера.
def execute() {
respondHtml(t("hello_world"))
}
Текущий язык перевода выбирается с помощью метода setLanguage, помимо этого можно использовать метод autosetLanguage для автоматического выбора языка в соответствии с Accept-Language браузера. Что бы получить шаблон pot, нужно выполнить sbt compile. Файлы с переводами нужно положить в classpath проекта (обычно в config/i18n). Если файл с переводом был изменен во время работы сервера, он будет перечитан и перевод применится без перезапуска сервера.
Если вы прочли эту статью до конца, то полагаю теперь вы знает порядка 70% функционала фреймворка. И мне кажется, это подтверждает мою мысль о том, что порог вхождения очень низок. Поэтому, рекомендую пробовать и задавать вопросы.
Оставшуюся часть материала вы всегда можете посмотреть на официальном сайте в учебнике. Вопросы которые я дополнительно хотел бы рассказать:
- Интеграция с JRebel
- Использование ActorAction
- Postbacks
- WebScoket, SockJS, EventSource
- Deploy
Источники
Демонстрационный проект
Демонстрационный проект показывающий большую часть того на что способен xitrum (кажется он не очень полезен для обучения):
git clone https://github.com/ngocdaothanh/xitrum-demos.git
cd xitrum-demos
sbt/sbt run
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какой фреймворк вы используете (Scala)
62.39% Play146
9.4% Lift22
2.99% Scalatra7
0.85% Unfiltered2
0% Uniscala Granite0
0% Gardel0
0.43% Mondo1
0% Amore0
0.43% Scales XML1
0.85% Belt2
0% Frank0
0% MixedBits0
0.43% Circumflex1
0% Scala Webmachine0
0% Bowler0
0% Sweet0
0% Scala Pages0
3.42% Xitrum8
7.26% Напишу в комментарии17
11.54% spray.io27
Проголосовали 234 пользователя. Воздержался 341 пользователь.