自分が書いている趣味プロダクトに、HTTPクライアントとして外部と通信するコードがあった。タイムアウトを指定しつつ通信させたかったがあまり資料が無かったのでメモしておく。
http4sとは
そもそもhttp4sとは、ScalaでHTTPまわりを扱うためのライブラリ。サーバとして使うこともできるし、クライアントとしても使うことができる。
ストリーミングライブラリであるfs2をベースに開発されており、Cats Effectなどといった他の関数型ライブラリとの相性が極めて良いのが特徴。例えば、HTTPリクエストの結果をIO
に入れてくれたりする。
そして、EmberClientとはhttp4s向けに開発されたfs2ベースの非同期動作が可能でコネクションプールを持つHTTPクライアントバックエンド。バックエンドは他にも選ぶことができ、JavaNetClientなどがある。
今回はEmberClientを使う。
lazy val root = (project in file(".")) .settings( name := "example", libraryDependencies ++= Seq( "org.typelevel" %% "cats-effect" % "3.3.12", "org.http4s" %% "http4s-ember-client" % "0.23.16", "org.http4s" %% "http4s-circe" % "0.23.16", "io.circe" %% "circe-generic" % "0.14.3", "io.circe" %% "circe-literal" % "0.14.3", )
import cats.effect.IO import org.http4s.ember.client._ import org.http4s.client._ import org.http4s.Request import org.http4s.Method import org.http4s.Headers import org.http4s.Uri import io.circe._ private lazy val client = { EmberClientBuilder.default[IO].build } // ストリーミングAPIを使う def getStream: IO[fs2.Stream[IO, Byte]] = client.use { c => val uri = Uri.fromString("http://localhost:8080/stream") val req = Request[IO](uri = uri.right.get) IO.pure(c.stream(req).flatMap(_.body)) } // 普通のGET def getJson(): IO[Json] = client.use { c => val req = Request[IO](uri = Uri.fromString("http://localhost:8080/some.json").right.get) c.expect[Json](req) }
fs2のストリームとしてデータを受け取れるため、これをそのままファイルに保存したりできる。
private def writeToFile(stream: fs2.Stream[IO, Byte], fileName: String) = { import fs2.io.file.{Files, Path} val target = Path(fileName) stream.through(Files[IO].writeAll(target)).compile.drain.as(target) } for { stream <- getStream files <- writeToFile(stream, s"./stream.wav") } yield ()
タイムアウトを設定する
さて、レスポンスが返ってくるまで時間がかかるリクエストを送信したい場合はタイムアウトを長めに設定したいことがある。そのような場合はEmberClientBuilder
のメソッドを使うが、微妙に差異があるいくつかのメソッドが存在している:
.withTimeout(5 minutes)
.withIdleTimeInPool(5 minutes)
.withIdleConnectionTime(5 minutes)
以下、それぞれについて解説する。
withTimeout
Scaladocによれば:
Sets the header receive timeout on connections.
ヘッダーが到着するまでに許容する時間のことだと思われる。
withIdleTimeInPool
Scaladocによれば:
Sets the connection pool's maximum time a connection can be idle. The timeout starts when a connection is returned the the pool, and reset when it is borrowed.
コネクションプールにコネクションを留めておくための時間だと思われる。
.withIdleConnectionTime
Scaladocによれば:
Sets the idle timeout on connections. The timeout is reset with each read or write.
しばらくデータが行き来しなくなってしまった場合に、それを待つ時間だと思われる。
一般的な用途であればこれを使うのが良さそう
タイムアウトの設定方法
EmberClientBuilder.default[IO]
にくっつける形で定義する。
import concurrent.duration._ import scala.language.postfixOps val client = EmberClientBuilder.default[IO].withIdleConnectionTime(5 minutes).build