Redian新闻
>
服务器推送事件:一种从服务器流式推送事件的简易方法 | Linux 中国

服务器推送事件:一种从服务器流式推送事件的简易方法 | Linux 中国

科技
 
导读:如果你只需要让服务器发送事件,相较于 Websockets,它们或许是一个更简便的选择。       
本文字数:4639,阅读时长大约:6分钟

哈喽!昨天我见识到了一种我以前从没见过的从服务器推送事件的炫酷方法:服务器推送事件🔗 html.spec.whatwg.org(server-sent events)!如果你只需要让服务器发送事件,相较于 Websockets,它们或许是一个更简便的选择。

我会聊一聊它们的用途、运作原理,以及我昨日在试着运行它们的过程中遇到的几个错误。

问题:从服务器流式推送更新

现在,我有一个启动虚拟机的 Web 服务,客户端轮询服务器,直到虚拟机启动。但我并不想使用轮询方式。

相反,我想让服务器流式推送更新。我跟 Kamal 说我要用 Websockets 来实现它,而他建议使用服务器推送事件不失为一个更简便的选择!

我登时就愣住了——那什么玩意???听起来像是些我从来没见过的稀罕玩意儿。于是乎我就查了查。

服务器推送事件就是个 HTTP 请求协议

下文便是服务器推送事件的运作流程。我-很-高-兴-地了解到它们就是个 HTTP 请求协议。

1.客户端提出一个 GET 请求(举个例子)https://yoursite.com/events 2.客户端设置 Connection: keep-alive,这样我们就能有一个长连接 3.服务器设置设置一个 Content-Type: text/event-stream 响应头 4.服务器开始推送事件,就比如下文这样:

  1. event: status
  2. data: one

举个例子,这里是当我借助 curl 发送请求时,一些服务器推送事件的样子:

  1. $ curl -N 'http://localhost:3000/sessions/15/stream'
  2. event: panda
  3. data: one
  4. event: panda
  5. data: two
  6. event: panda
  7. data: three
  8. event: elephant
  9. data: four

服务器可以根据时间推移缓慢推送事件,并且客户端也能够在它们到来时读取它们。你也可以将 JSON 或任何你想要的东西放在事件当中,就比如 data: {'name': 'ahmed'}

线路协议真的很简单(只需要设置 event: 和 data:,或者如果你愿意,可设置为 id: 和 retry:),所以你并不需要任何花里胡哨的服务器库来实现服务器推送事件。

JavaScript 的代码也超级简单(仅使用 EventSource)

以下是用于流式服务器推送事件的浏览器 JavaScript 的代码。(我从 服务器推送事件的 MND 页面🔗 developer.mozilla.org 得到的这个范例)

你可以订阅所有事件,也可以为不同类型的事件使用不同的处理程序。这里我有一个只接受类型为 panda 的事件的处理程序(就像我们的服务器在上一节中推送的那样)。

  1. const evtSource = new EventSource("/sessions/15/stream", { withCredentials: true })
  2. evtSource.addEventListener("panda", function(event) {
  3. console.log("status", event)
  4. });

客户端在中途不能推送更新

不同于 Websockets,服务器推送事件不允许大量的来回事件通讯。(这体现在它的字眼中 —— 服务器 推送所有事件)。初始的时候客户端发出一个请求,然后服务器发出一连串响应。

如果 HTTP 连接结束,它会自动重连

使用 EventSource 发出的 HTTP 请求和常规 HTTP 请求有一个很大的区别,MDN 文档中对此有所说明:

默认情况下,如果客户端和服务器之间的连接断开,则连接会重启。请使用 .close() 方法来终止连接。

很奇怪,一开始我真的被它吓到了:我打开了一个连接,然后在服务器端将其关闭,然后两秒过后客户端向我的传送终端发送了另一条请求!

我觉得这里可能是因为连接在完成之前意外断开了,所以客户端自动重新打开了它以防止类似情况再发生。

所以如果你不想让客户端继续重试,你就得通过调用 .close() 直截了当地关闭连接。

这里还有些其它特性

