🥰

http4s の最小構成:クライアント:シンプルにリクエストを送る

2023/01/22に公開

https://zenn.dev/110416/articles/aaf5dc2ed7ccf4
の続編です.

需要がありそうなら websocket, ストリーミング, 簡易的な jwt 認証や circe のカスタム Codec の使い方なども書くので気になる方はバッジを送ったりしてください💰

http response を文字列で受け取る

Scala CLI の例

req.scala
//> using scala "3.2.1"
//> using lib "org.typelevel::cats-effect::3.4.5"
//> using lib "org.http4s::http4s-core::1.0.0-M38"
//> using lib "org.http4s::http4s-ember-client::1.0.0-M38"


import cats.effect.*
import cats.effect.unsafe.implicits.global
import org.http4s.ember.client.EmberClientBuilder

val program = EmberClientBuilder.default[IO].build.use { client =>
    for
      // expect[T] の T に取得したいデータ型を指定する. 
      // ここではレスポンスを文字列で取得するので String を指定する.
      res <- client.expect[String]("http://httpbin.org/get")
      _ <- IO.println(res)
    yield ()
 }

@main def run = program.unsafeRunSync()

Tips

EmberClientBuilder.default[IO].buildResource[IO,Client] 型を返します.
ResourceInputStream#close() のように後始末が必要なデータ型を表現するための型です.

val program = EmberClientBuilder.default[IO].build.use { client =>

Http クライアントは解放時に保持したリソース、例えばコネクションを破棄したりするので Resource 型になります.

SBT の場合

build.sbt
scalaVersion := "3.2.1"
libraryDependencies ++= Seq(
  "org.typelevel" %% "cats-effect" % "3.4.5",
  "org.http4s" %% "http4s-core" % "1.0.0-M38",
  "org.http4s" %% "http4s-ember-client" % "1.0.0-M38",
)
src/main/scala/Main.scala
import cats.effect.*
import org.http4s.ember.client.EmberClientBuilder

object Main extends IOApp:
  def run(args: List[String]): IO[ExitCode] =
    val program = EmberClientBuilder.default[IO].build.use { client =>
      for
        res <- client.expect[String]("http://httpbin.org/get")
        _ <- IO.println(res)
      yield ()
    }
    program.as(ExitCode.Success)

Http response の json を circe でパースする

req.scala
//> using scala "3.2.1"
//> using lib "org.typelevel::cats-effect::3.4.5"
+//> using lib "io.circe::circe-core:0.15.0-M1"
+//> using lib "io.circe::circe-generic:0.15.0-M1"
//> using lib "org.http4s::http4s-core::1.0.0-M38"
//> using lib "org.http4s::http4s-ember-client::1.0.0-M38"
+//> using lib "org.http4s::http4s-circe::1.0.0-M38"

+import io.circe.*
// asJson などのメソッドを生やすための import
+import io.circe.syntax.*
import cats.effect.*
import cats.effect.unsafe.implicits.global
import org.http4s.ember.client.EmberClientBuilder
+import org.http4s.circe.CirceEntityCodec.circeEntityDecoder
// .{A => A'} は import 時に alias をつけるための文法
+import io.circe.Printer.{spaces2 => Print2Indents}

+case class HttpBinResponse(
+    args: Map[String,String],
+    headers: Headers,
+    origin: String,
+    url: String
+) derives Decoder, Encoder.AsObject
// Decoder は Json => HttpBinResponse への変換を
// Encoder は HttpResponse => Json への変換を行う

+case class Headers(
+    Accept: String,
+    Date: String,
+    Host: String,
      // フィールド名に `-` などを含む場合は "`"(バックティック)で
      // フィールド名をくくる
+    `User-Agent`: String,
+    `X-Amzn-Trace-Id`: String,
+)

val program = EmberClientBuilder.default[IO].build.use { client =>
    for
       // expect[T] の T に HttpBinResponse を指定する.
       // この T に入れる型は circe の Decoder を derives する必要がある.
       // (auto derivation すれば不要だがここでは割愛)
+      res <- client.expect[HttpBinResponse]("http://httpbin.org/get")
+      _ <- IO.println(Print2Indents print res.asJson)
    yield ()
}

@main def run = program.unsafeRunSync()

参考

https://yamitzky.hatenablog.com/entry/2016/05/13/204107

Discussion