http4s client 学习笔记

实践Scala的时候需要用Http Client,找到这篇Comparing Scala’s HTTP client librariesscalaj-http用起来最顺手,就在项目中用上了,但是后来发现scalaj-http不支持PATCH,而文章作者提到的Newman已经很久没有更新了,spray-client依赖于akkaplay-ws本身的目标用户很大部分是play的用户。所以最后的选择是使用Java实现的OKHttp

在寻找称手的Http Client的时候也找到过http4s,但是http4s的文档花了很大的篇幅介绍如何用http4s构建Http Server,在介绍http4s的client时只介绍了如何发起一个HTTP GET请求。便没有细看。前段时间在Scala China的微信群里面讨论HTTP Client时,hepin提到可以试试http4s,于是翻了源码,学习了一下http4s的简单使用。http4s的文档实在简陋,故而记录如下:

Demo

Dependencies

libraryDependencies ++= Seq(
  "org.http4s" %% "http4s-blaze-client" % "0.14.11a"
)

Http Get with Params

val client = PooledHttp1Client()
val httpize = Uri.uri("http://httpize.herokuapp.com")
// val url = Url.fromString("http://httpize.herokuapp.com/cookies/set?name=rendong&name=shaobo")

val url = httpize / "cookies" / "set" +? ("name", Seq("rendong", "shaobo"))
val task = client.expect[String](url)
val body = task.unsafePerformSync

Error Handling

val either = task.attempt.unsafePerformSync
either match {
  case -\/(e) =>
    e.printStackTrace()
  case \/-(body) =>
    println(body)
}

这里面的task实际上是scalaz的Task,either的类型是\/,是scalaz的Either实现版本。

Http Post

val req = Request(method = Method.POST, uri = httpize / "post").withBody("hello")
val task = client.expect[String](req)
task.attempt.unsafePerformSync match {
  case -\/(e) =>
    e.printStackTrace()
  case \/-(body) =>
    println(body)
}

http4s也支持PATCH, PUT, DELETE等操作,具体和Http Post的代码类似。

Response

val req = Request(method = Method.PATCH, uri = httpize / "post")
  .withBody("patch")
  .asInstanceOf[Task[Request]]
val task = client.fetch(req) {
  case Ok(response) =>
    Task(response)
  case response => Task(response)
}
val response = task.unsafePerformSync
println(s"header: ${response.headers}")
println(s"status: ${response.status}")
println(s"content-type: ${response.contentType}")
println(s"content-length: ${response.contentLength}")
println(s"body: ${response.body}")

运行的结果是:

header: Headers(Server: Cowboy, Connection: keep-alive, Content-Type: text/plain; charset=UTF-8, Content-Length: 20, Date: Sat, 26 Nov 2016 11:26:55 GMT, Via: 1.1 vegur)
status: 404 Not Found
content-type: Some(Content-Type: text/plain; charset=UTF-8)
content-length: Some(20)
body: Emit(Vector(ByteVector(20 bytes, 0x343034204e6f7420466f756e643a202f706f7374)))

这里面值得注意的是withBody得到的类型其实是Task[Request#Self](见参考文献)。如果你希望得到整个Response而不仅仅关注于response body,你就需要用fetch而不是expectgetfetch的特例,即GET请求的fetch

Body

上一节中拿到的body的类型是EntityBody,翻阅源码后发现其类型实际上是Process[Task, ByteVector]。涉及scalaz,就比较令人头疼了。读http4s的测试用例,就可以知道可以这样处理:

def getBody(body: EntityBody): String =
  (body.runLog.map(_.reduce(_ ++ _).toArray))
    .unsafePerformSync
    .map(_.asInstanceOf[Char])
    .mkString

println(s"decoded: ${getBody(response.body)}")

// result:
// decoded: 404 Not Found: /post

References

  1. Getting started doc for http4s
  2. scala类型系统:9) this别名&自身类型