Контроллеры и представления
===========================
Xitrum располагает тремя видами контроллеров или действий (actions):
стандартный контроллер ``Action``, ``FutureAction`` и актор контроллер ``ActorAction``.
Стандартный контроллер (normal action)
--------------------------------------
Реализация данного контроллера синхронная.
::
import xitrum.Action
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends Action {
def execute() {
respondText("Hello")
}
}
В случае наследования от xitrum.Action, ваш код будет выполнятся в потоке Netty's IO.
Это допустимо только в случае если ваш контроллер очень легковесный и не блокирующий
(возвращает ответ немедленно). Иначе Netty не сможет принимать новые подключения или
отправлять запросы клиентам.
FutureAction
------------
::
import xitrum.FutureAction
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends FutureAction {
def execute() {
respondText("hi")
}
}
В случае наследования от xitrum.FutureAction, код контроллера будет выполнятся в отдельном потоке (в том же пуле что и ``ActorAction``) не занимая потоки Netty.
Актор контроллер (actor action)
--------------------------------
Если вы хотите что бы контроллер был актором Akka наследуйтесь от ``ActorAction``:
::
import scala.concurrent.duration._
import xitrum.ActorAction
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends ActorAction with AppAction {
def execute() {
// See Akka doc about scheduler
import context.dispatcher
context.system.scheduler.scheduleOnce(3 seconds, self, System.currentTimeMillis())
// See Akka doc about "become"
context.become {
case pastTime =>
respondInlineView(s"It's $pastTime Unix ms 3s ago.")
}
}
}
Экземпляр актора будет создан на каждый запрос. Актор будет остановлен в момент закрытия подключения
или когда ответ будет отправлен клиенту. Для chunked запросов актор будет остановлен когда будет
отправлен последний chunk.
Актор будет выполняться в пуле потоков Akka в системе с именем "xitrum".
Отправка ответа клиенту
-----------------------
Что бы отправить данные клиенту используются функции:
* ``respondView``: при ответе использует шаблон ассоциированный с контроллером
* ``respondInlineView``: при ответе использует шаблон переданный как аргумент
* ``respondText("hello")``: ответ строкой "plain/text"
* ``respondHtml("...")``: ответ строкой "text/html"
* ``respondJson(List(1, 2, 3))``: преобразовать Scala объект в JSON и ответить
* ``respondJs("myFunction([1, 2, 3])")``
* ``respondJsonP(List(1, 2, 3), "myFunction")``: совмещение предыдущих двух
* ``respondJsonText("[1, 2, 3]")``
* ``respondJsonPText("[1, 2, 3]", "myFunction")``
* ``respondBinary``: ответ массивом байт
* ``respondFile``: переслать файл с использованием техники `zero-copy
Hello {s}!
) } } Фрагменты --------- MyAction.jade: ``scr/main/scalate/mypackage/MyAction.jade`` Шаблонизация с помощью фрагмента ``scr/main/scalate/mypackage/_MyFragment.jade``: :: renderFragment[MyAction]("MyFragment") Можно записать короче, если ``MyAction`` - текущий контроллер: :: renderFragment("MyFragment") Использование шаблона смежного контроллера ------------------------------------------ Использование метода ``respondView[ClassName]()``: :: package mypackage import xitrum.Action import xitrum.annotation.{GET, POST} @GET("login") class LoginFormAction extends Action { def execute() { // Respond scr/main/scalate/mypackage/LoginFormAction.jade respondView() } } @POST("login") class DoLoginAction extends Action { def execute() { val authenticated = ... if (authenticated) redirectTo[HomeAction]() else // Reuse the view of LoginFormAction respondView[LoginFormAction]() } } Один контроллер - много представлений ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Использование нескольких шаблонов для одного контроллера: :: package mypackage import xitrum.Action import xitrum.annotation.GET // Шаблоны автоматически не маршрутизируются // scr/main/scalate/mypackage/HomeAction_NormalUser.jade // scr/main/scalate/mypackage/HomeAction_Moderator.jade // scr/main/scalate/mypackage/HomeAction_Admin.jade trait HomeAction_NormalUser extends Action trait HomeAction_Moderator extends Action trait HomeAction_Admin extends Action @GET("") class HomeAction extends Action { def execute() { val userType = ... userType match { case NormalUser => respondView[HomeAction_NormalUser]() case Moderator => respondView[HomeAction_Moderator]() case Admin => respondView[HomeAction_Admin]() } } } Использование дополнительных не автоматических маршрутов выглядит утомительно, однако это более безопасно относительно типов (typesafe). Вы также можете использовать `` String`` указать местоположение шаблона: :: respondView("mypackage/HomeAction_NormalUser") respondView("mypackage/HomeAction_Moderator") respondView("mypackage/HomeAction_Admin") Компонент --------- Компоненты позволяют создавать переиспользуемое поведение и могут быть включены во множество представлений. Концептуально компонент очень близок к контроллеру, но: * Не имеет маршрутов, поэтому отсутствует метод ``execute``. * Компонент не отправляет ответ сервера, он просто выполняет шаблонизацию фрагмента. Поэтому внутри компонента, вместо вызовов ``respondXXX``, необходимо использовать ``renderXXX``. * Как и контроллеры, компонент может иметь ни одного, одно или множество связанных представлений. :: package mypackage import xitrum.{FutureAction, Component} import xitrum.annotation.GET class CompoWithView extends Component { def render() = { // Render associated view template, e.g. CompoWithView.jade // Note that this is renderView, not respondView! renderView() } } class CompoWithoutView extends Component { def render() = { "Hello World" } } @GET("foo/bar") class MyAction extends FutureAction { def execute() { respondView() } } MyAction.jade: :: - import mypackage._ != newComponent[CompoWithView]().render() != newComponent[CompoWithoutView]().render()