PageRenderTime 38ms CodeModel.GetById 31ms app.highlight 6ms RepoModel.GetById 0ms app.codeStats 0ms

/content/posts/gigahorse-010.ja.md

https://github.com/eed3si9n/eed3si9n.com
Markdown | 119 lines | 93 code | 26 blank | 0 comment | 0 complexity | d5ab64a2a46b2cfe57d9a1f7546c15a5 MD5 | raw file
  1---
  2title:       "Gigahorse 0.1.0"
  3type:        story
  4date:        2016-07-29
  5changed:     2016-08-02
  6draft:       false
  7promote:     true
  8sticky:      false
  9url:         /ja/gigahorse-010
 10aliases:     [ /node/205 ]
 11---
 12
 13  [1]: http://eed3si9n.com/gigahorse/ja/
 14  [AHC]: https://github.com/AsyncHttpClient/async-http-client/tree/1.9.x
 15  [netty]: http://netty.io
 16  [sslconfig]: https://github.com/typesafehub/ssl-config
 17  [config]: https://github.com/typesafehub/config
 18  [ws]: https://www.playframework.com/documentation/2.5.x/ScalaWS
 19  [dispatch]: http://dispatch.databinder.net/Dispatch.html
 20  [datatype]: http://www.scala-sbt.org/0.13/docs/Datatype.html
 21  [@wsargent]: https://github.com/wsargent
 22  [@n8han]: https://github.com/n8han
 23  [@Duhemm]: https://github.com/Duhemm
 24  [@wheaties]: https://github.com/wheaties
 25  [AutoLift]: https://github.com/wheaties/AutoLifts
 26  [stacking]: http://eed3si9n.com/herding-cats/ja/stacking-future-and-either.html
 27  [thegigahorse]: http://madmax.wikia.com/wiki/The_Gigahorse
 28
 29> 更新: Gigahorse 0.1.1 を使ってください。
 30
 31Gigahorse 0.1.0 をリリースした。これは Scala のための HTTP クライアントで、内部にAsync Http Client を使っている。詳しくは [Gigahorse ドキュメント][1]を書いたので、それを参照してほしい。ライブラリがどういう感じなのかを例でみるとこんな感じだ。
 32
 33```scala
 34scala> import gigahorse._
 35scala> import scala.concurrent._, duration._
 36scala> Gigahorse.withHttp(Gigahorse.config) { http =>
 37         val r = Gigahorse.url("http://api.duckduckgo.com").get.
 38           addQueryString(
 39             "q" -> "1 + 1",
 40             "format" -> "json"
 41           )
 42         val f = http.run(r, Gigahorse.asString andThen {_.take(60)})
 43         Await.result(f, 120.seconds)
 44       }
 45```
 46
 47<!--more-->
 48
 49### 背景
 50
 51HTTP クライアントライブラリが必要になる場面は常にある。Dispatch Reboot の代替を探してみて、AHC 以外に他の依存ライブラリを引っ張ってこないものが見つからなかったので、Play の [WS API][ws] を移植することにした。
 52JDK 8 をシステム要件にしたくなかったので敢えて AHC 1.9 を選んだが、そのうち AHC 2.0 版も出てくると思う。
 53WS API は Lightbend のエンジニアが堅牢化してきたライブラリなので、出発点として妥当なものだと思う。これが顕著なのは実用的なデフォルト値と [@wsargent][@wsargent] の [SSL Config][sslconfig] のセキュアなデフォルト設定だ。また、fluent スタイルの API も人気が高い。
 54
 55### sbt-datatype 0.2.3
 56
 57Gigahorse で使われている immutable なデータ型は [sbt-datatype][datatype] を用いて生成されている。これは僕が設計して [@Duhemm][@Duhemm] と一緒に実装した擬似 case class 生成を行うもので、Avro みたいなスキーマを使う:
 58
 59    {
 60      "name": "Config",
 61      "namespace": "gigahorse",
 62      "type": "record",
 63      "target": "Scala",
 64      "fields": [
 65        {
 66          "name": "connectTimeout",
 67          "type": "scala.concurrent.duration.Duration",
 68          "doc": [
 69            "The maximum time an `HttpClient` can wait when connecting to a remote host. (Default: 120s)"
 70          ],
 71          "default": "ConfigDefaults.defaultConnectTimeout",
 72          "since": "0.1.0"
 73        },
 74        ....
 75      ]
 76    }
 77
 78擬似 case class を使う理由は、バイナリ互換を保ったまま API の変更を行うようにするためだ。これを growable と言ったりする。`since` フィールドを使うことで複数の `apply` コンストラクタを生成することができる。また、バイナリ互換の無い `unapply` は生成せず、`copy` も内部では使っているが外部には公開していない。`copy` の代わりに fluent スタイルのメソッドを生成する:
 79
 80```scala
 81  def withConnectTimeout(connectTimeout: scala.concurrent.duration.Duration): Config = {
 82    copy(connectTimeout = connectTimeout)
 83  }
 84```
 85
 86使ってみていくつか追加の機能を付ける必要があって、その一つは手書きで便利関数を追加できる `extra` というフィールドだ。例えばこれを使って `Some(...)` のラッピングを行う:
 87
 88```scala
 89  def withAuth(auth: Realm): Config = copy(authOpt = Some(auth))
 90```
 91
 92### 関数を使う
 93
 94Gigahorse の API 設計は [@n8han][@n8han] の [Dispatch Reboot][dispatch] にも影響を受けている。具体的には、Dispatch は `Response => A` 関数を使って初っ端からレスポンスを変換することができるが、WS API は返ってきた `Future` に map をかける必要がある。Gigahorse はどちらのスタイルも使えるけども、ドキュメントは `http.run(r, f)` を強調して書かれている:
 95
 96```scala
 97val f = http.run(r, Gigahorse.asString andThen {_.take(60)})
 98```
 99
100### Either を使う
101
102`Future[A]` から `Future[Either[Throwable, A]]` への持ち上げ (lifting) も Dispatch からの影響だと言える。LGPL を回避するために実装は見てないけども、Dispatch は `Future` に implicit で `either` という拡張メソッドを付けている。
103僕は implicit をここでは使いたくなかったので、`FutureLifter` というちょっと雑な方法を考えた:
104
105```scala
106val f = http.run(r, Gigahorse.asEither map { Gigahorse.asString })
107```
108
109`asEither` はなんとなく関数っぽく見えるけど、`Right(...)` に map するだけじゃなくて `Left(...)``recoverWith(...)` するという作業もしている。それはそれでいいんだけども、複数の `Future[Either[Throwable, A]]` を合成することになると結局 Cats ([Future と Either の積み上げ][stacking]) とか Scalaz とか [@wheaties][@wheaties] の [AutoLift][AutoLift] が必要になるんじゃないかと思う。
110
111### 名前の由来
112
113<img src="/images/gigahorse-800.jpeg">
114
115Gigahorse という名前は Mad Max Fury Road で Immortan Joe が乗っている[カスタム車][thegigahorse]に由来する。禁酒法時代に moonshine runner と呼ばれる運び屋が乗っていた見た目は普通の改造車をまずコンセプトとして考えていた。そこから、連想で複数回観た世界破滅後のラットロッド讃歌である Mad Max Fury Road に行き着いた。実際に稼働する Big Block V8 エンジンが 2つも付いているというのもこのプロジェクトに合っていると思った。
116
117### まとめ
118
119Gigahorse は Scala のための新しい HTTP クライアントだけども、[AHC][AHC] などの既存のプロジェクトの先行研究に乗っかっている。使ってみたら、是非教えてほしい。