1. 序章

このチュートリアルでは、Scalatraフレームワークの基本的なハウツーを示します。

Scalatraは、Akka、Twirlテンプレート、データベース接続などの一般的なタスクのさまざまな統合を使用して、コアを上向きに構築できる小さなコアを備えたフレームワークです。

2. 設定

Gitterを使用してスケルトンアプリケーションを生成できます。 初期化を見てみましょう:

sbt new scalatra/scalatra.g8

組織名、バージョン、その他いくつかの標準的なGitterの質問に答えた後、プロジェクトが生成されます。

3. 最小限のアプリケーション

プロジェクトを生成したので、最初の展開に進むことができます。

sbt
>jetty:start

アプリケーションは、デフォルトでポート8080にデプロイされます。

単純なMyScalatraServletクラスは、 localhost:8080 にアクセスしたときに取得する、Twirlテンプレート hello.scala.htmlに含まれる単純な挨拶を返します。

class MyScalatraServlet extends ScalatraServlet {

  get("/") {
    views.html.hello()
  }

}

4. 構成

ほとんどのフレームワークはXML構成を必要としませんが、Scalatraではweb.xmlにいくつかの変更を加える必要があります。 ただし、開発者は、ほとんどの構成をweb.xmlエントリではなくScalaコードで行うことを選択できます。

web.xmlを見てみましょう。

<context-param>
    <param-name>org.scalatra.LifeCycle</param-name>
    <param-value>ScalatraDevelopmentBootstrap</param-value>
</context-param>

<listener>
    <listener-class>org.scalatra.servlet.ScalatraListener</listener-class>
</listener>

Scalatraを使用すると、ブートストラップクラス(この場合は単に ScalatraDevelopmentBootstrap クラス)を介してアプリケーションを構成できます。 ブートストラップクラスを使用すると、 ServletContext を介して初期構成をセットアップしたり、サーブレットをマウントしたりできます。

ScalatraDevelopmentBootstrapコードを見てみましょう。

override def init(context: ServletContext) {
  context.mount(new MyScalatraServlet, "/*")
  context.setInitParameter("org.scalatra.environment", "development")
}

言うまでもなく、環境固有のブートストラップクラスが必要です。 それでは、ScalatraProductionBootstrapコードを見てみましょう。

override def init(context: ServletContext) {
  context.mount(new MyScalatraServlet, "/*")
  context.setInitParameter("org.scalatra.environment", "production")
}

もちろん、サーブレットをマウントし、 web.xml ファイルを介してコンテキストパラメータを設定できますが、これはScalatraの方法です。

5. 認証

Scalatraでの認証は拡張機能として提供されます。 オプションの認証システムは、RubyのWardenのポートであるSentryです。 build.sbtファイルに依存関係を追加する必要があります。

libraryDependencies += "org.scalatra" %% "scalatra-auth" % 2.8.2

エンドポイントで認証を実施するには、2つの手順を実行する必要があります。

まず、ScentryStrategyを実装する必要があります。

class CustomBasicAuthStrategy(protected override val app: ScalatraBase, realm: String)
  extends BasicAuthStrategy[User](app, realm) {

  override protected def getUserId(user: User)(implicit request: HttpServletRequest, response: HttpServletResponse): String = user.email

  override protected def validate(userName: String, password: String)(implicit request: HttpServletRequest, response: HttpServletResponse): Option[User] = {
    if (userName == "user" && password == "pwd") Some(User(Random.nextLong(), "scalatra"))
    else None
  }
}

次に、実装したScentryStrategyを登録するScentrySuportトレイトを実装する必要があります

trait AuthSupport extends ScentrySupport[User] with BasicAuthSupport[User] {

  self: ScalatraBase =>

  override protected def fromSession: PartialFunction[String, User] = {
    email => User(Random.nextLong(), email)
  }

  override protected def toSession: PartialFunction[User, String] = {
    user => user.email
  }

  override protected def registerAuthStrategies(): Unit = {
    scentry.register("Basic", app => new CustomBasicAuthStrategy(app, realm))
  }

  override protected def configureScentry(): Unit = {
    scentry.unauthenticated {
      scentry.strategies("Basic").unauthenticated()
    }
  }

  protected val scentryConfig = new ScentryConfig {}

  override type ScentryConfiguration = ScentryConfig

  val realm = "Scalatra Basic Auth Example"

}

これで、サーブレットAuthSupportトレイトを使用できるようになりました。

class BasicAuthServlet extends ScalatraServlet with AuthSupport {

  get("/") {
    basicAuth()
    "Basic auth protected page"
  }

}

basicAuth()関数を呼び出す場合にのみ、認証が行われることに注意してください。

また、コントローラーの before()フィルターで呼び出すか、 before()として設定することで、すべてのエンドポイントに対して basicAuth()を呼び出さないようにすることもできます。 ]AuthSupportトレイトでフィルターします。

6. JSONサポート

Webフレームワークでは、HTTP応答を変換するために何らかの形式の構成または実装が必要になるのが一般的です。

興味深いことに、ScalatraServlet は、HTTP応答をScalaソースの文字列表現に変換します。 次のコントローラは、デフォルトの動作を示しています。

case class User(id: Long, email: String)

object User {
  def all = List(
    User(1L, "[email protected]"),
    User(2L, "[email protected]"),
    User(3L, "[email protected]"),
    User(4L, "[email protected]")
  )
}

get("/") {
  User.all
}

localhost:8080 / user にアクセスすると、次の応答が返されます。

List(User(1,[email protected]), User(2,[email protected]), User(3,[email protected]), User(4,[email protected]))

