Action と view
===============
Xitrumは3種類のActionを提供しています。
通常の ``Action`` 、``FutureAction`` 、そして ``ActorAction`` です。
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")
}
}
リクエストは下記の ``ActorAction`` と同じスレッドプールが使用されます。これはNettyのスレッドプールとは異なります。
ActorAction
-----------
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")``: 上記2つの組み合わせをJSONPとして送信します
* ``respondJsonText("[1, 2, 3]")``: contentTypeを"application/javascript"として文字列として送信します
* ``respondJsonPText("[1, 2, 3]", "myFunction")``: `respondJs` 、 `respondJsonText` の2つの組み合わせをJSONPとして送信します
* ``respondBinary``: バイト配列を送信します
* ``respondFile``: ディスクからファイルを直接送信します。 `zero-copy
Hello {s}!
) } } renderFragment -------------- MyAction.jadeが ``scr/main/scalate/mypackage/MyAction.jade`` にある場合、同じディレクトリにあるフラグメント ``scr/main/scalate/mypackage/_MyFragment.jade`` を返す場合: :: renderFragment[MyAction]("MyFragment") 現在のActionが``MyAction``の場合、以下のように省略できます。 :: renderFragment("MyFragment") 別のアクションに紐付けられた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]() } } ひとつのアクションに複数の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]() } } } 上記のようにルーティングとは関係ないアクションを記述することは一見して面倒ですが、 この方法はプログラムをタイプセーフに保つことができます。 またはテンプレートのパスを文字列で指定します: :: respondView("mypackage/HomeAction_NormalUser") respondView("mypackage/HomeAction_Moderator") respondView("mypackage/HomeAction_Admin") ``renderView``, ``renderViewNoLayout``, ``respondView``, ``respondViewNoLayout`` では ``src/main/scalate`` からのテンプレートファイルへのパス、 ``renderFragment`` にはフラグメントを配置したディレクトリーへのパスをクラスの代わりに指定することができます。 Component --------- 複数のViewに対して組み込むことができる再利用可能なコンポーネントを作成することもできます。 コンポーネントのコンセプトはアクションに非常に似ています。 以下のような特徴があります。 * コンポーネントはルートを持ちません。すなわち ``execute`` メソッドは不要となります。 * コンポーネントは全レスポンスを返すわけではありません。 断片的なviewを "render" するのみとなります。 そのため、コンポーネント内部では ``respondXXX`` の代わりに ``renderXXX`` を呼び出す必要があります。 * アクションのように、コンポーネントは単一のまたは複数の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()