服务端事件推送——HTTP协议的事件流(EventStream)

背景

最近由于工作要求需要使用Springboot搭建一个流式响应服务,即客户端发送一次请求,服务端需要多次响应才能返回完整的数据。使用场景就是与chatGPT对话,你问一个问题,页面会逐字将结果打印出来。

下面我在SpringBoot中可以简单的实现一下这种场景需求,即SSE(Server-Sent Events)模式

前端请求实现方式

目前前端的请求实现方式有两种,一个是采用EventSource实现,这种实现方式不支持自定义的请求头,也就没有办法再请求头部中增加Token这样的用户身份验证信息。并且该方式只支持GET请求方式。所以这种实现方式只适用于,不需要验证用户身份并且请求参数内容少的情况下。

若要传输更多的参数信息或者在请求头中增加自定义内容建议使用AbortController实现

若传输过程中链接断开,EventSource可以实现自动重新链接,AbortController不能实现自动重新链接。

使用EventSource实现

       // 建立连接let source = new EventSource('http://localhost:8080/sse/connect/' + userId);/*** 连接一旦建立,就会触发open事件* 另一种写法:source.onopen = function (event) {}*/source.addEventListener('open', function (e) {console.log("建立连接。。。");}, false);/*** 客户端收到服务器发来的数据* 另一种写法:source.onmessage = function (event) {}*/source.addEventListener('message', function (e) {console.log(e.data);});/*** 如果发生通信错误(比如连接中断),就会触发error事件* 或者:* 另一种写法:source.onerror = function (event) {}*/source.addEventListener('error', function (e) {if (e.readyState === EventSource.CLOSED) {console.log("连接关闭");} else {console.log(e);}}, false);

使用AbortController实现

<template><div><input v-model="name" placeholder="Enter your name"><button @click="sendPost">Send POST request</button><button @click="stopGenerating">Stop Generating</button><button @click="restartGenerating">Restart Generating</button><pre>{{ response }}</pre></div>
</template><script>
export default {data() {return {name: '',response: '',controller: new AbortController(),isStopped: false}},methods: {async sendPost() {this.controller = new AbortController()this.response = ''this.isStopped = falseconst response = await fetch('http://127.0.0.1:5000/stream', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ name: this.name }),signal: this.controller.signal})const reader = response.body.getReader()while (true) {if (this.isStopped) breakconst { done, value } = await reader.read()if (done) breakthis.response += new TextDecoder().decode(value)}
},stopGenerating() {this.controller.abort()this.isStopped = true},restartGenerating() {this.controller = new AbortController()this.sendPost()}}
}
</script>

后端响应实现方式

