なんとな~くしあわせ?の日記

「そしてそれゆえ、知識そのものが力である」 (Nam et ipsa scientia potestas est.) 〜 フランシス・ベーコン

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はRubySinatraを模したフレームワークです。
参考 → 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するやつだと思います。つまり、作り出したスレッドの終了を待たず処理を返す。
用途としてはリクエストのレスポンスをあまり気にしないバッチのリクエストをこれで作るとか?

非同期 (AsyncContext#dispatch (...))モデル

こっちが新しく作り出したスレッドを後でjoinするやつだと思います。つまり、リクエスト内で作り出したスレッド(一つとは限らない)を後で参照して結果を返すことができます。たぶんこれは複数同時に実行できるタスクがあればあるほど有用だと思います。まあ、そうでもなければ総合でかかる時間はServlet API 2.5とさほど変わらないのでは?

Scalatra + Akka

これでAsyncResultやFutureを使う準備が整いました。実際にやってみましょう。

ScalatraとAkkaの組み合わせについてのドキュメント

Akka | Async | Scalatra guides

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}")
}

実行してみた
f:id:panzer-jagdironscrap1:20151206003636p:plain

このコードを試したい場合scalatraの以下の場所「AkkaSupportSpec.scala」にコードを追加するのが簡単でしょう
scalatra/AkkaSupportSpec.scala at 7c9e48b4603db2f5a15f2c391c9b86bb9e8f8855 · scalatra/scalatra · GitHub

Futureとかについては他のいろいろな人が解説してるのでいいでしょうたぶん。

まとめ

・Non-blocking APIとはServlet API 2.5 -> 3.0の更新で生まれた仕様
・Scalatraで非同期処理が書けた

*1:routesにパス書くのとか、ディレクトリ構造とか、対話環境で内部のクラス参照できたりとか似てるよね?

*2:JavaのServlet API 2.5の仕様