0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

zio-http

Last updated at Posted at 2023-09-28

基本であるGETメソッド、POSTメソッド、クッキーの処理をそれぞれ見ていきます。
まず最初にセットアップです。

セットアップ

build.sbtについて既存のlibraryDependenciesを全て削除し、以下を記載します。

build.sbt
libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC2"

GETメソッド

後のPOSTメソッド利用のためにサービスパターンを導入したHello World!を出力するGETメソッドについて見ていきます。
まずは処理本体となる以下の2ファイルを追加します。

ApplicationService.scala
trait ApplicationService {
  def getTest: ZIO[Any, Throwable, Response]
}

object ApplicationService {
  def getTest: ZIO[ApplicationService, Throwable, Response] = {
    ZIO.serviceWithZIO[ApplicationService](_.getTest)
  }
}
ApplicationServiceImpl.scala
case class ApplicationServiceImpl() extends ApplicationService {
  override def getTest: ZIO[Any, Throwable, Response] = ZIO.attempt(Response.text("Hello World!"))
}

object ApplicationServiceImpl {
  val layer: ZLayer[Any, Nothing, ApplicationService] =
    ZLayer(ZIO.succeed(ApplicationServiceImpl()))

}

次に上記の処理本体を利用するコントローラとなるファイルを追加します。

TestController.scala
object TestController {
  def apply(): Http[ApplicationService, Throwable, Request, Response] = Http.collectZIO[Request] {
    case Method.GET -> Root / "text" => ApplicationService.getTest
  }
}

最後に最上位のファイルについて以下の様に作成します。

MainApp.scala
object MainApp extends ZIOAppDefault {

  override val run: ZIO[Any, Throwable, Nothing] =
    Server
      .serve(TestController().catchAllCauseZIO { _ =>
        // エラー処理
        ZIO.succeed(Response(status = Status.InternalServerError))
      })
      .provide(
        Server.default,
        ApplicationServiceImpl.layer
      )
}

上記のファイルを配置し、実行し、インターネットブラウザ等で「http://localhost:8080/text」にアクセスすると以下の出力が得られると思います。

Hello World!

上記のコードについて主要な部分について解説していきます。

サーバの起動

以下の部分でポート8080をlistenするサーバを立ち上げています。

MainApp.scala
    Server
      .serve(TestController().catchAllCauseZIO { _ =>
        // エラー処理
        ZIO.succeed(Response(status = Status.InternalServerError))
      })
      .provide(
        Server.default,
        ApplicationServiceImpl.layer
      )

Server.defaultと今回最も基本的なデフォルトの設定をDIしているため、ポート8080を待ち受けていますが、Server.defaultWithPort(8081)を渡すことで待ち受けるポート番号を8081に変更することもできます。
このように設定の変更を行うことも出来ます。

そしてServer.serve関数でコントローラに相当するインスタンスを渡しています。その際に今回の場合、ここでcatchAllCauseZIO関数以下でエラーハンドリングを行っています。

パスの指定

以下の部分で指定しています。

TestController.scala
case Method.GET -> Root / "text" => ApplicationService.getTest

今回の内容は最も基本的ですが、GETメソッドで/textのパスが指定された場合、ApplicationService.getTestに後の処理を委譲する処理を指定しています。
パス指定をRoot / "text" / pathVariableとすることでパス変数pathVariableも取ることが出来ます。

HTTPレスポンス内容の指定

以下の部分で指定しています。

ApplicationServiceImpl.scala
ZIO.attempt(Response.text("Hello World!"))

今回はステータスコード200でレスポンスを返却していますが、
例えばResponse.text("Hello World!").withStatus(Status.Found)と変更することでレスポンス時のステータスコードを302に変更するといったこともできます。

POSTメソッド

POSTメソッドの確認のため、GETメソッドのコードに対して、postTest関数の追加を行います。

ApplicationService.scala
trait ApplicationService {
  def getTest: ZIO[Any, Throwable, Response]
  def postTest(request: Request): ZIO[Any, Throwable, Response]
}

object ApplicationService {
  def getTest: ZIO[ApplicationService, Throwable, Response] = {
    ZIO.serviceWithZIO[ApplicationService](_.getTest)
  }

  def postTest(request: Request): ZIO[ApplicationService, Throwable, Response] = {
    ZIO.serviceWithZIO[ApplicationService](_.postTest(request))
  }
}
ApplicationServiceImpl.scala
case class ApplicationServiceImpl() extends ApplicationService {
  override def getTest: ZIO[Any, Throwable, Response] = ZIO.attempt(Response.text("Hello World!"))
  override def postTest(request: Request): ZIO[Any, Throwable, Response] = for {
    form <- request.body.asURLEncodedForm
    response <- form.get("userName") match
      case Some(userName) =>
        for {
          un <- userName.asText
        } yield Response.text(s"あなたの名前は${un}です。")
      case _ => ZIO.attempt(Response.text("userNameを指定してください"))
  } yield response
}

この処理を呼び出すためのパスをコントローラに追記します。

TestController.scala
object TestController {
  def apply(): Http[ApplicationService, Throwable, Request, Response] = Http.collectZIO[Request] {
    case Method.GET -> Root / "text"            => ApplicationService.getTest
    case req @ Method.POST -> Root / "postTest" => ApplicationService.postTest(req)
  }
}

