Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

http4sのEmberClientでタイムアウトを指定する

自分が書いている趣味プロダクトに、HTTPクライアントとして外部と通信するコードがあった。タイムアウトを指定しつつ通信させたかったがあまり資料が無かったのでメモしておく。

http4sとは

そもそもhttp4sとは、ScalaでHTTPまわりを扱うためのライブラリ。サーバとして使うこともできるし、クライアントとしても使うことができる。

http4s.org

ストリーミングライブラリであるfs2をベースに開発されており、Cats Effectなどといった他の関数型ライブラリとの相性が極めて良いのが特徴。例えば、HTTPリクエストの結果をIOに入れてくれたりする。

そして、EmberClientとはhttp4s向けに開発されたfs2ベースの非同期動作が可能でコネクションプールを持つHTTPクライアントバックエンド。バックエンドは他にも選ぶことができ、JavaNetClientなどがある。

http4s.org

今回は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 ()

fs2.io

タイムアウトを設定する

さて、レスポンスが返ってくるまで時間がかかるリクエストを送信したい場合はタイムアウトを長めに設定したいことがある。そのような場合は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
★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?