RESTful APIs
============
Разработка RESTful APIs с использованием Xitrum.
::
import xitrum.Action
import xitrum.annotation.GET
@GET("articles")
class ArticlesIndex extends Action {
def execute() {...}
}
@GET("articles/:id")
class ArticlesShow extends Action {
def execute() {...}
}
Подобным образом описываются POST, PUT, PATCH, DELETE, и OPTIONS запросы.
Xitrum автоматически обрабатывает HEAD запросы как GET с пустым ответом.
Для HTTP клиентов не поддерживающих PUT и DELETE (например, обычные браузеры), используется метод POST c параметрами ``_method=put`` или ``_method=delete`` внутри тела запроса.
При старте веб приложения, Xitrum сканирует аннотации, создает таблицу маршрутизации
и печатает ее в лог. Из лога понятно какое API приложение поддерживает на данный момент:
::
[INFO] Routes:
GET /articles quickstart.action.ArticlesIndex
GET /articles/:id quickstart.action.ArticlesShow
Маршруты (routes) автоматически строятся в духе JAX-RS и Rails. Нет необходимости
объявлять все маршруты в одном месте. Допускается включать одно приложение в другое.
Например, движок блога можно упаковать в JAR файл и подключить его в другое приложение,
после этого у приложения появятся все возможности блога. Маршрутизация осуществляется
в два направления, можно генерировать URL по контроллеру (обратная маршрутизация).
Автоматическое документирование ваших маршрутов можно выполнить используя
`Swagger Doc `_.
Кэш маршрутов
-------------
Для более быстро скорости запуска, маршруты кэшируются в файл ``routes.cache``.
В режиме разработчика, этот файл не используется. В случае изменения зависимостей
содержащих маршруты, необходимо удалить ``routes.cache``. Этот файл не должен попасть
в ваши систему контроля версий.
Очередность маршрутов
---------------------
Возможно вам потребуется организовать маршруты в определенном порядке.
::
/articles/:id --> ArticlesShow
/articles/new --> ArticlesNew
В данном случае необходимо что бы второй маршрут был проверен первым.
Для этих целей нужно использовать аннотацию ``First``:
::
import xitrum.annotation.{GET, First}
@GET("articles/:id")
class ArticlesShow extends Action {
def execute() {...}
}
@First // This route has higher priority than "ArticlesShow" above
@GET("articles/new")
class ArticlesNew extends Action {
def execute() {...}
}
``Last`` работает помещает маршрут на обработку последним.
Несколько маршрутов для одного контроллера
------------------------------------------
::
@GET("image", "image/:format")
class Image extends Action {
def execute() {
val format = paramo("format").getOrElse("png")
// ...
}
}
Точка в маршруте
----------------
::
@GET("articles/:id", "articles/:id.:format")
class ArticlesShow extends Action {
def execute() {
val id = param[Int]("id")
val format = paramo("format").getOrElse("html")
// ...
}
}
Регулярные выражения в маршруте
-------------------------------
Регулярные выражения могут быть использованы для задания ограничений в маршруте:
::
GET("articles/:id<[0-9]+>")
Обработка не стандартных маршрутов
----------------------------------
Использование символа ``/`` не допускается в именах параметров. Если есть необходимость в его
использовании вы можете определить маршрут следующим образом:
::
GET("service/:id/proxy/:*")
Например, данный маршрут будет обрабатывать запросы:
::
/service/123/proxy/http://foo.com/bar
Извлечение значение из части ``:*``:
::
val url = param("*") // Будет "http://foo.com/bar"
Ссылка на контроллер
--------------------
Xitrum пытается быть достаточно безопасным. Не пишите ссылки самостоятельно (в явном виде).
Используйте генератор ссылок:
::
myArticle.id)}>{myArticle.title}
Редирект на контроллер
----------------------
Читайте подробнее про `редирект `_.
::
import xitrum.Action
import xitrum.annotation.{GET, POST}
@GET("login")
class LoginInput extends Action {
def execute() {...}
}
@POST("login")
class DoLogin extends Action {
def execute() {
...
// After login success
redirectTo[AdminIndex]()
}
}
GET("admin")
class AdminIndex extends Action {
def execute() {
...
// Check if the user has not logged in, redirect him to the login page
redirectTo[LoginInput]()
}
}
Допускается делать редирект на тот же самый контроллер с помощью метода ``redirecToThis()``.
Форвардинг (перенаправление) на контроллер
------------------------------------------
Используйте ``forwardTo[AnotherAction]()``. ``redirectTo`` заставляет браузер делать новый запрос, в то
время как ``forwardTo`` работает в рамках одного запроса.
Определение Ajax запроса
------------------------
Используйте ``isAjax``.
::
// В контроллере
val msg = "A message"
if (isAjax)
jsRender("alert(" + jsEscape(msg) + ")")
else
respondText(msg)
Anti-CSRF
---------
Для запросов отличных от GET Xitrum автоматически защищает приложение от
`Cross-site request forgery `_ атаки.
Включите в шаблон ``antiCsrfMeta``:
::
import xitrum.Action
import xitrum.view.DocType
trait AppAction extends Action {
override def layout = DocType.html5(
{antiCsrfMeta}
{xitrumCss}
{jsDefaults}
Welcome to Xitrum
{renderedView}
{jsForView}
)
}
Тогда секция ```` будет включать в себя csrf-token:
::
...
...
...
Этот токен будет автоматически включен во все Ajax запросы jQuery как заголовок
``X-CSRF-Token`` если вы подключите `xitrum.js `_. xitrum.js подключается вызовом ``jsDefaults``. Если вы не хотите
использовать ``jsDefaults``, вы можете подключить xitrum.js следующим образом (или посылать токен самостоятельно):
::
antiCsrfInput и antiCsrfToken
-----------------------------
Xitrum использует CSRF токен из заголовка запроса с именем ``X-CSRF-Token``. Если заголовок
не установлен, Xitrum берет значение из параметра ``csrf-token`` переданного в теле запроса
(не из URL).
Если вы вручную создаете формы, и не используйте мета тэг и xitrum.js как сказано выше,
то вам нужно использовать методы контроллера ``antiCsrfInput`` или ``antiCsrfToken``:
::
form(method="post" action={url[AdminAddGroup]})
!= antiCsrfInput
::
form(method="post" action={url[AdminAddGroup]})
input(type="hidden" name="csrf-token" value={antiCsrfToken})
SkipCsrfCheck
-------------
Для некоторые API не требуется защита от CSRF атак, в этом случае проще всего
пропустить эту проверку. Для этого дополнительно наследуйте свой контроллер
от трейта xitrum.SkipCsrfCheck:
::
import xitrum.{Action, SkipCsrfCheck}
import xitrum.annotation.POST
trait Api extends Action with SkipCsrfCheck
@POST("api/positions")
class LogPositionAPI extends Api {
def execute() {...}
}
@POST("api/todos")
class CreateTodoAPI extends Api {
def execute() {...}
}
Управление маршрутами
---------------------
Xitrum автоматически собирает маршруты при запуске.
Для управления этими маршрутами используйте
`xitrum.Config.routes `_.
Например:
::
import xitrum.{Config, Server}
object Boot {
def main(args: Array[String]) {
// Вы можете поправить маршруты до запуска сервера
val routes = Config.routes
// Удаление маршрутов относящихся к конкретному классу
routes.removeByClass[MyClass]()
if (demoVersion) {
// Удаление маршрутов начинающихся с префикса
routes.removeByPrefix("premium/features")
// Допустимый вариант
routes.removeByPrefix("/premium/features")
}
...
Server.start()
}
}
Получение полных (сырых) данных запроса
---------------------------------------
Обычно когда mime тип запроса не соответствует ``application/x-www-form-urlencoded``,
предполагается что содержимое запроса будет обработано в ручном режиме.
Получение тела запроса в виде строки:
::
val body = requestContentString
JSON:
::
val myJValue = requestContentJValue // => JSON4S (http://json4s.org) JValue
val myMap = xitrum.util.SeriDeseri.fromJValue[Map[String, Int]](myJValue)
Если вам нужно получить полный доступ к запросу, используйте `request.getContent `_. Он возвращает `ByteBuf `_.
Документирование API
--------------------
Из коробки вы можете документировать API и использованием `Swagger `_.
Добавьте аннотацию ``@Swagger`` к контроллеру который нужно задокументировать
Xitrum генерирует `/xitrum/swagger.json `_.
Этот файл может быть использован в `Swagger UI `_
для генерации интерактивной документации.
Xitrum включает Swagger UI, по пути ``/xitrum/swagger-ui``,
например http://localhost:8000/xitrum/swagger-ui.
.. image:: ../img/swagger.png
Рассмотрим `пример `_:
::
import xitrum.{Action, SkipCsrfCheck}
import xitrum.annotation.{GET, Swagger}
@Swagger(
Swagger.Tags("image", "APIs to create images"),
Swagger.Description("Dimensions should not be bigger than 2000 x 2000"),
Swagger.OptStringQuery("text", "Text to render on the image, default: Placeholder"),
Swagger.Produces("image/png"),
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( // <-- Inherits other info from 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( // <-- Inherits other info from ImageApi
Swagger.Summary("Generate square image"),
Swagger.IntPath("width")
)
class SquareImageApi extends Api {
def execute {
val width = param[Int]("width")
// ...
}
}
`JSON для Swagger `_
будет генерироваться при доступе ``/xitrum/swagger``.
Swagger UI использует эту информацию для генерации интерактивной документации к API.
Возможные параметры на подобии Swagger.IntPath определяются шаблоном:
* ``<Тип переменной><Тип параметра>`` (обязательный параметр)
* ``Opt<Тип переменной><Тип параметра>`` (опциональный параметр)
Типы переменных: Byte, Int, Int32, Int64, Long, Number, Float, Double, String, Boolean, Date, DateTime
Типы параметров: Path, Query, Body, Header, Form
Подробнее о `типах переменных `_
и `типах параметров `_.