Skip to content

发送请求

配置客户端后,你可以开始发送 HTTP 请求。主要方法是使用接受 URL 作为形参的 .request()函数。在该函数内部,你可以配置各种请求形参:

  • 指定 HTTP 方法,例如 GETPOSTPUTDELETEHEADOPTIONSPATCH
  • 将 URL 配置为字符串,或者单独配置其组成部分(例如域、路径和查询形参)。
  • 使用 Unix 域套接字。
  • 添加请求头和 cookie。
  • 包含请求体——例如,纯文本、数据对象或表单形参。

这些形参由 HttpRequestBuilder类暴露。

kotlin
import io.ktor.client.request.*
import io.ktor.client.statement.*

val response: HttpResponse = client.request("https://ktor.io/") {
  // 配置 HttpRequestBuilder 暴露的请求形参
}

.request() 函数返回一个 HttpResponse 对象作为响应。HttpResponse 暴露了以各种格式(例如字符串、JSON 对象等)获取响应体的 API,以及检索响应形参(例如状态码、内容类型和请求头)的 API。关于更多信息,请参见接收响应

.request() 是一个挂起函数,这意味着它必须在协程或另一个挂起函数内部调用。关于挂起函数的更多信息,请参见 协程基础

指定 HTTP 方法

当调用 .request() 函数时,你可以使用 method 属性指定所需的 HTTP 方法:

kotlin
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*

val response: HttpResponse = client.request("https://ktor.io/") {
    method = HttpMethod.Get
}

除了 .request() 之外,HttpClient 还为基本的 HTTP 方法提供了特定的函数,例如 .get().post().put()。 上面的示例可以使用 .get() 函数进行简化:

kotlin
val response: HttpResponse = client.get("https://ktor.io/docs/welcome.html")

在这两个示例中,请求 URL 都被指定为一个字符串。你还可以使用 HttpRequestBuilder 单独配置 URL 的组成部分。

指定请求 URL

Ktor 客户端允许你通过多种方式配置请求 URL:

传递整个 URL 字符串

kotlin
val response: HttpResponse = client.get("https://ktor.io/docs/welcome.html")

单独配置 URL 组成部分

kotlin
client.get {
    url {
        protocol = URLProtocol.HTTPS
        host = "ktor.io"
        path("docs/welcome.html")
    }
}

在这种情况下,使用了 HttpRequestBuilder 提供的 url 形参。它接受 URLBuilder的实例,为构建复杂的 URL 提供了更大的灵活性。

若要为所有请求配置基本 URL,请使用 DefaultRequest 插件。

路径段

在前面的示例中,整个 URL 路径是使用 URLBuilder.path 属性指定的。 或者,你可以使用 appendPathSegments() 函数传递各个路径段。

kotlin
client.get("https://ktor.io") {
    url {
        appendPathSegments("docs", "welcome.html")
    }
}

默认情况下,appendPathSegments编码路径段。 要禁用编码,请改用 appendEncodedPathSegments()

查询形参

要添加 查询字符串形参,请使用 URLBuilder.parameters 属性:

kotlin
client.get("https://ktor.io") {
    url {
        parameters.append("token", "abc123")
    }
}

默认情况下,parameters编码查询形参。 要禁用编码,请改用 encodedParameters()

即使没有查询形参,trailingQuery 属性也可以用来保留 ? 字符。

URL 片段

井号 # 用于引入 URL 末尾的可选片段。 你可以使用 fragment 属性配置 URL 片段。

kotlin
client.get("https://ktor.io") {
    url {
        fragment = "some_anchor"
    }
}

默认情况下,fragment编码 URL 片段。 要禁用编码,请改用 encodedFragment()

指定 Unix 域套接字

Unix 域套接字仅在 CIO 引擎中受支持。 要将 Unix 套接字与 Ktor 服务器一起使用,请相应地配置服务器

要向监听 Unix 域套接字的服务器发送请求,在使用 CIO 客户端时调用 unixSocket() 函数:

kotlin
val client = HttpClient(CIO)

val response: HttpResponse = client.get("/") {
    unixSocket("/tmp/test-unix-socket-ktor.sock")
}

你还可以将 Unix 域套接字配置为默认请求的一部分。

设置请求形参

你可以指定各种请求形参,包括 HTTP 方法、请求头和 cookie。如果你需要为特定客户端的所有请求配置默认形参,请使用 DefaultRequest 插件。

请求头

你可以通过几种方式向请求添加请求头:

添加多个请求头

headers 函数允许你一次性添加多个请求头:

kotlin
client.get("https://ktor.io") {
    headers {
        append(HttpHeaders.Accept, "text/html")
        append(HttpHeaders.Authorization, "abc123")
        append(HttpHeaders.UserAgent, "ktor client")
    }
}

添加单个请求头

header 函数允许你追加单个请求头。

使用 basicAuthbearerAuth 进行授权

basicAuthbearerAuth 函数会添加带有相应 HTTP 方案的 Authorization 请求头。

关于高级身份验证配置,请参见Ktor 客户端中的身份验证和授权

Cookie

要发送 cookie,请使用 cookie() 函数:

kotlin
client.get("https://ktor.io") {
    cookie(name = "user_name", value = "jetbrains", expires = GMTDate(
        seconds = 0,
        minutes = 0,
        hours = 10,
        dayOfMonth = 1,
        month = Month.APRIL,
        year = 2023
    ))
}

Ktor 还提供了 HttpCookies 插件,它允许你在多次调用之间保留 cookie。如果安装了此插件,则使用 cookie() 函数添加的 cookie 将被忽略。

