Server-sent Events 如何实现对客户端的自定义消息推送

简介

Server-sent Events (SSE) 是一个独立于 WebSocket 的 HTML5 规范,它使用 HTTP 协议来实现服务器到客户端的事件推送,可以让服务器主动向客户端发送数据,而不是需要客户端定时读取。SSE 最常见的使用场景就是实现实时更新消息,比如聊天室、股票行情等。

SSE 基于常规的 HTTP 连接,通过浏览器与服务器维持一个单向的持久连接,浏览器向服务器发送一个 HTTP 请求,服务器的响应中设置了正确的 MIME 类型,然后保持连接打开,这样服务器可以发送任意数量的消息,直到客户端关闭连接或者网络连接中断。

本文将详细介绍 SSE 的实现方法和最佳实践,并提供一个完整的示例代码。

实现 SSE 的服务器端代码

在服务器端,需要设置正确的响应头 Content-Type 为 text/event-stream,其它的 HTTP 头都与标准的 HTTP 请求相同。下面是一个 Node.js 的 SSE 服务器实现:

const http = require('http');
const fs = require('fs');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    fs.createReadStream('./index.html').pipe(res);
  } else if (req.url === '/events') {
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive'
    });

    const id = (new Date()).toLocaleTimeString();
    setInterval(() => {
      res.write(`id: ${id}\nevent: ping\ndata: ${new Date()}\n\n`);
    }, 5000);

    req.socket.on('close', () => {
      clearInterval(intervalId);
      console.log(`Connection closed: ${id}`);
    });
  } else {
    res.writeHead(404);
    res.end();
  }
});

server.listen(3000, () => {
  console.log('Server started on http://localhost:3000');
});

代码中,当客户端通过浏览器访问 http://localhost:3000/ 时,服务器会返回 index.html 页面;当客户端访问 http://localhost:3000/events 时,服务器会发送一条事件数据,该事件类型为 ping,发送间隔为 5 秒。

客户端代码示例

在客户端,需要使用 JavaScript 来建立 SSE 连接,并监听服务器推送过来的消息。以下代码演示 SSE 的基本用法:

const eventSource = new EventSource('/events');
eventSource.onopen = e => {
  console.log('Connection opened');
};
eventSource.onmessage = e => {
  console.log(e.data);
};
eventSource.onerror = e => {
  console.error('Connection error');
};

当 SSE 连接建立后,客户端通过 onmessage 方法监听服务器发送的事件数据。需要注意的是,SSE 连接在默认情况下是不会自动重连的,如果需要自动重连,可以添加如下代码:

eventSource.onerror = e => {
  if (eventSource.readyState == EventSource.CLOSED) {
    console.error('Connection closed');
  } else {
    console.error('Connection error');
  }
  setTimeout(() => {
    eventSource = new EventSource('/events');
  }, 1000);
};

SSE 最佳实践

根据事件类型发送消息

在服务端发送事件时,可以通过指定事件类型来让客户端更加清晰地处理不同类型的事件:

res.write(`event: myEvent\ndata: ${JSON.stringify({ message: 'Hello world' })}\n\n`);

客户端在接收到事件时可以根据类型做出不同的处理:

eventSource.addEventListener('myEvent', e => {
  console.log(e.data.message);
});

使用 JSON 序列化数据

为了提高 SSE 的兼容性,建议在发送数据时使用 JSON 格式来序列化数据:

res.write(`data: ${JSON.stringify({ message: 'Hello world' })}\n\n`);

在客户端接收到数据后,可以通过 JSON.parse 方法进行反序列化:

eventSource.onmessage = e => {
  const data = JSON.parse(e.data);
  console.log(data.message);
};

启用 gzip 压缩

由于 SSE 的数据传输是基于文本的,可以通过启用 gzip 压缩来减少传输的数据量,从而提升 SSE 的性能。服务器端需要设置响应头 Content-Encoding 为 gzip,并将响应数据进行压缩:

const zlib = require('zlib');

// Write the response.
res.writeHead(200, {
  'Content-Type': 'text/event-stream',
  'Cache-Control': 'no-cache',
  'Connection': 'keep-alive',
  'Content-Encoding': 'gzip'
});

const stream = fs.createReadStream('myData.txt', 'utf8');
stream.pipe(zlib.createGzip()).pipe(res);

使用跨域资源共享 (CORS)

如果服务端和客户端不在同一个域下,需要使用跨域资源共享 (CORS) 来解决跨域问题:

服务端代码:

res.writeHead(200, {
  'Content-Type': 'text/event-stream',
  'Cache-Control': 'no-cache',
  'Connection': 'keep-alive',
  'Access-Control-Allow-Origin': '*'
});

客户端代码:

const eventSource = new EventSource('http://example.com/events');

总结

Server-sent Events 是一种轻量级的实时消息推送技术,与 WebSocket 相比,它的开销更小、易于实现和维护。通过本文的介绍和示例代码,你应该能够了解如何使用 SSE 实现自定义消息推送,并掌握 SSE 的最佳实践。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/659119faeb4cecbf2d656d8e


纠错反馈