Server-sent 事件:如何处理 CORS 错误及其解决方案

前言

随着互联网的发展,前后端分离的模式越来越流行,为了实现高效的数据传输,前端开发者通常需要使用 Server-sent 事件。Server-sent 事件 (SSE) 是一种可向客户端推送实时数据的浏览器 API,它的本质是持久化的 HTTP 连接,它允许服务器不断地向客户端推送数据流。

然而,使用 Server-sent 事件时经常会遇到跨域访问 (CORS) 错误。在本文中,我们将深入了解 Server-sent 事件和 CORS 错误,并提供解决方案及示例代码。

Server-sent 事件

Server-sent 事件 (SSE) 是一种使用 HTTP 服务器向浏览器推送数据的方法。该通信协议基于持久的 HTTP 连接,允许服务器推送数据流,而客户端则使用 EventSource API 来接收这些数据。在传输过程中,服务器可以轻松处理未决请求,从而解决了客户端与服务器间的并发性问题。

SSE API

以下是 SSE API 的基本语法:

参数说明:

  • url:SSE 服务器 URL。
  • eventSourceInitDict:可选参数,用于自定义 SSE 连接选项,例如自定义头部。

SSE 客户端 API 仅包含一个 EventSource 对象。使用它,我们可以通过指定 SSE 服务器的 URL 地址来建立数据通道:

然后,可以使用 onmessage 对象来接收数据:

SSE 格式

SSE 格式基于纯文本,使用了一个简单的格式:每个 SSE 事件由包含以下数据的标题与消息组成:

  • event-name:事件类型,通常用于进一步处理事件内容。
  • event-data:事件数据,与事件类型相关联。
  • event-id:事件 ID,允许服务器定义全局唯一事件 ID,客户端会将上一个事件 ID 保存下来,并使用 Last-Event-ID 头部重新连接时发送给服务器。
  • retry-time:在 SSE 连接中断后,可以通过使用此属性来指定重新连接的所需时间。

SSE 服务器

SSE 服务器是一种 HTTP 服务器,在服务器推送新数据时,客户端将自动接收到推送。因此,为了实现 SSE,需要部署一个 HTTP 服务器和 SSE 事件处理器。以下是 SSE 服务器的 Node.js 实现示例:

const http = require('http');

http.createServer(function(request, response) {
  response.writeHead(200, {
    'Content-Type': 'text/event-stream',      // 告诉浏览器:这是一个 SSE 服务
    'Cache-Control': 'no-cache',
    'Access-Control-Allow-Origin': '*',      // 开放 CORS
    'Connection': 'keep-alive'
  });
  
  // 每隔 5 秒向客户端发送服务器当前时间
  setInterval(function() {
    let time = new Date().toLocaleString();
    response.write('data: ' + time + '\n\n');
  }, 5000);
}).listen(8000);

客户端 JavaScript 代码:

const source = new EventSource('http://localhost:8000');

source.onmessage = function(event) {
  console.log(event.data);
};

CORS 错误

CORS (Cross-Origin Resource Sharing, 跨源资源共享) 是浏览器实施的一种保护机制,用于限制某个域的 JavaScript 与来自其他域的资源(比如音频、视频、web 字体等)之间的通信。这种机制可以防止恶意脚本对用户数据等的攻击。

但是,Server-sent 事件使用持久的 HTTP 连接,这种连接通常需要使用预检(OPTIONS)请求,这种请求会触发 CORS 机制,从而出现 CORS 错误,导致 SSE 连接中断。以下是如何解决 SSE 的 CORS 错误:

解决方案

通常,解决 CORS 错误的解决方案有以下几种方式:

方法 1:增加 Access-Control-Allow-Origin 头部

由于跨域问题,服务器通常需要设置响应头部 Access-Control-Allow-Origin,以允许来自任何域名的请求。例如:

然而,Server-sent 事件相比其他类型的请求,它不会立即断开连接,这样会导致在建立了连接之后才会发送 CORS 错误。对于这种情况,需要在响应头部添加以下两行:

以下是 Node.js 实现 SSE 服务器的示例:

const http = require('http');

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

  setInterval(function() {
    let time = new Date().toLocaleString();
    response.write('data: ' + time + '\n\n');
  }, 5000);
}).listen(8000);

方法 2:使用代理服务器

另一种解决方案是使用代理服务器将请求从浏览器转发到 SSE 服务器。这种方法可以控制请求的响应头部,从而解决 CORS 问题。以下是一个使用 Nginx 的代理服务器:

# Nginx 配置文件

http {
  server {
    location /api/sse {
      proxy_pass http://localhost:8000;
      proxy_set_header Access-Control-Allow-Origin "*";
      proxy_set_header Connection "keep-alive";
    }
  }
}

方法 3:使用 JSONP

JSONP(JSON with Padding,用于跨域通信)是一种利用 script 标签实现跨域请求的技术。JSONP 请求不是异步请求,而是通过设置回调函数的方式,将响应内容封装在函数执行中返回。

以下是 SSE 服务器使用 JSONP 解决 CORS 问题的 Node.js 实现示例:

const http = require('http');

http.createServer(function(request, response) {
  let callback = request.query.callback;
  response.write(callback + '(');

  setInterval(function() {
    let time = new Date().toLocaleString();
    response.write(JSON.stringify({time: time}) + ',');
  }, 5000);
}).listen(8000);

客户端 JavaScript 代码:

let script = document.createElement('script');
script.src = 'http://localhost:8000/?callback=doSomething';

function doSomething(data) {
  console.log(data.time);
}

document.body.appendChild(script);

总结

Server-sent 事件 (SSE) 是一种使用 HTTP 服务器向浏览器推送数据的方法,它可以帮助前端开发者实现高效的数据传输。然而,在使用 SSE 时,经常会遇到 CORS (跨源资源共享) 错误,在本文中,我们介绍了 SSE 的相关知识,并提供了三种解决 SSE 的 CORS 错误的方法。通过本文的学习,您可以深入了解 SSE,掌握 SSE 在实际项目中的应用。

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


纠错反馈