の続きです。
リクエストパスとパラメータ
前回までの例では、Routing にて単純な GET リクエストを処理しました。
POST など他のメソッドを受け付けるには以下のように、該当するHTTPメソッドに応答したもので受け付けるだけです。
fun Routing.root() { get("path") { } post("path") { } }
パスは以下のように構造化して指定することもできます。
route("a") { route("b") { get {…} post {…} } }
パスパラメータは以下のように利用できます。
get("/user/{login}") { val login = call.parameters["login"] }
POST された内容は以下のように取得することができます。
post { val params = call.receiveParameters() val name = params["name"] ?: "" }
入力フォームを作成する
前回と同様に HTML DSL にて POST 用の入力フォームを作成しましょう。
fun userForm( name: String = "", email: String = "", error: String = ""): HTML.() -> Unit = { head { //link(rel = "stylesheet", href = "/static/styles.css") link(rel = "stylesheet", href = "https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css") } body { div(classes = "container") { if (error.isNotBlank()) { p(classes = "text-danger") { +error } } postForm (action = "/user/new", encType = FormEncType.applicationXWwwFormUrlEncoded) { div { +"Name:" } div { textInput(name = "name", classes = "form-control") { value = name } } div { +"Email:" } div { textInput(name = "email", classes = "form-control") { value = email } } br div { submitInput(classes = "btn btn-outline-primary") { value = "Register" }} } } } }
CSS は bootstrap を使うようにしました。
以下のようなHTMLが生成されます。
<!DOCTYPE html> <html> <head> <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <form action="/user/new" enctype="application/x-www-form-urlencoded" method="post"> <div>Name:</div> <div><input type="text" name="name" class="form-control" value=""></div> <div>Email:</div> <div><input type="text" name="email" class="form-control" value=""></div> <br> <div><input type="submit" class="btn btn-outline-primary" value="Register"></div> </form> </div> </body> </html>
POST リクエストを処理する
冒頭で見たように、POST時の処理を作成しましょう。
App.kt
は以下のようになります。
package ktor.example import io.ktor.application.* import io.ktor.routing.* import io.ktor.html.* import io.ktor.http.content.* import io.ktor.request.* import io.ktor.response.* import io.ktor.routing.get import kotlinx.html.* data class User(val name: String, val email: String) fun Application.main() { routing { root() } } fun Routing.root() { route("/user/new") { get { call.respondHtml(block = userForm()) } post { val params = call.receiveParameters() val name = params["name"] ?: "" val email = params["email"] ?: "" if (name.isBlank() || email.isBlank()) { call.respondHtml(block = userForm(name, email, "Name and Email is required.")) } else { val user = User(name, email) call.respondText("OK [${user}]") } } } }
実行します。
$ ./gradlew run
http://localhost:8080//user/new
にアクセスしてみましょう。
入力エラー時には以下のようになります。
登録処理は今のところ、未実装なのでユーザ情報を表示するのみです。
JSON を返却する
JSON を扱うには ContentNegotiation
を install します。
Gson
や Jackson
、そして kotlinx.serialization
が使えます。ここでは kotlinx.serialization
を使います。
依存に io.ktor:ktor-serialization
を追加するとともにプラグイン org.jetbrains.kotlin.plugin.serialization
を使用します。
build.gradle.kt
は以下のようになります。
plugins { id("org.jetbrains.kotlin.jvm") version "1.4.20" id("org.jetbrains.kotlin.plugin.serialization") version "1.4.20" application } repositories { jcenter() } dependencies { implementation(platform("org.jetbrains.kotlin:kotlin-bom")) implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("io.ktor:ktor-server-netty:1.4.3") implementation("io.ktor:ktor-html-builder:1.4.3") implementation("io.ktor:ktor-serialization:1.4.3") implementation("ch.qos.logback:logback-classic:1.2.3") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") } application { mainClass.set("io.ktor.server.netty.EngineMain") }
Ktor で serialization を使うには、以下のように ContentNegotiation
を構成します。
install(ContentNegotiation) { json() }
返却するデータには @Serializable
でアノテートします。
import kotlinx.serialization.Serializable @Serializable data class User(val name: String, val email: String)
call.respond()
を呼べば JSON 形式でレスポンスされます。
val user = User(name, email)
call.respond(user)
App.kt
の全体は以下のようになりました。
package ktor.example import io.ktor.application.* import io.ktor.features.* import io.ktor.routing.* import io.ktor.html.* import io.ktor.request.* import io.ktor.response.* import io.ktor.routing.get import io.ktor.serialization.* import kotlinx.html.* import kotlinx.serialization.Serializable @Serializable data class User(val name: String, val email: String) fun Application.main() { install(ContentNegotiation) { json() } routing { root() } } fun Routing.root() { route("/user/new") { get { call.respondHtml(block = userForm()) } post { val params = call.receiveParameters() val name = params["name"] ?: "" val email = params["email"] ?: "" if (name.isBlank() || email.isBlank()) { call.respondHtml(block = userForm(name, email, "Name and Email is required.")) } else { val user = User(name, email) call.respond(user) } } } } fun userForm( name: String = "", email: String = "", error: String = ""): HTML.() -> Unit = { head { //link(rel = "stylesheet", href = "/static/styles.css") link(rel = "stylesheet", href = "https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css") } body { div(classes = "container") { if (error.isNotBlank()) { p(classes = "text-danger") { +error } } postForm (action = "/user/new", encType = FormEncType.applicationXWwwFormUrlEncoded) { div { +"Name:" } div { textInput(name = "name", classes = "form-control") { value = name } } div { +"Email:" } div { textInput(name = "email", classes = "form-control") { value = email } } br div { submitInput(classes = "btn btn-outline-primary") { value = "Register" }} } } } }
次回に続く。