使用SseEmitter实现

 @RequestMapping(value = "/talkeAbouttestSseEmitter")public SseEmitter talkeAbouttestSseEmitter(HttpServletResponse response, @RequestBody JSONObject object) throws IOException {SseEmitter emitter = new SseEmitter();logger.info("【prompt内容】:{}", object.getString("prompt"));String str = "       什么是爱而不得? \n" +"东边日出西边雨,道是无晴却有晴。\n" +"他朝若是同淋雪,此生也算共白头。\n" +"我本将心向明月,奈何明月照沟渠。\n" +"此时相望不相闻,愿逐月华流照君。\n" +"衣带渐宽终不悔,为伊消得人憔悴。\n" +"此情可待成追忆,只是当时已惘然。\n" +"人生若只如初见,何事西风悲画扇。\n" +"曾经沧海难为水,除却巫山不是云。\n" +"何当共剪西窗烛,却话巴山夜雨时。\n" +"天长地久有时尽,此恨绵绵无绝期。\n" +"\n";response.setHeader("Content-Type", "text/event-stream");response.setContentType("text/event-stream");response.setCharacterEncoding("UTF-8");response.setHeader("Pragma", "no-cache");new Thread(() -> {
//            // 响应流try {for (int i = 0; i < str.length(); i++) {// 指定事件标识  event: 这个为固定格式emitter.send(String.valueOf(str.charAt(i)));Thread.sleep(100);}emitter.send("stop");emitter.complete(); // Complete the SSE connection} catch (IOException e) {e.printStackTrace();}}).start();return emitter;}

使用HttpServlet实现

    @RequestMapping(value = "/talkeAbouttestEvent")public void talkeAbouttestEvent(HttpServletResponse response, @Param("prompt") String prompt) throws IOException {logger.info("【prompt内容】:{}", prompt);String str = "       什么是爱而不得? \n" +"东边日出西边雨,道是无晴却有晴。\n" +"他朝若是同淋雪,此生也算共白头。\n" +"我本将心向明月,奈何明月照沟渠。\n" +"此时相望不相闻,愿逐月华流照君。\n" +"衣带渐宽终不悔,为伊消得人憔悴。\n" +"此情可待成追忆,只是当时已惘然。\n" +"人生若只如初见,何事西风悲画扇。\n" +"曾经沧海难为水,除却巫山不是云。\n" +"何当共剪西窗烛,却话巴山夜雨时。\n" +"天长地久有时尽,此恨绵绵无绝期。\n" +"\n";// 响应流response.setHeader("Content-Type", "text/event-stream");response.setContentType("text/event-stream");response.setCharacterEncoding("UTF-8");response.setHeader("Pragma", "no-cache");try {// 指定事件标识  event: 这个为固定格式response.getWriter().write("event:open\n");response.getWriter().flush();for (int i = 0; i < str.length(); i++) {// 指定事件标识  event: 这个为固定格式
//                response.getWriter().write("event:msg\n");// 格式:data: + 数据 + 2个回车response.getWriter().write("data:{\"content\":\""+ String.valueOf(str.charAt(i)).getBytes(StandardCharsets.UTF_8) + "\"}\n\n");response.getWriter().flush();Thread.sleep(100);}// 指定事件标识  event: 这个为固定格式response.getWriter().write("event:error\n");response.getWriter().flush();
//            response.getWriter().close();} catch (IOException | InterruptedException e) {e.printStackTrace();} finally {}}

后端请求实现方式

 /*** ** @param url* @param json* @return*/public static BufferedReader sendJsonPostResveEventStream(String url, String json) {PrintWriter out = null;BufferedReader in = null;BufferedReader reader = null;try {log.info("sendPost - {}", url);log.info("json - {}", json);URL realUrl = new URL(url);HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();conn.setRequestMethod("POST");conn.setDoOutput(true);conn.setDoInput(true);conn.setUseCaches(false);conn.setRequestProperty("Connection", "Keep-Alive");conn.setRequestProperty("Charset", "UTF-8");conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");conn.setRequestProperty("accept", "application/json");if (json != null && !json.equals("")) {byte[] writebytes = json.getBytes();conn.setRequestProperty("Content-Length", String.valueOf(writebytes.length));OutputStream outwritestream = conn.getOutputStream();outwritestream.write(json.getBytes());outwritestream.flush();outwritestream.close();conn.getResponseCode();}if (conn.getResponseCode() == 200) {reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));return reader;}} catch (ConnectException e) {log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + json, e);} catch (SocketTimeoutException e) {log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + json, e);} catch (IOException e) {log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + json, e);} catch (Exception e) {log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + json, e);} finally {try {if (out != null) {out.close();}if (in != null) {in.close();}} catch (IOException ex) {log.error("调用in.close Exception, url=" + url + ",param=" + json, ex);}}return null;}

后端请求然后以事件流的方式发送给前端

    @PostMapping(value = "/talkeAbout", produces = "text/event-stream")public void talkeAbout(HttpServletResponse response, @RequestBody JSONObject object) throws IOException {response.setHeader("Content-Type", "text/event-stream");response.setContentType("text/event-stream");response.setCharacterEncoding("UTF-8");response.setHeader("Pragma", "no-cache");talkeAboutToXinference(object.getString("prompt"), response);}public void talkeAboutToXinference(String msg, HttpServletResponse response) throws IOException {String json = CHAT_PRARAM.replace("user_talke_about", msg);BufferedReader reader = HttpUtils.sendJsonPostResveEventStream("http://localhost/chat" + CHAT_CHAT_COMPLETIONS, json);if (reader == null) return;String line = "";while ((line = reader.readLine()) != null) {response.getWriter().write(line +"\n");response.getWriter().flush();}response.getWriter().close();}

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

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

相关文章

使用Ckman部署ClickHouse集群介绍

使用Ckman部署ClickHouse集群介绍 1. Ckman简介 ClickHouse Manager是一个为ClickHouse数据库量身定制的管理工具&#xff0c;它是由擎创科技数据库团队主导研发的一款用来管理和监控ClickHouse集群的可视化运维工具。目前该工具已在github上开源&#xff0c;开源地址为&…

Leetcode 3213. Construct String with Minimum Cost

Leetcode 3213. Construct String with Minimum Cost 1. 解题思路2. 代码实现 题目链接&#xff1a;3213. Construct String with Minimum Cost 1. 解题思路 这一题的话思路上还是比较直接的&#xff0c;就是一个trie树加一个动态规划&#xff0c;通过trie树来快速寻找每一个…

k8s-第七节-ConfigMap Secret

ConfigMap & Secret ConfigMap 数据库连接地址&#xff0c;这种可能根据部署环境变化的或者其他容器配置选项的包括容器更新或者扩容时可以统一配置 Kubernetes 为我们提供了 ConfigMap&#xff0c;可以方便的配置一些变量。 https://kubernetes.io/zh/docs/concepts/c…

Angluar 实现pdf页面预览以及编辑

之前用过一个pdf预览的lib&#xff0c;并且还支持在线编辑&#xff0c;和直接下载编辑之后的pdf和直接打印&#xff0c;还不错&#xff0c;记录下 PdfShowcase 首先安装依赖 npm install ngx-extended-pdf-viewer 然后引入 import { NgxExtendedPdfViewerModule } from &q…

硅纪元视角 | 中国电信“星辰大模型·软件工厂”,两分钟完成应用开发,效率飞跃!

在数字化浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;正成为塑造未来的关键力量。硅纪元视角栏目紧跟AI科技的最新发展&#xff0c;捕捉行业动态&#xff1b;提供深入的新闻解读&#xff0c;助您洞悉技术背后的逻辑&#xff1b;汇聚行业专家的见解&#xff0c;…

【数据结构】链表带环问题分析及顺序表链表对比分析

【C语言】链表带环问题分析及顺序表链表对比分析 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C语言学习之路 文章目录 【C语言】链表带环问题分析及顺序表链表对比分析前言一.顺序表和链表对比1.1顺序表和链表的区别1.2缓存利用率&#…

Leetcode 3211. Generate Binary Strings Without Adjacent Zeros

Leetcode 3211. Generate Binary Strings Without Adjacent Zeros 1. 解题思路2. 代码实现 题目链接&#xff1a;3211. Generate Binary Strings Without Adjacent Zeros 1. 解题思路 这一题比较简单&#xff0c;用一个递归算法即可实现。 2. 代码实现 给出python代码实现…

Linux基础: 二. Linux的目录和文件

文章目录 二. Linux的目录和文件1.1 目录概要1.2 目录详细说明 二. Linux的目录和文件 1.1 目录概要 command&#xff1a;ls / Linux的文件系统像一棵树一样&#xff0c;树干是根目录&#xff08;/&#xff09;&#xff0c;树枝是子目录&#xff0c;树叶是文件&#xff1b; …

亚信安全发布2024年6月威胁态势,高危漏洞猛增60%

近日&#xff0c;亚信安全正式发布《2024年6月威胁态势报告》&#xff08;以下简称“报告”&#xff09;&#xff0c;报告显示&#xff0c;6月份新增信息安全漏洞 1794个&#xff0c;高危漏洞激增60%&#xff0c;涉及0day漏洞占67.67%&#xff1b;监测发现当前较活跃的勒索病毒…

C++多线程学习笔记

创建线程(thread) #include<iostream> #include<thread> using namespace std;// 函数fun&#xff0c;接收一个整型参数并在无限循环中打印递增的值 void fun(int a) {while(1) {cout << a << "\n"; // 打印自增后的athis_thread::sleep_fo…

应用案例 | 基于物联网工控屏的工业离心机设备监控系统

案例概况 客户&#xff1a;博鲁班特&#xff08;BROADBENT&#xff09; 应用产品&#xff1a;宏集物联网工控屏 应用场景&#xff1a;离心机设备监控系统 一、前言 在现代工业生产中&#xff0c;离心机作为关键的分离设备&#xff0c;在生产过程中扮演着至关重要的角色。随…

谷粒商城学习笔记-17-快速开发-逆向工程搭建使用

文章目录 一&#xff0c;克隆人人开源的逆向工程代码二&#xff0c;把逆向工程集成到谷粒商城的后台工程三&#xff0c;以商品服务为例&#xff0c;使用逆向工程生成代码1&#xff0c;修改逆向工程的配置2&#xff0c;以Debug模式启动逆向工程3&#xff0c;使用逆向工程生成代码…

名企面试必问30题(二十四)—— 说说你空窗期做了什么?

回答示例一 在空窗期这段时间&#xff0c;我主要进行了两方面的活动。 一方面&#xff0c;我持续提升自己的专业技能。我系统地学习了最新的软件测试理论和方法&#xff0c;深入研究了自动化测试工具和框架&#xff0c;例如 Selenium、Appium 等&#xff0c;并通过在线课程和实…

ISA95-Part4-业务流程的解析与设计思路

MES/MOM系统实现ISA-95标准的业务流程通常遵循以下思路,并包含一系列内容。 一、功能模块: 1. 需求分析与规划: - 确定业务流程需求,包括订单管理、生产调度、库存控制等,并规划如何将这些流程与MES/MOM系统集成。 2. 系统集成架构设计: - 设计一个系统集成架构,确保M…

基于B/S模式和Java技术的生鲜交易系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;B/S模式、Java技术 工具&#xff1a;Visual Studio、MySQL数据库开发工具 系统展示 首页 用户注册…

【Java】详解String类中的各种方法

创建字符串 常见的创建字符串的三种方式&#xff1a; // 方式一 String str "hello world"; // 方式二 String str2 new String("hello world"); // 方式三 char[] array {a, b, c}; String str3 new String(array); "hello" 这样的字符串字…

Halcon 产品周围缺口检测

*读取一张图像read_image (Image, 原图.jpg)*获取图像大小get_image_size(Image, Width, Height)*关闭已经打开的窗口dev_close_window ()*打开新窗口dev_open_window(0, 0, Width, Height, black, WindowHandle) //打开指定大小的窗口*对图像进行阈值操作threshold (Image, R…

RedHat运维-Linux网络管理基础2-NetworkManager与其它

1. 查看NetworkManager接管网卡状态的命令是_______________________________&#xff1b; 2. 查看NetworkManager接管网卡状态的命令是_______________________________&#xff1b; 3. 查看NetworkManager接管网卡状态的命令是_______________________________&#xff1b; 4…

【链表】【双指针】1、合并两个有序链表+2、分隔链表+3、删除链表的倒数第N个结点+4、链表的中间结点+5、合并两个链表

3道中等2道简单 数组和字符串打算告一段落&#xff0c;正好最近做的几乎都是双指针&#xff0c;所以今天做链表&#xff01; 1、合并两个有序链表&#xff08;难度&#xff1a;简单&#xff09; 该题对应力扣网址 AC代码 思路简单 /*** Definition for singly-linked list.…

万和day01代码分析

将了数据库的多表之间的操作&#xff0c;实际应用到JDBC中去。 一共五张表&#xff0c; info存储的是具体的信息&#xff0c;edu job role 和info都是多对一的关系。 采用的是Java FX&#xff0c;界面采用xml去编写。 项目理解一 在JavaFX中&#xff0c;ObservableList 是一个…