当前解决服务端推送的方案有这几个:
-
客户端长轮询(不推荐使用)
-
websocket双向连接
-
iframe永久帧(不推荐使用)
-
EventSource
长轮训虽然可以避免短轮训造成的服务端过载,但在服务端返回数据后仍需要客户端主动发起下一个长轮训请求,等待服务端响应,这样仍需要底层的连接建立而且服务端处理逻辑需要相应处理,不符合逻辑上的流程简单的服务端推送;
websocket连接相对而言功能最强大,但是它对服务器的版本有要求,在可以使用websocket协议的服务器上尽量采用此种方式;
iframe永久帧则是在在页面嵌入一个专用来接受数据的iframe页面,该页面由服务器输出相关信息,如,服务器不停的向iframe中写入类似的script标签和数据,实现另一种形式的服务端推送。不过永久帧的技术会导致主页面的加载条始终处于“loading”状态,体验很差。
HTML5规范中提供了服务端事件EventSource,浏览器在实现了该规范的前提下创建一个EventSource连接后,便可收到服务端的发送的消息,这些消息需要遵循一定的格式,对于前端开发人员而言,只需在浏览器中侦听对应的事件皆可。
相比较上文中提到的3中实现方式,EventSource流的实现方式对客户端开发人员而言非常简单,兼容性上出了IE系的浏览器(IE、Edge)外其他都良好;对于服务端,它可以兼容老的浏览器,无需upgrade为其他协议,在简单的服务端推送的场景下可以满足需求。在浏览器与服务端需要强交互的场景下,websocket仍是不二的选择。
服务器发送事件 API 也就是
EventSource
接口,在你创建一个新的
EventSource
对象的同时,你可以指定一个接受事件的 URI。例如:
const evtSource = new EventSource("ssedemo.php");
备注: 从 Firefox 11 开始,EventSource开始支持CORS.虽然该特性目前并不是标准,但很快会成为标准。
如果发送事件的脚本不同源,应该创建一个新的包含 URL 和 options 参数的EventSource对象。例如,假设客户端脚本在 example.com 上:
const evtSource = new EventSource("//api.example.com/ssedemo.php", { withCredentials: true } );
一旦你成功初始化了一个事件源,就可以对 message 事件添加一个处理函数开始监听从服务器发出的消息了:
evtSource.onmessage = function(event) {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
newElement.innerHTML = "message: " + event.data;
eventList.appendChild(newElement);
上面的代码监听了那些从服务器发送来的所有没有指定事件类型的消息 (没有event字段的消息),然后把消息内容显示在页面文档中。
你也可以使用addEventListener()方法来监听其他类型的事件:
evtSource.addEventListener("ping", function(event) {
const newElement = document.createElement("li");
const time = JSON.parse(event.data).time;
newElement.innerHTML = "ping at " + time;
eventList.appendChild(newElement);
});
这段代码也类似,只是只有在服务器发送的消息中包含一个值为"ping"的event字段的时候才会触发对应的处理函数,也就是将data字段的字段值解析为 JSON 数据,然后在页面上显示出所需要的内容。
页面代码案例:
<!DOCTYPE html>
<meta charset="UTF-8">
<title>测试</title>
</head>
<div>服务器端推送测试</div>
<div id="serverSendEventDiv"></div>
</body>
<script type="text/javascript">
if(window.EventSource) {
const source = new EventSource("push");
let s = '';
source.addEventListener('open', function (e) {
console.log("连接打开")
}, false);
source.addEventListener('message', function (e) {
s += e.data + '<br/>';
document.getElementById("serverSendEventDiv").innerHTML = s;
source.addEventListener('close', function (e) {
if (e.readyState === EventSource.CLOSED) {
console.log("连接关闭")
} else {
console.log(e.readyState)
}, false);
source.addEventListener("error", function(err) {
console.log(JSON.stringify(err))
console.log(err)
err && err.status === 401 && console.log('not authorized')
} else {
alert("你的浏览器不支持sse")
</script>
服务器端发送的响应内容应该使用值为text/event-stream
的 MIME 类型。每个通知以文本块形式发送,并以一对换行符结尾。有关事件流的格式的详细信息,请参见事件流格式。
官方文档给了一个php版本代码示例:
date_default_timezone_set("America/New_York");
header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");
$counter = rand(1, 10);
while (true) {
echo "event: ping\n";
$curDate = date(DATE_ISO8601);
echo 'data: {"time": "' . $curDate . '"}';
echo "\n\n";
$counter--;
if (!$counter) {
echo 'data: This is a message at time ' . $curDate . "\n\n";
$counter = rand(1, 10);
ob_end_flush();
flush();
sleep(1);
上面的代码会让服务器每隔一秒生成一个事件流并返回,其中每条消息的事件类型为"ping",数据字段都使用了 JSON 格式,数组字段中包含了每个事件流生成时的 ISO 8601 时间戳。而且会随机返回一些无事件类型的消息。
java代码示例
package com.hj.ServerSendEvent;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Random;
* describe
* @author huangjuan
* @date 2023/2/15 9:59
@Controller
public class ServerSentEventController {
@RequestMapping(value = "/push",produces = "text/event-stream")
@ResponseBody
public String pushToBrowser() {
Random random = new Random();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
return "data: server send event push message test:" + random.nextInt() + "\n\n";
new EventSource
默认只支持get请求
用post的方式请求eventSource,常用的就是通过fetchEventSource这个库来实现
npm 仓库地址:https://www.npmjs.com/package/@microsoft/fetch-event-source
import { fetchEventSource } from '@microsoft/fetch-event-source';
const ctrl = new AbortController();
fetchEventSource('/api/sse', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
body: JSON.stringify({
foo: 'bar'
}),
signal: ctrl.signal,
});
但是我的项目是简单的html项目,需要把npm包转换成js文件
这里使用一个工具 browserify
,
npm install -g browserify
使用 browserify main.js -o bundle.js

压缩出来的js文件,有时需要把抛出来的方法挂在window下,然后在html里引入使用,看情况而定。
如果要对js文件 压缩混淆
npm i browserify minify crypto-js
browserify -r crypto-js -s CryptoJS > crypto.js // -r 指定npm包名 -s 在代码中使用的模块名
minify crypto.js > crypto.mini.js // 压缩js文件


这个我在监听错误的回调里面 抛出了一个错误throw new Error(err)
,发现可以解决
fetchEventSource(`${this.baseUrl}/testurl`, {
method: 'GET',
signal: that.abortController.signal,
headers: {
'Content-Type': 'application/json',
'devicetype': 'pc',
'token': that.token
async onopen(response) {
if (response.ok) {
console.log('连接了');
} else {
onmessage(msg) {
if (msg.event === 'FatalError') {
throw new Error(msg.data);
if (msg.data !== '[DONE]') {
onclose() {
onerror(err) {
throw new Error(err)
});
参考文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Server-sent_events/Using_server-sent_events#%E4%BA%8B%E4%BB%B6%E6%B5%81%E6%A0%BC%E5%BC%8F
https://developer.mozilla.org/zh-CN/docs/Web/API/EventSource
https://blog.csdn.net/suwk1009/article/details/129323226
https://www.npmjs.com/package/@microsoft/fetch-event-source
长轮训虽然可以避免短轮训造成的服务端过载,但在服务端返回数据后仍需要客户端主动发起下一个长轮训请求,等待服务端响应,这样仍需要底层的连接建立而且服务端处理逻辑需要相应处理,不符合逻辑上的流程简单的服务端推送;websocket连接相对而言功能最强大,但是它对服务器的版本有要求,在可以使用websocket协议的服务器上尽量采用此种方式;
vue使用EventSource
mounted() {
if(typeof (EventSource) !== 'undefined') { //支持eventSource
var postURL = 'http……';
this.source = new EventSource(postURL);
let self = this;//因EventSource中this的指向变了,所以要提前存储一下
this.source.onopen = function
iex(1)> {:ok, pid} = EventsourceEx.new("https://url.com/stream", stream_to: self)
{:ok, #PID<0>}
iex(2)> flush
%EventsourceEx.Message{data: "1", event: "message", id: nil}
%EventsourceEx.Message{data: "2", event: "message", id: nil}
%Eventsour
什么是ajax?
ajax的出现,刚好解决了传统方法的缺陷。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
XMLHttpRequest 对象
XMLHttpRequest对象是ajax的基础,XMLHttpRequest用于在后台与服务器交换...
MDN详解:Web API 接口参考>EventSource
(二)使用方式:
const eventSource = new EventSource('/api/test');
// 正常的EventSource,我们只关心以下三个事件
* open:订阅成功(和后端连接成功)
eventSource.addEventListener("open", function(e) {
console.log('ope
服务器发送事件
使用Spring WebFlux框架和React式Kafka的WebFlux服务器,该服务器公开了REST API,以使客户端发出安全的HTTP请求。 一旦在客户端和Web Flow服务器之间建立了安全连接,除非有必要,否则它将使用来自Kafka主题的消息并异步推送数据,而无需关闭与客户端的连接。
使用Reactive Kafka看一下Spring WebFlux上的这个。
鲍西斯订阅
使用 EventSource / Server Sent Events 进行 baucis 订阅的代码草图
这是向 baucis 添加 SSE 支持的可能实现的 v0 早期版本示例代码草图。 它不会编译,也不应该在生产中依赖它。
SSE 类似于 WebSockets,但为服务器和客户端之间的实时通信提供了更丰富的语义。
Server-Sent 事件 – 单向消息传递
Server-Sent 事件指的是网页自动获取来自服务器的更新。
以前也可能做到这一点,前提是网页不得不询问是否有可用的更新。通过服务器发送事件,更新能够自动到达。
例子:Facebook/Twitter 更新、股价更新、新的博文、赛事结果等。
浏览器支持
所有主流浏览器均支持服务器发送事件,除了 Internet Explorer。
接收 Server-Sent 事件通知
善于观察的朋友一定会敏锐地发现ChatGPT网页端是逐句给出问题答案的,同样,ChatGPT后台Api接口请求中,如果将Stream参数设置为True后,Api接口也可以实现和ChatGPT网页端一样的流式返回,进而更快地给到前端用户反馈,同时也可以缓解连接超时的问题。
Server-sent events(SSE)是一种用于实现服务器到客户端的单向通信的协议。使用SSE,服务器可以向客户端推送实时数据,而无需客户端发出请求。
SSE建立在HTTP协议上,使用基于文本的数据格式(通常是JSON)进行通信。客户端通过创建一个EventSource对象来与服务器建立连接,然后可以监听服务器发送的事件。服务器端可以随时将事件推送给客户端,客户端通过监听事件来接收这些数据。
ChatGPT的Server-sent events应用
首先打开ChatGPT网页端,随便问一个问题,然后进入网络选单,清空历史请求记录后,进行网络抓包监听:
可以看到,在触发了回答按钮之后,页面会往后端的backend-api/conversation对话接口发起请求,但这个接口的通信方式并非传统的http接口或者