Scala Spray
基本
特徴
- 完全非同期のノンブロッキング
- Actor/Futureベース
- ハイパフォーマンス
- ライトウェイト
- モジュール
- テスト可能
モジュール
sprayはフレームワークという扱いにはしたくないようで,
sprayはライブラリのスイートと言っている.
spray-xxxのように個々のライブラリがある.
個々のライブラリとすることで,ある機能では別のライブラリを使うという選択肢を
ユーザに与えてくれている.例えば,jsonでのリスポンスにjson4sを使うとか.
HTTPのレイヤーを切り離すことで,単なるTCPサーバにすれば,高スループットを達成できる.それはakka単体(akka cluster)でもいい気がするが.
spray-routingを入れると,当然RESTfulな設計が可能.
HTTPのレイヤーを使いたいがもっと高速にしたければ,spray-canを使えば良い.
RESTful
RESTfulにすればURLスキーマの設計が美しくなるので,まずはこれを取り上げる.
SimpleExample
比較的簡単なサンプルらしいが.動かすまで結構大変だった...
- SimpleExample.scala
package edu.kzk.spray.hello import scala.concurrent.duration._ import akka.actor.ActorSystem import spray.routing.SimpleRoutingApp import spray.http._ import MediaTypes._ import scala.util.{ Failure, Success } object SimpleExample extends App with SimpleRoutingApp { implicit val system = ActorSystem("simple-routing-app") import system.dispatcher startServer("localhost", port = 8080) { get { pathSingleSlash { redirect("/hello", StatusCodes.Found) } ~ path("hello") { complete { <html> <h1>Say hello to <em>spray</em> on <em>spray-can</em>!</h1> <p>(<a href="/stop?method=post">stop server</a>)</p> </html> } } } ~ (post | parameter('method ! "post")) { path("stop") { complete { system.scheduler.scheduleOnce(1.second)(system.shutdown())(system.dispatcher) "Shutting down in 1 second..." } } } }.onComplete { case Success(b) => println(s"Successfully bound to ${b.localAddress}") case Failure(ex) => println(ex.getMessage) system.shutdown() } }
Longer Example
このサンプルは動かないので注意.
そのままじゃ動かないけれど,urlのパスを抜き出る例があり,
spray-routingでRESTfulな設計にできることがわかる.
- LongerService.scala
import scala.concurrent.duration.Duration import spray.routing.HttpService import spray.routing.authentication.BasicAuth import spray.routing.directives.CachingDirectives._ import spray.httpx.encoding._ trait LongerService extends HttpService with MyApp { val simpleCache = routeCache(maxCapacity = 1000, timeToIdle = Duration("30 min")) val route = { path("orders") { authenticate(BasicAuth(realm = "admin area")) { user => get { cache(simpleCache) { encodeResponse(Deflate) { complete { // marshal custom object with in-scope marshaller getOrdersFromDB } } } } ~ post { // decompresses the request with Gzip or Deflate when required decompressRequest() { // unmarshal with in-scope unmarshaller entity(as[Order]) { order => // transfer to newly spawned actor detach() { complete { // ... write order to DB "Order received" } } } } } } } ~ // extract URI path element as Int pathPrefix("order" / IntNumber) { orderId => pathEnd { // method tunneling via query param (put | parameter('method ! "put")) { // form extraction from multipart or www-url-encoded forms formFields('email, 'total.as[Money]).as(Order) { order => complete { // complete with serialized Future result (myDbActor ? Update(order)).mapTo[TransactionResult] } } } ~ get { // JSONP support jsonpWithParameter("callback") { // use in-scope marshaller to create completer function produce(instanceOf[Order]) { completer => ctx => processOrderRequest(orderId, completer) } } } } ~ path("items") { get { // parameters to case class extraction parameters('size.as[Int], 'color ?, 'dangerous ? "no") .as(OrderItem) { orderItem => // ... route using case class instance created from // required and optional query parameters } } } } ~ pathPrefix("documentation") { // cache responses to GET requests cache(simpleCache) { // optionally compresses the response with Gzip or Deflate // if the client accepts compressed responses compressResponse() { // serve up static content from a JAR resource getFromResourceDirectory("docs") } } } ~ path("oldApi" / Rest) { pathRest => redirect("http://oldapi.example.com/" + pathRest, StatusCodes.MovedPermanently) } } }
SimpleRoutingAppを使わない場合
SimpleRoutingAppを使わないサンプルを書く指針.
サンプルコードはここを参照.
SimpleRoutingAppでやっていること
- HttpServiceをextendsしたクラスを作る
- そのクラスでActorを作る
- 同じファイル内にあるHttpServiceActorは直接Actorをextendsしている
- actorRefFactoryにActorのcontextをセット
- その中のreceiveメソッドでrunRouteを呼ぶ
- IO(Http) ? Http.Bind(serviceActor, interface, port, ... )でHttp Serverをスタート
なので,同じことをするクラスを作れば柔軟にカスタマイズできる.
Http Serverをスタートをするクラスを別に出して,ルーティングするActorを作ればいいと思う.
RoutingするActorクラス
Http Serverを起動するクラス
- ActorSystemを暗黙(implicit)で作る
- RoutingするActorクラスをactorOfする
- IO(Http) ? Http.Bind(RoutingActor, interface, port, ... )でHttp Serverをスタート
まとめると
- RoutingするActorクラス
- URL Routeに応じたActorクラス
- Http Serverを起動するクラス
を作ればいいと思う.
所感
参考
- http://spray.io/introduction/what-is-spray/
- http://spray.io/documentation/1.2.2/
- http://spray.io/documentation/1.1-SNAPSHOT/spray-routing/#spray-routing
- https://github.com/spray/spray/blob/master/examples/spray-routing/simple-routing-app/src/main/scala/spray/examples/Main.scala
- https://deromka.wordpress.com/2014/05/15/spray-vs-scalatra/