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 引擎中受支持。要在 Ktor 服务器中使用 Unix 套接字,请相应地配置服务器

要向侦听 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")
    }
}

你还可以通过 appendAll() 函数配合 Mapvararg Pair 来方便地添加多个标头:

kotlin
        client.get("https://ktor.io") {
            headers {
                // 使用可变实参对 (vararg Pair)
                appendAll(
                    HttpHeaders.Accept to "text/html",
                    HttpHeaders.Authorization to "abc123"
                )

                // 使用 Map
                appendAll(mapOf("foo" to "bar", "baz" to "qux"))
                appendAll(mapOf("test" to listOf("1", "2", "3")))

                // 使用具有多个值的自定义标头
                appendAll("X-Custom-Header" to listOf("val1", "val2"))
            }
        }

添加单个标头

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

欲了解更多详情,请参阅取消与超时