设置请求体

要设置请求体,请调用 HttpRequestBuilder提供的 setBody() 函数。 此函数接受不同类型的载荷,包括纯文本、任意类实例、表单数据和字节数组。

文本

可以通过以下方式实现以纯文本形式发送请求体:

kotlin
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*

val response: HttpResponse = client.post("http://localhost:8080/post") {
    setBody("Body content")
}

对象

启用 ContentNegotiation 插件后,你可以将类实例作为 JSON 发送到请求体中。为此,将类实例传递给 setBody() 函数,并使用 contentType() 函数将内容类型设置为 application/json

kotlin
val response: HttpResponse = client.post("http://localhost:8080/customer") {
    contentType(ContentType.Application.Json)
    setBody(Customer(3, "Jet", "Brains"))
}

关于更多信息,请参见Ktor 客户端中的内容协商和序列化

表单形参

Ktor 客户端提供了 submitForm() 函数,用于发送 application/x-www-form-urlencoded 类型的表单形参。以下示例演示了其用法:

kotlin
val client = HttpClient(CIO)
val response: HttpResponse = client.submitForm(
    url = "http://localhost:8080/signup",
    formParameters = parameters {
        append("username", "JetBrains")
        append("email", "[email protected]")
        append("password", "foobar")
        append("confirmation", "foobar")
    }
)
  • url 指定了发送请求的 URL。
  • formParameters 是使用 parameters 构建的一组表单形参。

关于完整示例,请参见 client-submit-form

要发送编码在 URL 中的表单形参,请将 encodeInQuery 设置为 true

上传文件

如果你需要随表单发送文件,可以使用以下方法:

对于这两种方法,你都需要使用 formData {} 函数来构建表单数据。

使用 .submitFormWithBinaryData()

.submitFormWithBinaryData() 函数会自动生成一个边界,适用于文件内容足够小,可以安全地使用 .readBytes() 读取到内存中的简单用例。

kotlin
        val client = HttpClient(CIO)

        val response: HttpResponse = client.submitFormWithBinaryData(
            url = "http://localhost:8080/upload",
            formData = formData {
                append("description", "Ktor logo")
                append("image", File("ktor_logo.png").readBytes(), Headers.build {
                    append(HttpHeaders.ContentType, "image/png")
                    append(HttpHeaders.ContentDisposition, "filename=\"ktor_logo.png\"")
                })
            }
        )

关于完整示例,请参见 client-upload

使用 MultiPartFormDataContent

为了高效地流式传输大型或动态内容,你可以将 MultiPartFormDataContentInputProvider 一起使用。 InputProvider 允许你将文件数据作为缓冲流提供,而不是完全加载到内存中,这使其非常适合处理大文件。使用 MultiPartFormDataContent,你还可以使用 onUpload 回调来监控上传进度。

kotlin
        val client = HttpClient(CIO)

        val file = File("ktor_logo.png")

        val response: HttpResponse = client.post("http://localhost:8080/upload") {
            setBody(
                MultiPartFormDataContent(
                    formData {
                        append("description", "Ktor logo")
                        append(
                            "image",
                            InputProvider { file.inputStream().asInput().buffered() },
                            Headers.build {
                                append(HttpHeaders.ContentType, "image/png")
                                append(HttpHeaders.ContentDisposition, "filename=\"ktor_logo.png\"")
                            }
                        )
                    },
                    boundary = "WebAppBoundary"
                )
            )
            onUpload { bytesSentTotal, contentLength ->
                println("Sent $bytesSentTotal bytes from $contentLength")
            }
        }

在多平台项目中,你可以将 SystemFileSystem.source()InputProvider 一起使用:

kotlin
InputProvider { SystemFileSystem.source(Path("ktor_logo.png")).buffered() }

你还可以手动构造具有自定义边界和内容类型的 MultiPartFormDataContent

kotlin
fun customMultiPartMixedDataContent(parts: List<PartData>): MultiPartFormDataContent {
    val boundary = "WebAppBoundary"
    val contentType = ContentType.MultiPart.Mixed.withParameter("boundary", boundary)
    return MultiPartFormDataContent(parts, boundary, contentType)
}

关于完整示例,请参见 client-upload-progress

二进制数据

要发送 application/octet-stream 内容类型的二进制数据,请将 ByteReadChannel实例传递给 setBody() 函数。 例如,你可以使用 File.readChannel() 函数为文件打开一个读取通道:

kotlin
val response = client.post("http://0.0.0.0:8080/upload") {
    setBody(File("ktor_logo.png").readChannel())
}

关于完整示例,请参见 client-upload-binary-data

并行请求

默认情况下,当你按顺序发送多个请求时,客户端会挂起每个调用,直到上一个调用完成。要并发执行多个请求,请使用 launch()async() 函数。以下示例演示了如何使用 async() 并行执行两个请求:

kotlin
coroutineScope {
    // 并行请求
    val firstRequest: Deferred<String> = async { client.get("http://localhost:8080/path1").bodyAsText() }
    val secondRequest: Deferred<String> = async { client.get("http://localhost:8080/path2").bodyAsText() }
    val firstRequestContent = firstRequest.await()
    val secondRequestContent = secondRequest.await()
}

关于完整示例,请参见 client-parallel-requests

取消请求

要取消请求,请取消运行该请求的协程。 launch() 函数返回一个 Job,可用于取消正在运行的协程:

kotlin
import kotlinx.coroutines.*

val client = HttpClient(CIO)
val job = launch {
    val requestContent: String = client.get("http://localhost:8080")
}
job.cancel()

关于更多详细信息,请参见取消与超时