動態

詳情 返回 返回

SSE請求多種實現方式總結 - 動態 詳情


文前推薦一下👉
前端必備工具推薦網站(圖牀、API和ChatAI、智能AI簡歷、AI思維導圖神器等實用工具):
站點入口:http://luckycola.com.cn/
在這裏插入圖片描述


什麼是SSE

SSE(Server-Sent Events)是一種用於實現服務器主動向客户端推送數據的技術,也被稱為“事件流”(Event Stream)。它基於 HTTP 協議,利用了其長連接特性,在客户端與服務器之間建立一條持久化連接,並通過這條連接實現服務器向客户端的實時數據推送。

SSE 和 Socket 區別
SSE(Server-Sent Events)和 WebSocket 都是實現服務器向客户端實時推送數據的技術,但它們在某些方面還是有一定的區別。

適用於場景
chatGPT 返回的數據 就是使用的SSE 技術

實時數據大屏 如果只是需要展示 實時的數據可以使用SSE技術 而不是非要使用webSocket


一、怎麼實現SSE請求(基礎版本)

1、前端實現:

EventSource 對象是 HTML5 新增的一個客户端 API,用於通過服務器推送實時更新的數據和通知。在使用 EventSource 對象時,如果服務器沒有正確地設置響應頭信息(如:Content-Type: text/event-stream),可能會導致 EventSource 對象無法接收到服務器發送的數據。

### 前端示例代碼

const sse = new EventSource('http://localhost:3000/api/sse' )

sse.addEventListener('open', (e) => {
    console.log(e.target)
})
//對應後端nodejs自定義的事件名lol
sse.addEventListener('lol', (e) => {
    console.log(e.data)
})

## 2、 nodejs 後端示例代碼

import express from 'express';
const app = express();
app.get('/api/sse', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/event-stream', //核心返回數據流
        'Connection': 'close'
    })
    const data = fs.readFileSync('./index.txt', 'utf8')
    const total = data.length;
    let current = 0;
    //mock sse 數據
    let time = setInterval(() => {
        console.log(current, total)
        if (current >= total) {
            console.log('end')
            clearInterval(time)
            return
        }
        //返回自定義事件名
        res.write(`event:lol\n`)
        /返回數據
        res.write(`data:${data.split('')[current]}\n\n`)
        current++
    }, 300)
})
app.listen(3000, () => {
    console.log('Listening on port 3000');
});

上面./index.txt的內容就是一些隨機的測試文本,如下

悲索之人烈焰加身,墮落者 不可饒恕,我即是引路的燈塔,也是淨化的清泉。
永恆燃燒的羽翼,帶我脱離凡間的沉淪,聖火將你洗滌。
今由烈火審判,於光明中得救。
利刃在手,制裁八方!

3、特點

這是一個最基礎的實現版本,但是存在一個問題:這種sse的實現方式只能是GET請求,所以對參數傳遞的長度會有嚴重的限制

比如在AI聊天場景這種方式就不太適合,其實我們也可以通過瀏覽器Fetch API實現SSE

二、Fetch API實現SSE(升級版本)

fetch 本身不直接支持流式輸出,但你可以使用 ReadableStream 和 TextDecoder 等 Web Streams API 來實現類似的效果。

1、 node後端代碼

代碼如下(示例):

router.get("/sse", (req: Request, res: Response)=>{
    console.log('/sse')
    res.writeHead(200, {
        'Content-Type': 'text/event-stream', //核心返回數據流
        'Connection': 'close'
    })
    const data = fs.readFileSync(path.join(__dirname, '../../source/index.txt'), 'utf8');
    const total = data.trim().length;
    let current = 0;
    //mock sse 數據
    const time = setInterval(() => {
        console.log(current, total)
        if (current >= total) {
            console.log('end')
            res.write(`event:end\n`)
            res.write(`data:\n\n`);
            clearInterval(time)
            return
        }
        //返回自定義事件名
        res.write(`event:lol\n`)
        // 返回數據
        res.write(`data:${data.split('')[current]}\n\n`);
        current++
    }, 50)
});

2、 前端Fecth請求實現

代碼如下(示例):

