全部產品
Search
文件中心

Edge Security Acceleration:HTMLStream API

更新時間:Oct 29, 2024

HTMLStream API是一種處理HTML流式資料的方法。通過HTMLStream API,您可以在邊緣節點上處理HTML流式資料,例如即時更新的股票資料或即時聊天記錄。HTMLStream API可以將HTML流式資料分塊傳輸,提高資料轉送效率。

背景介紹

在邊緣程式ER(EdgeRoutine)的情境中前端情境佔了很大一部分,由於邊緣有不少特殊的請求資料,例如UA、地理資訊和IP等,因此有不少需要在邊緣即時更改HTML的需求。常規的更改方式是使用Regex製作adhoc的解析器更改,但使用該方法容易出錯且不利用串流,使用開源JavaScript的解析器,例如parse5、htmlparser2等,對記憶體的消耗大、效能損失高。為瞭解決該問題,ER推出了邊緣的HTML流式解析器,專門用於流式的修改HTML代碼和頁面。

說明

該功能並非Web標準,屬於ER內建的擴充功能。

舉例說明

  • 情境描述

    假設您需要將HTML頁面中所有的<a/> 標籤修改後全部指向http://www.taobao.com,在ER中您可以使用以下代碼實現。

  • 程式碼範例

    async function handleRequest(request) {
      // 1.假設exmaple頁面返回的是待修改的HTML頁面。
      const response = await fetch("http://www.example.com");
      // 2.您需要設定好HTML流式解析器。流式解析器允許您設定多個不同CSS Selector
      //    文法的捕獲方式,然後註冊回呼函數進行改寫。
      const htmlStream = new HTMLStream(
        response.body, // 放入需要改寫的HTML流
        [[
          "a",         // 元素選取器,表示選擇所有的`a`標籤。
          {  
            // 註冊回呼函數對象,名為element的回呼函數會在a標籤(在DOM中為ElementNode)被調用。
            // 調用時您可以傳入object來更改e的資訊。
            element: function(e) {
              // 更改屬性href
              e.setAttribute("href", "http://www.taobao.com");
            }
          }
        ]]);
      
      // 3.返回修改後的請求到瀏覽器。HTMLStream是個ReadableStream,所以任何能使用
      //    ReadableStream的地方均可使用HTMLStream。
      return new Response(htmlStream);
    }
    
    export default {
      async fetch(request) {
        return handleRequest(request);
      }
    };
  • 結果分析

    以上代碼便是簡單的使用HTMLStream即時修改HTML頁面流的方法,具體分析如下:

    • 通過Fetch您可以得到一個對請求回複的流的表示,實際ER可能沒有從網路層讀取body,該設計可以減少緩衝引起的大量GC問題。

    • HTMLStream也是一個流,他的作用是TransformStream,即接受一個流,然後通過您註冊的重寫handler即時更改HTML頁面。如果您要修改一個HTML頁面,需要得到對應HTML的未經處理資料流,再把這個流當作輸入,放入到新建立的HTML流中,例如上述程式碼範例中的樣本2。

      • HTMLStream的第一個參數是一個流,表示HTML未經處理資料。

      • HTMLStream的第二個參數是個數組,表示一組重寫器。重寫器實際是一個數組,它包含一個選取器,用於選擇需要改寫的HTML及選擇一個對象,對象中的部分property會被當作回呼函數調用。例如上述例子中["a" , {....}] 用於聲明一個重寫器,實際表示一個元素為2的數組。第一個字串“a”表示元素選取器,在上述例子中表示找出文檔中的所有a標籤。第二個對象表示回調對象,對使用元素選取器而言,該對象可以包含以下三個函數:

        • element函數,簽名為function(e),當元素選取器選擇的element被解析時調用該函數。

        • comments函數,簽名為function(e),當element中嵌套的注釋被解析時調用該函數。

        • text函數,簽名為function(e),當element中的文字被解析時調用該函數。該函數支援調用多次,因為串流的原因,HTMLStream可能只是正在處理一個字串片段。

    • HTMLStream是一個流,您可以直接回複這個流,但HTMLStream內部不會進行資料緩衝,且HTMLStream和常見的parse5、htmlparser2不同,HTMLStream不會產生DOM樹,大大減少了處理時間和記憶體消耗,確保進行HTML解析的同時,保持高吞吐和並發。

重寫器

重寫器是用於註冊重寫指示的對象,實際為包含兩個元素的數組。對數組的具體說明如下:

  • 數組的第一個元素必須為String或null。

    • String:表示一個元素選取器,該選取器始終用於定位一個元素或標籤。

    • null:表示整個重寫器是針對整個文檔的。

      說明

      多數情況下您不需要用文檔層級的重寫器,文檔層級的重寫器無法定位元素。

  • 數組的第二個元素是一個JS對象,表示您註冊的回呼函數對象。

    當您使用元素選取器時,其註冊對象稱為“元素選取器回調”;當您使用文檔選取器時,其註冊對象稱為“文檔選取器回調”。

