application/octet-stream vs text/event-stream

9,747 阅读10分钟

先上效果视频:zhuanlan.zhihu.com/p/614824613?

最近的 AI 很火,各种人工智能相继问世,在使用这些工具的同时,发现了比较有意思的一个细节。对于 AI 的回复,会在页面上根据光标一个一个字的打印出来,就想去了解下这个的实现细节。

主要是了解 客户端和服务端对于回答的内容的数据实时传输的实现。

web 通信方式:

  • 轮询
  • 长轮询
  • 数据流
  • websocket
  • eventsource

我们这次说的 application/octet-stream 和 text/event-stream 分别属于上述通信方式的长轮询和eventsource。

  • SSE (server send events)content-type:text/event-stream

image-20230317143544053.png

  • 数据流content-type:application/*, 本次以 application/octet-stream 为例

image-20230317143641168.png

application/octet-stream

application/octet-stream 是什么?

application/octet-stream 是 MIME 中的一种。MIME 的独立类型有如下几种:

 text/plain
 text/html
 image/jpeg
 image/png
 audio/mpeg
 audio/ogg
 audio/*
 video/mp4
 application/*
 application/json
 application/javascript
 application/ecmascript
 application/octet-stream
 …

独立类型表明了对文件的分类:

类型描述典型示例
text表明文件是普通文本,理论上是人类可读text/plain, text/html, text/css, text/javascript
image表明是某种图像。不包括视频,但是动态图(比如动态 gif)也使用 image 类型image/gif, image/png, image/jpeg, image/bmp, image/webp, image/x-icon, image/vnd.microsoft.icon
audio表明是某种音频文件audio/midi, audio/mpeg, audio/webm, audio/ogg, audio/wav
video表明是某种视频文件video/webm, video/ogg
application表明是某种二进制数据application/octet-stream, application/pkcs12, application/vnd.mspowerpoint, application/xhtml+xml, application/xml, application/pdf

二进制文件没有特定或已知的 subtype,即使用 application/octet-stream

application/octet-stream

这是应用程序文件的默认值。意思是 未知的应用程序文件, 浏览器一般不会自动执行或询问执行。浏览器会像对待 设置了 HTTP 头Content-Disposition 值为 attachment 的文件一样来对待这类文件。

说人话就是,浏览器并不认得这是什么类型,也不知道应该如何展示,只知道这是一种二进制文件,因此遇到content-type为application/octet-stream的文件时,浏览器会直接把它下载下来。这个类型一般会配合另一个响应头Content-Disposition,该响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。

application/octet-stream 能做什么?使用场景是什么?

我们先看下下面的几种情况:

 Content-Type: application/octet-stream
 Content-Disposition: attachment; filename="picture.png"

这个的意思是:我不知道这个传输过来的东西是什么东西,把它存储为一个文件,文件名叫做picture.png.

 Content-Type: image/png
 Content-Disposition: attachment; filename="picture.png"

这个的意思是:我知道传输过来的是个png结尾的图片,把它存储为一个文件,文件名叫做picture.png.

 Content-Type: image/png
 Content-Disposition: inline; filename="picture.png"

这个的意思是:这是一个 png 图片,请显示它,除非你不知道如何显示 png 图片。否则,如果用户选择保存它,我们建议你保存它的文件名称为 picture.png。这个用户选择保存它一般是右键点击另存为的这个行为。

基于以上情况,可以了解到 application/octet-stream 的使用场景主要是配合 content-disposition 做文件传输。如果不配合 content-disposition,返回的就是文件流内容。

怎么使用 application/octet-stream ?

以 node 实现为例:

 router.post('/chat-process', auth, async (req, res) => {
   res.setHeader('Content-type', 'application/octet-stream')
 ​
   try {
     const { prompt, options = {} } = req.body as { prompt: string; options?: ChatContext }
     let firstChunk = true
     await chatReplyProcess(prompt, options, (chat: ChatMessage) => {
       // **数据传输
       res.write(firstChunk ? JSON.stringify(chat) : `\n${JSON.stringify(chat)}`)
       firstChunk = false
     })
   }
   catch (error) {
     res.write(JSON.stringify(error))
   }
   finally {
     // ** 终止数据传输
     res.end()
   }
 })

客户端数据接收:

 await fetchChatAPIProcess<Chat.ConversationResponse>({
   prompt: message,
   options,
   signal: controller.signal,
   onDownloadProgress: ({ event }) => {
     console.log(11111, event)
     const xhr = event.target
     const { responseText } = xhr
     // Always process the final line
     const lastIndex = responseText.lastIndexOf('\n')
     let chunk = responseText
     if (lastIndex !== -1)
       chunk = responseText.substring(lastIndex)
     try {
       const data = JSON.parse(chunk)
     }
     catch (error) {
       //
     }
   },
 })

其中,拿到的event格式为:

image-20230317150047526.png 当数据没有传输完毕时,每当数据有变化,都会源源不断的传到客户端来,客户端需要对拿到的event对象进行格式化处理:

 const xhr = event.target
 const { responseText } = xhr
 const data = JSON.parse(responseText)

有哪些优缺点?

使用 "application/octet-stream" 媒体类型来实现服务端推送也被称为基于长轮询的技术,其优缺点如下:

优点:

  • 能够在现有的 HTTP 协议基础上实现服务端推送,不需要额外的网络协议支持,因此非常容易部署和使用。
  • 可以通过控制间隔时间实现近实时的数据传输,适用于需要实时更新、但是更新频率不高的场景。
  • 支持广泛,几乎所有支持 HTTP 的客户端都可以使用该技术进行数据交换。

缺点:

  • 长轮询技术本身并没有提供完整的实时通信解决方案,在复杂场景下需要自行设计通信协议和处理机制,增加了开发难度和成本。
  • 长轮询技术会占用大量服务器资源,因为每个连接需要在服务器端保持打开状态,而且客户端也需要不断发送请求,增加了带宽和服务器负载压力。
  • 长轮询技术对消息的处理机制比较简单,不能很好地处理分发过程中的重试、ack 等问题,容易出现消息丢失或者重复消费等问题。

总体而言,基于长轮询的技术适合于一些轻量级的实时通讯场景,如在线聊天、实时公告等,但是在复杂的实时通讯需求下可能需要考虑更为专业的实时通讯方案。

类似的 MIME 分类为 application/* 的都可以依此进行数据传输。 sse.png

oetct.png

有哪些注意事项?

application/octet-stream 是一种使用广泛的二进制流数据格式,它用于在Internet上发送二进制文件,例如图片、音频、视频等。以下是在使用 application/octet-stream 时需要注意的几个要点:

  • 基于MIME类型的检测 由于 application/octet-stream 是一种通用的MIME类型,因此在数据交换过程中需要进行 特定文件类型的预处理或后处理。为了避免处理错误的数据,您应该始终使用处理了MIME类型的实用程序或库。这样可以确保您的应用程序能够处理正确的数据类型。
  • 考虑到文件大小 application/octet-stream 适用于发送大型二进制文件。当您从服务器下载大型文件时,它可以提高下载速度。但是,由于文件大小限制,如果您在发送数据时使用application/octet-stream,则需要考虑网络带宽带宽是否能够支持快速传输。
  • 安全信息需要特别处理 由于 application/octet-stream传输的数据是二进制文件,因此可能包含一些敏感或机密的信息。在发送或接收这些文件时,您应该确保传输过程中所使用的传输协议是安全的。例如,您可以使用HTTPS协议来进行安全传输。

text/event-stream

text/event-stream 是什么?

text/event-stream是一种用于服务器向客户端推送消息、事件和通知的数据格式,属于HTML5的一部分。text/event-stream将消息视为一系列流事件,以文本形式发送。该格式适用于实现服务器发送事件(SSE)的应用程序,其中某些服务器可以在客户端通信时单向推送数据。

工作原理:

客户端通过发送HTTP请求到服务器,请求获取特定资源的SSE流。当服务器有新的事件可以推送时,将推送到SSE流中。客户端会注意到流的变化,并从流中读取推送的事件。客户端可以使用JavaScript EventSource对象来消费推送的事件数据。

text/event-stream 能做什么?使用场景是什么?

主要用于实时应用程序的开发,如在线游戏、社交媒体、消息推送等场景。

  • 实时游戏

text/event-stream是实时游戏和在线游戏交互中常用的技术。服务器可以使用它向客户端实时推送有关游戏中其他玩家的数据,以便玩家可以及时跟踪游戏中发生的事情。

  • 社交网络

社交媒体网站使用text/event-stream的主要原因是实时通知其用户应用程序中新事件的发生。如果用户在聊天或其他活动中收到新消息或其他实时更新,则 text/event-stream可以在不需要用户刷新页面的情况下通知它们。

  • 处理大量数据

text/event-stream是处理大量数据的一种流行的技术,例如高解析度视频流或音频流。使用它可以轻松地传输大量的数据,从而提高应用程序的响应能力。

怎么使用 text/event-stream?

text/event-stream可以通过以下几个步骤来使用:

  • 服务器端设置SSE

在服务器端,您需要向客户端提供一个SSE流。您可以使用Node.js编写服务器,并使用特定的npm包将SSE流添加到您的服务器中。

  • 建立SSE连接

在客户端,您需要建立与SSE流的连接。您可以使用JavaScript中的EventSource对象来实现此操作。使用该对象可以连接到SSE流,并订阅来自服务器的事件信息。您还可以在EventSource对象中添加回调处理程序,以便处理由服务器推送的事件。

  • 推送事件

在服务器端,您需要将要推送的事件写入您的SSE流中。您可以使用res.write()方法并将事件作为文本字符串添加到SSE流中。事件字符串应该遵循text/event-stream数据格式的结构。

  • 关闭SSE连接

最后,在页面或会话结束时,请确保关闭SSE连接。您可以使用JavaScript中的EventSource对象的close()方法来实现此操作。

服务器端的代码:

 const http = require('http');
 ​
 http.createServer((req, res) => {
     res.writeHead(200, {
         'Content-Type': 'text/event-stream',
         'Cache-Control': 'no-cache',
         'Connection': 'keep-alive'
     });
 ​
     setInterval(() => {
         res.write('data: The server time is: ' + new Date() + '\n\n');
     }, 1000);
 ​
     req.connection.addListener('close', () => {
         console.log('SSE connection closed!');
     }, false);
 }).listen(8080);
 ​
 console.log('SSE Server listening on port 8080');

客户端的代码:

 const evtSource = new EventSource("http://localhost:8080/");
 ​
 evtSource.onmessage = function(event) {
     const serverTime = document.getElementById('serverTime');
     serverTime.innerHTML = event.data;
 };

有哪些优缺点?

  • 实时更新数据,更改数据立即在客户端进行显示。
  • 可以消除像轮询这样的开销大的技术。
  • 可以更好地使用资源,因为它可以一次连接多个资源。

有哪些注意事项?

  • 不要使用错误的数据格式: text/event-stream要求使用特殊的数据格式,将每个事件都规范化并与前面的事件分隔开。确保服务器返回的数据格式是正确的,否则客户端将无法正确解析数据。
  • 避免发送太多事件: 如果您发送太多事件,会给服务器带来负担,导致响应变慢。您可以通过在事件之间留出适当的时间来缓解这种情况,以避免服务器过度负担。
  • 考虑安全问题: 在使用text/event-stream时,安全也是值得注意的问题。您需要确保服务器向客户端发送的数据是安全的,以防止攻击,并且在处理客户端请求时也需要采取适当的安全措施。
  • 避免浏览器兼容性问题: 只有较新的浏览器才支持EventSource对象。因此,如果您的应用程序需要支持一些较旧的浏览器,则需要使用其他技术来实现实时更新数据。

先上效果视频:zhuanlan.zhihu.com/p/614824613?

参考:

Do I need Content-Type: application/octet-stream for file download?

http协议-html与http的区别:web通信方式