发送请求
代码示例: 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 引擎中受支持。 要将 Unix 套接字与 Ktor 服务器一起使用,请相应地配置服务器。
要向监听 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")
}
}
添加单个请求头
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()
函数。在这种情况下,将自动生成一个边界。 - 调用
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()
关于更多详细信息,请参见取消与超时。