SSE 是什么?

SSE 全称是 Server Sent Event,翻译过去的意思就是 服务器派发事件。

一个网页获取新的数据通常须要发送一个申请到服务器,也就是向服务器申请的页面。

应用 server-sent 事件,服务器能够在任何时刻向咱们的 Web 页面推送数据和信息。

这些被推送进来的信息能够在这个页面上作为 Events [0] + data 的模式来解决。

文言篇

SSE 的实质其实就是一个 HTTP 的长连贯,只不过它给客户端发送的不是一次性的数据包,而是一个 stream 流,格局为 text/event-stream。

所以客户端不会敞开连贯,会始终等着服务器发过来的新的数据流

SSE 与其余交互方式的区别

SSE 与 WS 有什么区别?

形式协定交互通道内容编码重连事件类型总结
SSEHTTP服务端单向推送默认文本默认反对断线重连反对自定义音讯类型轻量级
WebSocketWS(基于 TCP 传输层的应用层协定,RFC6455 [1] 对于它的定义规范)双向推送默认二进制手动实现NO扩展性、功能性弱小

TCP/IP 五层模型

SSE 兼容性

API
SSE6.079.06.05.011.5
WS4.012114.212.1

IE 不反对?兼容性不好?
event-source-polyfill 助你实现 SSE

接下来的示例不会通过这个 event-source-polyfill 实现。

正经人谁思考兼容性?

狗头护体.jpg

SSE 的 API

属性(只读)

名称作用类型备注
readyState以后状态Number0 — connecting1 — open2 — closed
url以后连贯的地址String
withCredentials是否开启凭据收集Boolean

办法

名称作用返回值
close客户端被动敞开连贯-

事件

名称作用返回值
onclose连贯敞开触发event
onopen连贯开启触发event
onmessage服务端音讯推动音讯触发event

服务端 API

字段

名称作用类型备注
data传输的文本String(默认)\ 能够传输JSON能够多行累加
event事件名称String可自定义
id以后推送 idString作为音讯的标识
retry超时重试工夫Number客户端在感知 server 连贯异样后。会通过 retry 设定工夫进行从新连贯

需要评审

咱们在后面曾经初步的理解了 SSE,并且晓得了它的一些 API。

咱们接下来须要做一件事件,理解需要

需要就是

需要评审完结

产品经理给你一张图都不错了,本人看着来吧。

技术评审

后面的 ** 产品经理居然只给了一张图

用户在关上窗口时,会建设一个 SSE 的连贯

服务端须要将建设这次连贯,保留在连接池中。

如果用户点击了构建,后台程序会执行打包命令

在打包中进行音讯的推送,而后前端进行音讯的展现。

接口评审

GET api/log/push (通信 API)

连贯 SSE 的通道,用于后续音讯通信

POST api/project/build (构建 API)

参数 projectPath 传入你须要构建的我的项目门路

用于我的项目构建,构建时触发音讯推送

沉着一下

等等~ 是不是说得太简略了

构建 API 如何帮咱们打包?

nodejs 外面有个 child_process[3] 模块叫做 子过程

你能够通过它执行 installbuild

如何触发通信 API 音讯推送?

child_process 是能够异步推送音讯

外面会有 stdoutstderr 钩子。

会感知到命令行的失常异样输入。

咱们在这一步进行音讯的实时推送岂不美哉。

解答完这两个疑难之后,咱们就能够欢快的写代码了

后端开发

引入须要用到的包

这些包看起来引了挺多,实际上与 SSE 相干的 只有 koa-sse-stream,它其实就是一个他人封装好的中间件,如果你看过阮一峰老师的 《Server-Sent Events 教程》[4]

const Koa = require('koa');const router = require('koa-router')();const KoaSSEStream = require('koa-sse-stream'); // 封装好的 SSE 中间件const child_process = require('child_process'); // Node 子过程const bodyParser = require('koa-bodyparser');const cors = require('@koa/cors');const moment = require('moment');const newDate = () => moment().format('YYYY-MM-DD HH:mm:ss');const app = new Koa(); app.use(cors());app.use(bodyParser());/*  接下来的代码存放处*/app.use(router.routes())  app.listen(3000);

通信 API

在后面咱们曾经说过每次减少一个连贯,会放到连接池中,而这个连接池相当于一个通讯录。

后续会遍历这个通讯录,进行音讯的推送

