发送请求
代码示例: client-configure-request
在配置客户端之后,你就可以开始发送 HTTP 请求了。实现这一点的首要方式是使用接受 URL 作为参数的 .request() 函数。在该函数内部,你可以配置各种请求参数:
- 指定 HTTP 方法,例如
GET、POST、PUT、DELETE、HEAD、OPTIONS或PATCH。 - 将 URL 配置为字符串,或分别配置其组件(例如域名、路径和查询参数)。
- 使用 Unix 域套接字。
- 添加标头和 Cookie。
- 包含请求体——例如,纯文本、数据对象或表单参数。
这些参数由 HttpRequestBuilder 类公开。
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 方法:
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() 函数简化:
val response: HttpResponse = client.get("https://ktor.io/docs/welcome.html")在这两个示例中,请求 URL 均被指定为字符串。你也可以使用 HttpRequestBuilder 分别配置 URL 组件。
指定请求 URL
Ktor 客户端允许你通过多种方式配置请求 URL:
传递整个 URL 字符串
val response: HttpResponse = client.get("https://ktor.io/docs/welcome.html")分别配置 URL 组件
client.get {
url {
protocol = URLProtocol.HTTPS
host = "ktor.io"
path("docs/welcome.html")
}
}在这种情况下,使用了 HttpRequestBuilder 提供的 url 参数。它接受 URLBuilder 的实例,为构建复杂的 URL 提供了更大的灵活性。
要为所有请求配置基础 URL,请使用
DefaultRequest插件。
路径段
在前面的示例中,整个 URL 路径是使用 URLBuilder.path 属性指定的。或者,你可以使用 appendPathSegments() 函数传递单个路径段。
client.get("https://ktor.io") {
url {
appendPathSegments("docs", "welcome.html")
}
}默认情况下,appendPathSegments 会对路径段进行编码。要禁用编码,请改用 appendEncodedPathSegments()。
查询参数
要添加 查询字符串 参数,请使用 URLBuilder.parameters 属性:
client.get("https://ktor.io") {
url {
parameters.append("token", "abc123")
}
}默认情况下,parameters 会对查询参数进行编码。要禁用编码,请改用 encodedParameters()。
即使没有查询参数,也可以使用
trailingQuery属性来保留?字符。
URL 片段
井号 # 在 URL 末尾引入了可选的片段。你可以使用 fragment 属性配置 URL 片段。
client.get("https://ktor.io") {
url {
fragment = "some_anchor"
}
}默认情况下,fragment 会对 URL 片段进行编码。要禁用编码,请改用 encodedFragment()。
指定 Unix 域套接字
Unix 域套接字仅在 CIO 引擎中受支持。要在 Ktor 服务器中使用 Unix 套接字,请相应地配置服务器。
要向侦听 Unix 域套接字的服务器发送请求,请在使用 CIO 客户端时调用 unixSocket() 函数:
val client = HttpClient(CIO)
val response: HttpResponse = client.get("/") {
unixSocket("/tmp/test-unix-socket-ktor.sock")
}你也可以将 Unix 域套接字配置为默认请求的一部分。
设置请求参数
你可以指定各种请求参数,包括 HTTP 方法、标头和 Cookie。如果你需要为特定客户端的所有请求配置默认参数,请使用 DefaultRequest 插件。
标头
你可以通过几种方式向请求添加标头:
添加多个标头
headers 函数允许你一次添加多个标头:
client.get("https://ktor.io") {
headers {
append(HttpHeaders.Accept, "text/html")
append(HttpHeaders.Authorization, "abc123")
append(HttpHeaders.UserAgent, "ktor client")
}
}你还可以通过 appendAll() 函数配合 Map 或 vararg Pair 来方便地添加多个标头:
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 函数允许你追加单个标头。
使用 basicAuth 或 bearerAuth 进行身份验证
basicAuth 和 bearerAuth 函数会添加带有相应 HTTP 方案的 Authorization 标头。
欲了解高级身份验证配置,请参阅 Ktor 客户端中的身份验证与授权。
Cookie
要发送 Cookie,请使用 cookie() 函数:
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() 函数。该函数接受不同类型的有效载荷,包括纯文本、任意类实例、表单数据和字节数组。
文本
发送纯文本作为请求体可以按以下方式实现:
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:
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 的表单参数。以下示例演示了其用法:
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。
上传文件
如果你需要随表单发送文件,可以使用以下方法:
- 使用
.submitFormWithBinaryData()函数。在这种情况下,将自动生成边界 (boundary)。 - 调用
post函数,并将MultiPartFormDataContent实例传递给setBody函数。MultiPartFormDataContent构造函数也允许你传递边界值。
对于这两种方法,你都需要使用 formData {} 函数构建表单数据。
使用 .submitFormWithBinaryData()
.submitFormWithBinaryData() 函数会自动生成边界,适用于文件内容足够小、可以安全地使用 .readBytes() 读取到内存中的简单场景。
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
为了高效地流式传输大型或动态内容,你可以将 MultiPartFormDataContent 与 InputProvider 结合使用。InputProvider 允许你以缓冲流的形式提供文件数据,而不是将其全部加载到内存中,这非常适合大文件。使用 MultiPartFormDataContent,你还可以使用 onUpload 回调监控上传进度。
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 结合使用:
InputProvider { SystemFileSystem.source(Path("ktor_logo.png")).buffered() }你也可以手动构造带有自定义边界和内容类型的 MultiPartFormDataContent:
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() 函数为文件打开读取通道:
val response = client.post("http://0.0.0.0:8080/upload") {
setBody(File("ktor_logo.png").readChannel())
}有关完整示例,请参阅 client-upload-binary-data。
并行请求
默认情况下,当你按顺序发送多个请求时,客户端会挂起每次调用,直到前一次调用完成。要并发执行多个请求,请使用 launch() 或 async() 函数。以下示例演示了如何使用 async() 并行执行两个请求:
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,可用于取消正在运行的协程:
import kotlinx.coroutines.*
val client = HttpClient(CIO)
val job = launch {
val requestContent: String = client.get("http://localhost:8080")
}
job.cancel()欲了解更多详情,请参阅取消与超时。