你还能在服务器推送事件中设置 id: 和 retry: 字段。似乎,如果你在服务器推送事件上设置,那么当重新连接时,客户端将发送一个 Last-Event-ID 响应头,带有它收到的最后一个 ID。酷!

我发现 W3C 的服务器推送事件页面🔗 html.spec.whatwg.org 令人惊讶地容易理解。

在设置服务器推送事件的时候我遇到了两个错误

我在 Rails 中使用服务器推送事件时遇到了几个问题,我认为这些问题挺有趣的。其中一个缘于 Nginx,另一个是由 Rails 引起的。

问题一:我不能在事件推送的过程中暂停

这个奇怪的错误是在我做以下操作时出现的:

  1. def handler
  2. # SSE is Rails' built in server-sent events thing
  3. sse = SSE.new(response.stream, event: "status")
  4. sse.write('event')
  5. sleep 1
  6. sse.write('another event')
  7. end

它会写入第一个事件,但不能写入第二个事件。我对此-非-常-困-惑,然后放开脑洞,试着理解 Ruby 中的 sleep 是如何运作的。但是 Cass 将我引领到一个与我有着相同困惑的 Stack Overflow 问答帖🔗 stackoverflow.com,而这里包含了让我为之震惊的回答!

事实证明,问题出在我的 Rails 服务器位于 Nginx 之后,似乎 Nginx 默认使用 HTTP/1.0 向上游服务器发起请求(为啥?都 2021 年了,还这么干?我相信这其中一定有合乎情理的解释,也许是为了向下兼容之类的)。

所以客户端(Nginx)会在服务器推送第一个事件之后直接关闭连接。我觉得如果在我推送第二个事件的过程中 没有 暂停,它继续正常工作,基本上就是服务器在连接关闭之前和客户端在争速度,争着推送第二部分响应,如果我这边推送速度足够快,那么服务器就会赢得比赛。

我不确定为什么使用 HTTP/1.0 会使客户端的连接关闭(可能是因为服务器在每个事件结尾写入了两个换行符?),但因为服务器推送事件是一个比较新的玩意儿,HTTP/1.0 (这种老旧协议)不支持它一点都会不意外。

设置 proxy_http_version 1.1 从而解决那个麻烦。好欸!

问题二:事件被缓冲

这个事情解决完,第二个麻烦接踵而至。不过这个问题实际上非常好解决,因为 Cass 已经建议将 stackoverflow 里另一篇帖的回答🔗 stackoverflow.com 作为前一个问题的解决方案,虽然它并没有是导致问题一出现的源头,但它-确-实-解-释-了问题二。

问题在这个示例代码中:

  1. def handler
  2. response.headers['Content-Type'] = 'text/event-stream'
  3. # Turn off buffering in nginx
  4. response.headers['X-Accel-Buffering'] = 'no'
  5. sse = SSE.new(response.stream, event: "status")
  6. 10.times do
  7. sse.write('event')
  8. sleep 1
  9. end
  10. end

我本来期望它每秒返回 1 个事件,持续 10 秒,但实际上它等了 10 秒才把 10 个事件一起返回。这不是我们想要的流式传输方式!

原来这是因为 Rack ETag 中间件想要计算 ETag(响应的哈希值),为此它需要整个响应为它服务。因此,我需要禁用 ETag 生成。

Stack Overflow 的回答建议完全禁用 Rack ETag 中间件,但我不想这样做,于是我去看了 链接至 GitHub 上的议题🔗 github.com

那个 GitHub 议题建议我可以针对仅流式传输终端应用一个解决方法,即 Last-Modified 响应头,显然,这么做可以绕过 ETag 中间件。

所以我设置为:

  1. headers['Last-Modified'] = Time.now.httpdate

然后它起作用了!!!

我还通过设置响应头 X-Accel-Buffering: no 关闭了位于 Nginx 中的缓冲区。我并没有百分百确定我要那样做,但这么做似乎更安全。

Stack Overflow 很棒

起初,我全身心致力于从头开始调试这两个错误。Cass 为我指向了那两个 Stack Overflow 帖子,一开始我对那些帖下提出的解决方案持怀疑态度(我想:“我没有使用 HTTP/1.0 啊!ETag 响应头什么玩意,跟这一切有关系吗??”)。

