HTTP中的event-stream,eventsource,SSE,chatgpt,stream request,golang

我们都知道chatgpt是生成式的,因此它返回给客户端的消息也是一段一段的,所以普通的HTTP协议无法满足,当然websocket是能满足的,但是这个是双向的通信,其实 SSE(Server-Sent Events) 正好满足这个需求。

SSE相比websocket的优点:

  • SSE是使用http协议,而websocket是一种单独的协议。
  • SSE是单向传输,只能服务端向客户端推送,websocket是双向。
  • SSE支持断点续传,websocket需要自己实现。
  • SSE支持自动重连、轻量级。
  • SSE支持发送自定义类型消息。
  • SSE的响应头Content-Typ:text/event-stream

要实现SSE,服务端需要设置以下Headers

"Content-Type""text/event-stream"
"Cache-Control""no-cache"
"Connection""keep-alive"
"Access-Control-Allow-Origin": "*" // 跨域问题
一、前端代码

我看网络上有两种实现方式:fetch 和 EventSource

fetch方式

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>Event Stream Demo</title><style type="text/css">body {font-family: Arial, sans-serif;text-align: center;}#event-stream-data {margin: 50px auto;max-width: 600px;border: 1px solid #ccc;padding: 10px;}</style>
</head><body><div id="event-stream-data"></div>
</body><script>const eventStreamDataElement = document.getElementById('event-stream-data');function handleEventStreamMessage(event) {console.log(event)const eventText = event.data;displayEvent(eventText);}function displayEvent(eventText) {const eventElement = document.createElement('p');eventElement.textContent = eventText;eventStreamDataElement.appendChild(eventElement);}function connectToEventStream() {fetch('http://127.0.0.1:8080/stream', {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded'},body: {data: 'example'}}).then(response => {const reader = response.body.getReader();const decoder = new TextDecoder();return reader.read().then(function processResult(result) {// console.log(result)if (result.done) {return;}const chunk = decoder.decode(result.value, {stream: true});handleEventStreamMessage({data: chunk});return reader.read().then(processResult);});}).catch(error => {console.error('Error occurred while fetching event stream:', error);});}connectToEventStream();
</script></html>

EventSource方式

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>Event Stream Demo</title><style type="text/css">body {font-family: Arial, sans-serif;text-align: center;}#event-stream-data {margin: 50px auto;max-width: 600px;border: 1px solid #ccc;padding: 10px;}</style>
</head><body><div id="event-stream-data"></div>
</body><script type="text/javascript">const eventStreamDataElement = document.getElementById('event-stream-data');function handleEventStreamMessage(event) {console.log(event)const eventText = event.data;displayEvent(eventText);}function displayEvent(eventText) {const eventElement = document.createElement('p');eventElement.textContent = eventText;eventStreamDataElement.appendChild(eventElement);}// 向后端服务器发起sse请求const es = new EventSource("http://127.0.0.1:8080/stream");// Event 和 Message 分开处理,需要显示的监听事件,否则不会处理事件es.onmessage = function (e) {handleEventStreamMessage(e);}// 监听事件流es.addEventListener("start", (e) => {handleEventStreamMessage(e);});es.addEventListener("end", (e) => {handleEventStreamMessage(e);// 一定要关闭连接,否则会一直轮训es.close()});es.onerror = function (e) {// readyState说明// 0:浏览器与服务端尚未建立连接或连接已被关闭// 1:浏览器与服务端已成功连接,浏览器正在处理接收到的事件及数据// 2:浏览器与服务端建立连接失败,客户端不再继续建立与服务端之间的连接console.log("readyState = " + e.currentTarget.readyState);}
</script></html>
二、GIN 中自带的 SSE
package mainimport ("time""github.com/gin-contrib/sse""github.com/gin-gonic/gin"
)func main() {engin := gin.Default()engin.Any("/stream", func(c *gin.Context) {c.Header("Access-Control-Allow-Origin", "*")c.Header("Access-Control-Allow-Headers", "*")// c.SSEvent("start", "start...")sse.Event{Id:    "1",Event: "start",Data:  "start...",}.Render(c.Writer)c.Writer.Flush()time.Sleep(1 * time.Second)for i := 0; i < 10; i++ {sse.Event{Id:   "1",Data: "SSE data",}.Render(c.Writer)c.Writer.Flush() // 需要手动刷新输出缓冲区time.Sleep(1 * time.Second)}// c.SSEvent("end", "end...")sse.Event{Id:    "1",Event: "end",Data:  "end...",}.Render(c.Writer)})engin.Run(":8080")
}

说明

// sse.Event
type Event struct {Event stringId    stringRetry uintData  interface{}
}

sse.Event 结构在渲染的时候会自动加上前缀和后面的回车,比如id:xxx\nevent:xxx\nretry:xxx\ndata:xxx\n\n,因此在设置内容的时候不需要关心format。

并且会自动填充两个响应头
Content-Type: text/event-stream
Cache-Control: no-cache

如果服务器端提供了event参数,那么客户端就需要使用addEventListener 显式监听这个事件,才会正常获取消息,否则事件不会触发。如果服务器端没有提供event 参数,只有id、data等,可以使用onmessage回调监听消息。

id 的意思是 lastEventId,用途不明。

完整的数据结构是:id:xxx\nevent:xxx\nretry:xxx\ndata:xxx\n\n

一般只需要data字段即可,后面接一个json串。

前端使用EventSource对象发起请求

在这里插入图片描述

在这里插入图片描述

使用 fetch 的方式发起请求,需要先打开调试并打开接口的响应预览tab,否则是看不到响应结果的。

在这里插入图片描述

在这里插入图片描述

使用EventSource对象发起请求与使用fetch方式的请求两者的区别在于,在处理响应结果的时候,前者是按照SSE协议来处理消息中的\n\n\n以及那几个字段;而后者则不会。下面是打印结果

EventSource示例

在这里插入图片描述

fetch示例

在这里插入图片描述

上面的实现仅仅是为了满足ChatGPT这种对话形式,或者说仅仅实现了一个长连接下的流式传输,即使不适用SSE也能实现。

如果想要实现真正的消息推送还需要对客户端连接进行管理,在这一块,SSE和websocket要做的事情差不多,这里就不展开了。

三、使用golang请求chatgpt

大部分的时候,在客户端和chatgpt之间还需要有一个代理层,即它代替用户向chatgpt发起请求,接收数据流,然后将数据流转发给用户。前面已经实现了SSE,所以,这里需要处理的是golang发起stream request。

package mainimport ("bufio""bytes""errors""fmt""io""log""net/http""strings""time"
)func main() {client := &http.Client{Timeout: time.Second * 20}req, _ := http.NewRequest("POST", "http://127.0.0.1:8080/stream", strings.NewReader(""))resp, err := client.Do(req)if err != nil {log.Fatal(err)}reader := bufio.NewReader(resp.Body)defer resp.Body.Close()for {rawLine, err := reader.ReadBytes('\n')if errors.Is(err, io.EOF) {return} else if err != nil {fmt.Println(err)return}fmt.Println(string(bytes.TrimRight(rawLine, "\n")))}
}
id:1
event:start
data:start...id:1
data:SSE dataid:1
data:SSE dataid:1
data:SSE dataid:1
data:SSE dataid:1
data:SSE dataid:1
data:SSE dataid:1
data:SSE dataid:1
data:SSE dataid:1
data:SSE dataid:1
data:SSE dataid:1
event:end
data:end...

golang对接openai:https://github.com/sashabaranov/go-openai

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/432358.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[数据集][目标检测]手机识别检测数据集VOC+YOLO格式9997张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;9997 标注数量(xml文件个数)&#xff1a;9997 标注数量(txt文件个数)&#xff1a;9997 标注…

用Python提取PowerPoint演示文稿中的音频和视频

将多种格式的媒体内容进行重新利用&#xff08;如PowerPoint演示中的音频和视频&#xff09;是非常有价值的。无论是创建独立的音频文件、提取视频以便在线分发&#xff0c;还是为了未来的使用需求进行资料归档&#xff0c;从演示文稿中提取这些媒体文件可以为多媒体内容的多次…

Remotion:使用前端技术开发视频

前言 最近做文章突然想到很多文章其实也可以用视频的方式来展现&#xff0c;以现在短视频的火爆程度&#xff0c;肯定能让更多的人看到。 恰巧最近看了很多关于动画的前端 js 库&#xff0c;那如果将这些动画帧连续起来&#xff0c;岂不是就成了一个视频吗&#xff1f; 而且…

本地电脑基于nginx的https单向认证和双向认证(自制证书+nginx配置)保姆级

目录 1、背景 2、运行环境 3、工具下载 3.1、OpenSSL下载 3.2、nginx下载 4、制作https证书&#xff1a; 4.1、CA与自签名&#xff1a; 4.2、制作CA根证书&#xff08;公钥&#xff09; 4.3、制作服务端证书&#xff1a; 4.4、制作客户端证书&#xff1a; 4.5、制作…

了解云计算工作负载保护的重要性,确保数据和应用程序安全

云计算de小白 云计算技术的快速发展使数据和应用程序安全成为一种关键需求&#xff0c;而不仅仅是一种偏好。随着越来越多的客户公司将业务迁移到云端&#xff0c;保护他们的云工作负载&#xff08;指所有部署的应用程序和服务&#xff09;变得越来越重要。云工作负载保护&…

性能指标前言:`DOMContentLoaded`和`load`

前言&#xff1a;DOMContentLoaded和load 最初&#xff0c;评价前端页面加载性能有两个指标&#xff1a;DOMContentLoaded和load事件&#xff0c;分别代表 DOM 树构造完成和首屏资源加载完成。 DOM 文档加载步骤&#xff1a; 解析 html 结构加载外部脚本和样式表文件解析并执…

生活英语口语柯桥学英语“再确认一下“ 说成 “double confirm“?这是错误的!

在追求英语表达的过程中&#xff0c;我们常常会遇到一些看似合理实则错误的表达习惯。今天&#xff0c;我们就来聊聊一个常见的误区——“再确认一下”被误译为“double confirm”。 “再次确认”不是double confirm 首先&#xff0c;我们需要明确&#xff0c;“double confi…

线性基定义性质及例题

线性基的定义 以上是官方给出的线性基的定义&#xff0c;但是需要一定的线性代数的基础&#xff0c;其实线性基很好理解&#xff0c;我们用下面一个例子去讲解 假设有3个数&#xff0c;1,2,3&#xff0c;我们这三个数互相异或总共有八种可能&#xff0c;我们能否找到一组数去…

HelpLook VS GitBook,在线文档管理工具对比

在线文档管理工具在当今时代非常重要。随着数字化时代的到来&#xff0c;人们越来越依赖于电子文档来存储、共享和管理信息。无论是与团队合作还是与客户分享&#xff0c;人们都可以轻松地共享文档链接或通过设置权限来控制访问。在线文档管理工具的出现大大提高了工作效率和协…

探索GPU算力在大模型和高性能计算中的无限潜能

在当今科技领域&#xff0c;大模型和高性能计算正以惊人的速度发展。大模型如语言模型、图像识别模型等&#xff0c;规模越来越大&#xff0c;精度越来越高&#xff0c;能够处理复杂的任务和生成逼真的结果。高性能计算则凭借强大的计算能力&#xff0c;推动着科学研究、工程设…

PMP与CMMI:两种管理方法的对比

PMP与CMMI&#xff1a;两种管理方法的对比 PMP&#xff1a;专注于项目管理CMMI&#xff1a;组织过程改进的框架总结&#xff1a;互补而非替代 在现代企业管理中&#xff0c;项目管理和组织能力成熟度模型集成&#xff08;CMMI&#xff09;是两个经常被提及的概念。虽然它们都是…

Java项目实战II基于Java+Spring Boot+MySQL的汽车销售网站(文档+源码+数据库)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在数字化时…

Clion使用vcpkg管理C/C++包

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Clion安装vcpkg二、使用步骤1.切换到清单模式2.开始安装包 三、测试代码总结 前言 Linux上的库基本都可以通过apt或yum等包管理工具来在线安装包&#xff…

力扣 简单 876.链表的中间结点

文章目录 题目介绍题解 题目介绍 题解 法一&#xff1a; class Solution {public ListNode middleNode(ListNode head) {ListNode cur head;int n 0;while (cur ! null) {n;cur cur.next;}ListNode curr head;for (int i 0; i < n / 2; i) {curr curr.next;}return …

Unity对象池的高级写法 (Plus优化版)

唐老师关于对物体分类的OOD的写法确实十分好&#xff0c;代码也耦合度也低&#xff0c;但是我有个简单的写法同样能实现一样的效果&#xff0c;所以我就充分发挥了一下主观能动性 相较于基本功能&#xff0c;这一版做出了如下改动 1.限制了对象池最大数量&#xff0c;多出来的…

【hot100-java】【括号生成】

R9-回溯篇 枚举填左括号 class Solution {private int n;private char[] path;private final List<String> retnew ArrayList<>();public List<String> generateParenthesis(int n) {this.nn;//所有括号长度都是n*2pathnew char [n*2];dfs(0,0);return ret;…

求10 个整数中最大值

我们需要10个整数之中求出10个整数之中的最大值所以我们先要将10个整数先放置到一个容器之中&#xff0c;我们初期就使用数组的形式存放10个数组即设置数组arr[10]&#xff0c;我们要将10个数组之中的数字输出出来&#xff0c;我们这里使用的是遍历循环输出数组。我们这里是使用…

Redis 字符串类型的典型应用场景

目录 1. 缓存功能 2. 计数功能 3. 共享会话&#xff08;Session&#xff09; 4. 手机验证码 前言 这里将详细介绍 Redis 字符串类型在实际开发中的几个典型应用场景&#xff0c;并提供相应的伪代码示例。 1. 缓存功能 场景描述 在许多Web应用中&#xff0c;数据通常需要…

这6个aigc软件,性价比之王

随着人工智能技术的迅猛发展&#xff0c;越来越多的应用程序开始集成AIGC&#xff08;人工智能生成内容&#xff09;功能&#xff0c;提升用户体验。本文将介绍六款实用的AIGC软件下载&#xff0c;帮助您在各个领域提高工作效率&#xff0c;释放创造力。 1、即时 AI 作为国内…

Acwing Floyd算法

Acwing Floyd算法 Floyd-Warshall 算法&#xff0c;用于解决图中任意两点之间的最短路径问题。Floyd-Warshall 是一种 多源最短路径算法&#xff0c;可以处理带正权或负权的边&#xff0c;但要求图中不能有负权回路。 通过三层循环对每个顶点作为中转点 k 进行更新。通过检查…