Android 和 ktor 的 HTTP 块请求
在这篇非常短的文章中,我将简要解释什么是块或流式 HTTP 请求,使用它有什么好处,以及它在 Android 中的工作原理。
Android 应用程序使用 HTTP 请求从后端下载数据。此信息在应用程序上存储和处理以使其正常运行。
HTTP 请求在 Android 上使用不同的框架执行。最常见的是 Retrofit 或 OkHttp。
简化底层网络操作,在识别托管请求 URL 的计算机的 IP 地址后,HTTP 请求如下所示:
OkHttpClient: --> GET https://api.yourserver.com/sandbox/v1/example/23aa13d2-b161-432d-a67e-c50e8783f7dd http/1.1
OkHttpClient: X-apikey: AATR1oqYAZzp6o6ndALfIk6GG1dOcDED
OkHttpClient: Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRw...
OkHttpClient: Accept-Language: en-US
OkHttpClient: Connection: close
OkHttpClient: Accept: application/json
OkHttpClient: Accept-Charset: UTF-8
OkHttpClient: User-Agent: Ktor client
OkHttpClient: Host: api.yourserver.com
OkHttpClient: Accept-Encoding: gzip
OkHttpClient: --> END GET
HTTP 请求包含(除其他外)以下字段:
- 使用的 HTTP 方法:GET、POST、PUT、PATCH 或 DELETE。实际上有 8 种不同的 HTTP 方法,剩下的是 CONNECT、OPTIONS 和 TRACE。
- 授权标头(比如 API 密钥,或者我们需要将自己标识为 lefit 客户端的 Auth 密钥)。
- 用于编码、语言、字符集、内容类型等的元数据标头。
HTTP 1.1 协议的完整规范可以在RFC 2616中找到,而 HTTP 1.0 规范可以在RFC 1945中找到。
在多个 SDK 为我们提供抽象层并简化这些操作的世界中,可能不需要经常检查 RFC。例如,Android 开发人员可以从使用已经提供所有必需实现的多个框架(Retrofit、OkHttp、Ktor 等)中受益。这并不总是这样:在以前,有必要以一定的频率检查 RFC,因为功能完整的 SDK 并不总是在每个堆栈上可用。
执行标准 HTTP 请求在较高级别工作,如下所示:
后端处理完请求后,立即将请求返回给客户端。这适用于大多数情况,但在某些情况下我们希望进一步优化。
想象一个包含复杂逻辑的端点,最终需要更多时间才能准备好完整数据。或者端点可能依赖于进一步的子查询来准备整个数据,这将需要一些时间才能准备好。在这种情况下,可能值得考虑使用块(或流式)请求。
HTTP Streaming 是一种数据传输技术,它允许后端通过无限期保持打开状态(或直到数据被处理)的单个 HTTP 连接向客户端连续发送数据块。像这样的请求有利于允许客户端立即处理某些数据,而后端处理其余数据。
这可能会提醒读者使用套接字。HTTP 和套接字的工作方式类似,尽管它们之间存在一些差异。
- Websockets 是事件驱动的,而 HTTP 不是。通常,实时通信的最佳选择是套接字,因为它们初始化和维护连接的开销较小。
- 套接字是一种全双工异步消息传递机制。客户端和服务器都可以独立地交换消息。
但是,在某些情况下,使用 HTTP 流式请求可能更方便。从基础设施到重用 HTTP 客户端已经处理的某些模型,问题可能很广泛。
Ktor 支持这种相对开箱即用的方式。以下代码段能够执行来自给定 API 的流式请求:
fun main() {
val client = HttpClient(CIO)
val file = File.createTempFile("files", "index")
runBlocking {
client.prepareGet("https://api.example.com").execute { httpResponse ->
val channel: ByteReadChannel = httpResponse.body()
while (!channel.isClosedForRead) {
val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
while (!packet.isEmpty) {
val bytes = packet.readBytes()
file.appendBytes(bytes)
println("Received ${file.length()} bytes from ${httpResponse.contentLength()}")
}
}
println("A file saved to ${file.path}")
}
}
}
要验证这是否有效,您可以使用类似于以下命令的命令对流式 API 执行 cURL:
curl --location --request GET 'https://api.example.com' --header 'X-apikey: yourAPIkey' --raw
当您执行此操作时,故事会出现一个有趣的转折。您将能够像往常一样看到来自后端的响应,但这次每个块将由一个数字分隔,指定下一个块的大小: