HTTP协议扫盲(八 )响应报文之 Transfer-Encoding=chunked方式

本文介绍了HTTP协议1.1中的分块传输编码(chunked transfer encoding),用于处理动态生成内容时无法预知长度的情况。分块编码允许服务器在发送消息头后逐步发送内容,每个块以十六进制大小标识,最后一块大小为0表示结束。文章还展示了如何构建和解析采用chunked编码的响应报文。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是chunked编码?

分块传输编码(Chunked transfer encoding)是只在HTTP协议1.1版本(HTTP/1.1)中提供的一种数据传送机制。以往HTTP的应答中数据是整个一起发送的,并在应答头里Content-Length字段标识了数据的长度,以便客户端知道应答消息的结束。

传统的Content-length解决方案:计算实体长度,并通过头部告诉对方。浏览器可以通过 Content-Length 的长度信息,判断出响应实体已结束

Content-length面临的问题:由于 Content-Length 字段必须真实反映实体长度,但是对于动态生成的内容来说,在内容创建完之前,长度是不可知的。

这时候要想准确获取长度,只能开一个足够大的 buffer,等内容全部生成好再计算。这样做一方面需要更大的内存开销,另一方面也会让客户端等更久。

 

我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界——分块编码(Transfer-Encoding: chunked)。

对于动态生成的应答内容来说,内容在未生成完成前总长度是不可知的。因此需要先缓存生成的内容,再计算总长度填充到Content-Length,再发送整个数据内容。这样显得不太灵活,而使用分块编码则能得到改观。

分块传输编码允许服务器在最后发送消息头字段。例如在头中添加散列签名。对于压缩传输传输而言,可以一边压缩一边传输。

二、如何使用chunked编码

如果在http的消息头里Transfer-Encoding为chunked,那么就是使用此种编码方式。

接下来会发送数量未知的块,每一个块的开头都有一个十六进制的数,表明这个块的大小,然后接CRLF("\r\n")。然后是数据本身,数据结束后,还会有CRLF("\r\n")两个字符。有一些实现中,块大小的十六进制数和CRLF之间可以有空格。

最后一块的块大小为0,表明数据发送结束。最后一

