实时获取服务端的数据,大家第一时间想到的是轮询和 WebSocket 两种方案,其实还有一种新方案 Server-sent events下文简称(SSE)。SSE 中的数据只能由服务端推向客户端
SSE 是基于 http 协议的服务器推送技术,数据只能从服务端到客户端。服务端把序列化后的数据发送给客户端,整个过程持续不断直至连接关闭
下面是 WebSocket、轮询和 SSE 的功能对比
基于服务端单向的向客户端推送信息的特性,SSE 使用场景主要有
下面讲解如何在客户端使用 SSE
const evtSource = new EventSource();
对于自定义事件,服务端和客户端一定要保持事件名一致。服务端通过自定义事件发送数据,
就会触发自定义事件。SSE 默认支持 message 事件,下面以 message 事件为例
evtSource.addEventListener("message", (event) => {
let payload;
try {
payload = JSON.parse(event.data); // <--- event.data 需要反序列化
console.log("receiving data...", payload);
} catch (error) {
console.error("failed to parse payload from server", error);
}
});
自定义事件的回调函数接收 event 对象,event.data 存着服务端发给客户端的数据但是需要反序列化
可以通过 Chrome Devtool 工具查看 eventsource 通信情况,如图所示
如果连接发生错误,就会触发 error 事件
evtSource.addEventListener("error", (err) => {
console.error("EventSource failed:", err);
});
SSE 提供 close 方法,用来关闭 SSE 连接
evtSource.close();
通过 caniuse 查看 SSE 浏览器兼容性,如图所示
除了 IE 浏览器不支持,其它现代浏览器都支持,所以放心大胆在项目中使用 SSE
在平常的工作中,每次写 SSE 的事件监听和错误处理会很麻烦。多个业务场景需要使用 SSE 时,就需要对 SSE 进行封装。接下来我们尝试封装一个简单的 SSE SDK,方便在项目中使用
当我们决定写 SSE 的 SDK 时,首先想到使用面向对象(OOP)进行封装。根据 SSE 的特性,那么库需要实现 subscribe 和 unsubscribe两个方法。通过确定 SSE 库使用方式,根据使用方式确定 SDK 的实现。我们可以在代码中这样使用,如下所示
// SSESdk 实例化
const SSE = new SSESdk(url, options);
// 订阅来自服务端的消息
SSE.subscribe("message", (data) => {
console.log("receive message from server", data);
});
// 取消订阅
SSE.unsuscribe();
我们要封装的库对外仅仅提供 subscribe 和 unsubscribe 两个 api,非常方便开发人员使用。subscribe 用来订阅来自服务端的消息, unsubscribe 用来取消订阅,关闭 SSE 连接,通过使用形式可以看出,使用 ES6 中的类语法。接下来我们先确定 SSE SDK 的大体结构
class SSEClient {
constructor() {}
subscribe(type, handler) {}
unsunscribe() {}
}
在 SSEClient 类中有三个方法需要实现,通过 constructor 接受可配置的参数,比如 SSE 建立连接失败后的重试次数和重试时间。subscribe 接收一个与后端保持一致的事件名和一个回调函数。unsunscribe 不需要传递任何参数,调用 unsunscribe 方法关闭SSE 连接
// SSE-client.js
class SSEClient {
constructor(url) {
this.url = url;
this.es = null;
}
subscribe(type, handler) {
this.es = new EventSource(url);
this.es.addEventListener("open", () => {
console.log("server sent event connect created");
});
this.es.addEventListener(type, (event) => {
let payload;
try {
payload = JSON.parse(event.data);
console.log("receiving data...", payload);
} catch (error) {
console.error("failed to parse payload from server", error);
}
if (typeof handler === "function") {
handler(payload);
}
});
this.es.addEventListener("error", () => {
console.error("EventSource connection failed for subscribe.Retry");
});
}
unsunscribe() {
if (this.es) {
this.es.close();
}
}
}
就这样实现了一个简单的 SSE SDK。首先根据 url 参数创建一个 SSEClient 实例,当调用 subscribe 方法时,才会根据传入的 url 建立 SSE 连接,然后监听对应的事件,一旦连接建立成功,后端向客户端发送数据,就可以从 handler 方法中拿到数据
这个库仅仅实现了非常基本的功能,代码封装上存在很多问题。比如 es 的事件全部杂糅在 subscribe 方法中、缺少 SSE 连接建立失败的重试等等功能。接下来我们对刚刚实现的 SSEClient SDK 进行优化
const defaultOptions = {
retry: 5,
interval: 3 * 1000,
};
class SSEClient {
constructor(url, options = defaultOptions) {
this.url = url;
this.es = null;
this.options = options;
this.retry = options.retry;
this.timer = null;
}
_onOpen() {
console.log("server sent event connect created");
}
_onMessage(handler) {
return (event) => {
this.retry = options.retry;
let payload;
try {
payload = JSON.parse(event.data);
console.log("receiving data...", payload);
} catch (error) {
console.error("failed to parse payload from server", error);
}
if (typeof handler === "function") {
handler(payload);
}
};
}
_onError(type, handler) {
return () => {
console.error("EventSource connection failed for subscribe.Retry");
if (this.es) {
this._removeAllEvent(type, handler);
this.unsunscribe();
}
if (this.retry > 0) {
this.timer = setTimeout(() => {
this.subscribe(type, handler);
}, this.options.interval);
} else {
this.retry--;
}
};
}
_removeAllEvent(type, handler) {
this.es.removeEventListener("open", this._onOpen);
this.es.removeEventListener(type, this._onMessage(handler));
this.es.removeEventListener("error", this._onError(type, handler));
}
subscribe(type, handler) {
this.es = new EventSource(url);
this.es.addEventListener("open", this._onOpen);
this.es.addEventListener(type, this._onMessage(handler));
this.es.addEventListener("error", this._onError(type, handler));
}
unsunscribe() {
if (this.es) {
this.es.close();
this.es = null;
}
if (this.timer) {
clearTimeout(this.timer);
}
}
}
我们将 SSEClient 中的三个事件方法分别提取为三个私有方法,_onOpen 方法在 event 触发 open 时调用,向控制台输出链接已经创建。
_onMessage 方法在后端向前端发送数据时触发,负责解析数据,并调用 handler 方法。_onError 方法在 SSE 发生错误时触发,
会在控制台输出错误的提示,根据开发者传入的重试次数,先关闭上一次的 SSE 链接,取消所有的事件监听,关闭定时器,
再开启递归调用 subscribe 方法进行重连, 一旦重连成功,重试次数恢复为设定的重试次数,如果超过重试次数依旧没有连接成功,那么 SSE 会彻底终止。需要开发人员排查具体原因
一个可以用在项目上的简单 SSE SDK 封装完
SSE 虽然很好,但是也有它先天不足,主要问题是不能通过 headers 传递 Authorization token。虽然可以把 token 放在 url 上解决不能传 token 的问题,但是又会引发 token 安全隐患。所以社区里有使用 xhr 和 fetch 模拟原生Server-sent events 的功能,解决不能通过 headers 传递 Authorization token 的问题。主要有两个第三方库,分别是 eventsource 和 event-source-polyfill,下面笔者详细讲述这两个库的使用
此库是 EventSource 客户端的纯 JavaScript 实现。使用方式很简单。在项目中安装依赖
yarn add eventsource
# Or npm install eventsource
然后从 eventsource 中导出 EventSource 类,然后实例化得到 es 实例
import EventSource from "eventsource";
const eventSourceInitDict = { headers: { authorization: "Bearer token" } };
const es = new EventSource(url, eventSourceInitDict);
es.addEventListener("message", (event) => {
console.log("receiving data from server:", JSON.parse(event.data));
});
eventsource 的实现用到了一些 node 标准库。分别是 https 和 http。
笔者将 eventsource 的部分源码列在下面。
// eventsource.js 源码如下
const https = require("https");
const http = require("http");
然而,浏览器环境并不支持 https 和 http 标准库。所以当我们在浏览器环境中使用 eventsource 时,需要做一些额外的工作。下面以 webpack5 为例子讲解解决办法
yarn add node-polyfill-webpack-plugin -D
然后在 webpack 配置文件使用该插件
// 项目中的 webpack 配置文件,比如 webpack.config.js
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
module.exports = {
// Other rules...
plugins: [new NodePolyfillPlugin()],
};
module.exports = {
// other configuration ...
resolve: {
fallback: {
https: false,
http: false,
},
},
};
做完上面的步骤后,eventsource 可以在浏览器中正常运行
如果不想改动 webpack 的配置,那么可以试试 event-source-polyfill 这个库
event-source-polyfill 的使用非常简单,使用 EventSourcePolyfill 替换原生的 EventSource
import { EventSourcePolyfill } from "event-source-polyfill";
var es = new EventSourcePolyfill(url, {
headers: {
authorization: "Bearer token",
},
});
es.addEventListener("message", (event) => {
console.log("receiving data from server:", JSON.parse(event.data));
});
eventsource 和 event-source-polyfill 只是在一定的程度上解决了 Authorization token 的问题,但它们也存在问题。这两个库提供的 close 方法只能关闭处于 pending 状态的 SSE 连接,因为 fetch 一旦从 pending 变为 resolved
或 reject, 其结果无法改变。当频繁的断开 SSE 连接和建立新 SSE 连接时,旧的 SSE 连接实际上并没有关闭,系统里会存在多个
SSE 连接,这样会带来很大的性能开销
可以将数据放入 url 中,断开当前的 SSE 连接,根据新 url 重新建立 SSE 连接
本篇文章讲述一种服务端向客户端推送信息的技术、它比 WebSocket 更简单更轻量化,比轮询性能好。简单介绍 Server-sent events 的技术原理和使用场景,并进行简单的封装,方便日常在项目中使用。推荐使用 eventsource 和 event-source-polyfill 第三方库解决不能通过 headers 传递 Authorization token 的问题。
参考链接:Server-sent events
原文来自:https://segmentfault.com/a/1190000043392930
归根结底,涨薪其实是达到自己价值与薪资的最佳匹配. 好比你就是一只股票,公司当然会选择那些估值远高于股指的股票. 所以唯有不断增长自己的价值,才会成为你在涨薪谈判中的重要筹码.
BT下载相信老司机们都接触过,为什么BT种子会慢慢被磁链取而代之?它们都可以用于BT下载,除了文件和字符串这表面上的区别,背后的技术上又有何不同?
SOAP用于在Web Service中把远程调用和返回封装成机器可读的格式化数据。REST形式上应该表述为客户端通过申请资源来实现状态的转换,在这个角度系统可以看成一台虚拟的状态机。
技术精进是一个持续增长的过程,而非一朝一夕,即便你在最短时间的掌握了大量的技术点,如何不及时应用到实际问题中,也很容易被遗忘。有朋友会说,我平时也挺努力的,一直不间断的学习
今天的文章,他将继续深入探讨这一话题,从管理的角度分享技术TL的核心职责,主要包括团队建设、团队管理、团队文化、沟通与辅导、招聘与解雇等,希望与大家共同探讨、交流。
根据近年数据,中国现有程序员500万左右,其中P1、P2数量占据了近100万,P8及以下程序员约有490万,P9及以上仅有10万。80后是企业的技术支柱,90后已开始逐步成为企业的中坚力量
技术的成长路上,少不了跟一些志同道合的人交流,阅读一些技术前辈们的经验分享。这一路走来,还是要感谢有技术社区的陪伴,让码字之余,在技术、以及技术以外,都有不少收获。
过去的这段时间里,不论是互联网巨头还是初创企业,都纷纷进行了一波优化。渐趋理智的资本淘汰了一批不能适应市场的业务,而业务的紧缩也淘汰了一批不能适应市场的程序员。
除了能够完成基本的PHP业务开发,还能够解决大部分深入复杂的技术问题,并且可以独立设计完成中大型的系统设计和开发工作;自己能够独立hold深入某个技术方向,在这块比较专业
认识的一个 10 人左右的团队,本来是用 PHP 的,这些年看到网上很多用 / 转 Go 的消息,于是团队里有不少人就焦虑了,希望找一个合适的切入时间,能够试一把 Go
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!