Action 과 view
===============
유연함을 위해, Xitrum은 3가지 형태의 Action을 제공합니다.
보통 ``Action``, ``FutureAction``, ``ActorAction`` 입니다.
Normal Action
-------------
::
import xitrum.Action
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends Action {
def execute() {
respondText("Hello")
}
}
요청은 Netty의 IO스레드로 직접로 처리되므로 시간이 걸리는 처리(블록처리)를 포함하면 안됩니다.
Netty의 IO스레드를 오래 사용하게 되면 Netty는 새로운 연결을 할 수 없거나 응답을 회신할 수 없게 되기 때문입니다.
FutureAction
------------
::
import xitrum.FutureAction
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends FutureAction {
def execute() {
respondText("hi")
}
}
요청은 Netty의 스레드 풀과는 별개로 다음의 ``ActorAction`` 과 같은 스레드 풀에서 처리됩니다.
Actor Action
------------
Action 을 Akka actor 처럼 정의하려면 ``ActorAction`` 을 상속하면 됩니다.
::
import scala.concurrent.duration._
import xitrum.ActorAction
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends ActorAction {
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.")
}
}
}
Actor 인스턴스는 요청이 발생할때 생성됩니다. 이 actor 인스턴스는 연결이 끊어지거나
``respondText``, ``respondView`` 등의 메소드를 통해 응답을 얻을때 중지됩니다.
청크응답의 경우 즉시 중지되지 않고 마지막 청크가 전송된 시점에서 중지됩니다.
요청은 "xitrum"이라고 불리는 Akka actor 시스템 스레드 풀에서 처리됩니다.
클라이언트로의 전송
--------------
Action으로 부터 클라이언트로 응답을 전송하려면 다음과 같은 방법을 사용합니다
* ``respondView``: 레이아웃을 포함하거나 포함하지 않고 View 템플릿을 전송합니다
* ``respondInlineView``: 레이아웃을 포함하거나 포함하지 않고 인라인으로 작성된 템플릿을 전송합니다
* ``respondText("hello")``: 레이아웃 파일을 사용하지 않고 문자열을 보냅니다
* ``respondHtml("...")``: contentType을 "text/html" 형식으로 문자열을 보냅니다
* ``respondJson(List(1, 2, 3))``: Scala 객체를 JSON으로 변환하여 contentType을 "application/json" 형식으로 보냅니다
* ``respondJs("myFunction([1, 2, 3])")`` contentType을 "application/javascript"으로 문자열을 보냅니다
* ``respondJsonP(List(1, 2, 3), "myFunction")``: 위 두 가지를 조합하여 JSONP로 보냅니다
* ``respondJsonText("[1, 2, 3]")``: contentType을 "application/javascript"으로 문자열을 보냅니다
* ``respondJsonPText("[1, 2, 3]", "myFunction")``: `respondJs`, `respondJsonText`의 두 가지 조합을 JSONP로 보냅니다
* ``respondBinary``: 바이트 배열로 보냅니다
* ``respondFile``: 디스크에서 파일을 직접 보냅니다. `zero-copy
Hello {s}!
) } } Render fragment -------------- MyAction.jade가 ``scr/main/scalate/mypackage/MyAction.jade`` 에 있는 경우 : 같은 디렉토리에 있는 조각파일을 반환하는 경우: ``scr/main/scalate/mypackage/_MyFragment.jade`` :: renderFragment[MyAction]("MyFragment") 현재 Action이 ``MyAction`` 의 경우에는 다음과 같이 생략이 가능합니다: :: renderFragment("MyFragment") 다른 Action의 View를 응답하는 경우 ---------------------------- 다음의 메소드를 사용합니다 ``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]() } } 하나의 Action - 여러 View사용 ~~~~~~~~~~~~~~~~~~~~~~~~~ :: package mypackage import xitrum.Action import xitrum.annotation.GET // These are non-routed actions, for mapping to view template files: // 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]() } } } 위와 같이 라우팅과 상관없는 작업을 설명하는것이 어려워 보일수는 있지만 이 방법은 프로그램이 형식에 대해 안정성을 유지할 수 있습니다. ``String`` 값을 이용하여 템블릿 위치를 지정할 수도 있습니다: :: respondView("mypackage/HomeAction_NormalUser") respondView("mypackage/HomeAction_Moderator") respondView("mypackage/HomeAction_Admin") Component --------- 여러 View에 통합 할 수 있는 재사용이 가능한 구성요소를 생성 수 있습니다. 구성 요소의 개념은 액션과 매우 비슷합니다. 다음과 같은 특징이 있습니다. * 구성요소는 루트가 없습니다. 즉 ``execute`` 메소드는 필요가 없습니다. * 구성요소는 전체 응답을 반환하지 않습니다. 단편적인 view를 "render" 하기만 합니다. 따라서, 구성요소 내부에서 ``respondXXX`` 대신 ``renderXXX`` 호출해야 합니다. * Action처럼 구성요소는 단일 혹은 여러 View와 연관이 있거나 연관성 없이 사용할 수 있습니다. :: 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()