欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java調(diào)用SSE流式接口并流式返回給前端實現(xiàn)打字輸出效果

 更新時間:2024年08月17日 15:27:39   作者:weixin_44680858  
在Web開發(fā)中,有時我們需要將文件以流的形式返回給前端,下面這篇文章主要給大家介紹了關于Java調(diào)用SSE流式接口并流式返回給前端實現(xiàn)打字輸出效果的相關資料,需要的朋友可以參考下

1.SSE概述

1.1 什么是是SSE

Server-Sent Events (SSE)SSE是一種簡單的事件推送技術(shù),它允許服務器異步地向客戶端發(fā)送更新,而無需客戶端顯式請求這些更新。這對于實時應用程序非常有用,例如股票價格更新、消息通知等。SSE基于HTTP協(xié)議,使用一個持久的HTTP連接來維持客戶端和服務端之間的通信。

2.2 與長鏈接(Long Polling)的區(qū)別

Server-Sent Events (SSE) 和長鏈接(Long Polling)都是實現(xiàn)服務器向客戶端推送數(shù)據(jù)的技術(shù),但它們之間存在一些關鍵區(qū)別。下面我將詳細解釋這兩種技術(shù)的不同之處:

長鏈接(Long Polling)

長鏈接是一種實現(xiàn)服務器推送數(shù)據(jù)到客戶端的技術(shù),它基于HTTP請求/響應模型。在這種模式下,客戶端發(fā)起一個HTTP請求,服務器在沒有數(shù)據(jù)可發(fā)送的情況下會保持連接打開,直到有數(shù)據(jù)可發(fā)送或者超時。一旦服務器有數(shù)據(jù)要發(fā)送,它就會響應客戶端的請求,并關閉連接??蛻舳私邮盏綌?shù)據(jù)后立即重新發(fā)起一個新的請求,從而保持與服務器的“長鏈接”。

特點:

  • 客戶端主動發(fā)起請求:客戶端需要不斷地向服務器發(fā)起請求以獲取數(shù)據(jù)。
  • 服務器被動響應:服務器只在客戶端請求時才發(fā)送數(shù)據(jù)。
  • 連接短暫:雖然每個連接可能會持續(xù)一段時間,但每次請求結(jié)束后連接會被關閉。
  • 實現(xiàn)簡單:易于用現(xiàn)有HTTP技術(shù)實現(xiàn)。
  • 兼容性好:幾乎所有瀏覽器都支持HTTP請求/響應模型。

Server-Sent Events (SSE)

Server-Sent Events 是一種更為現(xiàn)代的技術(shù),用于實現(xiàn)服務器向客戶端的單向數(shù)據(jù)推送。SSE基于HTTP協(xié)議,但使用了一個持久的HTTP連接來維持客戶端和服務端之間的通信。服務器可以主動向客戶端發(fā)送數(shù)據(jù),而不需要等待客戶端的請求。

特點

  • 服務器主動推送:服務器可以主動向客戶端發(fā)送數(shù)據(jù),而不需要客戶端發(fā)起請求。
  • 持久連接:客戶端和服務端之間建立了一個持久的連接,直到客戶端或服務器關閉該連接。
  • 格式特定:SSE使用特定的格式來發(fā)送數(shù)據(jù),包括data:字段和空行作為分隔符。
  • 資源效率高:由于連接是持久的,因此減少了建立連接的開銷。
  • 實現(xiàn)復雜度適中:雖然比長鏈接稍微復雜,但現(xiàn)代瀏覽器和服務器框架提供了良好的支持。

比較

  • 實時性:SSE提供更好的實時性,因為它不需要客戶端不斷發(fā)起請求。
  • 性能:SSE在性能上通常優(yōu)于長鏈接,因為它避免了重復建立連接的開銷。
  • 實現(xiàn)復雜度:SSE需要客戶端和服務端雙方的支持,而長鏈接可以更容易地在現(xiàn)有的HTTP基礎設施上實現(xiàn)。
  • 兼容性:SSE在現(xiàn)代瀏覽器中得到了廣泛支持,但對于一些舊版瀏覽器可能不適用;長鏈接則具有更好的向后兼容性。

