Ktorを使ったHTTPリクエスト・サーバー通信
概要
Ktorには、マルチプラットフォームの非同期HTTPクライアントが含まれています。
Ktorを使ってHTTTリクエストを作成し応答を処理する方法を試してみました。
導入方法
build.gradle.kts(またはbuild.gradle)ファイルを開き、次の2行を依存関係ブロックに追加します
dependencies {
implementation("io.ktor:ktor-client-core:1.6.5") // 追加
implementation("io.ktor:ktor-client-cio:1.6.5") // 追加
}
使い方
GETリクエストを送信してレスポンスを受け取る
GETリクエストを送信してレスポンスを受け取るシンプルな処理です。
suspend fun get(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.get("http://localhost:8081/test1.php")
text.value = response.receive()
}
この例では、CIOエンジンを使用しています。
CIOはJVMとAndroidで使用できます。
他のエンジンについては、Engines | Ktorをご覧ください。
GETリクエストにパラメータを付与して送信する
パラメータを付与する例です。
parameterメソッドにキーと値を指定します。
suspend fun get(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.get("http://localhost:8081/test1.php") {
parameter("param", "test")
}
text.value = response.receive()
}
リクエストにヘッダーを追加する
リクエストにヘッダーを追加するには、headers関数を使用します。
appendメソッドにヘッダー名と値を指定します。
現時点では、appendメソッドの使用には「@OptIn(InternalAPI::class)」をつけます。
@OptIn(InternalAPI::class)
suspend fun get(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.get("http://localhost:8081/test1.php") {
headers {
append(HttpHeaders.Authorization, "token")
append(HttpHeaders.UserAgent, "ktor client")
}
parameter("param", "test")
}
text.value = response.receive()
}
POSTメソッドでフォームデータを送信する。
HttpClientのsubmitFormメソッドでフォームデータを送信します。
@OptIn(InternalAPI::class)
suspend fun postForm(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.submitForm(
url = "http://localhost:8081/test_form.php",
formParameters = Parameters.build {
append("first_name", "Jet")
append("last_name", "Brains")
}
)
text.value = response.receive()
}
POSTメソッドでJSONを送信する
Jsonプラグインを有効にすると、オブジェクトをJSONとして送信できます。
build.gradle.kts(またはbuild.gradle)ファイルを開き、次の行を追加します
plugins {
kotlin("plugin.serialization") version "1.5.31" // 追加
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0") // 追加
implementation("io.ktor:ktor-client-serialization:1.6.5") // 追加
}
JSONにするクラスを作成します。
@Serializable
data class Customer(val id: Int, val firstName: String, val lastName: String)
HttpClientにJsonFeatureをインストールして設定します。
val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
prettyPrint = true
isLenient = true
})
}
}
HttpResponseのpostメソッドを使って送信します。
val response: HttpResponse = client.post("http://localhost:8081/test_json.php") {
contentType(ContentType.Application.Json)
body = Customer(3, "Jet", "Brains")
}
全体の処理は以下のようになります。
suspend fun postJson(text: MutableState<String>) {
val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
prettyPrint = true
isLenient = true
})
}
}
val response: HttpResponse = client.post("http://localhost:8081/test_json.php") {
contentType(ContentType.Application.Json)
body = Customer(3, "Jet", "Brains")
}
text.value = response.receive()
}
JSON形式のレスポンスを受け取る
Jsonプラグインをインストールすると、受信したJSONデータをデータクラスに変換できます。
(注意)レスポンスのヘッダーには「content-type: application/json;」が必要です。
suspend fun getJson(text: MutableState<String>) {
val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
prettyPrint = true
isLenient = true
})
}
}
val customer: Customer = client.get("http://localhost:8081/test_get_json.php")
text.value = "firstName =${customer.firstName}, lastName = ${customer.lastName}"
}
ファイルをアップロードする
ファイルをアップロードするときは、submitFormWithBinaryDataメソッドを使用し、formDataパラメータでアップロードするファイルを指定します。
onUploadでアップロードの進行状況を取得できます。
@OptIn(InternalAPI::class)
suspend fun upload(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.submitFormWithBinaryData(
url = "http://localhost:8081/test_upload.php",
formData = formData {
append("image", File("/home/yamamoto/img.png").readBytes(), Headers.build {
append(HttpHeaders.ContentType, "image/png")
append(HttpHeaders.ContentDisposition, "filename=img.png")
})
}
) {
onUpload { bytesSentTotal, contentLength ->
println("Sent $bytesSentTotal bytes from $contentLength")
}
}
text.value = response.receive()
}
レスポンスのパラメータを取得する
ステータスコードは、HttpResponseクラスのstatusプロパティで取得できます。
ヘッダーはHttpResponseクラスのheadersプロパティで取得できます。
特定のヘッダーは専用の関数が用意されています。
suspend fun responseParameter(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.get("http://localhost:8081/test_get.php")
text.value = """
status = ${response.status}
contentType = ${response.contentType()}
charset = ${response.charset()}
""".trimIndent()
println(response.headers)
}
サンプルプログラム
検証に使用したサンプルプログラムです。
build.gradle.kts
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.5.31"
id("org.jetbrains.compose") version "1.0.0-beta5"
kotlin("plugin.serialization") version "1.5.31"
}
group = "jp.gesource"
version = "1.0"
repositories {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
dependencies {
implementation(compose.desktop.currentOs)
implementation("io.ktor:ktor-client-core:1.6.5")
implementation("io.ktor:ktor-client-cio:1.6.5")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0")
implementation("io.ktor:ktor-client-serialization:1.6.5")
}
tasks.withType<KotlinCompile>() {
kotlinOptions.jvmTarget = "11"
}
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "KtorSample"
packageVersion = "1.0.0"
}
}
}
Main.kt
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.features.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.util.*
import kotlinx.coroutines.launch
import kotlinx.serialization.*
import java.io.File
@Composable
@Preview
fun App() {
val composableScope = rememberCoroutineScope()
val text = remember { mutableStateOf("") }
MaterialTheme {
Column {
Button(onClick = { composableScope.launch { get(text) } }) { Text(text = "get") }
Button(onClick = { composableScope.launch { postForm(text) } }) { Text(text = "postForm") }
Button(onClick = { composableScope.launch { postJson(text) } }) { Text(text = "postJson") }
Button(onClick = { composableScope.launch { getJson(text) } }) { Text(text = "getJson") }
Button(onClick = { composableScope.launch { upload(text) } }) { Text(text = "upload") }
Button(onClick = { composableScope.launch { responseParameter(text) } }) { Text(text = "responseParameter") }
Text(text = text.value)
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
@OptIn(InternalAPI::class)
suspend fun get(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.get("http://localhost:8081/test_get.php") {
headers {
append(HttpHeaders.Authorization, "token")
append(HttpHeaders.UserAgent, "ktor client")
}
parameter("param", "test")
}
text.value = response.receive()
}
@OptIn(InternalAPI::class)
suspend fun postForm(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.submitForm(
url = "http://localhost:8081/test_form.php",
formParameters = Parameters.build {
append("first_name", "Jet")
append("last_name", "Brains")
}
)
text.value = response.receive()
}
@Serializable
data class Customer(val id: Int, val firstName: String, val lastName: String)
suspend fun postJson(text: MutableState<String>) {
val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
prettyPrint = true
isLenient = true
})
}
}
val response: HttpResponse = client.post("http://localhost:8081/test_json.php") {
contentType(ContentType.Application.Json)
body = Customer(3, "Jet", "Brains")
}
text.value = response.receive()
}
suspend fun getJson(text: MutableState<String>) {
val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
prettyPrint = true
isLenient = true
})
}
}
val customer: Customer = client.get("http://localhost:8081/test_get_json.php")
text.value = "firstName =${customer.firstName}, lastName = ${customer.lastName}"
}
@OptIn(InternalAPI::class)
suspend fun upload(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.submitFormWithBinaryData(
url = "http://localhost:8081/test_upload.php",
formData = formData {
append("image", File("/home/yamamoto/img.png").readBytes(), Headers.build {
append(HttpHeaders.ContentType, "image/png")
append(HttpHeaders.ContentDisposition, "filename=img.png")
})
}
) {
onUpload { bytesSentTotal, contentLength ->
println("Sent $bytesSentTotal bytes from $contentLength")
}
}
text.value = response.receive()
}
suspend fun responseParameter(text: MutableState<String>) {
val client = HttpClient(CIO)
val response: HttpResponse = client.get("http://localhost:8081/test_get.php")
text.value = """
status = ${response.status}
contentType = ${response.contentType()}
charset = ${response.charset()}
""".trimIndent()
println(response.headers)
}