本文咱们来评论一个或许比较风趣的问题: Server-Sent Event(服务端推送事情,SSE)。
SSE尽管也是HTTP协议的标准技能,但一直处于比较边缘的状况。笔者也一直知道这个技能,但一直没有找到适宜的运用场合来实践。直到前一阵ChatGPT比较抢手,有人提到了它的会话进程的呼应信息是运用SSE而非大家以为的WS完成的,才有兴趣去了解了一下,而且在仔细的领会这个技能的原理和或许的运用场景,遂有了本文和相关的探讨。
另注,本来笔者想复现一下ChatGPT的恳求进程的,但在笔者的系统上,确看不懂这个交互进程,不知道是由于OpenAI的技能迭代仍是其他什么原因,看不到详细的恳求呼应数据,下面就只能就笔者自己的程序示例来进行阐明。
关于SSE
和一切HTTP协议的衍生技能一样,SSE也有其开展和完善的进程,笔者在网络上找到了如下资料,可以看到它最早的一个版别在2009年就现已有了,所以,它实际上是HTTP的一部分,而非HTML5那一波新技能(大约在2014年左右发布)的组成。
经过相关技能稳定的阅览和了解,笔者了解,SSE是一种服务端数据主动推送的技能,它的出现是为了处理常规Web运用简略恳求/呼应作业模型的一些约束和约束,然后提供愈加丰富的事务能力支撑。
与相似的技能和完成相比,它的主要特征和优势包含:
- 单向通讯: 从服务器向客户端推送数据,运用中要注意这一点,客户端只有一次树立恳求的时机
- 主动重连: 假如衔接断开,浏览器会主动从头衔接,这个其实是浏览器的完成支撑的
- 事情驱动: 服务器经过发送事情来推送新数据
- 轻量级: SSE运用文本格式,协议简略,低延迟
- 兼容性好:干流浏览器都支撑
其实,假如运用妥当,SSE的运用场景也可以十分广泛:
- 发布式的实时推送: 谈天音讯、股票报价、新闻通知等
- 日志流: 服务器日志实时输出到网页
- 数据流展现: 服务器数据流展现到实时更新的图表等
- 事务系统集成: 事务系统之间的单向数据和信息推送
同时,咱们也要注意到SSE的一些约束,然后可以在适宜的事务和场景中加以运用。
SSE作业原理和流程
下面,咱们来评论一下SSE的作业原理,这有助于咱们了解其技能特性。其作业原理和流程如下图所示:
这个进程包含:
- 客户端发送特定恳求树立衔接
- 服务器经过写入Response发送事情来推送数据
- 客户端经过事情监听接收更新的数据。
大致如此,但真正的魔鬼在细节里。下面咱们会有相关的完成代码,经过这些代码的剖析和解读,咱们将会看到,其实SSE没有任何共同的技能,仅仅在HTTP协议上进行的扩展。理论上而言,它也不需求客户端或许浏览器的特别支撑,便是说其实没有浏览器支撑版别的问题,只不过浏览器将其相关的处理封装称为标准功用,可以直接运用,而不需求开发者编写外部程序完成而已。
下面的代码,咱们分为服务器、客户端(nodejs程序)和浏览器三个部分评论,为了便利评论和研讨,笔者编写了相关的示例代码,这些代码的完成根据标准nodejs和浏览器环境,可以独立运转,没有任何第三方程序库和依赖。
服务端
咱们先来看一看下面根据nodejs http模块完成的SSE服务端的示例代码,然后再做简略的剖析:
const
http = require("http"),
HOST = "127.0.0.1",
PORT = 8088,
EVHEAD = "data: ",
EVEND = "nn";
const srvTime = (res)=>{
res.write( EVHEAD + "servertime - " + (0 | Date.now()/1000) + EVEND );
setTimeout(()=> srvTime(res), 1000 + 30000* Math.random());
}
const handle = (req,res)=>{
console.log("reqest:",req.url);
res.writeHead(200, {
"Access-Control-Allow-Origin": "*",
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
srvTime(res);
};
const start=()=>{
http
.createServer(handle)
.listen(PORT, HOST, null,()=>{
console.log("Service Started", HOST, PORT);
});
}; start();
nodejs内置HTTP Server的相关完成和作业办法,咱们假设读者现已十分了解,咱们这儿直接阐明SSE的完成关键。
首要,在收到客户端恳求的时分,需求经过呼应的头进行一些设置:
- 跨域: 由于咱们后面的浏览器客户端是HTML文件办法发动,需求跨域
- Content-Type: 这是第一个关键,需求声明呼应内容的类型,是event-stream(事情流)
- Cache-Control: 明显应该为无缓存
- Connection: 坚持衔接,十分好了解,这样才干继续呼应
其次,详细呼应的内容,便是第二个关键了,便是event-stream,这是一个约定标准的数据格式。它可以是一个字符串,咱们这儿的格式为:
“data: <内容> nn”
表示,这个片段的信息类型是data,nn作为数据包间的分隔符。详细的格式,或许需求参阅相关的技能文件。它的界说是这样的:
stream = [ bom ] *event
event = *( comment / field ) end-of-line
comment = colon *any-char end-of-line
field = 1*name-char [ colon [ space ] *any-char ] end-of-line
end-of-line = ( cr lf / cr / lf / eof )
eof = < matches repeatedly at the end of the stream >
; characters
lf = %x000A ; U+000A LINE FEED (LF)
cr = %x000D ; U+000D CARRIAGE RETURN (CR)
space = %x0020 ; U+0020 SPACE
colon = %x003A ; U+003A COLON (:)
bom = %xFEFF ; U+FEFF BYTE ORDER MARK
name-char = %x0000-0009 / %x000B-000C / %x000E-0039 / %x003B-10FFFF
; a Unicode character other than U+000A LINE FEED (LF), U+000D CARRIAGE RETURN (CR), or U+003A COLON (:)
any-char = %x0000-0009 / %x000B-000C / %x000E-10FFFF
; a Unicode character other than U+000A LINE FEED (LF) or U+000D CARRIAGE RETURN (CR)
这儿的data,应该便是一个数据的特色,其他可用的特色还包含id、event、retry等等,可以用于操控推送的进程。
这样,服务器在呼应的时分,设置好的头信息和坚持衔接状况,就可以依照数据格式,别离呼应相关的数据,就完成了服务器可以按需主动推送数据的需求。
本例中,呼应的详细内容,便是每隔一段随机时刻,向客户端推送一个服务器时刻的字符串。明显,这儿也可以便利的替换为恣意事务驱动的事务数据。
客户端
根据前面咱们了解到的SSE的作业原理,就知道在客户端方面其实应该也没有任何特其他东西,只需求可以处理分批呼应的数据就可以了。如以下代码:
const doClient = ()=>{
console.log("Client Starting...");
http
.get(`http://${HOST}:${PORT}`,res=>{
res.setEncoding('utf8');
res
.on('data', (chunk) => {
console.log("Get:", chunk);
})
.on('end', () => {
console.log("Client End");
process.exit(0);
});
})
.on("error",e=>{
})
}; setTimeout(doClient, 2000);
当然,这儿的客户端呼应,收到和处理的是纯数据,没有做任何格式的处理和转换。这儿笔者仅仅想阐明,客户端其实是不需求任何特其他设置,便是可以作业的。假如是实际的事务,或许需求做一些呼应数据和信息的处理。
浏览器
在实际的运用中,更多的状况,或许是运用浏览器,在前端适配并运用SSE的场景。下面提供了测试代码,这是一个HTML文件,可以配合前面的服务端代码,直接运用浏览器打开并运转:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>SSE Test</title>
</head>
<body>
<div id = "id_title"></div>
<script>
const tv_title = document.getElementById("id_title");
const evtSource = new EventSource("http://127.0.0.1:8088/sse");
evtSource.onmessage = (event) => {
console.log(event.data);
tv_title.innerText = event.data;
};
</script>
</body>
</html>
大部分代码都是无效的,仅仅为了保证文件完整性,其实中心代码便是两段:
- 根据SSE服务地址,创立一个新的EventSource目标实例,这个实例会主动发起HTTP恳求并和服务器树立永久衔接
- 为这个实例设置一个onmessage回调函数,对服务器的呼应进行处理,这儿仅仅简略的打印和显示出来
经过这段浏览器环境的代码,咱们应该可以看出来一些窍门了。笔者猜测,这个EventSource目标或许接口,其实便是封装了HTTP恳求和呼应处理的一个组件,它可以处理eventStream类型的数据并转换称为event目标,其间最重要的信息便是data特色,可以用于承载事务数据。
所以,逻辑上而言,假如浏览器不支撑SSE,咱们也可以编写js代码,运用标准http恳求进程来模仿完成SSE的功用。当然EventSource的完成和封装或许更好更强健,比如它在中断时,会主动尝试从头树立衔接,假如没有特其他原因,必定是优先运用原生功用的。
多客户端
这儿简略的探讨一下SSE在实际运用场景中或许需求考虑的一些问题。咱们的示例只有一个客户端,但实际的事务场景,必定需求支撑多个客户端,并有或许需求为它们提供个性化的信息推送。
WS的处理计划是运用一个列表来维护一切的WS衔接(Socket),并运用ID和角色来标识它们。SSE也可以运用相似的办法。但需求维护的或许便是一个response列表了(或许代价稍高)。
一个参阅的处理计划和流程如下:
- 在创立http服务时,运用一个路径来标识SSE作业的地址
- 客户端衔接时,需求运用这个作业路径,并需求提供用户或许客户端的标识和验证信息
- 服务端对客户端的验证经过之后,服务端将response目标,加入一个呼应目标列表,并呼应衔接成功的信息
- 在需求推送信息时,服务端根据事务遍历和查找呼应目标列表,对符合事务条件的呼应目标,调用其write办法发送呼应数据
- 客户端封闭时,服务端遍历呼应列表,找到封闭的呼应目标并从列表中清除
明显,这种作业模式也不适用于十分大规模的客户端集合,但用于服务器之间的集成,或许是一个比较适宜的计划。另,上述工程设计仅仅笔者的构想,现在没有实际的完成和实践,仅用于计划的评论和参阅。
和WS的比较
当然,咱们现已应该知道,WebSocket也是一种Web服务器和客户端双向通讯的机制。限于篇幅和主题,本文不会深化研讨WS的机制和完成,仅仅简略的阐明一下笔者对它们的比较和了解。
首要是原理不同,SSE基本上是标准的HTTP协议;而WS应该算是一个HTTP的衍生和晋级协议,所以SSE的兼容性和完成的代价而言,其实都比WS要好。其次,从开发的视点,SSE无需第三方库和程序,而WS的运用,由于或许会比较辅助,一般状况下会运用一些第三方库,如服务端的Socket.io等等。第三,WS是完全的双向通讯,运用愈加灵活;而SSE在初始树立衔接之后,就只能从服务端向客户端推送数据,运用场景受到一些约束。当然,这主要看需求和场合,简略的状况,只需求推送的状况,SSE应该更适宜,而且明显更安全一点。
所以,在实际的事务场景中,挑选WS或许SSE,在开发者了解了事务和技能特色之后,才干根据需求,挑选适宜的技能计划。
调试进程
这儿想再分享一下,在撰写本文,和在编写测试代码的时分,遇到的问题和处理和处理这些问题的进程。
笔者在简略的了解了一下SSE的原理和一些简略的示例代码之后,就着手依照自己的主意编写示例代码。服务端代码和客户端代码的编写和运转都十分顺利,由于都是nodejs环境,这两个部分的逻辑代码,乃至是写在一个js文件傍边的。程序很快的跑起来了,履行办法也是预想的那样。
然后略微增加一点复杂度,便是开端编写浏览器端的代码,为了简略起见(主要是懒,不想自己搭Web服务器),直接运用的是HTML文件办法。发现程序看起来是运转了,而且不出错,却永久不发送HTTP恳求。这才想到,或许会存在跨域的问题,现在的浏览器,对安全的设置仍是比较严厉的,这样,就有了服务端代码中跨域的设置。
跨域设置完成后,浏览器代码确实开端恳求了,但呼应的数据看不到。而且其时看到的是不断的重复恳求,服务端也能看到恳求而且呼应,但浏览器便是不处理呼应的数据。
然后再次仔细阅览了一下相关的技能文档,包含一些别人写的示例代码(他们的代码一般都用在Web结构中),就发现了这些呼应数据,怎样如同都是以”data:” 作为最初,”nn”作为结尾,再结合text/eventstream,觉得这或许便是标准需求的数据格式,调整程序后,便是现在这样样子,可以正确并依照想象的办法运转。
重复恳求的问题也处理好了。由于我写的程序的早期版别,在推送了几个数据后,就调用end办法封闭呼应了,但浏览器中的EventSource有重连机制,就会从头树立恳求呼应进程,这样就看到了重复恳求的状况。而自己写的客户端程序,没有从头恳求的机制,其时就没有发现这个问题。
在实践和过错中学习,或许的收获和前进更大,这次是有了更深化的领会。
小结
本文探讨了SSE技能的原理和特色,并提供了相关的示例代码进行论述和阐明。这个完成的关键包含:
- SSE是正常的HTTP协议完成和操作
- SSE服务端的完成是在呼应头中设置eventStream呼应类型和坚持衔接
- 在浏览器客户端运用EventSource目标完成SSE的衔接、呼应处理和衔接保持
- 可以自行完成和扩展HTTP恳求,来达到相同的效果
最终,笔者现已承认,EventSource目标,在nodejs环境中是不支撑的,还没有时刻研讨是否有相似的完成和代替技能。