說明

同一個HTMLStream可以設定多個不同的重寫器,但只能設定一個文檔選取器以及支援設定多個元素選取器。

元素選取器文法

元素選取器是CSS選取器的文法子集,文法是子集不代表語言和CSS選取器一模一樣。元素選取器的文法如下:

  • *:所有元素或標籤。

  • div:名為div的標籤。您可以以此類推其他的標籤名稱,可以是HTML標籤或定製化標籤。

  • E#id:名為E的標籤,標籤的屬性ID是值id

  • E.Class:名為E的標籤,標籤的屬性Class是值Class

  • E[attr]:名為E的標籤,標籤的屬性中含有名稱attr。

  • 元素屬性選擇:

    • E[attr="a"]:選擇標籤E,標籤的屬性含有名attr,且值為a,注意區分大小寫。

    • E[attr^="a"]:選擇標籤E,標籤的屬性含有名attr,且值為a,不區分大小寫。

    • E[attr$="a"]:選擇標籤E,標籤的屬性含有名attr,且值以“a”結尾。

    • E[attr^="a"]:選擇標籤E,標籤的屬性含有名attr,且值以“a”開頭。

    • E[attr*="a"]:選擇標籤E,標籤的屬性含有名attr,且值含有“a”。

    • E[attr|="a"]:選擇標籤E,標籤的屬性含有名attr,且值以“a-”開頭的一串英文逗號(,)分隔的列表,例如en-ch, en-us。

  • 序列選擇:

    • E F:選擇標籤F,且F存在父節點元素E中。

    • E > F:選擇標籤F,且F的直接父節點元素為E。

  • E:not(S):選擇元素E,S是另外一個選取器,當選取器值為false時,才能選中元素E。

元素選取器回調

元素選取器回調包含以下三個回呼函數。

回呼函數

說明

回呼函數簽名

element

該屬性必須為一個非非同步函數,該屬性會在元素被解析完畢時調用。

回呼函數的簽名為function(e),傳入對象為Element對象。更多資訊,請參見Element

comments

該屬性必須為一個非非同步函數,該屬性會在元素選取器選中的元素中存在注釋時被調用。

回呼函數的簽名為function(e),傳入對象為Comments對象。更多資訊,請參見Comments

text

該屬性必須為一個非非同步函數,該屬性會在元素選取器的元素text部分被解析時被調用。

回呼函數的簽名為function(e),傳入對象為TextChunk對象。更多資訊,請參見TextChunk

說明

該函數會被調用多次,當HTMLStream每次都從HTML未經處理資料流中讀一部分出來text時,每次解析完畢就會調用這個函數。如果您想看到整個text,需要自己緩衝text後拼接起來。

說明

元素選取器可以不包含以上三個函數,此時對應的元素會被直接輸出。如果您只想修改某一部分內容,只需要註冊對應的回呼函數。

文檔選取器

文檔選取器主要用於選擇文檔層級的內容。文檔選取器用null表示,同一個HTMLStream中只能設定一個文檔選取器。

文檔選取器回調

文檔選取器回呼函數和元素選取器回呼函數類似,文檔選取器回調包含以下四個回呼函數。

回呼函數

說明

回呼函數簽名

doctype

該屬性必須為一個非非同步函數,該屬性會在文檔的doctype被解析後被調用。

回呼函數的簽名為function(e),傳入對象為Doctype對象。更多資訊,請參見Doctype

comments

該屬性必須為一個非非同步函數,該屬性會在文檔層級的comments被調用。

回呼函數的簽名為function(e),傳入對象為Comments對象。更多資訊,請參見Comments

text

該屬性必須為一個非非同步函數,該屬性會在文檔層級非元素的text節點遇到時被調用。

回呼函數的簽名為function(e),傳入對象為TextChunk對象。更多資訊,請參見TextChunk

說明

該函數會被調用多次,當HTMLStream每次都從HTML未經處理資料流中讀一部分出來text時,每次解析完畢就會調用這個函數。如果您想看到整個text,需要自己緩衝text後拼接起來。

docend

該屬性必須為一個非非同步函數,該屬性會在文檔被解析完畢時調用,主要為了追加內容到文檔末端,通常可以以注釋的形式記錄一些debug資訊到HTML末尾,用於後續排查和追蹤問題。

回呼函數簽名為function(e),傳入對象為Docend對象。更多資訊,請參見Docend

異常錯誤處理

以上任何一個回呼函數中拋出的JS異常,都會在ER運行時被捕獲,同時HTMLStream將停止處理HTML流,並且將您的異常順勢傳播到外層。

  • 如果HTMLStream的reader.read是在JS中被觸發,您的異常會被重新拋出。

  • 如果HTMLStream的reader.read是在ER運行時被調用,例如返回給用戶端,那麼異常將被ER運行時吞掉,用戶端會看到一個被截斷的內容,原因是HTMLStream是一個流,可能一部分已經發送至用戶端。該操作與現在的TransformStream的流式讀寫處理異常方式類似。

