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.xdnf.cn/news/146359.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【操作教程】视频监控系统EasyCVR视频汇聚管理平台如何添加用户和角色?

视频监控平台/视频监控系统EasyCVR视频汇聚管理平台以其强大的拓展性、灵活的部署方式、高性能的视频能力和智能化的分析能力&#xff0c;为各行各业的视频监控需求提供了优秀的解决方案。通过简单的配置和操作&#xff0c;用户可以轻松地进行远程视频监控、存储和查看&#xf…

永磁同步电机谐波抑制算法(8)——基于神经网络的傻瓜式(无需知道谐波频率)谐波抑制

1.简介 前面的内容已经介绍了很多谐波抑制的方法&#xff1a;多同步、PIR、陷波器等等。也介绍了比较多的谐波来源&#xff1a;死区&#xff08;5、7、11、13等次相电流谐波&#xff09;、绕组不对称&#xff08;基波不等幅值、3次相电流谐波&#xff09;等等。 上述的方法都…

vue3集成google第三方登陆

网上资源很多&#xff0c;但乱七八糟&#xff0c;踩坑几小时后&#xff0c;发现下面的方式没问题。 npm install vue3-google-login 插件文档&#xff1a;vue3-google-登录 (devbaji.github.io) 修改main.js import ./assets/main.css import { createApp } from vue impor…

Dockerfile部署xxljob

使用Dockerfile部署xxljob 1. 背景 我们在使用定时任务调度时&#xff0c;通常会使用xxljob容器化部署xxljob&#xff0c;通常使用 docker pull xuxueli/xxl-job-admin:2.4.0 拉取镜像并启动容器。这种方式对于x86架构服务器来说&#xff0c;没有任何问题。但是在arm架构的服…

结构体、共用体、Makefile

