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

相关推荐
山间朝暮-CanEve12 小时前
ONE NET MQTT+HTTP多端控制
网络·网络协议·http
007php0072 天前
HTTPS域名443端口证书到期问题排查与解决
开发语言·网络·网络协议·计算机网络·http·golang·https
engchina2 天前
使用Express.js和SQLite3构建简单TODO应用的后端API
javascript·sqlite·express
谢大旭2 天前
http 请求类型及其使用场景
http
5xidixi3 天前
HTTP(1)
网络·网络协议·http
lingllllove3 天前
使用 HTTP::Server::Simple 实现轻量级 HTTP 服务器
服务器·网络协议·http
别致的影分身3 天前
Linux网络 HTTP cookie 与 session
网络·网络协议·http
esmember4 天前
电路研究9.2.6——合宙Air780EP中HTTP——HTTP GET 相关命令使用方法研究
网络·网络协议·http·at指令
霸王蟹4 天前
http和https的区别?
网络·笔记·网络协议·学习·http·https
精通HelloWorld!4 天前
使用HttpClient和HttpRequest发送HTTP请求
java·spring boot·网络协议·spring·http