function streamOutput(msg) {
  // 發送 POST 請求
  fetch('/sse', {
    method:"POST",
    body:JSON.stringify({ "content": msg}),
    timeout: 0,
    dataType:"text/event-stream",
    headers:{
      "Content-Type":"application/json"
    },
  }).then(response => {
    // 檢查響應是否成功
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    // 返回一個可讀流
    return response.body;
  }).then(body => {
    disableLoading();
    const reader = body.getReader();
    // 讀取數據流
    function read() {
      return reader.read().then(({ done, value }) => {
        // 檢查是否讀取完畢
        if (done) {
          console.log('已傳輸完畢');
          return;
        }
        // 處理每個數據塊
        console.log('收到的數據:', value);
        
        // 繼續讀取下一個數據塊
        read();
      });
    }
    // 開始讀取數據流
    read();
  }).catch(error => {console.error('Fetch error:', error);});
}

3、特點

這種方式的優點很直接可以支持POST請求方式來實現SSE效果,而且請求參數長度可以得到很大的拓展,符合長文本輸入的需求.另外Fetch是瀏覽器原生API支持度好,簡單易用.

三、Fecth結合EventSource實現SSE(終極版本)

這種方式結合了兩種實現方式,是不是很特別,他的實現類似Websoket,後端需要通過保存前端的EventSource 隊列來管理,我們直接上代碼

1、node後端代碼示例

const express = require('express');  
const router = express.Router();  
  
let clients = [];  
  
// 創建一個 SSE 端點  
router.get('/events', (req, res) => {  
    res.setHeader('Content-Type', 'text/event-stream');  
    res.setHeader('Cache-Control', 'no-cache');  
    res.setHeader('Connection', 'keep-alive');  
  
    // 發送初始消息  
    res.write('data: {"status": "connected"}\n\n');  
  
    // 將客户端添加到監聽列表  
    clients.push(res);  
  
    req.on('close', () => {  
        // 客户端斷開連接時從列表中移除  
        clients = clients.filter(client => client !== res);  
        console.log('Client disconnected');  
    });  
  
    // 保持連接打開  
    res.on('close', () => {  
        clients = clients.filter(client => client !== res);  
    });  
});  
  
// 生成 PPT 併發送更新到所有客户端  
router.post('/sse', async (req, res) => {  
    let markdownContent = `這是語段測試文本\n\n成東南側不對稱\n\n呢哈哈哈哈哈哈哈哈`;
    const slideContents = markdownContent.split('\n\n');  
  
    for (let slideContent of slideContents) {  
        const event = JSON.stringify({   
            finish: false,
            content:  slideContent
        });  
  
        // 發送消息到所有連接的客户端  
        clients.forEach(client => {  
            if (client.writable) {  
                client.write(`data: ${event}\n\n`);  
            }  
        });  
    }  
  
    // 完成後可以發送一個完成消息,但通常 SSE 不需要這樣做  
    // 因為客户端可以通過關閉連接來檢測完成  
    let endEvent =  JSON.stringify({   
            finish: true,
            content:  ''
        });  
    client.write(`data: ${endEvent}\n\n`);  
});  
  
module.exports = router;

2、前端代碼示例

    <script>  
        // SSE連接  
        var evtSource = new EventSource('/events');  
  
        evtSource.onmessage = function(e) {  
            console.log("EventSource收到信息:", e.data) 
        };  
  
        evtSource.onerror = function(e) {  
            console.error('EventSource failed:', e);  
            evtSource.close();  
        };  
  
        function sendMesssageFn() {  
    // 使用fetch API發送POST請求  
    fetch('/sse', {  
        method: 'POST', // 或者 'PUT'  
        headers: {  
            'Content-Type': 'application/json',  
        },  
        body: JSON.stringify(data), // 必須是JSON字符串  
    })  
    .then(response => {  
        if (!response.ok) {  
            throw new Error('Network response was not ok');  
        }  
        return response.text(); // 或者返回response.json()如果後端返回JSON  
    })  
    .then(text => {  
        // 這裏假設後端返回純文本消息  
    })  
    .catch(error => {  
        console.error('There was a problem with your fetch operation:', error);  
    });  
}

sendMesssageFn();
    </script> 

3、特點

這種方式雖然也是通過fecth進行信息請求通信,但是不同的是他的消息監聽仍然是通過EventSource實現的,所以不需要通過getReader解析信息.

四、總結

SSE是一種單工的通信方式,實現方式十分多樣,每一種實現都有各自的優點缺點,應該根據需求進行合理的選擇.

user avatar zhidechaomian_detxs7 頭像 dingtongya 頭像 alibabawenyujishu 頭像 ting_61d6d9790dee8 頭像 zaotalk 頭像 user_2dx56kla 頭像 dirackeeko 頭像 openfuyao 頭像 k21vin 頭像 leexiaohui1997 頭像 huajianketang 頭像 inslog 頭像
點贊 170 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.