回調參數

每個回呼函數都會收到一個對象,表示選擇的HTML標籤或相關部分,該對象稱為回調參數。本文為您介紹Element、TextChunk和Comments等回調參數。

說明
  • 所有的回調參數必須在回呼函數中調用才有效,在回呼函數以外的地方調用回調參數對象的屬性或方法會導致JS異常。您可以將需要的部分儲存到其他JS對象或資料結構中。

  • 本文中出現的所有option都表示一個對象,對象中的屬性html可以被設定為true或false,用於表示該內容是否被解釋為HTML內容或純文字內容。如果將html設定為false,HTMLStream會對其做html encoding/esacping

Element

  • 定義

    該對象會在Element回呼函數被調用時提供給您,表示某個被選擇的HTML標籤。

  • 屬性

    • tagName(string):標籤名字。

    • attributes(iterator):返回一個迭代器,該迭代器返回所有的屬性[name, value]

    • removed(bool):唯讀,表示該元素是否被刪除。刪除一個元素使用remove,多數情況下需要根據這個標籤跳過該元素,因為元素已經被刪除。

    • namespaceURI:唯讀,表示該元素的命名空間URI,例如svg或script元素。

  • 方法

    • 修改屬性

      • getAttribute(name):查詢某個元素的屬性名稱。

      • setAttribute(name, value):設定和修改某個元素的屬性名稱。

      • hasAttribute(name):查詢某個元素的屬性名稱是否存在。

      • removeAttribute(name):刪除某個元素的屬性名稱。

      說明

      屬性名稱和值要求必須是string類型。

    • 修改內容

      • before(data, option):將內容插入到該元素之前,即元素的標籤之前。

      • after(data, option):將內容插入到該元素之後,即元素的標籤之後。

      • prepend(data, option):將內容插入到該元素的內容之前,即元素的開啟標籤結束的後方,例如<div>(prepend) |aaaa|(append)</div>

      • append(data, option):將內容插入到該元素的內容之後,即元素的結束標籤結束的前方,例如<div>(prepend) |aaaa|(append)</div>

      • replace(data, option):替換整個元素,包括標籤和嵌套的標籤都會被替換。

      • setInnerContent(data, option):設定元素內容,標籤和屬性不變。

      • remove():刪除該元素,removed屬性為true。

      • removeAndKeepContent():刪除元素的標籤和屬性,保留內容。

TextChunk

  • 定義

    該對象會在text回呼函數被調用時提供給您,代表某個被選擇的HTML的Text部分。

  • 屬性

    • removed(bool):唯讀,表示該元素是否被刪除。刪除一個元素使用remove,多數情況下需要根據這個標籤跳過該元素,因為元素已經被刪除。

    • text(string):唯讀,表示該text的內容。該text有可能只是一部分text,如果此字串為空白,代表這是最後一個chunk,您可以趁機把所有的text拼接起來。

    • lastInTextNode(bool):唯讀,表示是否為最後一個text。如果為true,text屬性返回Null 字元串。

  • 方法

    修改內容的方法如下:

    • before(data, option):將內容插入到該元素之前,即元素的標籤之前。

    • after(data, option):將內容插入到該元素之後,即元素的標籤之後。

    • replace(data, option):替換整個元素,包括標籤和嵌套的標籤都會被替換。

    • remove():刪除該元素,removed屬性為true。

Comments

  • 定義

    該對象會在comments回呼函數被調用時提供給您,代表某個被選擇的HTML的注釋部分。

  • 屬性

    • removed(bool):唯讀,表示該元素是否被刪除。刪除一個元素使用remove,多數情況下需要根據這個標籤跳過該元素,因為元素已經被刪除。

    • text(string):可讀可寫,表示注釋的內容或改變注釋內容。

  • 方法

    修改內容的方法如下:

    • before(data, option):將內容插入到該元素之前,即元素的標籤之前。

    • after(data, option):將內容插入到該元素之後,即元素的標籤之後。

    • replace(data, option):替換整個元素,包括標籤和嵌套的標籤都會被替換。

    • remove():刪除該元素,removed屬性為true。

Doctype

  • 定義

    該對象會在doctype回呼函數被調用時提供給您,代表某個被選擇的HTML的Doctype部分。

  • 屬性

    • name(string):唯讀,表示doctype名字。

    • publicId(string):唯讀,如果有表示public identifier,否則返回null。

    • systemId(string):唯讀,如果有表示system identifier,否則返回null。

Docend

  • 定義

    該對象會在docend回呼函數被調用時提供給您,代表整個HTML的末尾部分。

  • 方法

    append(string, option) :用於向HTML流末尾加入若干內容。