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