リクエストの準備

Estimated reading time: 4 minutes

リクエストの作成

クライアントの設定が完了したので、次はリクエストの実行の準備をしましょう。 ほとんどの単純なリクエストは、下記のパターンになります。

val response = client.'http-method'<'ResponseType'>("url-string")

Kotlin のジェネリクス型の型推論を用いると、さらにシンプルになります。

val response: ResponseType = client.'http-method'("url-string")

例えば レスポンスすべてを String 型で受け取るために GET リクエストするには下記のように記述します。

val htmlContent = client.get<String>("https://en.wikipedia.org/wiki/Main_Page")
// 下記と同じ
val content: String = client.get("https://en.wikipedia.org/wiki/Main_Page")

生のビット列に興味がある場合は、 ByteArray 型で受け取ります。

val channel: ByteArray = client.get("https://en.wikipedia.org/wiki/Main_Page")

完全な HttpResponse が必要な場合は HttpResponse 型で受け取ります。

val response: HttpResponse = client.get("https://en.wikipedia.org/wiki/Main_Page")

デフォルトでは、 HttpResponse はすべてメモリ上にダウンロードされます。 レスポンスを部分的にダウンロードする方法やストリームデータを扱う方法については、 ストリーミング 章を参照してください。

そして、 Json feature を用いることで、自作のデータクラスに格納できます。

@Serializable
data class User(val id: Int)

val response: User = client.get("https://myapi.com/user?id=1")

いくつかのレスポンス型は Closeable を実装している場合があり、その場合はリソースを保持し続けることに注意してください。

リクエストのカスタマイズ

我々は GET リクエストだけでは生きていけません。 Ktor には、どの HTTP メソッドも利用可能で、複雑なリクエストを作成でき、様々な方法でレスポンスを処理することが可能な柔軟性があります。

デフォルトの HTTP メソッド

request と同様に、一般的な HTTP メソッド (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) でリクエストを実行するための拡張関数があります。

val text = client.post<String>("http://127.0.0.1:8080/")

リクエストメソッドを呼ぶ際に、ラムダ式で URL 、 HTTP メソッド、リクエストボディ、ヘッダなどのリクエストパラメータを指定することができます。

val text = client.post<String>("http://127.0.0.1:8080/") {
    header("Hello", "World")
}

HttpRequestBuilder は下記のようになっています。

class HttpRequestBuilder : HttpMessageBuilder {
    var method: HttpMethod

    val url: URLBuilder
    fun url(block: URLBuilder.(URLBuilder) -> Unit)

    val headers: HeadersBuilder
    fun header(key: String, value: String)
    fun headers(block: HeadersBuilder.() -> Unit)

    var body: Any = EmptyContent

    val executionContext: CompletableDeferred<Unit>
    fun setAttributes(block: Attributes.() -> Unit)
    fun takeFrom(builder: HttpRequestBuilder): HttpRequestBuilder
}

HttpClient クラスはいくつかの基本的な機能のみ提供しており、リクエストを構築するためのメソッドは拡張関数として提供されます。

HttpClient ビルダー拡張関数 に記載があります。

メソッドのカスタマイズ

呼び出しに加えて、型付きのリクエストとして振る舞う request メソッドがあり、 StringHttpResponse 、または任意の型を指定可能です。 リクエストを作成する際に、 URL と HTTP メソッドを指定する必要があります。

val call = client.request<String> {
    url("http://127.0.0.1:8080/")
    method = HttpMethod.Get
}

form の POST (送信)

form の情報を送信するのに便利な拡張関数がいくつか用意されています。 詳細は こちら に記載があります。

submitForm メソッド

client.submitForm(
    formParameters: Parameters = Parameters.Empty,
    encodeInQuery: Boolean = false,
    block: HttpRequestBuilder.() -> Unit = {}
)

クエリ文字列にエンコードされた Parameters でリクエストする (GET ではデフォルト) か、 encodeInQuery パラメータに応じて multipart としてエンコードされた Parameters でリクエストする (POST ではデフォルト) ことができます。

submitFormWithBinaryData メソッド

client.submitFormWithBinaryData(
    formData: List<PartData>,
    block: HttpRequestBuilder.() -> Unit = {}
): T

