Scalatraとnon-blocking APIについてメモ
Scalatraを少しだけお仕事で使ったのですが、よく考えたらその仕組みを知らないなあと思ったので復習を兼ねて記事を書いてみます。
Scala Advent Calendar 2015 6日目の @Hiroyuki-Nagata です.
前の人は @xuwei_kさん,次の人は @dakatsukaさん です.
www.adventar.org
Scalatraとは
Play FrameworkがRuby on Railsを模したものだとすれば*1、ScalatraはRubyのSinatraを模したフレームワークです。
参考 → Scalatra: Sinatraに似たScalaのウェブフレームワーク
ざっくりとした機能を主観でまとめてみると
・Javaのサーブレットにあたる部分として、DSLでルーティングを書くとそのままその中に処理を書ける
公式サイトに書いてある例をそのまま引き写すと
package com.example.app import org.scalatra._ class HelloWorldApp extends ScalatraFilter { // HTTP メソッドを意味するget, post, put, deleteを書いて、ルーティングをその中に書くともう機能する get("/") { <h1>Hello, {params("name")}</h1> } // Rubyとかでも同じだったりしますが、メソッドの最後にある要素がreturnする }
実際処理を書いて見たければ以下のようなリポジトリをcloneしてきて改造してみるのが良いと思う。
github.com
Non-blocking API
Scala自体の話とはずれますが、Non-blocking APIがよく話題になるので、つらつらと意味合いから調べてみたいと思います。
まず、以下のサイトを読むといろいろわかります。
Tomcat 7の新機能で何ができるようになるのか?(2):Tomcat 7も対応したServlet 3.0の変更点 後編 (1/3) - @IT 」
要は、「Servlet API 2.5までは、Servlet上でスレッドを生成・起動するのは非推奨だったが、Servlet API 3.0ではそれを非同期処理で使用可能にした」、という話です。例えば、Servletに対してHTTP requestが来るとアプリケーションサーバはスレッドをひとつ立てて処理を終えたらスレッドを終わらせる。この時途中で重い外部リソースへのリクエスト(外部のREST/SOAPリクエスト、DBアクセス、ファイルアクセス)があった場合、スレッドはその処理の終了を待つために処理が重くなってしまう。これをブロッキングと呼んでいるようです。*2
この仕組みによりServlet内でスレッドを立てたり終わらせる処理が書けるようになりました。そのモデルは2種類あるようです。
非同期(AsyncContext#start(Runnable))モデル
こっちが新しく作り出したスレッドをdetachするやつだと思います。つまり、作り出したスレッドの終了を待たず処理を返す。
用途としてはリクエストのレスポンスをあまり気にしないバッチのリクエストをこれで作るとか?
Scalatra + Akka
これでAsyncResultやFutureを使う準備が整いました。実際にやってみましょう。
ScalatraとAkkaの組み合わせについてのドキュメント
Futureについて
Future と Promise - Scala Documentation
とりあえず、先ほどの非同期処理のどちらでもリクエスト処理用のスレッドを無視して新しいスレッドを立てたいので、それをやってみます。
サンプルコード
・/learningにGETアクセスするとスレッドが2つ立って以下のことをします
・めうめうAが「meu meu A」とつぶやいて5秒後に「meu meu A end meu...」とつぶやきます
・めうめうBが「meu meu B」とつぶやいて10秒後に「meu meu B end meu...」とつぶやきます
・メインのスレッドはそんなことを無視してリクエストされたコンテキストを標準出力に出します
get("/learning") { println("Received request !!!") new AsyncResult { val is = Future { println("meu meu A...") Thread.sleep(5000) println("meu meu A end meu...") } } new AsyncResult { val is = Future { println("meu meu B...") Thread.sleep(10000) println("meu meu B end meu...") } } Ok(body = s"${request.getContextPath}") }
実行してみた
このコードを試したい場合scalatraの以下の場所「AkkaSupportSpec.scala」にコードを追加するのが簡単でしょう
scalatra/AkkaSupportSpec.scala at 7c9e48b4603db2f5a15f2c391c9b86bb9e8f8855 · scalatra/scalatra · GitHub
Futureとかについては他のいろいろな人が解説してるのでいいでしょうたぶん。
*1:routesにパス書くのとか、ディレクトリ構造とか、対話環境で内部のクラス参照できたりとか似てるよね?