したがって、コントローラーのJSON応答を有効にする必要があります。 まず、ScalatraJSONライブラリを含めましょう。

libraryDependencies ++= Seq(
  "org.scalatra" %% "scalatra-json" % "2.8.2",
  "org.json4s"   %% "json4s-jackson" % "4.0.1"
)

ここで、 UserController にJSON出力サポートを追加するには、 JacksonJsonSupportトレイトにミックスインし、 before()フィルターを適用し、暗黙のJSON形式を定義する必要があります。

UserConrtollerがJSONサポートでどのように見えるかを見てみましょう。

class UserServlet extends ScalatraServlet with JacksonJsonSupport {

  protected implicit lazy val jsonFormats: Formats = DefaultFormats

  before() {
    contentType = formats("json")
  }

  get("/") {
    User.all
  }

最後に、 localhost:8080 / user にもう一度アクセスして、JSON応答を取得しましょう。

[{"id":1,"email":"[email protected]"},{"id":2,"email":"[email protected]"},{"id":3,"email":"[email protected]"},{"id":4,"email":"[email protected]"}]

UserServlet でJSONを受信するために、それ以上の変更は必要ありません。 JSONを読み取る例を見てみましょう。

post("/:userId") {
  val userId = params("userId")
  val body = parsedBody.extract[User]
  log(s"userId: $userId, body: $body")
  ...

7. データベース

このチュートリアルでは、データベースアクセスに、Slickデータアクセスライブラリを使用します。 まず、依存関係をbuild.sbtファイルに追加しましょう。

libraryDependencies ++= Seq(
  "com.typesafe.slick" %% "slick" % "3.3.2",
  "com.h2database" % "h2" % "1.4.196",
  "com.mchange" % "c3p0" % "0.9.5.2"
)

Slickを使用してデータベーステーブルをケースクラスにマップする方法を見てみましょう。

class Users(tag: Tag) extends Table[User](tag, "user") {
  def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
  def email = column[String]("email")
  override def * = (id, email) <> (User.tupled, User.unapply)
}

val users = TableQuery[Users]

def all = users.result

def insert(user: User) = users += user

また、データベースとしてc3p0接続プールとh2を使用しました。 リソースフォルダにc3p0プロパティファイルを追加する必要があります。

c3p0.driverClass=org.h2.Driver
c3p0.jdbcUrl=jdbc:h2:mem:test
c3p0.user=root
c3p0.password=
c3p0.minPoolSize=1
c3p0.acquireIncrement=1
c3p0.maxPoolSize=50

構成が邪魔にならないので、ブートストラップクラスのデータベース接続を初期化できます。 そのために、ScalatraDevelopmentBootstrapにいくつかの追加を行います。

val cpds = new ComboPooledDataSource

// code ...

override def init(context: ServletContext) {
  val db = Database.forDataSource(cpds, None)
  context.mount(new UserServlet(db), "/user/*")
  context.mount(new DbServlet(db), "/db/*")
  // code...

override def destroy(context: ServletContext): Unit = {
  super.destroy(context)
  cpds.close()
}

高度なデータベース移行システムがない場合、DbServletがデータベース管理APIの役割を果たします。

みてみましょう:

class DbServlet(val db: Database) 
  extends ScalatraServlet with DbSupport with FutureSupport {

  post("/create-users") {
    db.run(UserRepo.users.schema.create)
  }

  post("/insert-users") {
    db.run(UserRepo.insertData)
  }

  post("/destroy-users") {
    db.run(UserRepo.users.schema.drop)
  }

}

最後に、UserServletUserRepoを使用できます。

class UserServlet(val db: Database) extends ScalatraServlet with JacksonJsonSupport with DbSupport {

  // ...

  get("/") {
    db.run(UserRepo.all)
  }

  // ...

8. クルクル回す

Scalatra サーブレットでHTMLをインライン化することもできますが、Play2テンプレートエンジンであるTwirlを使用してHTMLビューを表示することを選択しました。

Twirlをインストールするには、build.sbtファイルでSbtTwirlプラグインを有効にする必要があります。

enablePlugins(SbtTwirl)

回転レイアウトは、 src / main / twirl /layoutsフォルダーおよびsrc/ main / twirl /viewsの下のビューに追加できます。

Twirlをデモンストレーションするために、 hello.scala.html を変更し、環境変数を使用してグリーティングメッセージを出力します。

@(env: String)
@layouts.html.default("Scalatra: a tiny, Sinatra-like web framework for Scala", "Welcome to Scalatra"){
  <p>Hello, Twirl from @env!</p>
}

使用したデフォルトのHTMLレイアウトは次のとおりです。

@(title: String, headline: String)(body: Html)
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
    <h1>@headline</h1>
    @body
  </body>
</html>

ここで、このビューをScalatraサーブレットにバインドする必要があります。 MyScalatraServlet にエンドポイントを追加して、hello.scala.htmlビューを提供しましょう。

class MyScalatraServlet extends ScalatraServlet {

  get("/") {
    val env = getServletContext.getInitParameter("org.scalatra.environment")
    views.html.hello(env)
  }
}

これは、開発構成の localhost:8080エンドポイントに対するcurlの結果です。

~ curl -X GET http://localhost:8080
<html>
  <head>
    <title>Scalatra: a tiny, Sinatra-like web framework for Scala</title>
  </head>
  <body>
    <h1>Welcome to Scalatra</h1>
    
  <p>Hello, Twirl from development!</p>

  </body>
</html>

9. 結論

この記事では、Scalatraを使用してWebフレームワークの最も基本的な機能のいくつかを実装しました。

いつものように、上記の例のコードはGitHubから入手できます。