使用 BigPipe 實現(xiàn)長鏈接

BigPipe 簡介

2010年的 Facebook 提出 BigPipe 技術,通過將站點分解為多個 pagelet 小塊,每個pagelet 獲取數(shù)據與渲染均是獨立的。

BigPipe 模式可以實現(xiàn) pagelet 的數(shù)據一旦返回,就可以無阻塞的在瀏覽器端進行渲染,以此來實現(xiàn)大型復雜頁面的性能加速。

分塊傳輸編碼

分塊傳輸編碼是在 HTTP/1.1 版本中引入的一種數(shù)據傳輸機制,允許服務器為返回的內容維持持久連接,向客戶端發(fā)送多個分塊的數(shù)據。

當我們在請求一個圖片資源的時候,瀏覽器可以通過響應頭中 Content-Length 長度信息,判斷出響應體結束,但是,大多數(shù)情況下,服務端輸出的內容長度不能確定,無法通過長度信息,來判斷實體的邊界,這個時候就就需要使用分塊傳輸編碼,在響應頭中會出現(xiàn)了 Transfer-Encoding: chunked 這樣的標識。

每個經過 chunked 編碼后的分塊包含兩個部分:數(shù)據以及數(shù)據的長度信息,當遇到分塊長度為 0,表示該分塊沒有內容,實體結束,Content-EncodingTransfer-Encoding 二者經常會結合來用,對傳輸?shù)膬热菥幋a壓縮,提高傳輸效率,BigPipe 就是基于分塊傳輸編碼,實現(xiàn)頁面的分塊加載。

使用 Node.js 實現(xiàn)最小化示例

BigPipe 的服務端可以用各種語言實現(xiàn),這里介紹的使用 Node.js 作為服務端語言,主要用到的是下面的兩個方法

  • response.write(chunk[, encoding][, callback])
  • response.end([data][, encoding][, callback])

當我們多次調用 response.write,數(shù)據自動被流式傳輸,向瀏覽器提供多了連續(xù)的響應體片段,chunk 可以是字符串或 buffer 類型, res.end 用來告訴服務器,已發(fā)送所有響應頭和主體,callback 是成功執(zhí)行后的回調方法。

const server = http.createServer(async(req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html');
    res.write(`<DOCTYPE html>
                <html lang="en">
                <head ><title>BigPipe</title></head>
                <body>`);
    res.write(`<div>1</div>`);
    await sleep(1000);
    res.write(`<div>2</div>`);
    res.end(` </body></html>`);
}).listen(3000);

通過 htttp.createServer 啟動一個簡單的 web 服務,通過 res.write 連續(xù)發(fā)送多個響應體片段,首先輸出的是 body 以上片段,然后發(fā)送 <div>1</div>,間隔1s,發(fā)送<div>2</div>,最后調用 res.end,閉合標簽,結束響應體傳輸。在頁面上,我們先看到 1,1s之后,會出現(xiàn) 2。

基于 express 框架實現(xiàn)頁面的分塊加載

前端頁面

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>BigPipe Demo</title>
        <style>
            .box {
                width: 100px;
                height: 100px;
            }
        </style>
        <script>
            const BigPipe = {
                view(selector, temp) {
                    document.querySelector(selector).innerHTML = temp;
                }
            };
        </script>
    </head>
    <body>
        <div id="a" class="box">loading...</div>
        <div id="b" class="box">loading...</div>
        <div id="c" class="box">loading...</div>
    </body>
</html>

服務端代碼

const express = require('express');
const fs = require('fs');
const app = express();
const renderModule = (moduleId, res) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const template = `<span>${moduleId.toUpperCase()}</span>`;
            res.write(
                `<script>BigPipe.view('#${moduleId}', '${template}');</script>`
            );
            resolve();
        }, 1000 * (Math.random() * 3 + 1));
    });
};
app.get('/', (req, res, next) => {
    const layoutHtml = fs.readFileSync(__dirname + '/layout.html').toString();
    res.write(layoutHtml);
    const moduleIds = ['a', 'b', 'c'];
    const promises = moduleIds.map(moduleId => renderModule(moduleId, res));
    Promise.all(promises).then(() => {
        res.end();
    });
});
app.listen(3000, () => console.log('server started at : 3000'));

