finchは関数型なFinagleの薄いラッパー。CatsやShapelessを使っている。
なにやら速いらしい。
Impressive results by #Finch (now #Scala 2nd fastest HTTP library) running @techempower benchmark (430k QPS peak): pic.twitter.com/YeBMnJeQ5W
— Vladimir Kostyukov (@vkostyukov) 2016年2月27日
なぜ速いのか?
http://vkostyukov.net/posts/how-fast-is-finch/
今回は触ってみただけなので、速いことより使い勝手をみていくことにする。
Hello World
import io.finch._
import com.twitter.finagle.Http
val api: Endpoint[String] = get("hello") { Ok("Hello, World!") }
Http.serve(":8080", api.toService)
READMEに載ってるやつ。これでsbt run
してhttp://localhost:8080/hello
にアクセスできる。
Parameter
GETやPOSTでのパラメータの受け取りの例。
// パスの一部として取得
// GET /hello/:id
get("hello" :: int) { id: Int =>
...
}
// クエリパラメータとして取得
// GET /hello/:id?name={string}
get("hello" :: int :: param("name")) { (id: Int, name: String) =>
...
}
// Intで取得
// GET /hello?id={int}
get("hello" :: param("id").as[Int]) { id: Int =>
...
}
// case classに詰めて取得
// GET /hello?a={string}&b={string}
case class Foo(a: String, b: String)
val ab: Endpoint[String :: String :: HNil] = param("a") :: param("b")
get("hello" :: ab.as[Foo]) { foo: Foo =>
...
}
Endpointを組み合わせると、HListで表現するのでGenericでcase classに相互変換可能と。
Validation
数と文字数の制限しか用意されてないもよう。ValidationRules
// GET /hello?a={string}
val a = param("a").should(beShorterThan(5)) // aの長さ < 5
get("hello" :: a) { foo: Foo =>
...
}
独自定義も簡単に書ける。
val a = param("a").should("a should be a")(_ == "a")
val aRule = ValidationRule[String]("a should be a")(_ == "a")
val a = param("a").should(aRule)
and/orで組み合わせる。
val aRule = ValidationRule[String]("a should be a")(_ == "a") or ValidationRule[String]("a should length 3")(_.length == 3)
cookieやheader等も同様。
val cookieUid = cookie("uid").should("uid invalid format")(_.value.length == 9)
get("hello" :: cookieUid) { cc: Cookie =>
...
}
そういえばforも。
val x: Endpoint[(String, String)] = for {
a <- param("a")
b <- cookie("b")
} yield (a, b.value)
とりあえずplayのFormより全然よさそう...
とにかくEndpointになるので、組み合わせやすい。sprayのDirectiveっぽさがある。
Derive
自動導出してくれてるのでcase classごとのencode/decodeの定義が不要。
jsonを受け取ってjsonで返す例。
import io.finch.circe._
import io.circe.generic.auto._
case class User(id: Int, name: String)
// POST /hello
// {"id":"int", "name":"name"}
post("hello" :: body.as[User]) { user: User =>
Ok(user)
}
case classそのまま渡せばjsonにしてくれるので便利。
Routing
Endpointを組み合わせていく。
val hello: Endpoint[String] = get("hello") { Ok("hello") }
val world = get("world") { Ok("world") }
val hw = hello :+: world
さらに組み合わせて最終的に1つに。
val foo = get("foo") { Ok("foo") }
val bar = get("bar") { Ok("bar") }
val fb = foo :+: bar
val route: Endpoint[String :+: String :+: String :+: String :+: CNil] = hw :+: fb
型がネストしてしまうが:+:するときにフラットにしてくれてる。
これがしたいためにshapelessにクラス追加してるので気合いが入ってる。
おわり
Endpointが組み合わせやすく、型安全だし再利用性も高そう。
あと、あくまでfinagleがベースなのでsqlとか書くにしてもfinagle-mysqlとか使わないと遅くなったりしそう。
興味あればドキュメントを読むといいと思われます。( ・∀・)ノシ