服務端事件推送——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.pswp.cn/web/41413.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/41413.shtml
英文地址,請注明出處:http://en.pswp.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 是一個…