客戶端處理分塊返回的數(shù)據

一般的 ajax 請求的處理,只有兩種情況:成功、失敗。,如何處理正在執(zhí)行中的數(shù)據呢? 我們可以借助xhr.readyState 屬性,該屬性表示請求的狀態(tài),一共有5個狀態(tài)

  • 0: 請求未初始化
  • 1: 服務器連接已建立
  • 2: 請求已接收
  • 3: 請求處理中
  • 4: 請求已完成,且響應已就緒

我們經常用到是 xhr.readyState === 4 ,然后結合 xhr.status 來判斷請求的成功或者失敗,執(zhí)行對應的回調方法。

為了處理分塊返回的數(shù)據,我們需要監(jiān)聽 xhr.readyState === 3,每次有分塊數(shù)據返回的時候,都會觸發(fā) xhr.onreadystatechage 的方法,進入 this.readyState === 3 的判斷執(zhí)行語句,實現(xiàn)分塊數(shù)據的成功回調方法。

注意:當分塊數(shù)據返回的時間較短的時候,會出現(xiàn)多個分塊一起返回的情況,所以我們不能用字符串截取的方式,把已結束的 chunk 存放在數(shù)組中。

以下只是部分代碼,用來分析客戶端處理分塊返回數(shù)據的過程

let xhr = new XMLHttpRequest();
let chunked = [];
xhr.onreadystatechange = function() {
    if ([3, 4].includes(this.readyState)) {
        // 因為請求響應較快時,會出現(xiàn)一次返回多個塊,所以使用取出數(shù)組新增項的做法
        if (this.response) {
            let chunks = this.response.match(/<chunk>(.*?)<\/chunk>/g);
            chunks = chunks.map((item: string): string => item.replace(/<\/?chunk>/g, ''));
            const data = chunks.slice(chunked.length);
            data.forEach(item => {
                try {
                    callback(JSON.parse(item));
                } catch (e) {
                    callback(options.onData(item));
                }
            });
            chunked = chunks;
        }
    }
}

小結

當我們遇到批量處理,后端處理時間比較長的時候,我們可以引入 BigPipe 方案,將每條記錄的結果,一個一個的分塊返回的,實時渲染出來;當我們遇到一個頁面出現(xiàn)很多模塊,大量 api 請求的時候,就可以考慮 BigPipe 方案,分塊返回數(shù)據。

如果這篇文章對您有幫助,記得給作者點個贊,謝謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • AJAX 原生js操作ajax 1.創(chuàng)建XMLHttpRequest對象 var xhr = new XMLHtt...
    碧玉含香閱讀 3,563評論 0 7
  • Ajax和XMLHttpRequest 我們通常將Ajax等同于XMLHttpRequest,但細究起來它們兩個是...
    changxiaonan閱讀 2,391評論 0 2
  • ajax作為前端開發(fā)必需的基礎能力之一,你可能會使用它,但并不一定懂得其原理,以及更深入的服務器通信相關的知識。在...
    蕭玄辭閱讀 887評論 0 0
  • Ajax是Asynchronous JavaScript and XML的縮寫,這一技術能夠向服務器請求額外的數(shù)據...
    博聞強記富內斯閱讀 236評論 0 0
  • 27、移動端響應式布局開發(fā) 響應式布局開發(fā) 1、什么是響應式布局開發(fā)?把我們開發(fā)完成的產品,能夠讓其適配不同的設備...
    萌妹撒閱讀 1,208評論 0 0

友情鏈接更多精彩內容