zio-http

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

# セットアップ

``build.sbt``について既存の``libraryDependencies``を全て削除し、以下を記載します。
```scala:build.sbt
libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC2"
```

# GETメソッド

後のPOSTメソッド利用のためにサービスパターンを導入した``Hello World!``を出力するGETメソッドについて見ていきます。
まずは処理本体となる以下の2ファイルを追加します。
```scala:ApplicationService.scala
trait ApplicationService {
  def getTest: ZIO[Any, Throwable, Response]
}

object ApplicationService {
  def getTest: ZIO[ApplicationService, Throwable, Response] = {
    ZIO.serviceWithZIO[ApplicationService](_.getTest)
  }
}
```
```scala: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()))

}
```

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

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

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

```scala: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」にアクセスすると以下の出力が得られると思います。
```shell
Hello World!
```

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

## サーバの起動

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

```scala: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``関数以下でエラーハンドリングを行っています。

## パスの指定

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

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

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

## HTTPレスポンス内容の指定

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

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

# POSTメソッド

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

```scala: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))
  }
}
```
```scala: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
}
```

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

```scala: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``を指定し、リクエストを行うと以下の様な値を得ることが出来ます。

```shell
あなたの名前はuserNameです。
```

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

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

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

## 実処理部分

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

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

```scala:ApplicationServiceImpl.scala
form <- request.body.asURLEncodedForm
```

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

```scala:ApplicationServiceImpl.scala
form.get("userName")
```

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

# Cookieの利用

最後にクッキーの利用について見ていきます。
POSTメソッドの場合と同様に``setCookie``関数、``getCookie``関数の追加を行います。
```scala: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))
  }
}
```
```scala: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")) // 文字化け対策をしています
}
```

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

```scala: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``を呼び出すと以下の様にクッキーの一覧が出力されます。

```shell
Request(key,hello)
```

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

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

## クッキーの設定

まず、以下の様に設定したいクッキーのインスタンスを作成します。
```scala:ApplicationServiceImpl.scala
responseCookie <- ZIO.attempt(Cookie.Response("key", "hello"))
```

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

```scala:ApplicationServiceImpl.scala
Response.ok.addCookie(responseCookie)
```

## クッキーの読み取り

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

```scala:ApplicationServiceImpl.scala
request.header(Header.Cookie)
```

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

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

# 終わりに

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

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

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

前章:[ZIOの並列処理](https://qiita.com/wc_sangen/private/5154da992e0fb67166fb)
次章:[外部Web APIアクセス](https://qiita.com/wc_sangen/private/a32012693458719deb17)

# 演習
1. 今回のGETメソッド例について実際に動かして挙動を確かめてください。
    - 解答例は以下になります。
        https://github.com/hatuda/zio-practice/tree/CHAPTER6-Get
1. 今回のPOSTメソッド例について実際に動かして挙動を確かめてください。
    - 解答例は以下になります。
        https://github.com/hatuda/zio-practice/tree/CHAPTER6-Post

1. 今回のCookieの利用例について実際に動かして挙動を確かめてください。
    - 解答例は以下になります。
        https://github.com/hatuda/zio-practice/tree/CHAPTER6-Cookie