sse实现消息推送

协议描述

客户端

使用 EventSource

服务端

添加响应头:Content-Type: text/event-stream

SSE 协议原生只支持 GET 请求,如果想使用非 GET 的请求来实现 SSE,需要安装三方库(下文有示例)。

时序

用顺序图表示整个交互过程,如下图所示:

message 数据格式

参考:www.ruanyifeng.com/blog/2017/0...

每一行都以 id: 或者 event: 或者 data: 或者 retry: 开头,以\n 结尾,最后一行以\n\n 结尾 id/event/data/retry: value\n 如果 data 有多行,格式如下: data: xxx\n data: yyy\n\n

如果返回的是复杂对象,需要对整个对象序列化。

如果服务端是 nodejs,数据格式示例如下:

javascript 复制代码
`data: ${JSON.stringify({
  message: "Update from server",
})}\n`;

使用场景

服务端推送数据到前端

示例代码

用 GET 发送请求

前端

创建 EventSource 对象,设置 onmessage 的回调(可以用 addEventListener 注册回调)

javascript 复制代码
const source = new EventSource(url);
source.onmessage = (e) => {
  const data = JSON.parse(e.data);
  const message = data.message;

  this.list.push(message);
};
服务端
用 express 实现
javascript 复制代码
app.get("/rest/express/sse", async (req, res) => {
  res.set({
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  });
  res.flushHeaders();
  setInterval(() => {
    const data = {
      message: `Current time is ${new Date().toLocaleTimeString()}`,
    };
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  }, 1000);
});
用 koa 实现
基于 koa-sse-stream 实现
javascript 复制代码
const sse = require("koa-sse-stream");
app.use(
  sse({
    maxClients: 5000,
    pingInterval: 30000,
  })
);
router.get("/rest/sse", async (ctx, next) => {
  ctx.header = {
    "Content-Type": "text/event-stream",
  };
  ctx.sse.send("get notice");
  for (let i = 0; i < 5; i++) {
    await new Promise((resolve) => {
      setTimeout(() => {
        ctx.sse.send("interval notice");
        resolve();
      }, 500);
    });
  }
  ctx.sse.sendEnd();
});
不使用 koa-sse-stream 实现

采用 nodejs 原生的 stream 实现,下面的示例采用了PassThrough stream.

javascript 复制代码
const { PassThrough } = require("stream");

router.get("/rest/sse/native", (ctx, next) => {
  ctx.set({
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  });
  const stream = new PassThrough();
  ctx.status = 200;

  for (let i = 0; i < 5; i++) {
    setTimeout(() => {
      stream.write(`id: ${i}\n`);
      stream.write(
        `data: ${JSON.stringify({
          message: "Update from server",
        })}\n`
      );
      stream.write("retry: 1000\n");
      stream.write("\n\n");
    }, i * 1000);
  }
  ctx.body = stream;
});
浏览器运行效果

用非 GET 的 method 发送请求

前端

安装 @microsoft/fetch-event-source

shell 复制代码
npm i -S @microsoft/fetch-event-source
javascript 复制代码
import { fetchEventSource } from "@microsoft/fetch-event-source";

fetchEventSource("/rest/sse", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    foo: "bar",
  }),
  onmessage: (ev) => {
    console.log(ev.data);
    this.list.push(ev.data);
  },
  onerror(err) {
    console.log("err: ", err);
  },
  signal: AbortController.signal,
});
服务端

使用 koa 时,需要安装 koa-sse-stream

javascript 复制代码
router.post("/rest/sse", async (ctx, next) => {
  ctx.header = {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache, no-transform",
  };
  ctx.sse.send("post notice");

  for (let i = 0; i < 5; i++) {
    await new Promise((resolve) => {
      setTimeout(() => {
        ctx.sse.send("interval post notice");
        resolve();
      }, 200);
    });
  }
  ctx.sse.sendEnd();
});

遇到的问题

sse 请求会等到流结束后才返回,而不是分批返回

现象
原因

回调函数中,streamawait 阻塞,没有立即返回

错误代码
javascript 复制代码
router.get("/rest/sse/native", async (ctx, next) => {
  ctx.set({
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  });
  const stream = new PassThrough();
  ctx.status = 200;

  for (let i = 0; i < 5; i++) {
    await new Promise((resolve) => {
      // 这行前面不能加await
      setTimeout(() => {
        stream.write(
          `data: ${JSON.stringify({
            message: "Update from server",
          })}\n`
        );
        stream.write("retry: 1000\n");
        stream.write("\n\n");
        resolve();
      }, i * 1000);
    });
  }
  ctx.body = stream;
});
解决
javascript 复制代码
router.get("/xxx", async (ctx) => {});

回调如果有将 stream.write 封装成 promise,不要 await 这个 promise.

使用 nodejs 的 PassThrough stream,返回的 message 中,data 为空

现象
原因

没有使用 koa-sse-stream 来返回 message,但是 koa 应用又引入了koa-sse-stream

错误代码
javascript 复制代码
const Koa = require("koa");
const sse = require("koa-sse-stream");
const app = new Koa();
// app应用不应该引入koa-sse-stream
app.use(
  sse({
    maxClients: 5000,
    pingInterval: 30000,
  })
);
router.get("/rest/sse/native", (ctx, next) => {
  ctx.set({
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  });
  const stream = new PassThrough();
  ctx.status = 200;

  for (let i = 0; i < 5; i++) {
    setTimeout(() => {
      stream.write(
        `data: ${JSON.stringify({
          message: "Update from server",
        })}\n`
      );
      stream.write("retry: 1000\n");
      stream.write("\n\n");
    }, i * 1000);
  }
  ctx.body = stream;
});
解决

koa应用取消引入 koa-sse-stream

相关推荐
wbs_scy21 分钟前
仿 muduo 高并发服务器项目:封装 HTTP 请求响应并用状态机完成增量解析
运维·服务器·http
ShineWinsu1 小时前
对于Linux:http的解析
linux·网络·c++·网络协议·http·请求·响应
会编程的土豆16 小时前
MySQL 入门:库、表、行、主键是什么
linux·数据库·网络协议·http
BullSmall19 小时前
AFL++ HTTP Mode(网络 / HTTP 服务模糊测试)完整安装教程
网络·网络协议·http
凉凉的知识库1 天前
什么是 HTTP Keep-Alive?一文讲清连接复用机制
网络协议·http·面试
wlsh151 天前
HTTP 协议
http
张人玉1 天前
基于 Vue 3 + ECharts + Express + SQLite 构—— LDA 模型的新闻舆论主题识别与情感分析系统
vue.js·echarts·express
巨量HTTP2 天前
Python爬虫动态换IP实战,彻底解决IP403封禁、限流问题(附完整代码)
爬虫·python·tcp/ip·http
TlSfoward2 天前
如何用 TLS 与 HTTP 指标降低误伤 TLSFOWARD抓包工具
网络·网络协议·http
沐苏瑶2 天前
计算机网络核心笔记:打通 TCP/UDP 与 HTTP/HTTPS 底层逻辑(重点上)
笔记·计算机网络·http·https