在这个视频中,我将演示如何在 OpenResty 中进行流式 HTTP 响应体输出。

1
2
3
cd ~/
mkdir stream-resp/
cd stream-resp/

我们一如既往地创建子目录结构。

1
mkdir logs conf html

我们快速写出模板配置。

1
vim conf/nginx.conf

并在此文件中进行以下编辑。

  1. 我们创建一个 HTTP 服务器,监听 8080 端口。
  2. 增加一个 /test 位置。
  3. 指定这个 application/octet-stream MIME 类型是很重要的,这样才能让 Chrome 网络浏览器满意。
  4. 通过 content_by_lua_block 指令添加一些 Lua 代码。
  5. 我们每隔一秒输出一行输出。
  6. 我们需要显式调用 ngx.flush 方法来刷新 Nginx 的写缓冲区。这是一个 100% 非阻塞的调用。
  7. 而我们使用 ngx.sleep 在每次循环迭代中睡一秒钟。这也是非阻塞的。
  8. 然后我们为 html 目录创建一个根位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
worker_processes 1;

events {
worker_connections 1024;
}

http {
server {
listen 8080 reuseport;

location = /test {
default_type application/octet-stream;

content_by_lua_block {
for i = 1, 4 do
ngx.say("hello ", i)
ngx.flush(true)
ngx.sleep(1) -- sec
end
}
}

location / {
default_type text/html;
root html;
}
}
}

我们现在就来检查一下整个目录树。

1
tree .

截图 16

看起来不错。

现在启动这个 OpenResty 应用程序。

1
openresty -p $PWD/

截图 18

是时候用 curl 来查询我们的 HTTP 位置了。

1
curl 'http://127.0.0.1:8080/test'

截图 19

酷,确实是每秒生成一行字!

为了验证一切是否真的是非阻塞的,我们可以用 weighttp 工具加载这个 HTTP API。请注意,这将需要一段时间,因为我们在这里故意放慢响应速度。

1
weighttp -c 500 -k -n 500 127.0.0.1:8080/test

截图 21

因此,在 500 个并发请求的情况下,我们仍然可以实现每秒 120 个以上的请求!需要注意的是,每个请求需要 4 秒才能完成。而这里我们只使用了一个工作进程和一个操作系统线程。

我们仍然可以将并发量提高很多,但是我们需要对 Nginx 配置进行相应的调整。

1
vim conf/nginx.conf

比如把 worker_connections 调到一个较大的数值。

截图 24

通过启用访问日志缓冲也可以获得更好的性能。我们还可以减少请求内存池的大小。

现在让我们创建一个 HTML 页面,在 Web 浏览器中进行测试。

1
vim html/a.html

我们在这个文件中进行以下编辑。

  1. 增加一个 DIV 标签,以保持输出。
  2. 添加一些 JavaScript 来发送 AJAX 请求到我们之前的 HTTP 位置。
  3. 我们取出 DIV 元素。
  4. 让我们添加一个 JavaScript 函数来进行流式响应接收。
  5. 确保有新的数据。
  6. 将我们的新数据追加到 DIV 元素中。在这里,我们是懒惰的。我们不需要对特殊的 HTML 字符进行转义。
  7. 而且我们每秒都会检查新的传入响应数据。
  8. 火狐支持更巧妙的方式,但我们也要支持 Chrome。
  9. 最后,我们处理响应体流的结束。
  10. 最后一次检查响应数据。
  11. 删除我们的定期计时器。
  12. 并将最终的输出结果追加到网页上。
  13. 为了简洁起见,我们在此省略错误处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!doctype html>
<html>
<body>
<div style="margin: 2em;" id="out"></div>

<script>
(function () {
let xhttp = new XMLHttpRequest();
xhttp.open("GET", "/test", true);
xhttp.send();

let div = document.getElementById("out")

let total_len = 0
let check_resp = function () {
let resp = xhttp.responseText
let len = resp.length

if (len > total_len) {
let new_data = resp.substring(total_len, len)
total_len = len;

div.innerHTML += new_data + "<br/>"
}
};

let timer = setInterval(check_resp, 1000);
check_resp();

xhttp.onreadystatechange = function () {
if (this.readyState == 4) {
check_resp();
clearInterval(timer);
div.innerHTML += "done<br/>";
}
};
})();
</script>
</body>
</html>

我们再来检查一下整个目录树。

1
tree .

截图 44

我们不需要重新加载或重新启动 OpenResty 服务器,因为它只是一个静态的 HTML 页面。

1
ps aux|grep nginx|grep -v /tmp/

截图 45

是时候用 Chrome 打开这个 HTML 页面了。

截图 46

生效了!这就是我今天要讲的全部内容。 如果你喜欢这个教程,请订阅这个博客网站和我们的 YouTube 频道B 站频道。谢谢!

关于本文和关联视频

本文和相关联的视频都是完全由我们的 OpenResty Demo 系统从一个极简单的剧本文件自动生成的。

关于作者

章亦春是开源项目 OpenResty® 的创始人,同时也是 OpenResty Inc. 公司的创始人和 CEO。他贡献了许多 Nginx 的第三方模块,相当多 Nginx 和 LuaJIT 核心补丁,并且设计了 OpenResty XRay 等产品。

关注我们

如果您喜欢本文,欢迎关注我们 OpenResty Inc. 公司的博客网站 。也欢迎扫码关注我们的微信公众号:

我们的微信公众号

翻译

我们提供了英文版原文和中译版(本文) 。我们也欢迎读者提供其他语言的翻译版本,只要是全文翻译不带省略,我们都将会考虑采用,非常感谢!