Dispatch を使ってみる

このエントリは Scala Advent Calendar の 18 日目になります。
Dispatch というライブラリを使ってみます。

Dispatch とは

Dispatch というのは、Scala 用 の HTTP クライアントライブラリです。Dispatch には classic と reboot という二種類があり、前者は古い方、後者は一から書き直された新しい方っぽいです。
そして、「Dispatch scala」でみつかる http://dispatch.databinder.net/Dispatch.html は reboot の方です。でも、github で 単に dispatch となっているのは classic で、reboot の方は dispatch/reboot です。ドキュメント上は無印=reboot、github上は無印=classicという感じでなんだか混乱します。

今回使用するのは新しい方の Dispatch (つまり reboot の方) です。以降は reboot の方についての記述になります。

Dispatch では実際の HTTP 周りの処理を async-http-client に任せていて、Dispatch 自体はこのライブラリに対する薄いラッパーのような感じになっています。そのため実際に使用する場合、async-http-client の方を調べる必要があるかもしれません。

準備

Dispatch を利用できるように、以下を記述した build.sbt ファイル*1を作成します。
依存ライブラリに dispatch-core を指定しているだけです。また、JSON を扱えるように dispatch-json4s-native も追加しておきます。

libraryDependencies ++= Seq(
  "net.databinder.dispatch" %% "dispatch-core" % "0.11.0",
  "net.databinder.dispatch" %% "dispatch-json4s-native" % "0.11.0"
)

sbt の console 上で試す

sbt の console を起動して実際に Dispatch を使用して GET や POST をしてみたいと思います。
依存ライブラリをダウンロードするため、初回の起動はちょっと時間がかかるかもしれません。


$ sbt console
[info] Set current project to root-2013-advent-calendar (in build file:/C:/development/workspaces/sandbox/scala/2013-advent-calendar/)
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.10.2 (Java HotSpot(TM) Client VM, Java 1.6.0_23).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import dispatch._, Defaults._
import dispatch._
import Defaults._

scala>

dispatch パッケージ直下に主要な部品が定義されているので、 import dispatch._ で利用できるようにします。
また、 dispatch.Defaults には Future の実行で必要な暗黙のパラメータに渡す値が定義されているようです(defaults.scala)。

GET

GET リクエストを発行してみます。


scala> Http(url("[]https://api.github.com/gists?per_page=1[]") OK as.String)
res1: dispatch.Future[String] = scala.concurrent.impl.Promise$DefaultPromise@1e9ed79

scala> res1()
res2: String = [{"url":"[]https://api.github.com/gists/7995189[]","forks_url":"http
s://api.github.com/gists/7995189/forks","commits_url":"[]https://api.github.com/g[]
~~(snip)~~
2-16T21:59:24Z","description":"","comments":0,"user":{"login":"thion","id":3336
77,"avatar_url":"[]https://gravatar.com/avatar/3d082acb2ebd10448ee5cf790cdc3c96?d[]
=https...

scala>

url("...") の部分でリクエストを構築しています。上記の例では URL を文字列で指定していますが、ホスト名、パス、クエリパラメータを別個に指定することも可能です。request.scala にリクエストの構築に使用する部品が定義されています。

リクエストの結果となるレスポンスをどのように扱うかを OK as.String の部分で指定しています。レスポンスが成功(2xx)の場合のみ処理して、レスポンスボディを文字列として取り出すレスポンスハンドラを指定しています。OKhandlers.scala に、as.Stringas/core.scala に定義があります。
Http(...) で構築したリクエストを実行し、その結果をレスポンスハンドラで処理しています。実際には Http(...) の戻り値は Future インスタンスのため、すぐにレスポンスが得られているわけではありません。res1()*2することで、レスポンスが取得されるまでブロックして、その値を取り出しています。*3


以下は、リクエスト構築の際に host 等を使用する場合の例になります。また、as.json4s.Json を指定することでレスポンスを文字列としてではなく JSON として取り出しています。*4


scala> Http(host("api.github.com").secure / "gists" <<? Map("per_page" -> "1") OK as.json4s.Json)
res3: dispatch.Future[org.json4s.JValue] = scala.concurrent.impl.Promise$DefaultPromise@abedf1

scala> import org.json4s._
scala> import org.json4s.native.JsonMethods._
scala> pretty(render(res3()))
res4: String =
[{
"url":"[]https://api.github.com/gists/8004290[]",
"forks_url":"[]https://api.github.com/gists/8004290/forks[]",
~~(snip)~~
"comments":0,
"user":{
...
scala>

POST

続いて POST リクエストを発行してみます。
基本的には GET の場合と同様にリクエストを構築して実行するだけです。


scala> import org.json4s.native.Serialization, Serialization.{write}
import org.json4s.native.Serialization
import Serialization.write

scala> write(Map("public" -> true, "files" -> Map("file1.txt" -> Map("content" -> "String file contents"))))
res5: String = {"public":true,"files":{"file1.txt":{"content":"String file contents"}}}

scala> Http(host("api.github.com").secure / "gists" << res5 OK as.json4s.Json)
res6: dispatch.Future[org.json4s.JValue] = scala.concurrent.impl.Promise$DefaultPromise@2407b8

scala> pretty(render(res6()))
res7: String =
{
"url":"[]https://api.github.com/gists/8005261[]",
"forks_url":"[]https://api.github.com/gists/8005261/forks[]",
~~(snip)~~
"description":null,
"comments":0,
...
scala>

<< を使用してリクエストボディに JSON 文字列を指定しているのが、GET の例との差になります。*5
リクエストに対してどのような操作が可能かは requests.scala に定義されている RequestVerbs を継承している各種 trait を見るとわかるかと思います。

また、GET / POST 両方の例とも、GET なのか POST なのかを明示的に指定していません。これはリクエストのデフォルトが GET になっていて、またリクエストボディを指定した場合はデフォルトが POST となるため、上記の例では明示的な指定が不要でした。host("...").GET のように記述することで明示できます。


以上、簡単にではありますが Dispatch を試すときの手がかりになれば幸いです。

おまけ

Verbs 引数 説明
HEAD, GET, POST, PUT, DELETE, PATCH, TRACE, OPTIONS - HTTP メソッドを指定
url - リクエストの URL 文字列を取得
/ String *6 パスを追加
secure - https:// でアクセスする
<:< Traversable[(String, String)] ヘッダを指定
<< Traversable[(String, String)] リクエストボディにパラメータを指定
<< String リクエストボディに文字列を指定
<<< java.io.File ファイルアップロード
<<? Traversable[(String, String)] クエリパラメータを指定

*1:sbtを使用します

*2:EnrichedFutureで Future に apply メソッドが追加されています

*3:Future の使い方については割愛させていただきます

*4:json4s については割愛

*5:GET の例では <<? を使用してクエリパラメータを指定していました

*6:toString されるオーバーロードがあるので AnyVal でもOK