Ktorを使ったHTTPリクエスト・サーバー通信

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)
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください