Xitrum

  • May
  • 26
  • 2015

IT

Xitrum 3.24.0

Xitrum 3.24.0 Released!

主な変更点

1. #535 Componentの言語が常に"en"となってしまうバグが修正されました。

2. #527 Nettyのバージョンが4.0.26から4.0.28になりました。

3. #528 Akkaのバージョンが2.3.8から2.3.11になりました。

今回のリリースからセマンティックバージョニングに準拠するようになりました。
例えば Xitrum 3.24.1はXitrum 3.24.0と互換性があります。
Xitrum 3.25.0はXitrum 3.24.xとは互換性がなくなります。


3.23.1 から 3.24.0 へのアップデート方法

build.sbtを修正します。(3.24.0は基本的には3.23と互換性があります。)
参考: https://github.com/xitrum-framework/xitrum-new/commit/6cc7bff09b19d2ecc91e4f82bfeaecf5ebf5ff9e

-libraryDependencies += "tv.cntt" %% "xitrum" % "3.23.1"
              +libraryDependencies += "tv.cntt" %% "xitrum" % "3.24.0"

  • May
  • 26
  • 2015

IT

Xitrum 3.23(3.23.1)

Xitrum 3.23 Released!

Xitrum 3.23にはバグがあるため3.23.1を使用してください。

主な変更点

1. #512 maxHeaderSizeの設定ができるようになりました。

3.22 から 3.23.1 へのアップデート方法

build.sbtを修正します。
参考: https://github.com/xitrum-framework/xitrum-new/commit/770d4500e1d74d1ebb509654cb8ca530f6d6236c

-libraryDependencies += "tv.cntt" %% "xitrum" % "3.22"
              +libraryDependencies += "tv.cntt" %% "xitrum" % "3.23.1"

テンプレートエンジンにxitrum-scalateを使用している場合
xitrum-scalateのバージョンを2.4にアップデートする必要があります。
xitrum-scalate 2.3はxitrum3.23系と互換性がありません。


maxHeaderSizeを変更する場合はxitrum.confを修正します。
+maxHeaderSize = 8192
  • May
  • 26
  • 2015

IT

Xitrum 3.22

Xitrum 3.22 Released!

https://groups.google.com/forum/#!topic/xitrum-framework/YWClyCoAfnU

主な変更点

1. Scalaのバージョンが2.1.4から2.1.5になりました。

2. #519 Nettyのバージョンが4.0.24から4.0.25になりました。

3. #501 Akkaのバージョンが2.3.8から2.3.9になりました。

4. #459 ルート収集処理の最適化が行われました。Xitrumのルーティングに関係ないパッケージ(例えば "io.netty.*"や"akka.*"など)は無視されるようになりました。Eclipse上でXitrumが起動できない問題の多くはメモリ容量が足りなくなることに起因していますが、この修正でこの問題の解決が期待できます。


3.21 から 3.22 へのアップデート方法

build.sbtを修正するだけです。(Scalaバージョンとxitrumバージョン)

-scalaVersion := "2.11.4"
              +scalaVersion := "2.11.5"
-libraryDependencies += "tv.cntt" %% "xitrum" % "3.20"
              +libraryDependencies += "tv.cntt" %% "xitrum" % "3.21"

  • Dec
  • 25
  • 2014

IT

Xitrum 3.21

Xitrum 3.21 Released!

https://groups.google.com/d/msg/xitrum-framework/Jzmxf9X1pAo/anEyvOyxmYsJ

主な変更点


1. #492 Akkaのバージョンが 2.3.7から2.3.8になりました。

2. #489 BeforeFilterで`forwardTo` が呼ばれた場合のバグ修正

3. #491 `paramso`が削除されました。 指定したキーのパラメーターがない場合、`params`は `Seq.empty`を返すようになります。

4. #455 `PoLoader` はdevelopmentモードで `src/main/resources/i18n` ディレクトリを監視します。これによって、EclipseやIntelliJで開発中にも国際化対応されたメッセージが表示されます。

3.20 から 3.21 へのアップデート方法

build.sbtを修正するだけです。

