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

相关推荐
mmmayang5 小时前
基于 QUIC 的 HTTP_3
网络·网络协议·http
火山上的企鹅6 小时前
Codex实战:APP远程升级服务搭建(一)NodeJS_Express
express
meilindehuzi_a12 小时前
深入理解 Ajax 异步请求:从 XMLHttpRequest 到 Node.js HTTP 服务实践
http·ajax·node.js
伶俜6613 小时前
鸿蒙原生应用实战(九)ArkUI 天气预报 App:HTTP 请求 + 定位 + 动效
http·华为·harmonyos
逻极14 小时前
HTTP/HTTPS 协议从入门到精通:从原理到性能提升400%的完整路径(协议优化实战)
网络协议·http·性能优化·https·tls
芒鸽14 小时前
HarmonyOS 网络编程实战:HTTP、WebSocket 与 Socket 通信详解
网络·http·harmonyos
努力的lpp14 小时前
渗透主流工具完整参数手册(sqlmap、Nmap、Hydra、Dirsearch、Xray)
javascript·网络协议·测试工具·安全·http·工具
李白的天不白15 小时前
http https
网络协议·http·https
代码中介商1 天前
HTTP进化史:从1.0到3.0的核心变革
网络·网络协议·http
matrixmind81 天前
HTTPX:Python 下一代 HTTP 客户端
python·其他·http·httpx