但结果证明,我确实无意中使用  HTTP/1.0,并且 Rack ETag 中间件确实给我带来了问题。

因此,也许这个故事告诉我,有时候计算机就是会以奇怪的方式相互作用,其它人在过去也遇到过计算机以完全相同的奇怪方式相互作用的问题,而 Stack Overflow 有时会提供关于为什么会发生这些情况的答案 : )

我认为重要的是不要随意从 Stack Overflow 中尝试各种解决方案(当然,在这种情况下不会有人建议这样做!)。对于这两个问题,我确实需要去仔细思考,了解发生了什么,还有为什么更改这些设置会起作用。

就是这样!

今天我要继续着手实现服务器推送事件,因为昨天一整天我都沉浸在上述这些错误里。好在我学到了一个以前从未听说过的易学易用的网络技术,心里还是很高兴的。

(题图:MJ/4c08a193-086e-4efe-a662-00401c928c41)


via: https://jvns.ca/blog/2021/01/12/day-36--server-sent-events-are-cool--and-a-fun-bug/

作者:Julia Evans 选题:lujun9972 译者:Drwhooooo 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

LCTT 译者 :Drwhooooo
🌟🌟
翻译: 2.0 篇
|
贡献: 118 天
2023-01-10
2023-05-07
https://linux.cn/lctt/Drwhooooo
欢迎遵照 CC-BY-SA 协议规定转载,
如需转载,请在文章下留言 “转载:公众号名称”,
我们将为您添加白名单,授权“转载文章时可以修改”。


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
如何设置自己的保护隐私的 DNS 服务器 | Linux 中国大家最喜欢的 Linux 终端字体 | Linux 中国Arch Linux 的最佳 GUI 包管理器 | Linux 中国使用 ChatGPT AI 从英文文本生成 Linux 命令 | Linux 中国IEEE在联合国气候变化大会第27次缔约方会议(COP27)讨论了六个气候变化的简易解决方案【春游欧洲】(2)没见过三宝,岂敢称自己去过荷兰最佳开源电子邮件服务器 | Linux 中国如何在 Ubuntu 和其他 Linux 下安装 IDLE Python IDE | Linux 中国Kali Linux 推出用于防御性安全加固的 “Kali Purple” | Linux 中国如何在 Linux 中合并 PDF 文件 | Linux 中国优化 Linux 服务器的 9 个小技巧,让 Linux 服务器效率起来艾客素杜斯Linux 上 5 款最好的 EPUB 阅读器 | Linux 中国如何在 Linux 系统中使用 Ventoy 创建多重引导的 U 盘 | Linux 中国世界上只有两个 Linux 发行版:Arch Linux 与其它 | Linux 中国blendOS 的目标:取代所有的 Linux 发行版 | Linux 中国如何在 RHEL 8 上安装 FreeIPA 服务器 | Linux 中国如何在 Linux 中使用旧相机作为网络摄像头 | Linux 中国7 个超轻量级 Linux 发行版 | Linux 中国如何逐步安装 Kubernetes(k8s)指标服务器 | Linux 中国猫咪日常 2023-03-11risiOS:一个易于使用的基于 Fedora 的 Linux 发行版 | Linux 中国TUXEDO Stellaris 16(Gen5)是目前所能找到的终极 Linux 笔记本电脑 | Linux 中国如何在 RHEL 9 上配置 DHCP 服务器 | Linux 中国为什么程序员喜欢为 Linux 打包 | Linux 中国Agustín Hernández:中美洲建筑背景下的未来主义巨构《白内障手术》究竟能卖多少钱终端基础:在 Linux 中创建文件 | Linux 中国我們夜裏的靈魂在 Linux 上用 Kdenlive 编辑视频 | Linux 中国Reminders:一个漂亮的开源 Linux 应用,可帮助你完成工作 | Linux 中国Linux 实现简易的 Shell 命令行解释器如何在 Rocky Linux 9 / AlmaLinux 9 上安装 KVM | Linux 中国被挂上热搜的“体重48公斤”事件:一种“新型潮流”正在蔓延终端基础:在 Linux 中删除文件和文件夹 | Linux 中国
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。