基本であるGETメソッド、POSTメソッド、クッキーの処理をそれぞれ見ていきます。
まず最初にセットアップです。
セットアップ
build.sbt
について既存のlibraryDependencies
を全て削除し、以下を記載します。
libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC2"
GETメソッド
後のPOSTメソッド利用のためにサービスパターンを導入したHello World!
を出力するGETメソッドについて見ていきます。
まずは処理本体となる以下の2ファイルを追加します。
trait ApplicationService {
def getTest: ZIO[Any, Throwable, Response]
}
object ApplicationService {
def getTest: ZIO[ApplicationService, Throwable, Response] = {
ZIO.serviceWithZIO[ApplicationService](_.getTest)
}
}
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()))
}
次に上記の処理本体を利用するコントローラとなるファイルを追加します。
object TestController {
def apply(): Http[ApplicationService, Throwable, Request, Response] = Http.collectZIO[Request] {
case Method.GET -> Root / "text" => ApplicationService.getTest
}
}
最後に最上位のファイルについて以下の様に作成します。
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するサーバを立ち上げています。
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
関数以下でエラーハンドリングを行っています。
パスの指定
以下の部分で指定しています。
case Method.GET -> Root / "text" => ApplicationService.getTest
今回の内容は最も基本的ですが、GETメソッドで/text
のパスが指定された場合、ApplicationService.getTest
に後の処理を委譲する処理を指定しています。
パス指定をRoot / "text" / pathVariable
とすることでパス変数pathVariable
も取ることが出来ます。
HTTPレスポンス内容の指定
以下の部分で指定しています。
ZIO.attempt(Response.text("Hello World!"))
今回はステータスコード200でレスポンスを返却していますが、
例えばResponse.text("Hello World!").withStatus(Status.Found)
と変更することでレスポンス時のステータスコードを302に変更するといったこともできます。
POSTメソッド
POSTメソッドの確認のため、GETメソッドのコードに対して、postTest
関数の追加を行います。
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))
}
}
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
}
この処理を呼び出すためのパスをコントローラに追記します。
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パラメータを受け取ります。
まず、以下でパラメータ全体を取得します。
form <- request.body.asURLEncodedForm
次に以下で特定のパラメータ、今回の場合はuserName
パラメータを取得します。
form.get("userName")
指定されたPOSTパラメータが送られている場合はSome(userName)
にマッチし、それ以外は_
にマッチするようにしています。
Cookieの利用
最後にクッキーの利用について見ていきます。
POSTメソッドの場合と同様にsetCookie
関数、getCookie
関数の追加を行います。
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))
}
}
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")) // 文字化け対策をしています
}
この処理を呼び出すためのパスをコントローラに追記します。
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をよびだしてください
処理について詳しく見ていきます。
クッキーの設定
まず、以下の様に設定したいクッキーのインスタンスを作成します。
responseCookie <- ZIO.attempt(Cookie.Response("key", "hello"))
上記で作成したインスタンスを以下の様にResponse
オブジェクトにaddCookie
関数を使い、セットすることでクッキーを登録できます。
Response.ok.addCookie(responseCookie)
クッキーの読み取り
クッキー自体はRequest
オブジェクトから以下の方法で取り出せます。
request.header(Header.Cookie)
上記の方法で取得した値はOption型であるため、設定されている場合とされていない場合で処理を分け、取得したい形式で具体的にクッキーを取得します。
今回は全てのクッキーについて文字列形式で取得しています。
case Some(ck) => ck.value.toChunk.mkString("")
終わりに
以上が基本的なzio-http
の使い方になります。
大まかな流れとしては以下の様になります。
- 最初に
Server.serve
関数でサーバを設定 - パスを設定
case Method.GET -> Root / "text" => ApplicationService.getTest
- トレイトに対して実装(トレイトは上記の場合
ApplicationService
です)
次回は他のサーバとの通信を行うためのzio-http
のクライアントについて見ていきます。
前章:ZIOの並列処理
次章:外部Web APIアクセス
演習
-
今回のGETメソッド例について実際に動かして挙動を確かめてください。
-
今回のPOSTメソッド例について実際に動かして挙動を確かめてください。
-
今回のCookieの利用例について実際に動かして挙動を確かめてください。