-
HTTP流式返回(Stream)是一種服務器向客戶端傳輸數據的方式允許數據分塊發送而不是一次性發送完畢。
這樣客戶端可以在接收到第一部分數據時就開始處理,而不必等待整個響應完成。 -
應用場景:
2.1 業務場景:圖表的監聽(股票行情);體育比分;ota升級進度條;chartgpt聊天消息返回
2.2 開發場景:視頻流,文件的下載 -
分塊傳輸的具體過程
3.1 客戶端請求數據:客戶端向服務器發送HTTP請求。
3.2 服務器響應頭:服務器發送響應頭,其中包含Transfer-Encoding: chunked,表明將使用分塊傳輸編碼。
3.3 發送數據塊:數據分塊并依次發送每個塊
3.4 結束塊:所有數據塊發送完后,服務器發送一個大小為0的結束塊,表示數據傳輸完成。 -
客戶端處理步驟
4.1 讀取響應頭:客戶端首先讀取響應頭(Transfer-Encoding: chunked),了解使用了分塊傳輸編碼。
4.2 逐塊處理數據:客戶端依次讀取每個塊的大小和數據內容,并進行處理。處理完一個塊后,繼續讀取下一個塊,直到讀取到大小為0的結束塊。 -
流式返回工作原理:
5.1 塊大小:每個數據塊的大小以十六進制數表示,后跟一個回車換行(CRLF,·\r\n’)。
5.2 數據塊:實際的數據塊(以二進制形式傳輸)。
5.3 終止塊:最后一個數據塊大小為0(‘01r\n\z\n’),表示數據傳輸結束。
ps:HTTP流式返回(也稱為分塊傳輸編碼,chunked transfer encoding)實際上是通過十六進制表示塊的大小,然后傳輸實際數據塊的二進制內容。因此,流式返回的數據塊以二進制形式傳輸,但每個數據塊的大小通過十六進制來表示。
-
優點:
6.1 實時性:流式返回允許客戶端在接收到部分數據時就開始處理,減少了等待時間。
6.2 減少內存消耗:服務器不需要一次性準備所有數據,適用于大數據量的傳輸。
6.3 節省帶寬:僅在有數據更新時傳輸,避免不必要的開銷。 -
缺點:
7.1 丟包和重傳:在網絡不穩定的情況下,數據塊可能會丟失或損壞,導致數據不完整。雖然可以通過重傳機制解決,但這增加了實現的復雜度和系統的負擔。
7.2 瀏覽器兼容性:并非所有瀏覽器都完美支持流式返回,尤其是一些老舊的瀏覽器,可能無法正確處理分塊傳輸編碼(Chunked Transfer Encoding)或Fetch API中的流處理。 -
常見問題:
8.1 實時聊天應用:如果網絡不穩定,可能導致消息丟常見問題,需要復雜的錯誤處理和重傳機制
8.2大文件下載:如果網絡中斷,文件下載可能不完整,需要支持斷點續傳功能。 -
常見優化方案:
9.1 錯誤處理和重傳:實現可靠的錯誤檢測和重傳機制,確保數據的完整性和正確性。
9.2 優化連接管理:使用高效的連接管理和負載均衡技術,減少長時間連接對服務器資源的占用。
9.3 客戶端處理優化:在客戶端實現高效的數據塊拼接和解析邏輯,確保數據處理的順序和正確性。 -
前端代碼示例:
// app/api/streaming/route.js
const story = ["李焊玲是一個女漢子。","她力大無窮,","喜歡挑戰各種極限運動。","有一天,","她決定去攀登一座險峻的高山。","在山腳下,","她遇到了一群正準備放棄的登山者。","李焊玲鼓勵他們,","說,","只要堅持,","沒有什么是不可能的。","于是,","她帶領這群登山者一起向山頂進發。","一路上,","她用自己的力量幫助大家克服各種困難,","大家都被她的勇氣和毅力所感染。","最終,","在李焊玲的帶領下,","他們成功登上了山頂。","大家歡呼雀躍,","李焊玲卻笑著說,","這只是人生中的一個小挑戰,","未來還有更多的冒險等著我們。","從那以后,","李焊玲的故事在登山者中廣為流傳,","她成為了大家心目中的英雄。"
];export async function GET(request) {const readable = new ReadableStream({async start(controller) {// 第一塊數據// controller.enqueue("\n");// 模擬延遲并逐步發送數據塊const encoder = new TextEncoder();for (let i = 0; i < story.length; i++) {await new Promise((resolve) => setTimeout(resolve, story[i].length*50));controller.enqueue(encoder.encode(`${story[i]} \n`));}// 關閉流controller.close();},});return new Response(readable, {headers: {"Content-Type": "text/plain","Transfer-Encoding": "chunked",},});
}
PS:
ReadableStream
和 TransformStream
是 JavaScript 中用于處理流數據的接口。
ReadableStream
ReadableStream
用于從源讀取數據,可以異步地獲取數據。例如,可以用來讀取文件、網絡請求等。以下是一個基本的示例:
const readableStream = new ReadableStream({start(controller) {// 初始化數據controller.enqueue('Hello, ');controller.enqueue('World!');controller.close();},pull(controller) {// 當 consumer 請求更多數據時調用},cancel(reason) {// 當 consumer 取消讀取時調用console.log('Stream cancelled, reason:', reason);}
});const reader = readableStream.getReader();reader.read().then(function processText({ done, value }) {if (done) {console.log('Stream complete');return;}console.log(value);return reader.read().then(processText);
});
TransformStream
TransformStream
用于對流數據進行轉換。它接受一個輸入流,并產生一個輸出流。以下是一個簡單的示例:
const transformStream = new TransformStream({start(controller) {// 初始化},transform(chunk, controller) {// 對每個數據塊進行處理controller.enqueue(chunk.toUpperCase());},flush(controller) {// 在流結束時處理剩余的數據console.log('All chunks transformed.');}
});const readable = new ReadableStream({start(controller) {controller.enqueue('Hello, ');controller.enqueue('world!');controller.close();}
});const writable = new WritableStream({write(chunk) {console.log(chunk);},close() {console.log('All data written.');}
});readable.pipeThrough(transformStream).pipeTo(writable);
以上示例展示了如何創建一個 ReadableStream
以及如何使用 TransformStream
對數據進行轉換,然后通過 pipeThrough
將轉換后的數據傳輸到一個 WritableStream
。
這兩個接口可以非常靈活地處理流數據,適用于許多異步數據處理場景。