服务器发送事件:WebSocket 的轻量级替代方案

作为开发人员,我确信最常用的后端通信模式之一是 HTTP 请求。当我们需要来自服务器的数据时,我们总是使用它们。

但是通过 HTTP 请求调用,您只有在请求时才能获取数据,这意味着一旦您请求任何数据,服务器就会返回响应并关闭连接。

现在,如果您需要连续的数据,例如实时比赛比分更新,该怎么办? 您会怎么做? 我相信大多数人会说“WebSockets”。

WebSockets 的问题在于它是双向通信协议,确实它有像聊天应用程序这样的用例,但并不是每个用例都需要双向通信,如果我们只需要服务器向我们发送实时数据怎么办?

这是使用服务器发送事件的完美场景。

那么,什么是服务器发送事件?

**服务器发送事件 (SSE)** 是通过 HTTP 协议进行的通信。它允许服务器在建立初始连接后向客户端发送数据。

Connection Flow For SSE

如何设置和使用**服务器发送事件**?

与任何普通 HTTP 请求一样,SSE 连接也必须由客户端(浏览器)发起。您必须在服务器以及客户端代码上进行一些配置。

您的第一步是在 HTTP 请求中设置正确的标头。

  • 将 Content-Type 标头设置为 text/event-stream。
  • 将 Cache-Control 标头设置为 no-cache。
  • 要保持活动的连接头。
  • Example of Request and Response Headers.

    虽然大多数框架默认都这样做,但了解它与普通 HTTP 请求有何不同还是不错的。

    您的第一步是从后端返回以下格式的每个数据:

  • event — 指定数据类型的字符串,默认值为 message,您可以在 eventSource.onmessage 上进行监听。如果您决定使用任何自定义字符串,则可以使用 addEventListener("MyCustomEvent", (event) => {}) 监听该事件。
  • 数据——传递您自己的自定义数据,可以是任何东西,字符串或 JSON 编码的对象。
  • id — 从后端创建一个唯一的 Id,可能是增量值。
  • 让我们深入研究实施部分,我将使用 Spring Boot 作为后端服务器,并使用 Angular 作为前端。

    我使用 Spring Boot,因为我对此很熟悉,但您可以在自己的编程语言中轻松找到设置。您可以在此处参考 PHP 中的实现。

    // using Fiegn Client for https://random-data-api.com/api/v2/
    private final RandomUserFiegnClient randomUserFiegnClient;
    
    @GetMapping(path = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux> streamEvents() {
        return Flux.interval(Duration.ofSeconds(3))
               .publishOn(Schedulers.boundedElastic())
               .handle((sequence, sink) -> sink.next(ServerSentEvent.builder()
                    .id(String.valueOf(sequence))
                    .event("message")
                    .data(randomUserFiegnClient.getRandomUser().toString())
                    .build()));
    }

    让我们了解一下主要部分,

  • 产生 = MediaType.TEXT_EVENT_STREAM_VALUE 基本上将 Content-Type 标头设置为 text/event-stream。
  • Flux.interval(Duration.ofSeconds(3)) 每 3 秒将最新数据发送回客户端。
  • data(randomUserFiegnClient.getRandomUser().toString()) 使用随机数据API返回随机用户信息。
  • 一旦设置完成,我们就设置客户端代码。

    // Create an Event Source Object.
    // The server-sent event API is contained in the EventSource interface.
    const eventSource = new EventSource('http://localhost:8080/sse');
    
    // Once created, Listen on OnOpen Event.
    // This will let you if connection to backend is established or Not.
    eventSource.onopen = (e) => {
      console.log("The connection has been established.");
      // Add Your Business Logic Here.
      eventSource.onmessage = (event) => {
        console.log(event.lastEventId); // unique event id from the server.
        console.log(event.type); // type of event
        console.log(event.data); // actual Data.
      };
    };

    上述代码是基本设置,当然,根据您的使用情况,您必须对其进行大量修改。让我们看看如何检查连接错误或在完成后关闭连接。

    eventSource.onerror = (err) => {
      // Maybe display a generic error message, and helpful for developer to debug.
      console.error("EventSource failed:", err);
    };
    
    // Let's say you are now done, and You want to close the connection.
    eventSource.close();

    确保关闭连接,以防止服务器过载。

    让我们看看最终的实现:

    现在来总结一下,您应该使用“服务器发送事件(SSE)”进行单向数据流。

  • 实时比赛比分更新、通知或仪表板。
  • 跟踪系统。
  • 或者如果您只是希望服务器向您发送实时数据,而客户端则无需进行任何通信。
  • 最后一点,请记住它的局限性:

  • 连接数 — 如果您不使用 HTTP/2,那么您可以设置的最大连接数是 6。对于 Chrome,请在此处查看问题;对于 Firefox,请在此处查看问题。
  • 数据格式——您只能发送 UTF-8 消息,遗憾的是不支持二进制数据。