次期Scalatraのバックエンドとしてのhttp4s

Scalatraの次期バックエンドをhttp4sにしようという計画は数年前からありつつ中々進んでいなかったのですが(ブランチに途中まで試みたと思われる残骸があるのですが、そもそもScalatraの開発自体が停滞しており絶賛放置されていました)、そろそろやってみようかと思いhttp4sを触り始めています。

github.com

http4sとは?

http4sはいわゆるノンブロッキングなHTTPツールキットで、今だとAkka HTTPが競合になるかと思います。http4sはAkka HTTP同様ルーティング用のDSLも備えていて単体でシンプルなWebフレームワークとして使用することもできます。

http4sの特徴の1つとして、http4s用に開発したアプリケーション(http4sではサービスと呼びます)を、サーブレットとして動かすためのアダプタが用意されているという点があります。現在ScalatraはサーブレットベースのWebフレームワークであることで、

といったメリットがあります。これらは既存のJavaベースのシステムに部分的にScalaを導入して行く際に便利なのですが、バックエンドをhttp4sに移行することでサーブレットを使うか、ハイパフォーマンスなblaze(fs2)を使うかを選択することができるようになり、Scalatraの活用の幅がより広がるのではないかと思います。

http4sを動かしてみる

http4sのサービスはこんな感じで実装します(最新のマイルストーンビルドである0.18.0-M5を使用しています)。

package com.example.http4squickstart

import cats.effect.IO
import io.circe._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

object HelloWorldService extends Http4sDsl[IO] {
  val service = HttpService[IO] {
    case GET -> Root / "hello" / name =>
    Ok(Json.obj("message" -> Json.fromString(s"Hello, ${name}")))
  }
}

blazeで動かす場合はこんな感じ。

package com.example.http4squickstart

import cats.effect.IO
import org.http4s.dsl.Http4sDsl
import org.http4s.server.blaze.BlazeBuilder
import org.http4s.util.StreamApp

object BlazeServer extends StreamApp[IO] with Http4sDsl[IO] {
  def stream(args: List[String], requestShutdown: IO[Unit]) =
    BlazeBuilder[IO]
      .bindHttp(8080, "0.0.0.0")
      .mountService(HelloWorldService.service, "/")
      .serve
}

サーブレットで動かす場合はこんな感じのリスナーを作っておけばOKです。これをxsbt-web-pluginで実行したり、warにしてサーブレットコンテナにデプロイする感じかと思います。

package com.example.http4squickstart

import javax.servlet.annotation.WebListener
import javax.servlet.{ServletContextEvent, ServletContextListener}
import org.http4s.servlet.syntax.ServletContextSyntax

@WebListener
class Bootstrap extends ServletContextListener with ServletContextSyntax {
  override def contextInitialized(sce: ServletContextEvent) = {
    val context = sce.getServletContext
    context.mountService("helloService", HelloWorldService.service)
  }
  override def contextDestroyed(sce: ServletContextEvent) = ()
}

まだ細かいところまで見れていませんが、同じサービスがバックエンドを変えても動作することが確認できました。

http4sは国内ではだいぶマイナー感がありますが、Typelevelプロジェクトの1つ(まだIncubatorですが)ということもあり、海外ではそこそこ使われている気配があります。今のところ開発も活発ですが、Scalatraのバックエンドに採用したものの、肝心のhttp4sの開発が止まってしまうと厳しいので、そのあたりの様子も見つつ本当に移行できるかどうかを検討していきたいと思います。