-libraryDependencies += "tv.cntt" %% "xitrum" % "3.20"
              +libraryDependencies += "tv.cntt" %% "xitrum" % "3.21"
              
  • Dec
  • 11
  • 2014

IT

Xitrum 3.20

Xitrum 3.20 Released!

https://groups.google.com/d/msg/xitrum-framework/GOxo4rpFWEk/xuagrdy8MUsJ

主な変更点


1. #482 `paramo`、`params` そして `paramso` は値が空欄の場合はフィルタリングしません。 

指定した値が空欄の場合、Xitrum 3.17までと同様にExceptionがスローされます。


2. #484 `RequestVar` と `SessionVar`でOptionの各メソッドが使えるようになりました。

`RequestVar` と `SessionVar`は振る舞いについては、Xitrumガイドを参照してください。


3. #481 cacheシステムについて、developmentモードでの有効化、productionモードでの無効化を選択できるようになりました。

xitrum.conf の例:

https://github.com/xitrum-framework/xitrum-new/blob/master/config/xitrum.conf#L76


3.19 から 3.20 へのアップデート方法

いくつかの設定ファイルを修正するだけで簡単にアップデートできます。

例:

https://github.com/xitrum-framework/xitrum-new/commit/df4ba5dc95b0aa7169dc8841a6f1b0ca4c213eed


Xitrumプロジェクトページの一番下にいくつかのサンプルプロジェクトがあるので、そちらも参考にしてみてください。
http://xitrum-framework.github.io/

  • Nov
  • 15
  • 2014

IT

Xitrum 3.19

Xitrum 3.18 Released!

https://groups.google.com/d/msg/xitrum-framework/D-7gzo-QVsQ/tWdBfBWaB_8J

主な変更点

1. Netty

Nettyのバージョンが 4.0.23 to 4.0.24に更新されました:
http://netty.io/news/2014/10/29/4-0-24-Final.html

Netty 4.0.24 はPOODLE脆弱性対策のためSSLv3.0が無効になりました。

Xitrum組み込みのHTTPSサーバー(それはNettyをベースにしています)を使用している場合、プロジェクトをXitrum3.19に更新することが推奨されます。

(もし Xitrum 2.x 系を使用している場合、Xitrum 2.16に更新してください。)

2. Beforeフィルターでtrue/falseを返す必要がなくなりました。

以前は、Beforeフィルターは必ずtrue/falseを返却する必要がありましたが、
今バージョンからはBeforeフィルターで何かをrespondした場合、その後actionは呼ばれることはありません。

3. いくつかの便利なメソッドが追加されました。

https://github.com/xitrum-framework/xitrum/issues/466
(xitrum.util.Loader.bytesToFileの追加)
https://github.com/xitrum-framework/xitrum/issues/467
(xitrum.util.SeriDeseriに関する変更)
https://github.com/xitrum-framework/xitrum/issues/474
(SessionVerに対するisEmpyの追加)

4. respondViewにテンプレートファイルのロケーションを文字列として渡すことができるようになりました。

以前は、1つのアクションに複数のテンプレートを紐付ける場合
* ダミーアクションクラス/トレイトをテンプレート毎に作成する必要があり
* respondViewの際にそのクラス/トレイトを指定する必要がありました。

今バージョンからは、テンプレートファイルのロケーションを文字列で指定するだけで
複数のテンプレートを使い分ける事ができます。
詳しくはガイドを参照してください。
http://xitrum-framework.github.io/guide/3.19/ja/action_view.html#id7

-----
3.18 から 3.19 へのアップデート方法

Xitrumのバージョンを3.19にXitrum-scalateのバージョンを2.3に更新する必要があります。

              -libraryDependencies += "tv.cntt" %% "xitrum" % "3.18"
              +libraryDependencies += "tv.cntt" %% "xitrum" % "3.19"
              
              -libraryDependencies += "tv.cntt" %% "xitrum-scalate" % "2.2"
              +libraryDependencies += "tv.cntt" %% "xitrum-scalate" % "2.3"
              
              
