What is spray?
What is spray
spray | Introduction » What is spray?
adtech × scala勉強会でsprayの事例が幾つかあったので軽く触ってみることにしました。上のWhat-is-sprayで紹介されている内容をここでも簡単に記述しておくと以下のような感じです。2014.12.23日現在、最新のVersionは1.3.2のようです。尚、本記事の実験環境はCentOS Linux release 7.0.1406 (Core)で動かしています。
- Akka上で動く、HTTP/RESTをサポートしたクライント/サーバーサイドのライブラリ。
- JVMのlayerを触りたい場合もレガシーなJavaの実装が必要なくscalaのAPIの記述で事が足りる。
- sprayはフレームワークというよりはライブラリ志向で作られている(哲学的に)。
- 完全非同期、Non-Blocking。
- ActorとFutureベース。
- ハイパフォーマンス、軽量、テスト可能。
- twirlというtemplate engineもある。
- 中心となるspray-io、インメモリのキャッシュであるspray-caching、spray-io上でClient/Serverとして動くspray-can、Servlet上で動かすためのspray-servletなどのモジュールが用意されている。
Getting Start
Hello world
Getting Started with sbt — Installing sbt on Linux
まずはscalaのbuild toolをinstall、次にsprayを動かしてみます。プロジェクト名はhello-worldとします。以下の実行によりHTMLを吐き出すところまでは出来ました。spray-templateはspray-can上でspray-routingを実行するためのtemplateなようです。$ wget https://dl.bintray.com/sbt/rpm/sbt-0.13.7.rpm $ sudo yum localinstall sbt-0.13.7.rpm -y $ git clone git://github.com/spray/spray-template.git hello-world $ cd hello-world $ sbt > test > re-start $ curl "http://127.0.0.1:8080/" <html> <body> <h1>Say hello to <i>spray-routing</i> on <i>spray-can</i>!</h1> </body> </html>Source Code
以下のパスに設定ファイルおよび起動、実行されているプログラムが記載されています。
hello-world/src/main/resources/application.conf hello-world/src/main/scala/com/example/Boot.scala hello-world/src/main/scala/com/example/MyServer.scala実行したいPathinfoを切り替えたい場合はMyServer.scalaの中のMyServiceに記載されているpathのパラメータを変更します。
// this trait defines our service behavior independently from the service actor trait MyService extends HttpService { val myRoute = path("hello") { get { respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here complete { <html> <body> <h1>Say hello to <i>spray-routing</i> on <i>spray-can</i>!</h1> </body> </html> } } } } }re-start後に再度curlします。helloのパスが有効化されました。
$ curl "http://127.0.0.1:8080/" The requested resource could not be found. $ curl "http://127.0.0.1:8080/hello" <html> <body> <h1>Say hello to <i>spray-routing</i> on <i>spray-can</i>!</h1> </body> </html>以下のように記述するとpathの条件分岐が可能です。
val myRoute = path("hello") { get { respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here complete { <html> <body> <h1>Say hello to <i>spray-routing</i> on <i>spray-can</i>!</h1> </body> </html> } } } } ~ path("world") { get { respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here complete { <html> <body> <h1>Say world to <i>spray-routing</i> on <i>spray-can</i>!</h1> </body> </html> } } } }上記はvalおよびpathの定義で分岐させましたが、traitの分岐も可能です。こうするとCodeの可読性が良くなる気がします。
class MyServiceActor extends Actor with ServiceHello with ServiceWorld { def actorRefFactory = context def receive = runRoute(helloRoute ~ worldRoute) } trait ServiceHello extends HttpService { val helloRoute = path("hello") { get { respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here complete { <html> <body> <h1>Say hello to <i>spray-routing</i> on <i>spray-can</i>!</h1> </body> </html> } } } } } trait ServiceWorld extends HttpService { val worldRoute = path("world") { get { respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here complete { <html> <body> <h1>Say world to <i>spray-routing</i> on <i>spray-can</i>!</h1> </body> </html> } } } } }twirl
spray/twirl
TwirlというTemplateEngineを導入してみます。まずはtwirlをproject/plugin.sbt、build.sbtに以下の内容を追加します。各行の追加は空行が必要なので気をつけてください。# project/plugin.sbt addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2") resolvers += "spray repo" at "http://repo.spray.io" addSbtPlugin("io.spray" % "sbt-twirl" % "0.7.0") # build.sbt Twirl.settings次にtemplateを追加します。main直下にtwirlディレクトリを作成し、index.scala.htmlなどを配置します。index.scala.htmlの内容を以下のものとします。templateの記述方法はplayframeworkと同等になります。最後にMyServer.scalaで変数のpに値を渡してあげると、埋め込んで出力してくれます。
// MyServer.scala trait MyService extends HttpService { val myRoute = path("") { get { respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here complete ( html.index.render("twirl").body ) } } } } // index.scala.html @(p: String) <!DOCTYPE html> <html> <body> <p>Hello-World @p</p> </body> </html>curl "http://127.0.0.1:8080/" <!DOCTYPE html> <html> <body> <p>Hello-World twirl</p> </body> </html>
MysqlとのConnection
Slick OR Mapper
sprayからmysqlに接続するためにSlickというOR Mapperを利用します。まずはmysqlとの接続driverとslickをbuild.sbtに加えます。Slickは使用するVersionによって記述が大きく異なるので注意が必要です。この記事では2.1.0を利用しています。Slick - Scala Language Integrated Connection Kit — Slick 2.1.0 documentation
$ diff -u build.sbt build.sbt.bak --- build.sbt 2014-12-23 13:45:33.476276736 +0900 +++ build.sbt.bak 2014-12-23 13:39:23.122278989 +0900 @@ -16,8 +16,6 @@ "com.typesafe.akka" %% "akka-actor" % akkaV, "com.typesafe.akka" %% "akka-testkit" % akkaV % "test", "org.specs2" %% "specs2-core" % "2.3.11" % "test", - "mysql" % "mysql-connector-java" % "5.1.25", - "com.typesafe.slick" %% "slick" % "2.1.0" ) }あまり良く無い例ですが、http://127.0.0.1:8080にアクセスしたタイミングでMyServiceから直接Mysqlに接続してTableの作成、データのInsertを行います。理想としてはDBSchemaClass、Connectionを別途定義することだと思うので、気になった人は自由に書き変えてください。
trait MyService extends HttpService { class Customers(tag: Tag) extends Table[(Int, String, String)](tag, "customers") { def id = column[Int]("id", O.PrimaryKey) def name = column[String]("name") def email = column[String]("email") def * = (id, name, email) } val customers = TableQuery[Customers] Database.forURL("jdbc:mysql://localhost/test?user=root&password=", driver = "com.mysql.jdbc.Driver") withSession { implicit session => try { (customers.ddl).drop } catch { case e:Exception => println("table not found") } (customers.ddl).create customers ++= Seq( (1, "foo", "foo@com"), (2, "bar", "bar@com") ) } val myRoute = path("") { get { respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here complete { <html> <body> Create/InsertDB is Completed </body> </html> } } } } }$ curl "http://127.0.0.1:8080/" <html> <body> Create/InsertDB is Completed </body> </html> mysql> select * from customers; +----+------+---------+ | id | name | email | +----+------+---------+ | 1 | foo | foo@com | | 2 | bar | bar@com | +----+------+---------+ 2 rows in set (0.00 sec)