この記事は ハンズラボ Advent Calendar 2018 19日目の記事です。
おはようございます。@naokiurです。
今年2回目です。
よろしくお願い致します。
前回、Ktorのテストメソッドを通して、
Kotlinの言語を、少し学ぶことができました。
今回は、KtorでWeb APIを作成していきたいと思います。
環境
- MacBook Pro (Retina 13-inch、Early 2015)
- macOS High Sierra 10.13.6
- Java 1.8.0_152
- IntelliJ IDEA CE 2018.2.5
実施したこと
- APIのテストクラスを作成する
- GETメソッド
- POSTメソッド
- APIを作成する
- GETメソッド
- POSTメソッド
サンプル
分かりやすい例で、
以下のような ユーザーを管理
するAPIを作成したいと思います。
- GETメソッドのAPIで、存在するユーザーの一覧を取得する
show
- POSTメソッドのAPIで、ユーザーを追加する
create
ユーザー
は、すごくシンプルに、以下の属性を持つようにします。
- ユーザーID
- ユーザー名
- フルネーム
結果
APIのテストクラスを作成する
まず前回学んだ、テストクラスから作成したいと思います。
showメソッドのテストメソッド
@Test
fun testShow() = withTestApplication(Application::api) {
handleRequest(HttpMethod.Get, "/show").run {
assertEquals(HttpStatusCode.OK, response.status())
println(response.content)
}
}
レスポンスの中身を確認して、期待値と等しいかを確認したほうが良いと思いますが、
今は、シンプルにレスポンスの中身を出力する程度に留めます。
前回学んだように、
.run {}
は、 TestApplicationCall
に対して実行しているため、
TestApplicationCall
のフィールドである、
response
にアクセスすることができます。
response
には、 Application::api
の実行結果である以下を参照することができます。
- status()
- content
それらを確認し、テストメソッドの判定基準としております。
createメソッドのテストメソッド
@Test
fun testCreate() = withTestApplication(Application::api) {
val gson = GsonBuilder().setPrettyPrinting().create()
val param = User(UserId(3), UserName("test"), FullName("test", "hoge"))
handleRequest(HttpMethod.Post, "/create") {
addHeader(HttpHeaders.Accept, ContentType.Text.Plain.toString())
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(gson.toJson(param))
}.run {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("Success", response.content)
}
}
JSON形式をPOSTするテストメソッドです。
gsonを用いて、
User
クラスをJSON形式に変換し、パラメータとしています。
class User constructor(
val userId: UserId,
var userName: UserName,
var fullName: FullName) {
}
class UserId(val id: Int) {
fun equals(userId: UserId): Boolean {
return id == userId.id
}
}
class FullName constructor(val firstName: String, val lastName: String) {
val name = "$firstName $lastName"
}
class UserName constructor(val name: String) {
}
この時点では、
テストクラス以外を作成していないので、
コンパイルエラーです。
APIを作成する
ここで、ようやくKtorにおけるAPIの作成です。
Ktorは、
とてもシンプルにルーティングを定義することができる
と思っています。
fun Application.api() {
routing {
get("/show") {
}
post("/create") {
}
}
}
ルーティング自体はこれだけです。
Application
の拡張関数を作成し、
routing
内に、各リクエストのハンドリングを記載する形です。
このままですと、
何も処理していないため、当然ながらテストが失敗となってしまいます。
APIサーバを起動する
この時点で、APIサーバとして起動することができます。
このままだと、何も返却しておらず、ブラウザ上で確認できないので、
レスポンスを作成します。
レスポンスを作成する
APIサーバが起動されたか確認したいため、
シンプルなレスポンスを記載します。
fun Application.api() {
routing {
get("/show") {
call.respond("show!")
}
post("/create") {
call.respond("create!")
}
}
}
レスポンスは、call.respond
に格納することで、
指定することができます。
実は… call.respond(message: Any)
を実行すると、
レスポンスのHTTPステータスが 200
になるので、
この時点で、上記テストは通ってしまいます
テストコードは、もっと適切に書いていきたいと思います ent:
APIサーバを実行する
IntelliJの Run
で、
MainClassに io.ktor.server.netty.EngineMain
を指定してあげることで、
実行することができます。
GETメソッドであるshow
テストコードは通ってしまいましたが、
JSONを返却するところまで実装したいと思います。
fun Application.api() {
install(ContentNegotiation) {
gson {
setDateFormat(DateFormat.LONG)
setPrettyPrinting()
}
}
routing {
get("/show") {
val testUser = User(UserId(1), UserName("test1"), FullName("firstTest1", "lastTest1"))
call.respond(testUser)
}
post("/create") {
call.respond("create!")
}
}
}
JSONで返却するために、 install
関数を用いました。
Ktorでは、これを用いることで、
リクエストやレスポンスに機能的な処理を注入することができます。
(routingも、installにより注入された処理の一つです。)
その中のContentNegotiation
は、
HTTPヘッダの Content-Type
と Accept
を自動変換してくれる機能で、
そこに gson
を指定することで、
JSON形式に自動変換してくれます。
複数APIを作成し、レスポンスを返却する際、
何度も変換処理を書かなくて済みますね。
POSTメソッドであるcreate
リクエストを受け取って、
登録する処理を作成します。
DB接続は別の回に持ち越しさせて頂き、
ここではリクエストの内容を出力、テストコードが通るところまで実装します。
fun Application.api() {
install(ContentNegotiation) {
gson {
setDateFormat(DateFormat.LONG)
setPrettyPrinting()
}
}
routing {
get("/show") {
val testUser = User(UserId(1), UserName("test1"), FullName("firstTest1", "lastTest1"))
call.respond(testUser)
}
post("/create") {
val parameter = call.receive<User>()
println(parameter.userId.id)
println(parameter.userName.name)
println(parameter.fullName.name)
call.respond("Success")
}
}
}
call.receive<User>()
で、リクエストを取得します。
テストコード上でJSON形式でリクエストを送信しており、
そのJSON構造とマッチするクラスを指定してあげると、
自動的にマッピングしてくれます。
受け取ったリクエストを、
そのまま別のビジネスロジックに渡すことができそうで、
便利ですね。
おわりに
駆け足になってしまいましたが、
GET/POSTのAPIを作成することができました。
今後はDB接続、
ログ出力、エラーハンドリングなどの構築方法という、アプリケーション基盤、
多量のAPIを作成した際のクラス設計など、
習得していきたいと思います。
ハンズラボ Advent Calendar 2018 、
20日目は、@yktakaha4 さんです!