当前位置: 首页 > Web前端 > JavaScript

Server-sentevents技术深入讲解

时间:2023-03-27 01:02:48 JavaScript

前言要实时从服务器获取数据,大家首先想到的就是轮询和WebSocket。事实上,有一种称为服务器发送事件(SSE)的新解决方案。SSE中的数据只能从服务端推送到客户端。SSE是一种基于http协议的服务器推送技术,数据只能从服务器发送到客户端。服务端将序列化后的数据发送给客户端,整个过程一直持续到连接关闭WebSocketvspollingvsSSE下面是WebSocket、polling和SSE的功能对比SSE和polling使用HTTP协议,现有服务端软件两者均受支持。WebSocket是一个独立的协议。SSE是一个简单易用的轻量级WebSocket;WebSocket使用起来比较复杂,轮询简单。SSE默认支持断开和重连。WebSocket需要实现断开和重连。SSE一般用于传输文本和二进制数据,在传输前需要进行编码。WebSocket默认支持二进制数据的传输。SSE支持自定义消息类型。WebSocket支持双向推送消息。SSE是单向轮询。性能开销大,轮询时间长导致客户端更新数据不及时。使用场景是基于服务器端向客户端单向推送信息。SSE使用场景主要包括Sass平台消息通知信息流网站实时更新数据使用。下面介绍如何使用SSE在客户端创建EventSource实例,并发起与服务器端的连接。constevtSource=newEventSource();自定义事件对于自定义事件,服务器和客户端必须保持相同的事件名称。当服务端通过自定义事件发送数据时,自定义事件就会被触发。SSE默认支持消息事件。我们以消息事件为例。evtSource.addEventListener("message",(event)=>{letpayload;try{payload=JSON.parse(event.data);//<---event.data需要反序列化console.log("接收数据...",payload);}catch(error){console.error("无法从服务器解析payload",error);}});自定义事件回调函数接收事件对象,event.data存放的是服务端发送给客户端的数据,但是需要反序列化。可以通过ChromeDevtool工具查看eventsource通信状态,如图1-自定义事件名称,服务端和客户端要求保持一致2-EventStreamTab,数据就在这里3-服务端推送数据的错误处理给客户端如果连接失败,会触发一个error事件关闭连接SSE提供了关闭SSE连接的close方法evtSource.close();浏览器兼容性通过caniuse查看SSE浏览器兼容性,如图除了IE浏览器浏览器不支持,其他现代浏览器都支持,所以在项目中随意使用SSE,在日常工作中简单封装一下。每次都要写SSE事件监听和错误处理,会很麻烦。当多个业务场景需要使用SSE时,需要对SSE进行封装。接下来我们尝试封装一个简单的SSESDK,方便在项目中使用。当我们决定写一个SSESDK时,我们首先想到的是使用面向对象(OOP)进行封装。根据SSE的特点,图书馆需要实现两个方法:subscribe和unsubscribe。通过确定SSE库的使用方式,SDK实现将根据其使用方式来确定。我们可以在代码中这样使用,如下所示//SSESdk实例化constSSE=newSSESdk(url,options);//从服务器订阅消息SSE.subscribe("message",(data)=>{console.log("从服务器接收消息",data);});//取消订阅SSE.unsubscribe();我们要封装的库只提供了订阅和取消订阅两个API,非常方便开发者使用。subscribe用于订阅来自服务器的消息,unsubscribe用于取消订阅并关闭SSE连接。从使用形式可以看出,使用了ES6中的类语法。接下来,我们首先确定SSESDK类的总体结构SSEClient{constructor(){}subscribe(type,handler){}unsuncribe(){}}SSEClient类中要实现三个方法,它们接受可配置的参数通过构造函数,比如SSE建立连接失败后的重试次数和重试时间。subscribe接受一个与后端一致的事件名称和一个回调函数。unsuscribe不需要传递任何参数,调用unsuscribe方法关闭SSE连接//SSE-client.jsclassSSEClient{constructor(url){this.url=url;这个.es=null;}subscribe(type,handler){this.es=newEventSource(url);this.es.addEventListener("open",()=>{console.log("serversenteventconnectcreated");});this.es.addEventListener(type,(event)=>{letpayload;try{payload=JSON.parse(event.data);console.log("receivingdata...",payload);}catch(错误){console.error("无法从服务器解析负载",error);}if(typeofhandler==="function"){handler(payload);}});this.es.addEventListener("error",()=>{console.error("EventSourceconnectionfailedforsubscribe.Retry");});}unsuncribe(){if(this.es){this.es.close();}}}这实现了一个简单的SSESDK。首先根据url参数创建一个SSEClient实例。当调用subscribe方法时,会根据传入的url建立SSE连接,然后监听相应的事件。建立连接成功后,后端向客户端发送数据,可以使用handler方法来中间获取数据的库,只实现了很基础的功能,代码封装存在很多问题。比如es的事件都混在subscribe方法中,缺少SSE连接建立失败等重试功能。接下来我们对刚刚实现的SSEClientSDK进行优化constdefaultOptions={retry:5,interval:3*1000,};classSSEClient{constructor(url,options=defaultOptions){this.url=url;这个.es=null;this.options=选项;this.retry=options.retry;这个。计时器=空;}_onOpen(){console.log("serversenteventconnectcreated");}_onMessage(handler){return(event)=>{this.retry=options.retry;让有效载荷;尝试{payload=JSON.parse(event.data);console.log("接收数据...",payload);}catch(error){console.error("无法从服务器解析负载",error);}if(typeofhandler==="function"){handler(payload);}};}_onError(type,handler){return()=>{console.error("EventSourceconnectionfailedforsubscribe.Retry");如果(this.es){this._removeAllEvent(类型,处理程序);这个。取消订阅();}if(this.retry>0){this.timer=setTimeout(()=>{this.subscribe(type,handler);},this.options.interval);}否则{这个。重试-;}};}_removeAllEvent(type,handler){this.es.removeEventListener("open",this._onOpen);this.es.removeEventListener(type,this._onMessage(handler));这个.es.removeEventListener("error",this._onError(type,handler));}subscribe(type,handler){this.es=newEventSource(url);this.es.addEventListener("open",this._onOpen);这个.es.addEventListener(类型,this._onMessage(处理程序));this.es.addEventListener("error",this._onError(type,handler));}unsuscribe(){if(this.es){this.es.close();这个.es=null;}if(this.timer){clearTimeout(this.timer);我们将SSEClient中的三个事件方法提取为三个私有方法,_onOpen方法是在事件中触发open时调用的,链接输出到控制台创建后端向前端发送数据时触发的_onMessage方法,负责解析数据,调用handler方法。SSE发生错误时会触发_onError方法,并在控制台输出错误信息。根据开发者传入的重试次数,先关闭最后一个SSE连接,取消所有事件监听,关闭定时器,然后递归调用subscribe方法进行重连。一旦重连成功,重试次数将恢复为设置的重试次数。如果重试次数超过重试次数,仍然连接不成功,那么SSE将彻底终止。具体原因需要开发商核实。项目中可以使用的一个简单的SSESDK封装了第三方库SSE。SSE虽然很好,但也有其先天的缺点。主要问题是授权令牌无法通过标头传递。虽然可以将token放在url上,解决无法传递token的问题,但会造成token安全隐患。所以社区有使用xhr和fetch模拟nativeServer-sentevent的功能,解决Authorizationtoken不能通过headers传递的问题。主要有两个第三方库,分别是eventsource和event-source-polyfill。下面笔者将详细介绍这两个库的使用。eventsource是EventSource客户端的纯JavaScript实现。使用方法非常简单。在项目中安装依赖yarnaddeventsource#或者npminstalleventsource然后从eventsource中导出EventSource类,然后实例化得到es实例importEventSourcefrom"eventsource";consteventSourceInitDict={headers:{authorization:"Bearertoken"}};constes=newEventSource(url,eventSourceInitDict);es.addEventListener("message",(event)=>{console.log("从服务器接收数据:",JSON.parse(event.data));});eventsource的实现使用了一些节点标准库。它们分别是https和http。下面笔者列出了eventsource的部分源码。//eventsource.js源码如下consthttps=require("https");consthttp=require("http");但是浏览器环境不支持https和http标准库。所以我们在浏览器环境下使用eventsource时,需要做一些额外的工作。下面以webpack5为例来说明一下解决方法。需要在webpack配置文件中添加node-polyfill-webpack-plugin插件yarnaddnode-polyfill-webpack-plugin-D然后在webpack配置文件中使用该插件//项目中的webpack配置文件,如aswebpack.config.jsconstNodePolyfillPlugin=require("node-polyfill-webpack-plugin");module.exports={//其他规则...plugins:[newNodePolyfillPlugin()],};或者在webpack的回调中单独配置module.exports={//其他配置...resolve:{fallback:{https:false,http:false,},},};完成以上步骤后,eventsource就可以了如果不想更改webpack的配置,可以试试event-source-polyfill。库event-source-polyfillevent-source-polyfill使用起来非常简单。使用EventSourcePolyfill替换原来的Ev??entSourceimport{EventSourcePolyfill}from"event-source-polyfill";vares=newEventSourcePolyfill(url,{headers:{authorization:"Bearertoken",},});es.addEventListener("message",(event)=>{console.log("从服务器接收数据:",JSON.parse(event。数据));});不足之处eventsource和event-source-polyfill只是在一定程度上解决了Authorizationtoken的问题,但它们也存在问题。这两个库提供的close方法只能关闭pending状态的SSE连接,因为一旦fetch从pending变为resolved或reject,结果就无法改变了。当频繁断开SSE连接和建立新的SSE连接时,旧的SSE连接实际上并没有关闭,系统中会存在多个SSE连接,会带来很大的性能开销FAQSSE无法向服务器发送数据?可以将数据放入url,断开当前的SSE连接,根据新的url重新建立SSE连接。总结本文描述了一种将信息从服务器推送到客户端的技术。查询性能良好。简单介绍一下Server-sentevents的技术原理和使用场景,做一个简单的封装,方便项目中的日常使用。推荐使用eventsource和event-source-polyfill第三方库解决Authorizationtoken无法通过headers传递的问题。参考链接Server-sentevents