<think>我们正在处理一个关于使用Kotlin解析HTTP chunked响应的问题。根据系统指令,我们需要使用正确的LaTeX格式,用中文回答,并生成相关问题。同时,回答中引用的段落末尾需要添加引用标识。 首先,我们需要理解Transfer-Encoding: chunkedHTTP响应。分块传输编码(chunked transfer encoding)是HTTP/1.1中定义的一种数据传输机制,用于在未知内容长度时逐块发送数据。每个块由一个十六进制数字表示的长度(以CRLF结尾)和实际数据(后跟CRLF)组成。最后以一个长度为0的块表示结束。 在Kotlin中,我们可以使用标准的Java网络库(如HttpURLConnection)或第三方库(如OkHttp)来处理。这里我们将展示一个使用HttpURLConnection的基本示例,并手动解析分块数据。 步骤: 1. 建立HTTP连接并获取输入流。 2. 读取输入流,直到遇到分块结束标记(即一个长度为0的块)。 3. 对于每个块,先读取块大小(十六进制字符串),然后读取对应长度的数据。 4. 将每个块的数据合并成完整的内容。 注意:由于分块传输编码中每个块后面都有CRLF,我们需要在读取时跳过这些分隔符。 下面是一个简单的代码示例: ```kotlin import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader import java.net.HttpURLConnection import java.net.URL fun main() { val url = URL("http://example.com") // 替换为实际URL val connection = url.openConnection() as HttpURLConnection connection.requestMethod = "GET" // 设置必要的请求头 // connection.setRequestProperty("Accept", "application/json") // 示例 // 检查响应是否使用分块传输编码 val transferEncoding = connection.getHeaderField("Transfer-Encoding") if (transferEncoding != null && transferEncoding.contains("chunked")) { // 获取输入流 val inputStream: InputStream = connection.inputStream val reader = BufferedReader(InputStreamReader(inputStream)) val content = StringBuilder() var chunkSize: Int do { // 读取块大小行(十六进制字符串) val chunkSizeLine = reader.readLine() ?: break chunkSize = chunkSizeLine.toIntOrNull(16) ?: 0 if (chunkSize > 0) { // 读取chunkSize个字符 val buffer = CharArray(chunkSize) reader.read(buffer, 0, chunkSize) content.append(buffer) // 跳过块末尾的CRLF(上面readLine已经读取了块大小后的CRLF,但这里读取的是指定长度的字符,接下来需要读取这个块后面的CRLF) reader.readLine() // 读取块数据后的CRLF } } while (chunkSize > 0) // 读取可选的尾部头域(如果有的话,这里忽略) // 注意:在最后一个块(大小为0)之后,可能会有尾部头域,但我们的循环已经结束。 println("完整内容: $content") } else { // 不是分块传输,按常规方式读取 val content = connection.inputStream.bufferedReader().use { it.readText() } println(content) } connection.disconnect() } ``` 注意:上述代码是一个基础示例,实际应用中可能需要考虑更多边界情况,例如网络错误、超时、大块处理等。此外,我们假设每个块的大小行单独一行,并且块数据后面紧跟着一个CRLF(即readLine()会读取掉这个CRLF)。 另外,我们忽略了尾部头域(Trailer headers),这在某些情况下可能需要处理。 使用第三方库(如OkHttp)可以更简单地处理分块传输,因为库会自动处理分块解码。例如,使用OkHttp: ```kotlin import okhttp3.OkHttpClient import okhttp3.Request fun main() { val client = OkHttpClient() val request = Request.Builder() .url("http://example.com") .build() client.newCall(request).execute().use { response -> val body = response.body?.string() println(body) } } ``` OkHttp会自动处理Transfer-Encoding: chunked,所以我们不需要手动解析。 生成相关问题: 1. 除了分块传输编码,HTTP还有哪些传输编码方式? 2. 在Kotlin中,如何处理HTTP响应的超时问题? 3. 如何用Kotlin实现一个支持分块传输的HTTP服务器? 4. 分块传输编码有什么优缺点? 5. 在解析分块数据时,如何避免内存溢出? 以上问题可以帮助用户深入理解HTTP分块传输和Kotlin网络编程。 注意:在回答中,我们引用了HTTP/1.1的分块传输编码,参考了系统给出的引用[1]的内容,但我们的回答主要基于HTTP协议标准和Kotlin编程知识。</think>### Kotlin 解析 Transfer-Encoding: chunked HTTP 响应报文的方法 在 HTTP/1.1 协议中,当响应报文的 `Transfer-Encoding` 首部字段值为 `chunked` 时,表示报文主体采用分块传输编码格式。这种格式允许服务器在未知内容长度时逐块发送数据[^1]。以下是 Kotlin 中的完整解析方法: #### 1. 分块传输格式解析 分块数据格式为: ``` <16进制块大小>\r\n <块数据>\r\n 0\r\n\r\n // 结束标志 ``` 例如: ```http HTTP/1.1 200 OK Transfer-Encoding: chunked 7\r\n Kotlin \r\n 6\r\n Rocks!\r\n 0\r\n\r\n ``` #### 2. Kotlin 解析实现 ```kotlin import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader fun parseChunkedResponse(inputStream: InputStream): String { val reader = BufferedReader(InputStreamReader(inputStream)) val content = StringBuilder() while (true) { // 1. 读取块大小行 val chunkSizeLine = reader.readLine() ?: break val chunkSize = chunkSizeLine.toIntOrNull(16) ?: 0 // 2. 检查结束标志 if (chunkSize == 0) { // 读取尾部头域(可选) reader.readLine() // 跳过最后的空行 break } // 3. 读取块数据 val buffer = CharArray(chunkSize) reader.read(buffer, 0, chunkSize) content.append(buffer) // 4. 跳过块结束符 \r\n reader.readLine() } return content.toString() } ``` #### 3. 使用 OkHttp 自动处理 推荐使用 OkHttp 库自动处理分块传输: ```kotlin import okhttp3.OkHttpClient import okhttp3.Request fun fetchChunkedData(url: String): String { val client = OkHttpClient() val request = Request.Builder().url(url).build() client.newCall(request).execute().use { response -> if (!response.isSuccessful) throw Exception("请求失败") // 自动处理分块解码 return response.body?.string() ?: "" } } ``` #### 4. 关键注意事项 1. **十六进制转换**:块大小是十六进制字符串,需用 `.toInt(16)` 转换 2. **行结束符**:必须正确处理 `\r\n` 分隔符 3. **尾部头域**:分块结束后可能有额外的头域(RFC 7230 规范) 4. **编码处理**:需根据 `Content-Type` 处理字符编码(如 UTF-8) 5. **大文件优化**:处理大文件时应使用缓冲流避免内存溢出 > 示例解析流程: > ``` > 输入流: "7\r\nKotlin \r\n6\r\nRocks!\r\n0\r\n\r\n" > 步骤1: 读取 "7" → 块大小=7 > 步骤2: 读取7字符 → "Kotlin " > 步骤3: 读取 "6" → 块大小=6 > 步骤4: 读取6字符 → "Rocks!" > 步骤5: 读取 "0" → 结束 > 结果: "Kotlin Rocks!" > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值