總結(jié)

選擇哪種技術(shù)取決于你的具體需求。如果你的應用需要較低延遲的數(shù)據(jù)推送,并且可以依賴現(xiàn)代瀏覽器和服務器環(huán)境,那么SSE是一個不錯的選擇。如果你需要更廣泛的瀏覽器兼容性,并且對實時性要求不是特別高,那么長鏈接可能更適合你。

2.通過okhttp調(diào)用SSE流式接口并流式返回給前端

環(huán)境要求

  • Spring Framework 5.0
  • Jdk1.8

使用okhttp相關依賴

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.2.0</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp-sse</artifactId>
    <version>4.2.0</version>
</dependency>

示例

@GetMapping(value = "/test1", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter SseTest1() {
        SseEmitter sseEmitter = new SseEmitter();
        String prompt = "";
        String url = "";
        FormBody formBody = new FormBody.Builder().add("prompt", prompt).build();
        Request request = new Request.Builder().url(url).post(formBody).build();
        // 使用EventSourceListener處理來自服務器的SSE事件
        EventSourceListener listener = new EventSourceListener() {
            @Override
            public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
                log.info("Connection opened.");
            }
            @Override
            public void onClosed(@NotNull EventSource eventSource) {
                log.info("Connection closed.");
                sseEmitter.complete();
            }
            @Override
            public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) {
                try {
                    JSONObject jsonObject = JSONUtil.parseObj(data);
                    String event = jsonObject.getStr("event");
                    if ("message".equals(event)) {
                        sseEmitter.send(jsonObject.getStr("answer"));
                    }
                } catch (Exception e) {
                    log.error("推送數(shù)據(jù)失敗", e);
                }
            }
            @Override
            public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable t, @Nullable Response response) {
                log.error("Connection failed.", t);
                sseEmitter.completeWithError(t);
            }
        };
        OkHttpClient client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(50, TimeUnit.SECONDS).readTimeout(10, TimeUnit.MINUTES).build();
        EventSource.Factory factory = EventSources.createFactory(client);
        factory.newEventSource(request, listener);
        return sseEmitter;
    }

注意

  • 該接口需為Get請求,ContentType為 text/event-stream
  • SseEmitter 是Spring Framework 5.0引入的一個新特性,用于簡化Server-Sent Events (SSE) 的實現(xiàn)。它提供了一種簡單的方式來發(fā)送事件數(shù)據(jù)到客戶端,特別適用于構(gòu)建實時數(shù)據(jù)推送的應用程序。

3. 如果Spring Framework 低于5.0,可使用Servlet 3.0進行流式返回

使用AsyncContext:Servlet 3.0 引入了異步支持,允許Servlet在不同的線程中處理請求。你可以使用AsyncContext來啟動一個異步線程,在該線程中發(fā)送SSE事件。

配置async-supported使用AsyncContext前需配置async-supported

async-supported元素用于指定Servlet是否支持異步處理。這個配置通常是在部署描述符 web.xml 文件中進行設置的。

配置示例

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>com.example.MyServlet</servlet-class>
        <async-supported>true</async-supported>
    </servlet>

    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/myServlet</url-pattern>
    </servlet-mapping>

</web-app>

后端代碼示例

