PageRenderTime 93ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/content/posts/gigahorse-010.md

https://github.com/eed3si9n/eed3si9n.com
Markdown | 118 lines | 92 code | 26 blank | 0 comment | 0 complexity | 665087125879de24510d2d3c62151f7e 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: /gigahorse-010
  10. aliases: [ /node/204 ]
  11. ---
  12. [1]: http://eed3si9n.com/gigahorse/
  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/stacking-future-and-either.html
  26. [thegigahorse]: http://madmax.wikia.com/wiki/The_Gigahorse
  27. > Update: please use Gigahorse 0.1.1
  28. Gigahorse 0.1.0 is now released. It is an HTTP client for Scala with Async Http Client underneath. Please see [Gigahorse docs][1] for the details. Here's an example snippet to get the feel of the library.
  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. ### background
  44. There's a constant need for HTTP client library. Looking around for Dispatch Reboot replacement, I couldn't find something that doesn't pull in extra dependencies beyond AHC, so I decided to port Play's [WS API][ws]. Because I didn't want to require JDK 8, I've intentionally picked AHC 1.9, but hopefully AHC 2.0 version will follow up eventually.
  45. WS API is one of the hardened libraries by Lightbend engineers, so it made sense as a good starting point. This is true especially around sensible default values and secure defaults in [@wsargent][@wsargent]'s [SSL Config][sslconfig]. The fluent style API also seems to be popular too.
  46. ### sbt-datatype 0.2.3
  47. The immutable datatypes used in Gigahorse are generated using [sbt-datatype][datatype], which is a pseudo case class generator that I designed and implemented together with [@Duhemm][@Duhemm]. It uses Avro-like schema like this:
  48. {
  49. "name": "Config",
  50. "namespace": "gigahorse",
  51. "type": "record",
  52. "target": "Scala",
  53. "fields": [
  54. {
  55. "name": "connectTimeout",
  56. "type": "scala.concurrent.duration.Duration",
  57. "doc": [
  58. "The maximum time an `HttpClient` can wait when connecting to a remote host. (Default: 120s)"
  59. ],
  60. "default": "ConfigDefaults.defaultConnectTimeout",
  61. "since": "0.1.0"
  62. },
  63. ....
  64. ]
  65. }
  66. and it generates pseudo case classes that's growable over time. Using the `since` field, it can generate multiple `apply` constructor, and it does not generate `unapply` or expose `copy` because they can not grow in binary compatible way. Instead it generates fluent style method:
  67. ```scala
  68. def withConnectTimeout(connectTimeout: scala.concurrent.duration.Duration): Config = {
  69. copy(connectTimeout = connectTimeout)
  70. }
  71. ```
  72. This also motivated us to add a few features such as `extra` field to hand-code convenience functions, for example to do `Some(...)` wrapping:
  73. ```scala
  74. def withAuth(auth: Realm): Config = copy(authOpt = Some(auth))
  75. ```
  76. ### using functions
  77. The API design of Gigahorse is also influenced by that of [Dispatch Reboot][dispatch] by [@n8han][@n8han]. In particular, Dispatch uses function `Response => A` to transform the response from the beginning, while with WS API, you would map over the returned `Future`. Gigahorse allows both styles, but the docs emphasizes the `http.run(r, f)`:
  78. ```scala
  79. val f = http.run(r, Gigahorse.asString andThen {_.take(60)})
  80. ```
  81. ### using Either
  82. Another influence from Dispatch is lifting of `Future[A]` to `Future[Either[Throwable, A]]`. To avoid LGPL, I didn't look at the implementation, but Dispatch adds extention method `either` on `Future` using implicits that does that.
  83. I wanted to avoid implicits here, so instead I created a hacky solution called `FutureLifter` that looks like this:
  84. ```scala
  85. val f = http.run(r, Gigahorse.asEither map { Gigahorse.asString })
  86. ```
  87. `asEither` kind of feels like a function, but in addition to mapping to `Right(...)` it also does `recoverWith(...)` to `Left(...)`. This is fine, but you also would end up with multiple `Future[Either[Throwable, A]]`, so you might need Cats ([Stacking Future and Either][stacking]), Scalaz, and/or [@wheaties][@wheaties]'s [AutoLift][AutoLift] to compose them sanely.
  88. ### naming
  89. <img src="/images/gigahorse-800.jpeg">
  90. Gigahorse is named after [the custon vehicle][thegigahorse] driven by Immortan Joe in Mad Max Fury Road. I was thinking around the concept of modified stock cars of the moonshine runners. That lead me to the post-apocalyptic rat rod mayhem that is Mad Max Fury Road, which I've seen multiple times. The fact that it has two working Big Block V8 mortors felt right for this project.
  91. ### summary
  92. Gigahorse is a new HTTP client for Scala, but it's based on the foundation of existing works like [AHC][AHC]. Let me know if you try it.