低成本打造一個帶寬無限的網站 —— No.4 數據流優化

4
分塊處理

上一篇曾提到,咱們可對資源加密存儲,而後在 SW 中進行解密。前端

理論上這固然可行,但事實上會出現一些問題:咱們必須等整個資源下載完成後,才能開始解密操做。這對於用戶體驗,會產生很大的影響。git

假若有個 1MB 的圖片,經過 100 KB/s 的速度加載,那麼要 10 秒後才能解密再展現;然而正常狀況下,圖片是邊加載邊顯示的,並不會讓用戶等好久,而後一次性展現全部的。github

爲了解決這個問題,一個期待已久的新標準終於到來,那就是 Stream API。瀏覽器

有了流的支持,數據就能夠漸進處理,而沒必要等待完整的。例如,咱們使用 fetch 分塊讀取內容:網絡

// fetch 分塊讀取演示
async function load(url) {
    let res = await fetch(url);
    console.log('response:', res);

    let reader = res.body.getReader();
    for (;;) {
        let r = await reader.read();
        if (r.done) {
            break;
        }
        console.log('chunk:', r.value);
    }
    console.log('end');
}

load('https://raw.githubusercontent.com/EtherDream/_/master/pic.jpg');

演示:codepen.io/anon/pen/zPKrGX多線程

同時,SW 也支持數據分塊輸出給下游:負載均衡

// SW 分塊輸出
let stream = new ReadableStream({
    start(controller) {
        ...
        input.ondata = function(chunk) {
            controller.enqueue(chunk);
        };
        input.onend = function() {
            controller.close();
        };
        ...
    }
});

let res = new Response(stream, ...);
...

二者結合,咱們就能夠實現邊下載、邊解密、邊輸出的效果。因而對於加密的圖片、視頻等資源,也能按部就班地展現了!async

下載加速

除了解密、解壓縮等場合,數據流還可用於傳輸優化。例如,用戶下載大文件的場合。ide

因爲免費空間單個節點的帶寬是有限的,所以下載速度不會太快。這時就能夠經過 SW 作加速了 —— 咱們同時從多個節點獲取相應的文件片斷,而後依次輸出到響應流裏:fetch

在用戶看來,這只是瀏覽器默認的單線程下載,但事實上內部已經過 SW 加速,和傳統的多線程下載軟件並沒有本質區別!

固然,就算免費空間不支持 Range 請求也不要緊,咱們可事先把大文件分紅多個小文件上傳,而後分別加載便可。

動態加速

上一篇提到,經過 SW 可對故障節點「實時無縫」的切換。如今有了數據流,咱們可將其發揮到極致,甚至能在傳輸的過程當中進行調整。

例如,SW 默認選擇節點 1 加載資源,但發現速度沒有預期的那麼快,因而可增長節點 2 參與加速:

這樣,咱們就能根據用戶的實際網絡狀況,在端上動態調整,從而實現更智能的負載均衡!

插入腳本

有時候,咱們但願給站點下全部頁面的頭部插入一個 JS 腳本。

這個功能,若是沒有數據流支持的話,那麼 SW 必須得下載整個 HTML 才能修改;而如今,咱們只需改造最早返回的幾個 chunk 便可!

不過須要注意的是,chunk 是二進制層面截斷的,所以可能把多字節字符截成兩半,致使出現亂碼。

爲此,咱們須要用「流模式」解碼字符串。例如:

// stream decode example
let dec = new TextDecoder();
let chunk1 = new Uint8Array([228, 189, 160, 229, 165]);
let chunk2 = new Uint8Array([189]);

dec.decode(chunk1, {stream: true});     // "你"
dec.decode(chunk2, {stream: true});     // "好"

若是 chunk 末尾的字符不完整,那麼不足的部分則被暫存在內部,下次解碼時會自動加在開頭。

這樣,咱們就能用字符串方法,更方便地操做二進制數據了:

let dec = new TextDecoder();
let enc = new TextEncoder();

input.ondata = function(chunk) {
    // 二進制 -> 字符串
    let str = dec.decode(chunk, {stream: true});

    // 插入腳本元素
    str = str.replace(/<head/i, '<script ...><head');

    // 字符串 -> 二進制
    chunk = enc.encode(str);
    ...
};

固然,這裏的邏輯還有點瑕疵 —— 假如 <head 這個字符串正好跨越兩個 chunk,那就沒法匹配到了。

因爲 JS 不支持流模式的正則匹配,所以能夠用個土辦法:若是 str 匹配不到,則截掉末尾 5 個字符,而後將尾巴暫存起來,拼到下一次的頭部。。。這樣雖然沒有流那麼嚴格,但實現簡單,而且也很高效。

此外,因爲咱們只需替換一次,所以以後可跳過這步,無需解碼、匹配、編碼了。

小結

在數據流的配合下,SW 可實現很是豐富的玩法。不過目前只有 Chrome 瀏覽器支持 Stream API,所以兼容性也是個較大的問題。相信隨着新標準的普及,從此使用前端加速的網站,必定會愈來愈多。

然而對於咱們的「免費空間」來講,除了兼容性問題以外,還有 SW 的各類使用限制也是一個挑戰。所以如何繞過 SW 的使用限制,也是須要咱們思考的。

相關文章
相關標籤/搜索