流式(chunked)概念
-
Chunked流式接口是一种用于在网络传输中分块发送数据的方法。在传统的请求/响应模式中,数据通常以完整的实体(例如,完整的HTML页面或文件)的形式发送。而在Chunked流式接口中,数据被分成较小的块(chunks),每个块都带有自己的长度信息,并逐个发送。
-
使用Chunked流式接口,发送方可以将数据划分为较小的块,然后逐个发送,而不需要等待整个实体的完整性。这样可以提供更高的传输效率和更快的响应时间,尤其在处理大文件或大量数据时。接收方则可以逐块接收数据,并立即处理,而不需要等待完整的数据传输完成。
-
在HTTP协议中,Chunked流式传输通常用于在不知道整个响应内容长度的情况下传输数据,或者在需要逐步处理响应内容的情况下。通过使用Chunked编码,服务器可以动态生成和发送数据块,而无需事先将整个响应内容存储在内存中。
-
使用Chunked流式接口时,HTTP头部中的Transfer-Encoding字段会设置为"chunked",并且每个数据块都会在发送前带有一个表示其长度的十六进制数值。数据块之间使用特定的分隔符进行分隔。
-
总之,Chunked流式接口是一种将数据分块发送的方法,可以提高传输效率和响应速度,特别适用于处理大文件或大量数据的情况。
流式(chunked)实现思路:
流式(chunked)传输是通过在HTTP协议中使用Chunked编码来实现的。下面是流式传输的基本实现过程:
-
服务器生成数据块:服务器根据需要生成数据块,并确定每个数据块的大小。
-
数据块编码:每个数据块都以十六进制表示其大小,并在数据块内容之前添加该大小值。例如,如果一个数据块的大小为10字节,那么编码后的大小值为"A"(十六进制10),然后跟着10个字节的数据。
-
发送数据块:编码后的数据块被发送到客户端。
-
接收数据块:客户端接收到数据块后,解码数据块的大小,并根据大小值读取相应数量的字节数据。
-
处理数据块:客户端可以立即处理接收到的数据块,而不需要等待完整的响应内容。
-
重复步骤3-5:服务器继续生成和发送后续的数据块,直到所有数据块都被发送完毕。
-
结束传输:当所有数据块都发送完毕后,服务器会发送一个空的数据块,用于表示传输结束。这个空的数据块以单个的"0"(十六进制0)表示。
通过这种方式,数据可以以逐块的方式进行传输,而不需要等待完整的响应内容。客户端可以根据需要逐块接收和处理数据,从而实现流式传输的效果。
示例代码演示(服务端选择其一即可)
springboot(服务器端)中如何搭建
import org.springframework.http.ResponseEntity;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/chunked")
@Slf4j
public class TestController {
@GetMapping("/chunked-test")
public ResponseEntity<StreamingResponseBody> getData() {
// 创建一个发送句柄
StreamingResponseBody responseBody = outputStream -> {
// 模拟真实场景下连续发送数据
for (int i = 0; i < 10; i++) {
// 延迟等待:模拟真实场景
try {
Thread.sleep(1000);
}catch (Exception e){}
// 数据块编码
String data = "{\"name\": \"name-"+i+"\"}";
// 写入数据
outputStream.write(data.getBytes());
// 发送数据
outputStream.flush();
}
};
// 响应到客户端
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(responseBody);
}
}
注意: 不需要引入额外的包,正常的springboot项目即可(本文使用的egg版本为:2.6.1)
egg(服务器端)中如何搭建
- EGG代码: controller中的一个方法
import { Controller } from 'egg';
// 创建一个sleep方法,进行延迟等待
const sleep = function (ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
};
export default class TestController extends Controller {
/**
* chunked发送数据
*/
public async chunkedSendData() {
const { ctx } = this;
// 设置HTTP头部信息
ctx.set('Transfer-Encoding', 'chunked');
ctx.set('Content-Type', 'application/octet-stream');
try {
// 一定要设置响应状态
ctx.status = 200;
// 使用egg中的响应作为发送句柄
const rawResponse = ctx.res;
// 模拟真实场景下连续发送数据
for (const item of [1, 2, 3, 4, 5, 6, 7, 8]) {
// 延迟等待:模拟真实场景
await sleep(1000);
// 发送数据块
rawResponse.write(JSON.stringify({ name: 'name-' + item }));
}
// 结束请求
rawResponse.end();
} catch {
ctx.status = 500;
}
}
}
- 路由文件中写法(正常书写就行): router.js
import { Application } from 'egg';
export default (app: Application) => {
const { controller, router } = app;
// chunked发送数据测试:
router.get('/chunked/chunked-test', controller.test.chunkedSendData);
};
注意: 不需要任何的插件,正常的Egg项目即可(本文使用的egg版本为:2.6.1)
使用nginx作为代理
- 将
/api/chunked/chunked-test
请求代理到http://127.0.0.1:7001/chunked/chunked-test
server {
...
location /api/chunked/ {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
// 设置不需要缓存,也就是实时输出
proxy_buffering off;
// 设置http版本,支持实时输出
proxy_http_version 1.1;
// chunked打开(下面两句话作用不大)
chunked_transfer_encoding on;
proxy_set_header Connection "";
proxy_pass http://127.0.0.1:7001/chunked/;
}
}
注1:Nginx自版本1.3.9起开始支持Chunked传输。此版本以及之后的版本都包含对Chunked传输的支持,无需安装模块即可支持
使用场景
- 实时输出场景(GPT)
- 数据实时要求比较高(商场首页)
注意事项
- 微信小程序作为chunked客户端,请查阅:《微信小程序之流式(chunked)响应》