一、结构体 1.1 结构体变量的初始化和赋值的方式 struct Student{int id;char name[32];int score; } struct Student s; s.id 1001; strcpy(s.name,"zhangsan"); s.score 98; struct Student{int id;char name[32];int score; }s; s.id 1001; strcpy(s.name,&…

数据清洗与预处理:从网页中提取的数据处理技术

目录 引言 一、数据清洗与预处理概述 1.1 数据清洗的定义 1.2 数据清洗的重要性 二、数据清洗与预处理的步骤 2.1 数据获取 2.2 数据去重 2.3 缺失值处理 2.4 异常值处理 2.5 数据格式化与标准化 2.6 数据转换与编码 2.7 数据整合与关联 2.8 数据可视化 三、技术…

C++11——function与bind

包装器 function包装器function的介绍function的使用function的使用场景function的意义 bind包装器bind的介绍bind的使用 function包装器 function的介绍 function是用来包装函数的&#xff0c;所以叫做包装器或者适配器&#xff0c;fuction的本质其实是一个类模板。 functio…

基于多域名,通过云运营商弹性负载,Nginx配置等基于的多租户系统部署

已经开发好久的系统&#xff0c;因为业务上没有需求&#xff0c;没有做上线部署&#xff0c;此系统为多租户系统&#xff0c;原来设计是通过租户码参数来识别的&#xff0c;每个租户访问&#xff0c;需要传自己的码过来&#xff0c;才能确定是哪个租户登录系统&#xff0c; 今…

数据结构和算法之树形结构(1)

文章出处: 数据结构和算法之树形结构(1) 关注码农爱刷题&#xff0c;看更多技术文章&#xff01;&#xff01; 树形结构是数据结构四种逻辑结构之一&#xff0c;也是被广泛使用的一种逻辑结构&#xff0c;它描述的是数据元素之间一对多的逻辑关系。树是一种非线性的数据结构&a…

SOMEIP_ETS_119: SD_Indicate_wrong_l4proto_param

测试目的&#xff1a; 验证DUT能够拒绝一个引用了带有错误l4proto参数&#xff08;既不是UDP也不是TCP&#xff09;的IPv4端点选项的SubscribeEventgroup消息&#xff0c;并以SubscribeEventgroupNAck作为响应。 描述 本测试用例旨在确保DUT遵循SOME/IP协议&#xff0c;当接…

基于单片机的智能电话控制系统设计

摘要&#xff1a; 为了能够使用电话实现电器设备的控制&#xff0c;文中通过单片机及双音多频解码集成电路&#xff0c;使用用 户通过电话输入相应的指令就能够实现远程设备的智能化控制。文章主要对系统的构成、软件及 硬件设计进行了简单的介绍&#xff0c;并且对其中的电路…

出现conda不是内部或外部命令,也不是可运行的程序或批处理文件。的解决办法

发现是我的环境变量不对&#xff0c;需要改成conda.exe所在的目录下 如果不知道自己conda.exe在哪的 可以下载个everything这个软件 找东西很快 找到后 点击环境变量-系统变量-Path-新建-&#xff08;你的conda.exe所在目录&#xff1a;绝对路径&#xff09; 完成上述操作…

Day4:杨辉三角

题目&#xff1a;给定一个非负整数numRows,生成杨辉三角的前numRows行。在杨辉三角中&#xff0c;每个数就是左上方和右上方数的和。 import java.util.ArrayList; import java.util.List;public class Test {public static List<List<Integer>> generate(int numR…

【学术会议征稿】2024年先进控制系统与自动化技术国际学术会议(ACSAT 2024)

2024年先进控制系统与自动化技术国际学术会议&#xff08;ACSAT 2024&#xff09; 2024 International Conference on Advanced Control Systems and Automation Technologies 2024年先进控制系统与自动化技术国际学术会议&#xff08;ACSAT 2024&#xff09;将于2024年11月15…

solidwork装配体取消零件固定

前面有固定导致零件移动不了 右键&#xff0c;找到浮动

Three.js 3D人物漫游项目(上)

本文目录 前言1、项目构建1.1 安装依赖1.2 初始化1.3 项目结构1.4 初始化的项目运行 2、加载模型2.1 threejs三要素2.1.1 代码解读 2.2 加载模型2.2.1 代码解读 2.3 效果 前言 在数字技术的浪潮中&#xff0c;三维图形渲染技术以其独特的魅力&#xff0c;正逐步渗透到我们生活的…

react hooks--useMemo

概述 相当于计算属性!!! useMemo实际的目的也是为了进行性能的优化。 ◼ 如何进行性能的优化呢&#xff1f;  useMemo返回的也是一个 memoized&#xff08;记忆的&#xff09; 值&#xff1b;  在依赖不变的情况下&#xff0c;多次定义的时候&#xff0c;返回的值是相同…

后台数据管理系统 - 项目架构设计-Vue3+axios+Element-plus(0920)

十三、文章分类页面 - [element-plus 表格] Git仓库&#xff1a;https://gitee.com/msyycn/vue3-hei-ma.git 基本架子 - PageContainer 功能需求说明&#xff1a; 基本架子-PageContainer封装文章分类渲染 & loading处理文章分类添加编辑[element-plus弹层]文章分类删除…

k8s中的微服务

目录 一、什么是微服务 二、微服务的类型 三、IPVS模式 1、ipvs模式配置方式 &#xff08;1&#xff09;在所有节点中安装ipvsadm &#xff08;2&#xff09;修改master节点的代理配置 &#xff08;3&#xff09;重启pod 四、微服务类型详解 1、clusterip 示例&#…

Flink提交任务

第3章 Flink部署 3.1 集群角色 3.2 Flink集群搭建 3.2.1 集群启动 0&#xff09;集群规划 表3-1 集群角色分配 具体安装部署步骤如下&#xff1a; 1&#xff09;下载并解压安装包 &#xff08;1&#xff09;下载安装包flink-1.17.0-bin-scala_2.12.tgz&#xff0c;将该jar包…