Play ドキュメントを Skinny で書くと - Manipulating Results, Session and Flash scopes, Content negotiation
これは Play framework 2.x Scala Advent Calendar 2013 の 13 日目です。
http://www.adventar.org/calendars/114
なお、この記事で触れる Play は 2.2.1、Skinny は 0.9.20 です。両者とも最新バージョンでは API や仕様が変更になっている場合があります。
Manipulating Results
http://www.playframework.com/documentation/2.2.x/ScalaResults
Changing the default Content-Type
val textResult = Ok("Hello World!") val xmlResult = Ok(<message>Hello World!</message>)
これは Skinny の場合もコードは同じですが
def hello = Ok("Hello World!") def hello = Ok(<message>Hello World!</message>)
Content-Type: text/html;charset=UTF-8 として応答します。text/plain や application/xml として応答したい場合は contentType を指定してください。
Play では as で続けて指定することで Content-Type を変えることができますが
val xmlResult = Ok(<message>Hello World!</message>) val htmlResult2 = Ok(<h1>Hello World!</h1>).as(HTML)
Skinny(Scalatra)では、以下のように setter で指定します。
beforeAtction() { contentType = "text/plain" } def hello = Ok("Hello World!") def hello = { contentType = "application/xml" Ok(<message>Hello World!</message>) }
Manipulating HTTP headers
val result = Ok("Hello World!").withHeaders( CACHE_CONTROL -> "max-age=3600", ETAG -> "xx")
上記の Play のコードと同じことは
def hello = { response.setHeader("Cache-Control", "max-age=3600") response.setHeader("ETag", "xx") "Hello World!" }
のようにします。
Setting and discarding cookies
val result = Ok("Hello world").withCookies( Cookie("theme", "blue")) val result2 = result.discardingCookies(DiscardingCookie("theme"))
Skinny は Scalatra の SweetCookies でスッキリ書けます。
http://www.scalatra.org/2.2/api/index.html#org.scalatra.SweetCookies
cookies += "theme" -> "blue" cookies -= "theme"
Changing the charset for text based HTTP responses.
object Application extends Controller { implicit val myCustomCharset = Codec.javaSupported("iso-8859-1") def index = Action { Ok(<h1>Hello World!</h1>).as(HTML) } } def HTML(implicit codec: Codec) = { "text/html; charset=" + codec.charset }
似たようなアプローチもできますが、あまり必要にも見えないので Skinny ではシンプルにやるのがよいのではないでしょうか。beforeAction でデフォルトを指定しておいて、それと異なる時だけ指定すればよいと思います。
object Application extends Controller { override def charset = "iso-8859-1" def setContentTypeAsHTML() = { contentType = s"text/html; charset=${charset}" } def index = { setContentTypeAsHTML() Ok(<h1>Hello World!</h1>) } }
Session and Flash scopes
http://www.playframework.com/documentation/2.2.x/ScalaSessionFlash
How it is different in Play
Play はサーバ側にセッションを保持せず Cookie に値を保存しています。
Scalatra は Servlet のセッションを扱うので JSESSIONID にひもづいた HttpServletSession です。Servlet のセッションに状態を持つ場合は複数台の Servlet コンテナを運用する際は Sticky Session にすることになります。この辺は何か別のアプローチのサポートも考えたいのですが、まだ未着手です*1。
Reading a Session value
def index = Action { implicit request => session.get("connected").map { user => Ok("Hello " + user) }.getOrElse { Unauthorized("Oops, you are not connected") } }
Skinny では Scalatra の RichSession を使います。
http://www.scalatra.org/2.2/api/index.html#org.scalatra.servlet.RichSession
def index = { session.get("connected").map { user => Ok(s"Hello ${user}") }.getOrElse { Unauthorized("Oops, you are not connected") } }
これはコードの見た目はほとんど一緒ですね。
Storing data in the Session
Ok("Welcome!").withSession("connected" -> "user@gmail.com")
Skinny は Response に with でつなげる必要はありません。これは connected だけの session になる例なので、このようになります。
session.clear() session += "connected" -> "user@gmail.com" Ok("Welcome!")
次に session に値を追加する例ですが
Ok("Hello World!").withSession(session + ("saidHello" -> "yes"))
Skinny では clear がなくなるだけです。
session += "saidHello" -> "yes" Ok("Hello World!")
最後に attribute を削除するのは
Ok("Theme reset!").withSession(session - "theme")
Skinny では、このようになります。
session -= "theme" Ok("Theme reset!")
簡単ですね。
Discarding the whole session
Ok("Bye").withNewSession
セッションの中身を掃除するには clear() で attributes を破棄します。
session.clear
Ok("Bye")
Flash scope
def index = Action { implicit request => Ok { flash.get("success").getOrElse("Welcome!") } } def save = Action { Redirect("/home").flashing( "success" -> "The item has been created") }
Scalatra の flash は更新可能な Map なのでそのように操作するだけです。Rails でもおなじみの flash.now もあります。
def index = { flash("success") = "Welcome!" Ok() } def save = { flash += "success" -> "The item has been created" }
Body parsers
アーキテクチャが異なるので Skinny(Scalatra)に Body Parser はありません。request から body を取得するには
val body: String = request.body val is: InputStream = request.inputStream
あたりを操作する感じになります。
Action composition
アーキテクチャが異なるので Skinny(Scalatra)に 合成可能な Action という概念はありません。
Content negotiation
val list = Action { implicit request => val items = Item.findAll render { case Accepts.Html() => Ok(views.html.list(items)) case Accepts.Json() => Ok(Json.toJson(items)) }
Format を implicit parameter で render に渡すとよしなに分岐してレンダリングしてくれます。
def list()(implicit format: Format = Format.HTML) = withFormat(format) { // respondTo で対応していなかったら 406 応答 set("items", Item.findAll()) render(s"/items/list") // HTML, JSON, XML }
以上です。明日は @daneko0123 さんです。
*1:Scalatra 側でいいソリューションが出てきたら、自作しなくてすむので嬉しいですが