PartData のリストから、 multipart の POST リクエストをを生成できます。 PartDataPartData.FormItem か、 PartData.BinaryItem または PartData.FileItem です。

PartData のリクエストを生成するために、 formData ビルダを利用することができます。

val data: List<PartData> = formData {
    // append 可能な型 : String, Number, ByteArray, Input
    append("hello", "world")
    append("number", 10)
    append("ba", byteArrayOf(1, 2, 3, 4))
    appendInput("input", size = knownSize.orNull()) { openInputStream().asInput() }
    // ヘッダも指定可能
    append("hello", "world", headersOf("X-My-Header" to "MyValue"))
}

カスタムヘッダの指定

HttpRequestBuilder でリクエストを構築する際に、カスタムヘッダを指定することができます。 StringValuesBuilder を継承した val headers: HeadersBuilder を用います。 You can add or remove headers using it, or with the header convenience methods. headers を用いることでヘッダ情報の追加および削除が可能です。 header という便利メソッドもあります。

// this : HttpMessageBuilder

// ヘッダを追加する便利メソッド
header("My-Custom-Header", "HeaderValue")

// headers: HeadersBuilder のメソッド呼び出しでヘッダを構築する例
headers.clear()
headers.append("My-Custom-Header", "HeaderValue")
headers.appendAll("My-Custom-Header", listOf("HeaderValue1", "HeaderValue2"))
headers.remove("My-Custom-Header")

// `headers` 便利ビルダー関数を用いたヘッダ情報の構築
headers { // this: HeadersBuilder
    clear()
    append("My-Custom-Header", "HeaderValue")
    appendAll("My-Custom-Header", listOf("HeaderValue1", "HeaderValue2"))
    remove("My-Custom-Header")
}

HeaderBuilder API の全容は こちら にあります。

Specifying a body for requests

POSTPUT リクエストでは、 body プロパティの指定か可能です。

client.post<Unit> {
    url("http://127.0.0.1:8080/")
    body = // ...
}

HttpRequestBuilder.body プロパティには OutgoingContent 型 (およびそのサブタイプ型) または String 型のインスタンスを指定することができます。

  • body = "HELLO WORLD!"
  • body = TextContent("HELLO WORLD!", ContentType.Text.Plain)
  • body = ByteArrayContent("HELLO WORLD!".toByteArray(Charsets.UTF_8))
  • body = LocalFileContent(File("build.gradle"))
  • body = JarFileContent(File("myjar.jar"), "test.txt", ContentType.fromFileExtension("txt").first())
  • body = URIFileContent("https://en.wikipedia.org/wiki/Main_Page")

JsonFeature をインストール済で content type に application/json を指定している場合、 body に任意の型のインスタンスを指定することで、自動的に JSON にシリアライズされます。

data class HelloWorld(val hello: String)

val client = HttpClient(Apache) {
    install(JsonFeature) {
        serializer = GsonSerializer {
            // GsonBuilder の設定
            serializeNulls()
            disableHtmlEscaping()
        }
    }
}

client.post<Unit> {
    url("http://127.0.0.1:8080/")
    body = HelloWorld(hello = "world")
}

別法 (組み込みの JsonSerializer を利用)

val json = io.ktor.client.features.json.defaultSerializer()
client.post<Unit>() {
    url("http://127.0.0.1:8080/")
    body = json.write(HelloWorld(hello = "world")) // OutgoingContent が生成される
}

別法 (Jackson を利用 (JVM のみ))

val json = jacksonObjectMapper()
client.post<Unit> {
    url("http://127.0.0.1:8080/")
    body = TextContent(json.writeValueAsString(userData), contentType = ContentType.Application.Json)
}

トップレベルではない クラスを Gson が認識しないことに注意してください。
関数内にあるクラスを送信しようとすると、 null が送信されます。

multipart/form-data のアップロード

Ktor HTTP クライアントは MultiPart リクエストの生成をサポートしています。 リクエストの body にて MultiPartFormDataContent(parts: List<PartData>)OutgoingContent として利用することで実現できます。

最も簡単な使用方法は submitFormWithBinaryData メソッド を参照してください。

あるいは、 body に直接指定することもできます。

val request = client.request {
    method = HttpMethod.Post
    body = MultiPartFormDataContent(formData {
        append("key", "value")
    })
}