参考
https://github.com/xitrum-framework/xitrum-new/commit/0235cdfa768161b12f8c414d8f3151a7f8c3ea59

[Xitrumことはじめ][基本編] 7. リクエストとスコープ: CSRF対策

Xitrumことはじめ (基本編)

Xitrumことはじめシリーズでは、Xitrumを使ったWebアプリケーション開発を勉強します。

目次はこちら

記事とサンプルコードはMITライセンスでgithubで公開します。

7. リクエストとスコープ:

今回はリクエストに直接アクセスする方法を取り上げたいと思います。

公式ドキュメントは以下のページが参考になります。

7-4. CSRF対策

7-3-1. XitrumのCSRF対策の仕組み

[Xitrum.Action](https://github.com/xitrum-framework/xitrum/blob/561d214d2af847c7ad4ab7bf1b3b1b9835f0f9a0/src/main/scala/xitrum/Action.scala#L85-L90)は、
リクエストメソッドが、POSTPUTPATCHDELETEの場合、デフォルトでCSRF対策トークンチェックを行います。

if ((request.getMethod == HttpMethod.POST ||
     request.getMethod == HttpMethod.PUT ||
     request.getMethod == HttpMethod.PATCH ||
     request.getMethod == HttpMethod.DELETE) &&
    !isInstanceOf[SkipCsrfCheck] &&
    !Csrf.isValidToken(this)) throw new InvalidAntiCsrfToken

では、このCSRFトークンチェックの内容はというと、
[Csrf.isValidToken](https://github.com/xitrum-framework/xitrum/blob/46f330ac6c360688417406dcc1539ebb8704b721/src/main/scala/xitrum/scope/session/Csrf.scala#L18-L29)の処理は次の用に、
リクエストヘッダーあるいはリクエストボディに指定されたキーでセットされたトークンと、セッション内のトークンが一致しているかを判定しています。
トークン自体はランダムな文字列ですが、シリアライズされた形式でセッションに保存されています。

def isValidToken(action: Action): Boolean = {
  // The token must be in the request body for more security
  val bodyTextParams = action.handlerEnv.bodyTextParams
  val headers        = action.handlerEnv.request.headers
  val tokenInRequest = Option(headers.get(X_CSRF_HEADER)).getOrElse(action.param(TOKEN, bodyTextParams))
 
  // Cleaner for application developers when seeing access log
  bodyTextParams.remove(TOKEN)
 
  val tokenInSession = action.antiCsrfToken
  tokenInRequest == tokenInSession
}

7-3-2. AntiCSRFトークンのセット

クライアントはリクエストヘッダーまたはリクエストボディにセッション内のトークンと一致する値を含める必要があります。

HTMLメタタグとXitrum.jsを用いてリクエストヘッダーにトークンを含める方法

レイアウト内で、{antiCsrfMeta}を使用することでメタタグが生成されます。
Scaffoldプロジェクトにあるように通常デフォルトレイアウトで用います。

!!! 5
html
  head
    != antiCsrfMeta
    != xitrumCss
    meta(content="text/html; charset=utf-8" http-equiv="content-type")

こう書くことで、以下のようにHTMLに展開されます。

<!DOCTYPE html>
<html>
  <head>
    <meta name="csrf-token" content="9f16c39b-3456-4020-9695-484d101908ca"/>
    <link href="/webjars/xitrum/3.18/xitrum.css?mhIAFrxv3tBMQXtHcoYT7w" type="text/css" rel="stylesheet" media="all"/>
    <meta content="text/html; charset=utf-8" http-equiv="content-type"/>

Xitrum.jsを使用した場合AJAXリクエスト送信時に、このメタタグの値をリクエストヘッダーの値に自動的に含めてくれます。
Xitrum.jsのインポートの仕方は以下のとおりです。

!= jsDefaults
 
または、
 
script(type="text/javascript" src={url[xitrum.js]})

リクエストボディにトークンを含める方法

form(method="post" action={url[SiteIndex]})
  != antiCsrfInput
 
#または
 
form(method="post" action={url[SiteIndex]})
  input(type="hidden" name="csrf-token" value={antiCsrfToken})

いずれも以下のように展開されます。

<form method="post" action="/">
  <input type="hidden" name="csrf-token" value="9f16c39b-3456-4020-9695-484d101908ca"/>
</form>

7-3-3. CSRF対策をスキップする

debug時やcurlクライントなどCSRF対策を省略したい場合があります。
ActionとHTTPメソッドアノテーションの回で確認したように、
SkipCsrfCheckを継承したActionを使用することで前述の!isInstanceOf[SkipCsrfCheck]という条件にあてはまらなくなるなるので、
CSRF対策チェックは実行されません。


次回は、CSRFトークンも保存されているセッションおよびクッキーについて掘り下げたいと思います。

[Xitrumことはじめ][基本編] 7. リクエストとスコープ: FullHttpRequest

Xitrumことはじめ (基本編)

Xitrumことはじめシリーズでは、Xitrumを使ったWebアプリケーション開発を勉強します。

目次はこちら

記事とサンプルコードはMITライセンスでgithubで公開します。

7. リクエストとスコープ:

今回はリクエストに直接アクセスする方法を取り上げたいと思います。

公式ドキュメントは以下のページが参考になります。

7-3. FullHttpRequest

7-3-1. リクエストに直接アクセスする

作成したActionクラスのexecuteメソッドが呼ばれた時、生のリクエストFullHttpRequest
requestという変数に、リクエストボディはrequestContentStringという変数で文字列として取得することができます。

RequestRawExample.scala
@GET("/requestraw")
class RequestRawIndex extends Action {
  def execute() {
    val whaleRequest = request
    log.debug("Request:" + whaleRequest.toString)
    respondText(
s"""
Request:${whaleRequest.toString}
"""
    )
  }
}
 
@GET("/requestbody")
class RequestBodyIndex extends Action {
  def execute() {
    val body = requestContentString
    log.debug("body:" + requestContentString)
    respondText(
s"""
body:${requestContentString}
"""
    )
  }
}

これらのURLにアクセスすると以下のような結果となります。

curl -X GET http://localhost:8000/requestraw\?query\=Hello -H "X-MyHeader:World" -d "message=xxx"
 
Request:DefaultFullHttpRequest(decodeResult: success)
GET /requestraw?query=Hello HTTP/1.1
User-Agent: curl/7.32.0
Host: localhost:8000
Accept: */*
X-MyHeader: World
Content-Length: 11
Content-Type: application/x-www-form-urlencoded
 
 
curl -X GET http://localhost:8000/requestbody\?query\=Hello -H "X-MyHeader:World" -d "message=xxx"
 
body:message=xxx

7-3-2. リクエストヘッダーにアクセスする

HTTP Headerにアクセスするにはparamparamoは使用することができません。
直接リクエストから取得する必要があります。

RequestHeaderExample.scala
@GET("/requestheader")
class RequestHeaderIndex extends Action {
  def execute() {
    val headers = request.headers
    log.debug("Header:" + headers.toString)
 
    val entries = headers.entries
    log.debug("Entries:" + entries.toString)
 
    val myHeader = headers.get("X-MyHeader")
    log.debug("X-MyHeader:" + myHeader.toString)
 
 
    respondText(
s"""
Header:${headers.toString}
Entries:${entries.toString}
X-MyHeader:${myHeader.toString}
"""
    )
  }
}

結果は以下のようになります。

curl -X GET http://localhost:8000/requestheader\?query\=Hello -H "X-MyHeader:World" -d "message=xxx"
 
Header:io.netty.handler.codec.http.DefaultHttpHeaders@2f1bc1e1
Entries:[User-Agent=curl/7.32.0, Host=localhost:8000, Accept=*/*, X-MyHeader=World, Content-Length=11, Content-Type=application/x-www-form-urlencoded]
myHeader:World

ヘッダーパラメーターがparamparamoの対象外である理由としては、
通常のActionからはヘッダーパラメータにアクセスする機会が少ないからであるといえます。
ヘッダーパラメータをアプリケーションが利用するユースケースとして、認証処理などが考えられます。
たとえば、X-APP_TOKENなどといった形式でアプリケーションのトークンを全てのリクエストのヘッダーに含めて認証を行うアプリの場合、
認証処理は全てのActionに共通であるため、通常はフィルターが使用されます。
リクエストへの直接のアクセスはフィルターに限定して、通常のActionはそのActionのためのリクエスト(クエリー、パス、ボディ)のみを参照する設計がよいと考えられます。
フィルターを使ったサンプルはまた次の機会にやってみます。

[Xitrumことはじめ][基本編] 7. リクエストとスコープ: リクエストパラメーター

Xitrumことはじめ (基本編)

Xitrumことはじめシリーズでは、Xitrumを使ったWebアプリケーション開発を勉強します。

目次はこちら

記事とサンプルコードはMITライセンスでgithubで公開します。

7. リクエストとスコープ:

今回はリクエストパラメーターの扱い方を取り上げたいと思います。

公式ドキュメントは以下のページが参考になります。

7-2. リクエストパラメーター

7-2-1. リクエストパラメータにアクセスする

XitrumのActionで扱えるリクエストパラメータは以下の通りです。

リクエストパラメーターには2種類あります:

テキストパラメータ
ファイルアップロードパラメーター(バイナリー)
テキストパラメーターは scala.collection.mutable.Map[String, List[String]] の型をとる3種類があります:

queryParams: URL内の?以降で指定されたパラメーター 例: http://example.com/blah?x=1&y=2
bodyTextParams: POSTリクエストのbodyで指定されたパラメーター
pathParams: URL内に含まれるパラメーター 例: GET("articles/:id/:title")
これらのパラメーターは上記の順番で、 textParams としてマージされます。 (後からマージされるパラメーターは上書きとなります。)

bodyFileParams は scala.collection.mutable.Map[String, List[ FileUpload ]] の型をとります。

今回は、テキストパラメータについてのみ試します。
テキストパラメータは、paramおよび、paramoメソッドで取得することができます。

paramで指定したキーが存在しない場合、Xitrumが自動で400 Bad Requestをレスポンスします。
paramoは指定したキーをOption型として取得し、存在しない場合はNoneとなります

また、bodyTextParamsについては、POST、PUT,PATCHメソッドの場合のみ取得されます。

RequestParamExample.scala
@GET("/requestparam/:path1/:path2")
@POST("/requestparam/:path1/:path2")
class RequestParamIndex extends Action with SkipCsrfCheck {
  def execute() {
 
    // From path param
    val path1  = param("path1")
    val path2  = param("path2")
    log.debug("path1"+path1)
    log.debug("path2"+path2)
 
    // From query param
    val query1  = param("query1")
    val query2  = param("query2")
    log.debug("query1"+query1)
    log.debug("query2"+query2)
 
    // From body param when HTTP method is POST, PUT, PATCH
    val body1   = param("body1")
    val body2   = param("body2")
    log.debug("body1"+body1)
    log.debug("body2"+body2)
 
    respondText(
s"""
textParams:${textParams}
queryParams:${queryParams}
bodyTextParams:${bodyTextParams}
pathParams:${pathParams}
path1:${path1}
path2:${path2}
query1:${query1}
query2:${query2}
"""
    )
  }
}
 
@GET("/requestparamoption/:path1/:path2")
@POST("/requestparamoption/:path1/:path2")
class RequestParamOptionIndex extends Action with SkipCsrfCheck {
  def execute() {
 
    // From path param
    val path1  = paramo("path1")
    val path2  = paramo("path2")
    log.debug("path1"+path1)
    log.debug("path2"+path2)
 
    // From query param
    val query1  = paramo("query1")
    val query2  = paramo("query2")
    log.debug("query1"+query1)
    log.debug("query2"+query2)
 
    // From query param
    val body1   = paramo("body1")
    val body2   = paramo("body2")
    log.debug("body1"+body1)
    log.debug("body2"+body2)
 
    respondText(
s"""
textParams:${textParams}
queryParams:${queryParams}
bodyTextParams:${bodyTextParams}
pathParams:${pathParams}
path1:${path1}
path2:${path2}
query1:${query1}
query2:${query2}
body1:${body1}
body2:${body2}
"""
    )
  }
}

それぞれのURLにGETとPOSTでアクセスすると以下の様な結果となります。

curl -X GET http://localhost:8000/requestparam/x/y\?query1\=q1\&query2\=q2 -H "X-MyHeader:World" -d "body1=b1" -d "body2=b2"
Missing param: body1
 
 
curl -X POST http://localhost:8000/requestparam/x/y\?query1\=q1\&query2\=q2 -H "X-MyHeader:World" -d "body1=b1" -d "body2=b2"
 
textParams:Map(path2 -> List(y), path1 -> List(x), body2 -> List(b2), query2 -> List(q2), body1 -> List(b1), query1 -> List(q1))
queryParams:Map(query2 -> List(q2), query1 -> List(q1))
bodyTextParams:Map(body2 -> List(b2), body1 -> List(b1))
pathParams:Map(path2 -> List(y), path1 -> List(x))
path1:x
path2:y
query1:q1
query2:q2
 
 
curl -X GET http://localhost:8000/requestparamoption/x/y\?query1\=q1\&query2\=q2 -H "X-MyHeader:World" -d "body1=b1" -d "body2=b2"
 
textParams:Map(path2 -> List(y), path1 -> List(x), query2 -> List(q2), query1 -> List(q1))
queryParams:Map(query2 -> List(q2), query1 -> List(q1))
bodyTextParams:Map()
pathParams:Map(path2 -> List(y), path1 -> List(x))
path1:Some(x)
path2:Some(y)
query1:Some(q1)
query2:Some(q2)
body1:None
body2:None
 
 
curl -X POST http://localhost:8000/requestparamoption/x/y\?query1\=q1\&query2\=q2 -H "X-MyHeader:World" -d "body1=b1" -d "body2=b2"
 
textParams:Map(path2 -> List(y), path1 -> List(x), body2 -> List(b2), query2 -> List(q2), body1 -> List(b1), query1 -> List(q1))
queryParams:Map(query2 -> List(q2), query1 -> List(q1))
bodyTextParams:Map(body2 -> List(b2), body1 -> List(b1))
pathParams:Map(path2 -> List(y), path1 -> List(x))
path1:Some(x)
path2:Some(y)
query1:Some(q1)
query2:Some(q2)
body1:Some(b1)
body2:Some(b2)

7-2-2 型を指定してパラメータを取得する

リクエストパラメータのデフォルトはString型です。
paramおよび、paramoに型を指定することで任意の型でパラメータを取得することができます。
デフォルトのコンバータは以下の用に定義されており、独自の型に変換する場合は、Action内でconvertTextParamをオーバーライドします。
なおconvertTextParamをオーバーライドする際に、Scala 2.10ではTypeの代わりにManifestを使用することに注意してください。

RequestParamConvertExample.scala
@GET("/requestparamconvert/:one")
class RequestConvertIndex extends Action {
  def execute() {
 
      val one_as_String   = param[String]("one")
      val one_as_Char     = param[Char]("one")
      val one_as_Byte     = param[Byte]("one")
      val one_as_Short    = param[Short]("one")
      val one_as_Int      = param[Int]("one")
      val one_as_Long     = param[Long]("one")
      val one_as_Float    = param[Float]("one")
      val one_as_Double   = param[Double]("one")
      val one_as_Implicit = param("one")
 
 
    respondText(
s"""
one_as_String =>   Class:${one_as_String.getClass.toString}, Value:${one_as_String}
one_as_Char =>     Class:${one_as_Char.getClass.toString},   Value:${one_as_Char}
one_as_Byte =>     Class:${one_as_Byte.getClass.toString},   Value:${one_as_Byte}
one_as_Short =>    Class:${one_as_Short.getClass.toString},  Value:${one_as_Short}
one_as_Int =>      Class:${one_as_Int.getClass.toString},    Value:${one_as_Int}
one_as_Long =>     Class:${one_as_Long.getClass.toString},   Value:${one_as_Long}
one_as_Float =>    Class:${one_as_Float.getClass.toString},  Value:${one_as_Float}
one_as_Double =>   Class:${one_as_Double.getClass.toString}, Value:${one_as_Double}
one_as_Implicit => Class:${one_as_Implicit.getClass.toString}, Value:${one_as_Implicit}
"""
    )
  }
}
 
case class MyClass(value:String)
 
@GET("/requestparamconvertcustome/:one")
class RequestConvertCustomeIndex extends Action {
  override  def convertTextParam[T: TypeTag](value: String): T = {
    val t = typeOf[T]
    val any: Any =
           if (t <:< typeOf[String])  value
      else if (t <:< typeOf[MyClass]) MyClass(value)
      else if (t <:< typeOf[Int])    value.toInt
      else throw new Exception("convertTextParam cannot covert " + value + " to " + t)
    any.asInstanceOf[T]
  }
 
  def execute() {
 
      val one_as_MyClass  = param[MyClass]("one")
      val one_as_String   = param[String]("one")
      val one_as_Int      = param[Int]("one")
 
 
    respondText(
s"""
one_as_String =>   Class:${one_as_String.getClass.toString},  Value:${one_as_String}
one_as_MyClass=>   Class:${one_as_MyClass.getClass.toString}, Value:${one_as_MyClass}
one_as_Int =>      Class:${one_as_Int.getClass.toString},     Value:${one_as_Int}
"""
    )
  }
}

実行結果は以下のようになります。

oshidatakeharu@oshida [~] curl -X GET http://localhost:8000/requestparamconvert/1
 
one_as_String =>   Class:class java.lang.String, Value:1
one_as_Char =>     Class:char,   Value:1
one_as_Byte =>     Class:byte,   Value:1
one_as_Short =>    Class:short,  Value:1
one_as_Int =>      Class:int,    Value:1
one_as_Long =>     Class:long,   Value:1
one_as_Float =>    Class:float,  Value:1.0
one_as_Double =>   Class:double, Value:1.0
one_as_Implicit => Class:class java.lang.String, Value:1
 
 
oshidatakeharu@oshida [~] curl -X GET http://localhost:8000/requestparamconvertcustome/1
 
one_as_String =>   Class:class java.lang.String,  Value:1
one_as_MyClass=>   Class:class quickstart.action.MyClass, Value:MyClass(1)
one_as_Int =>      Class:int,     Value:1

7-2-3. リクエストボディをJSONとして扱う

Restful APIサーバなど、クライアントとのインターフェイスをJSONで定義したアプリケーションの場合など、
JSONをそのままMapとして扱えると便利です。Xitrumでは以下の方法で実現することができます。

RequestJSONExample.scala
@GET("/requestbodyjson")
class RequestBodyJsonIndex extends Action {
  def execute() {
    val bodyJson = requestContentJson[Map[String, Any]]
    log.debug("body as Json:" + bodyJson)
 
    bodyJson match {
      case Some(v) => log.debug("Successfully parsed")
      case None =>    log.debug("Failed to parse")
    }
    respondText(
s"""
body as Json:${bodyJson}
"""
    )
  }
}

requestContentJsonはパースに失敗した場合はNoneを返します。

curl -X GET http://localhost:8000/requestbodyjson\?query\=Hello -H "X-MyHeader:World" -d "{\"message\":\"xxx\",\"code\":1,\"list\":[1,2,3],\"bool\":true}"
 
body as Json:Some(Map(message -> xxx, code -> 1, list -> List(1, 2, 3), bool -> true))
 
 
curl -X GET http://localhost:8000/requestbodyjson\?query\=Hello -H "X-MyHeader:World" -d "{\"json\":{\"nest\":1}}"
 
body as Json:Some(Map(json -> Map(nest -> 1)))
 
 
curl -X GET http://localhost:8000/requestbodyjson\?query\=Hello -H "X-MyHeader:World" -d "invalidjson"
 
body as Json:None

次回は生のリクエストにアクセスする方法をやります。

[Xitrumことはじめ][基本編] 7. リクエストとスコープ: チャネルパイプライン

Xitrumことはじめ (基本編)

Xitrumことはじめシリーズでは、Xitrumを使ったWebアプリケーション開発を勉強します。

目次はこちら

記事とサンプルコードはMITライセンスでgithubで公開します。

7. リクエストとスコープ:

今回からはリクエストおよびスコープについて勉強します。
クライアントからのリクエストがどのように処理されるか、リクエストパラメーターの扱い方などを取り上げたいと思います。

公式ドキュメントは以下のページが参考になります。

7-1. チャネルパイプラインとActionのライフサイクル

アプリケーションのロジックでリクエストを処理するうえで、Xitrumの処理の流れの概要を掴んでおきたいと思います。
Xitrumアプリケーションを起動時に

xitrum.Server.start()

と呼び出す必要があります。xitrum.Serverは、
NettyのChannelPipelineを構築し、Nettyサーバを起動しています。

Xitrumがデフォルトで構築するチャネルパイプラインは、
次のようなイメージです

                                        +~~~~~~~~~~~~~~~~~~~~~~+
                                ------->|         Action       |
                                |       +~~~~~~~~~~~~~~~~~~~~~~+
                                |                   |
+-------------------------------|-------------------+---------------+
| ChannelPipeline               |                   |               |
|                               |                   |               |
|    +---------------------+    |                   |               |
|    |  BadClientSilencer  |    |                   |               |
|    +----------+----------+    |                   |               |
|              /|\              |                   |               |
|               |          _____|                  \|/     OutBound |
|    +--------------------/+            +-----------+----------+    |
|    |      Dispatcher     |            |     XSendResource    |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +---------------------+            +-----------+----------+    |
|    |   MethodOverrider   |            |       XSendFile      |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    |      UriParser      |            |   FixiOS6SafariPOST  |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  .               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    |    WebJarsServer    |            |    OPTIONSResponse   |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    |   PublicFileServer  |            |        SetCORS       |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    |    BaseUrlRemover   |            |     Env2Response     |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    |     Request2Env     |            | ChunkedWriteHandler  |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    | HttpRequestDecoder  |            | HttpResponseEncoder  |    |
|    +----------+----------+            +-----------+----------+    |
| InBound      /|\                                  |               |
+---------------+-----------------------------------+---------------+
                |                                  \|/
+---------------+-----------------------------------+---------------+
|               |                                   |               |
|       [ Socket.read() ]                    [ Socket.write() ]     |
|                                                                   |
|  Netty Internal I/O Threads (Transport Implementation)            |
+-------------------------------------------------------------------+

クライアントからのリクエストに対して、NettyのI/Oスレッド上でそれぞれパイプラインが生成・実行されます。
リクエストはInboundハンドラーを経て、DispacherにてルーティングにマッチしたActionへディスパッチされます。
このパイプラインを経ることで、Actionが実行される段階では生のリクエストはアプリケーション開発者にとって利用しやすい形になっています。
ActionはrespondViewなどを実行した段階で処理を終え、処理の流れはOutBoundへと進みます。

クライアントからのリクエスト毎に割り当てられるこのスレッド上でActionクラスのインスタンスは生成されます。
一般にアプリケーション開発者が意識すべきActionのライフサイクルは
Actionのexecuteメソッド内に限られており、
アノテーションで指定されたルーティングにマッチするリクエストを受け付けた時に、
Actionインスタンスは生成され、executeメソッドが実行されます。
executeメソッドの最後にrespondXXXを実行することoutBoundハンドラーへの電文を作成しActionは役目を終えます。

WebSocketおよびSockJSクライアントのように、接続を持続するリクエストに対しては、
Action以降の不要なハンドラーが削除されます

なお、XitrumにはActionの実行をFutureやAkkaのスレッドで実行する仕組みも用意されています。
その話はまた今度。

クライアントからのリクエストがActionに届く流れがは大体こんな感じなので、
次回は実際にHTTPリクエストをAction内で扱うための便利機能について使ってみたいと思います。