NestJS 编写 SSE 接口推送数据

做项目的时候遇到了顺便就记一下相关的内容。

SSE

Server-Sent Events(SSE)技术,它是一种用于实现服务器向客户端实时推送数据的Web技术。SSE基于HTTP协议,允许服务器将数据以事件流(Event Stream)的形式发送给客户端。客户端通过建立持久的HTTP连接,并监听事件流,可以实时接收服务器推送的数据。

SSE 和 Websocket

SSE 常常被用在与 Websocket 比较,但是他们的底层和使用有很大的区别,具体如下:

  • SSE是服务器向客户端的单向通信,也就是连接完成后,只有后端可以向前端推送数据,前端被动的接收数据。而WebSocket是双向通信,后端和前端之间可以进行实时的双向数据交换
  • SSE 使用基于HTTP 协议的长连接,通过的 HTTP 请求和响应来建立连接;而WebSocket 使用基于 TCP 的协议,通过建立 WebSocket 连接来实现双向通信。
  • SSE 长用在实时场景中,比如事实更新价格和数据,实时获取服务器运行状态、日志等信息等。而 WebSocket 多用在双向通信的场景,比如多人聊天、协作编辑等

SSE 的原理

SSE在服务器和客户端之间打开一个单向通道,服务器响应的不是一次性的数据包,而是 text/event-stream 类型的数据流信息,在有数据变更时从服务器流式传输到客户端。
在这里插入图片描述

SSE 的后端代码(Nest.js)

因为项目是 nest.js 的所以这里就用 nest.js 来写一个例子,框架搭建过程就省略了,用的是基础的脚手架,以下的 SSE 的核心代码:

  • 首先 nestjs 已经给我们封装好了 Sse 请求,我们只需要使用@Sse 装饰器填写对应的路由就可以使用它了
import { Controller, Get, Header, Sse } from '@nestjs/common';
import { AppService } from './app.service';
import { Observable, interval, map } from 'rxjs';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}
  @Sse('/sse')
  sse(): Observable<any> {
    return interval(1000).pipe(map((_) => ({ data:'hello world' } )));
  }
}
  • 当然你这样写也是可以的,因为SSE 请求本质也是 HTTP GET 请求,只不过 Content-Type 变成了 text/event-stream。
  @Get('/sse')
  @Sse()
  @Header('Content-type', 'text/event-stream')
  sse(): Observable<any> {
    return interval(1000).pipe(map((_) => ({ data: 'hello world2' })));
  }
  • 这里我们发送的数据用到了 RxJS 库的可观察的对象(Observable) ,它采用了观察者模式设计,主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己。可观察对象是惰性的,只有被订阅后才会执行。其中 Interval 函数的作用是每隔一段时间发出一个数值,你可以可以使用 RxJS 库的其他函数来实现内容的推送,只需要返回的内容是一个 Observable 对象即可实现 SSE 接口功能。
  • 关于这部分的详细内容可以自行查阅 RxJS 库
// 间隔一秒发送数据
return interval(1000).pipe(map((_) => ({ data: 'hello world' })));
// 订阅后推送
const ob$ = new Observable((subscriber) => {
  subscriber.next('hello')
  subscriber.next('world')
  subscriber.complete()
})
return ob$

SSE 的前端代码(React.js)

前端采用的是 React 来编写,我们需要通过 EventSource 函数来实现,它的作用是打通与一个 SSE 接口的连接,之后就可以一直接收其数据了:

const eventSource = new EventSource("http://localhost:3000/sse");
function Test() {
  const [cnt, useCnt] = useState("");
  eventSource.onmessage = ({ data }) => {
    useCnt(cnt + data);
  };
  return (
    <div>
        {cnt}
    </div>
  );
}

其中 message 事件是收到信息时触发的,可以通过以下两种方式来触发

eventSource.addEventListener("message", (event) => {});
eventSource.onmessage = (event) => {};

其中 open 事件是连接建立时触发的

eventSource.addEventListener("open", (event) => {});
eventSource.onopen = (event) => {};

其中 error 事件是发生异常时触发的

eventSource.addEventListener("error", (event) => {});
eventSource.onerror = (event) => {};

当我们离开页面不再需要订阅这个接口时,我们可以通过 close 方法来关闭这个连接

eventSource.close();

一般情况下我们在场景中需要做的是在进入页面时连接 SSE,退出页面时关闭连接,所以我们使用 useEffect 来解决这个问题:

let eventSource: EventSource | null = null;
useEffect(() => {
  eventSource = new EventSource("http://localhost:3000/sse");
  eventSource.onmessage = ({ data }) => {
    console.log(data)
  };
  return (() => {
    eventSource && eventSource.close();
  })
},[])

SSE 存在的问题

  • 由于SSE依赖于HTTP长连接,如果连接数量过多,可能会导致服务器资源不足。
  • 虽然大多数现代浏览器都支持SSE,但仍然有一些旧版本的浏览器不支持。在使用SSE时,要确保您的目标客户端支持SSE,或者提供备用的实时数据推送机制。
  • SSE无法发送二进制数据,只能发送 UTF-8 编码的文本。如果应用程序需要发送二进制数据,就需要使用 Websocket。所以SSE一般多用于推送数据信息给前端,也不用在下载传输文件等内容上。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼老萌新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值