// 连接池const clientList = [];// koa-sse-stream 配置const SSE_CONF = {    maxClients: 2, // 最大连接数    pingInterval: 40000 // 重连工夫}router.get('/api/log/push', KoaSSEStream(SSE_CONF), ctx => {    // 每次连贯会进行一个 push    clientList.push(ctx.sse);})

构建 API

咱们在这个接口调用的时候,先进行响应,不然这个接口会始终等到构建实现之后再响应。

谁也不晓得构建实现某个我的项目,须要破费多长时间,可能设置的申请超时工夫都过了也没响应。

router.post('/api/project/build', ctx => {    // 接管我的项目绝对路径    const { projectPath } = ctx.request.body;    try {        // 先响应        ctx.body = {            msg: '开始构建,请注意下方的构建信息。'        }        // 再执行构建        buildProject(projectPath)    } catch (error) {        ctx.body = {            msg: error        }    }})

child_process 执行打包命令

简略的封装了一个执行脚本的函数,前面再通过 callback 进行音讯的推送

/** * 执行命令 * @param {String} script 须要执行的脚本 * @param {Function} callback 回调函数 * @returns  */const implementCommand = async (script, callback) => {    callback(script)    return new Promise((resolve, reject) => {        try {            const sh = child_process.exec(script, (error, stdout, stderr) => {                // 这里的 stdout stderr 在执行之后才会触发                if (error) {                    reject(error);                    callback(error)                }                resolve()            });            // 胜利的推送            sh.stdout.on('data', (data) => {                callback(data)            })            // 谬误的推送            sh.stderr.on('data', (error) => {                callback(error)            })        } catch (error) {            callback(error)            reject()        }    })}

打包我的项目 + 音讯推送

buildProject 只负责整合须要执行哪些命令

整合之后调用 implementCommand 执行命令

implementCommand 会执行 messagePush 进行音讯推送。

/** * 打包我的项目 * @param {String} projectPath 打包门路 */const buildProject = async projectPath => {    // 执行 install 命令    await implementCommand(`cd ${projectPath} && yarn install`, messagePush)    // 执行 build 命令    await implementCommand(`cd ${projectPath} && yarn build`, messagePush)    messagePush('打包实现!!!') }/** * 音讯推送 * @param {String} content 须要推送的内容 */const messagePush = content => {    clientList.forEach(sse => sse.send(`[${currentTime()}] ${content}`))    // send 自定义事件写法    // clientList.forEach(sse => sse.send({ data: content, event: 'push' }))}

服务端执行过程示意图

到这里服务端就曾经全副实现了,看得出来构建 + 音讯推送是最麻烦的一步,所以我画了个草图。

什么?看不懂?放过我吧,就这程度...

前端开发

本次需要,前端比较简单,只须要三步

  • 连贯 SSE (调用 通信 API)
  • 构建我的项目 (调用构建 API)
  • 接管到 SSE 的推送进行内容展现

HTML 构造

<!-- 输出我的项目的门路 --><input type="text" value="C:/xxx" id="dirPath"><!-- 构建点击的按钮 --><button id="buildSubmit">构建</button><!-- 输入内容的载体 --><pre><code id="app"></code></pre>

开启 SSE 连贯

// 通过 new EventSource 开启 SSEconst source = new EventSource(`http://127.0.0.1:3000/api/log/push`);// 监听 message 事件source.onmessage = event => {  // 挂到载体下面  app.innerHTML += `${event.data} \n`}

调用构建 API

通过构建按钮点击之后触发调用构建接口

buildSubmit.onclick = () => {  // 构建之前清空载体  app.innerHTML = '';  const projectPath = dirPath.value;  // 做了个简略校验  if(!projectPath) return alert('指标打包门路不能为空');  // 发动申请  $.ajax({    url: 'http://127.0.0.1:3000/api/project/build',      method: 'post',      data: {        projectPath // 我的项目门路      },        // 胜利回调      success: res => {        alert(res.msg)      }  })}

到这里咱们就曾经开发完了这个需要,
大家能够入手去试一试

当然这只是 SSE 一些根本的应用而已。

写在前面

如果大家对这篇文章有任何质疑,能够留言或者私信给我。我将逐个回复(我理解的就回复的快一些,不晓得的,我会查问材料再进行回复,就能够略微慢一些~)

注解

[0] Event -- https://developer.mozilla.org...

[1] RFC6455 -- https://www.rfc-editor.org/rf...

[2] event-source-polyfill -- https://www.npmjs.com/package...

[3] child_process -- https://nodejs.org/dist/lates...

[4] Server-Sent Events 教程 -- http://www.ruanyifeng.com/blo...