上記のコードを実行し、各種のAPIの確認ツールを使い、パラメータuserNameを指定し、リクエストを行うと以下の様な値を得ることが出来ます。

あなたの名前はuserNameです。

上記のコードについて詳しく見ていきます。

POSTメソッドの場合のパス指定

GETメソッドの場合とほとんど変わりませんが先頭にreq:Requestを付けることにより、POSTパラメータなど様々な情報を取り出せるようにします。

実処理部分

ここではPOSTメソッドの場合のパス指定で受け取ったRequestオブジェクトを使い、POSTパラメータを受け取ります。

まず、以下でパラメータ全体を取得します。

ApplicationServiceImpl.scala
form <- request.body.asURLEncodedForm

次に以下で特定のパラメータ、今回の場合はuserNameパラメータを取得します。

ApplicationServiceImpl.scala
form.get("userName")

指定されたPOSTパラメータが送られている場合はSome(userName)にマッチし、それ以外は_にマッチするようにしています。

Cookieの利用

最後にクッキーの利用について見ていきます。
POSTメソッドの場合と同様にsetCookie関数、getCookie関数の追加を行います。

ApplicationService.scala
trait ApplicationService {
  (中略)
  def setCookie: ZIO[Any, Throwable, Response]
  def getCookie(request: Request): ZIO[Any, Throwable, Response]
}

object ApplicationService {
  (中略)
  def setCookie: ZIO[ApplicationService, Throwable, Response] = {
    ZIO.serviceWithZIO[ApplicationService](_.setCookie)
  }
  def getCookie(request: Request): ZIO[ApplicationService, Throwable, Response] = {
    ZIO.serviceWithZIO[ApplicationService](_.getCookie(request))
  }
}
ApplicationServiceImpl.scala
case class ApplicationServiceImpl() extends ApplicationService {
  (中略)
  override def setCookie: ZIO[Any, Throwable, Response] = for {
    responseCookie <- ZIO.attempt(Cookie.Response("key", "hello"))
  } yield Response.ok.addCookie(responseCookie)

  override def getCookie(request: Request): ZIO[Any, Throwable, Response] = for {
    cookie <- ZIO.attempt{request.header(Header.Cookie) match
      case Some(ck) => ck.value.toChunk.mkString("")
      case _ => "先にsetCookieをよびだしてください"}
    _ <- Console.printLine("cookie:"+cookie)
  } yield Response.text(cookie).setHeaders(Headers("Content-Type", "text/plain;charset=UTF-8")) // 文字化け対策をしています
}

この処理を呼び出すためのパスをコントローラに追記します。

TestController.scala
object TestController {
  def apply(): Http[ApplicationService, Throwable, Request, Response] = Http.collectZIO[Request] {
    case Method.GET -> Root / "text"            => ApplicationService.getTest
    case req @ Method.POST -> Root / "postTest" => ApplicationService.postTest(req)
    case Method.GET -> Root / "setCookie"       => ApplicationService.setCookie
    case req @ Method.GET -> Root / "getCookie" => ApplicationService.getCookie(req)
  }
}

これを実行し、最初にhttp://localhost:8080/setCookieを呼び出し、次にhttp://localhost:8080/getCookieを呼び出すと以下の様にクッキーの一覧が出力されます。

Request(key,hello)

最初にhttp://localhost:8080/setCookieを呼び出していないなど、クッキーが無い場合には以下の出力になります。

先にsetCookieをよびだしてください

処理について詳しく見ていきます。

クッキーの設定

まず、以下の様に設定したいクッキーのインスタンスを作成します。

ApplicationServiceImpl.scala
responseCookie <- ZIO.attempt(Cookie.Response("key", "hello"))

上記で作成したインスタンスを以下の様にResponseオブジェクトにaddCookie関数を使い、セットすることでクッキーを登録できます。

ApplicationServiceImpl.scala
Response.ok.addCookie(responseCookie)

クッキーの読み取り

クッキー自体はRequestオブジェクトから以下の方法で取り出せます。

ApplicationServiceImpl.scala
request.header(Header.Cookie)

上記の方法で取得した値はOption型であるため、設定されている場合とされていない場合で処理を分け、取得したい形式で具体的にクッキーを取得します。
今回は全てのクッキーについて文字列形式で取得しています。

ApplicationServiceImpl.scala
case Some(ck) => ck.value.toChunk.mkString("")

終わりに

以上が基本的なzio-httpの使い方になります。
大まかな流れとしては以下の様になります。

  1. 最初にServer.serve関数でサーバを設定
  2. パスを設定
    case Method.GET -> Root / "text"            => ApplicationService.getTest
    
  3. トレイトに対して実装(トレイトは上記の場合ApplicationServiceです)

次回は他のサーバとの通信を行うためのzio-httpのクライアントについて見ていきます。

前章:ZIOの並列処理
次章:外部Web APIアクセス

演習

  1. 今回のGETメソッド例について実際に動かして挙動を確かめてください。

  2. 今回のPOSTメソッド例について実際に動かして挙動を確かめてください。

  3. 今回のCookieの利用例について実際に動かして挙動を確かめてください。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?