@GetMapping("/test3")
    public void SseTest3(HttpServletRequest req, HttpServletResponse resp) {
        resp.setContentType("text/event-stream");
        resp.setCharacterEncoding("UTF-8");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setHeader("Connection", "keep-alive");

        try {
            //
            AsyncContext asyncContext = req.startAsync(req, resp);
            asyncContext.setTimeout(10 * 60 * 1000);
            PrintWriter writer = asyncContext.getResponse().getWriter();

            String prompt = "";
            String url = "";
            FormBody formBody = new FormBody.Builder().add("prompt", prompt).build();
            Request request = new Request.Builder().url(url).post(formBody).build();
            // 使用EventSourceListener處理來自服務器的SSE事件
            EventSourceListener listener = new EventSourceListener() {
                @Override
                public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
                    log.info("Connection opened.");
                }

                @Override
                public void onClosed(@NotNull EventSource eventSource) {
                    log.info("Connection closed.");
                    writer.write("data: __stop__\n\n");
                    writer.flush();
                    asyncContext.complete();
                }

                @Override
                public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) {
                    try {
                        JSONObject jsonObject = JSONUtil.parseObj(data);
                        String event = jsonObject.getStr("event");
                        if ("message".equals(event)) {
                            String answer = jsonObject.getStr("answer");
                            log.info("message: {}", answer);
                            writer.write("data: " + answer + "\n\n");
                            writer.flush();
                        }
                    } catch (Exception e) {
                        log.error("推送數(shù)據(jù)失敗", e);
                    }
                }

                @Override
                public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable t, @Nullable Response response) {
                    log.error("Connection failed.", t);
                    asyncContext.complete();
                }
            };
            OkHttpClient client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(50, TimeUnit.SECONDS).readTimeout(10, TimeUnit.MINUTES).build();
            EventSource.Factory factory = EventSources.createFactory(client);
            factory.newEventSource(request, listener);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

        }
    }

注意返回數(shù)據(jù)格式:

// 以data: 開頭  /n/n結(jié)束
"data: xxxxx /n/n"

4. 前端調(diào)用SSE接口

方式1 使用JavaScript的 EventSource API

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SSE Example</title>
</head>
<body>
    <div id="events"></div>
    <script>
        const source = new EventSource('/sse');

        source.onmessage = function(event) {
            const data = JSON.parse(event.data);
            // 約定一個結(jié)束標識
            if(data == '__stop__') {
            	source.close()
            	return
            }
            document.getElementById('events').innerHTML += `<p>${data.message}</p>`;
        };

        source.onerror = function(error) {
            console.error('Error occurred:', error);
            source.close();
        };
    </script>
</body>
</html>

注意

  • 后端返回需返回完整消息的對象(包括換行符),例:{“data”: “哈哈哈/n/n”},如果后端將data取出,則會導致?lián)Q行符丟失!
  • EventSource 只支持Get請求,如果請求參數(shù)過長會導致調(diào)用失??!

方式2 使用 fetchEventSource 插件

安裝插件

npm install --save @microsoft/fetch-event-source

簡單示例

// 導入依賴
import { fetchEventSource } from '@microsoft/fetch-event-source';
 
send() {
	const vm = this;
	const ctrlAbout = new AbortController();
	const { signal } = ctrlAbout;
	fetchEventSource(Url, {
	  method: 'POST',
	  headers: {
	    "Content-Type": 'application/json',
	    "Accept": 'text/event-stream'
	  },
	  body: JSON.stringify(data),
	  signal: ctrl.signal, // AbortSignal
	  onmessage(event) {
	     console.info(event.data);
	     // 在這里操作流式數(shù)據(jù)
	     const message = JSON.parse(event.data)
	     vm.content += message.data
	  },
	  onclose(e) {
	     // 關閉流
	     // 中斷流式返回
	     ctrl.abort()
	  }
	  onerror(error) {
	    // 返回流報錯
		console.info(error);
		// 中斷流式返回
		ctrl.abort()
		throw err // 直接拋出錯誤,避免反復調(diào)用
	  }
	})
}

注意

  • 傳參時需注意參數(shù)類型為json字符串

5. 使用原生的http調(diào)用SSE流式接口

示例

@GetMapping("/test2")
    public void SseTest2() {
        String urlAddr = "";
        BufferedReader reader = null;
        try {
            URL url = new URL(urlAddr);
            // 建立鏈接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Accept", "text/event-stream");
            connection.setRequestProperty("Content-type", "application/json; charset=UTF-8");
            connection.setRequestProperty("Cache-Control", "no-cache");
            connection.setRequestProperty("Connection", "keep-alive");
            // 允許輸入和輸出
            connection.setDoInput(true);
            connection.setDoOutput(true);
            // 設置超時為0,表示無限制
            connection.setConnectTimeout(0);
            connection.setReadTimeout(0);
            // 傳參
            String params = "prompt=哈哈哈哈";
            // 寫入POST數(shù)據(jù)
            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
            out.write(params.getBytes(StandardCharsets.UTF_8));
            out.flush();
            out.close();

            // 讀取SSE事件
            reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
            StringBuilder eventBuilder = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
            // 斷開鏈接
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IoUtil.close(reader);
        }
    }

總結(jié) 

到此這篇關于Java調(diào)用SSE流式接口并流式返回給前端實現(xiàn)打字輸出效果的文章就介紹到這了,更多相關Java調(diào)用SSE流式接口并返回前端內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Springboot自動配置與@Configuration配置類詳解

    Springboot自動配置與@Configuration配置類詳解

    這篇文章主要介紹了SpringBoot中的@Configuration與自動配置,在進行項目編寫前,我們還需要知道一個東西,就是SpringBoot對我們的SpringMVC還做了哪些配置,包括如何擴展,如何定制,只有把這些都搞清楚了,我們在之后使用才會更加得心應手
    2022-07-07
  • Java代碼重構(gòu)的幾種模式詳解

    Java代碼重構(gòu)的幾種模式詳解

    這篇文章詳細介紹了Java代碼重構(gòu)的幾種模式,有需要的朋友可以參考一下
    2013-10-10
  • JAVA泛型的繼承和實現(xiàn)、擦除原理解析

    JAVA泛型的繼承和實現(xiàn)、擦除原理解析

    這篇文章主要介紹了JAVA泛型的繼承和實現(xiàn)、擦除原理解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • Java concurrency之CountDownLatch原理和示例_動力節(jié)點Java學院整理

    Java concurrency之CountDownLatch原理和示例_動力節(jié)點Java學院整理

    CountDownLatch是一個同步輔助類,在完成一組正在其他線程中執(zhí)行的操作之前,它允許一個或多個線程一直等待。 下面通過本文給大家分享Java concurrency之CountDownLatch原理和示例,需要的的朋友參考下吧
    2017-06-06
  • Spring實例化bean的方式代碼詳解

    Spring實例化bean的方式代碼詳解

    這篇文章主要介紹了Spring實例化bean的方式代碼詳解,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • Spring循環(huán)依賴??的解決方式詳解

    Spring循環(huán)依賴??的解決方式詳解

    這篇文章主要介紹了Spring循環(huán)依賴??的解決方式,??循環(huán)依賴??是指兩個或多個Bean互相依賴,形成閉環(huán),導致Spring無法正常完成依賴注入,需要的朋友可以參考下
    2025-05-05
  • Java中正則表達式split()特殊符號使用詳解

    Java中正則表達式split()特殊符號使用詳解

    這篇文章主要介紹了Java中正則表達式split()特殊符號使用詳解, 文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07
  • Spring cloud alibaba之Ribbon負載均衡實現(xiàn)方案

    Spring cloud alibaba之Ribbon負載均衡實現(xiàn)方案

    Spring cloud Ribbon是基于Netflix Ribbon實現(xiàn)的一套客戶端的負載均衡工具,Ribbon客戶端提供一系列完善的配置,如超時、重試等,Ribbon也可以實現(xiàn)自己的負載均衡算法,感興趣的朋友跟隨小編一起看看吧
    2021-07-07
  • mybatis-plus分頁無效問題解決

    mybatis-plus分頁無效問題解決

    本文主要介紹了mybatis-plus分頁無效問題解決,原因是配置分頁插件的版本問題,舊版本和新版本的MyBatis-Plus需要不同的分頁配置,感興趣的可以了解一下
    2025-03-03
  • 關于idea更新到2020.2.3無法創(chuàng)建web項目原因 library is not specified

    關于idea更新到2020.2.3無法創(chuàng)建web項目原因 library is not specified

    這篇文章主要介紹了關于idea更新到2020.2.3無法創(chuàng)建web項目原因 library is not specified,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10

最新評論