Cloudflare的HTML解析歷史(上),cloudflare的api解析Cloudflare的HTML解析歷史(上)什么是HTML流量重寫器?HTML流量 重寫器接受HTML字符串或字節流輸入,將其解析為令牌或任何其他結構化中間表示(IR),例如抽象語法樹(AST)。然后,它在轉換回HTML之前對標記執行轉換。這......
什么是HTML流量重寫器?
HTML流量 重寫器接受HTML字符串或字節流輸入,將其解析為令牌或任何其他結構化中間表示(IR),例如抽象語法樹(AST)。然后,它在轉換回HTML之前對標記執行轉換。這就提供了在處理字節時修改,提取或添加到現有HTML文檔的功能。將其與標準的HTML樹解析器進行比較,后者需要檢索整個文件以生成完整的DOM樹。基于樹的重寫器將花費更長的時間來交付第一個處理的字節,并且需要更多的內存。
HTML重寫器
例如你擁有一個擁有很多歷史內容的大型網站,現在希望通過HTTPS來提供該網站。你將很快遇到通過HTTP提供資源(圖像、腳本、視頻)的漏洞,因為這種“混合內容”打開了一個安全漏洞,瀏覽器將警告或阻止這些資源。這意味著,更新網站每個頁面上的每個鏈接可能很困難,甚至不可能。使用HTML流量重寫器,你可以選擇任何HTML標記的URI屬性,并將任何HTTP鏈接更改為HTTPS。研究人員在2016年構建了此功能,即自動HTTPS重寫以解決客戶的混合內容問題。
下文,我將詳細介紹如何從在HTML頁面中查找電子郵件地址的簡單想法開始,以構建幾乎符合規范的HTML解析器,然后到匹配虛擬機的CSS選擇器的旅程。
在邊緣重寫
通過Cloudflare重寫內容時,我們不想影響網站性能。設計HTML流量重寫器的平衡在于,通過保留盡可能少的信息,同時保留重寫匹配標記的能力,來最大程度地減少響應字節流中的暫停。
與瀏覽器中使用的HTML解析器相比,要求的差異包括:
輸出延遲
對于瀏覽器來說,文檔對象模型(DOM)是解析過程的最終目的,但在本例中,我們必須解析,重寫并序列化回HTML。對于Cloudflare的反向代理,邊緣服務器上的任何內容處理都會導致服務器與眼球之間的延遲。要最小化HTML處理的延遲影響,這涉及解析,重寫和序列化回HTML。在所有這些階段中,我們都希望盡可能快地將延遲最小化。
什么是邊緣服務器
隨著互聯網及其應用的快速發展,絕大多數企業都建立自己的網站,增強對外聯絡,加速業務流程,客戶對網站系統訪問的響應時間,網站內容以及所提供服務的可靠性,即時性等要求也越來越高,使得以單臺服務器來支撐整個網站的系統已無法滿足客戶需求,取而代之的是采用兩到三層架構的一組服務器.第一層是跟用戶直接發生聯系的前端服務器,也稱為邊緣服務器。
邊緣服務器為用戶提供一個進入網絡的通道和與其它服務器設備通訊的功能,通常邊緣服務器是一組完成單一功能的服務器,如防火墻服務器,高速緩存服務器,負載均衡服務器,DNS服務器等。第二層是中間層,也稱為應用服務器,包括Web表現服務器,Web應用服務器等.第三層是后端數據庫服務器。
解析器的加載量
通常情況下,瀏覽器很少需要處理大小超過1Mb的HTML頁面,并且平均頁面加載時間最多約為3s。 HTML解析不是頁面加載過程的主要瓶頸,因為瀏覽器在運行腳本和加載其他關鍵用戶資源時會被阻塞。我們可以粗略估計,對于瀏覽器的HTML解析器來說,大約3Mbps的加載量是可以接受的。在Cloudflare,每個CPU擁有數百兆的流量,因此我們需要一個解析器,其速度要快一個數量級。
內存限制
比如簡單的HTML標記在瀏覽器中打開時,將消耗大量系統內存,最后終止瀏覽器選項卡(解析器將消耗所有這些內存):
不幸的是,即使對于HTML流量重寫,也不可避免地需要緩沖部分輸入。考慮以下兩個HTML代碼段:
當在HTML頁面末尾遇到HTML時,這些看似相似的HTML片段將被完全區別對待。第一個片段將被解析為開始標記,第二個片段將被忽略。僅通過查看 字符后的標記名,解析器無法確定是否找到了開始標記。它需要遍歷搜索結束“”的輸入以做出決定,緩沖中間的所有內容,以便稍后將其作為開始標簽令牌發快遞給使用者。
這一要求迫使瀏覽器在最終放棄內存不足漏洞之前無限期地緩沖內容,在本文的示例中,我們無法花費數百兆的內存來解析單個HTML文件(實際的限制甚至更嚴格,甚至每個請求使用十幾KB都是不可接受的)。就內存使用而言,我們需要比其他實現復雜得多,并且可以優雅地處理所有提供的內存容量不足以完成解析的情況。
v0:隨機(Adhoc)解析器
查找和模糊電子郵件
2010年,Cloudflare決定提供一項功能,以阻止流行的電子郵件抓取工具。這種保護的基本思想是查找和模糊頁面上的電子郵件,然后使用注入的JavaScript代碼在瀏覽器中將其解碼回去。聽起來很簡單,對吧?你搜索任何看起來像電子郵件的東西,對其進行編碼,然后使用一些JavaScript魔術對其進行解碼,然后將結果呈現給最終用戶。
但是,即使這樣看似簡單的任務也已經需要解決幾個問題。首先,我們需要定義什么是電子郵件。實際上,甚至臭名昭著的RFC都涵蓋了整個RFC,實際上,它已經過時且不完整,因為新RFC添加了許多有效的電子郵件構造,包括Unicode支持。現在,讓我們關注一個更高層次的問題:轉換流量內容。
來自網絡的內容以數據包的形式出現,這些數據包必須由我們的服務器緩沖并解析為HTTP。你無法預測內容的分割方式,這意味著你始終需要對其中的某些內容進行緩沖,因為要替換的內容可以存在于多個輸入塊中。
假設我們決定使用簡單的正則表達式,比如 [\w.]+@[\w.]+ 。如果通過的內容包含電子郵件“test@example.org”,它可能被分成以下幾塊:
為了保持首字節時間(TTFB)和一致的速度,我們希望確保在確定前一個塊不適合用于替換時立即釋放它。
最簡單的方法是將正則表達式轉換為狀態機或有限自動機,盡管你可以手動完成此操作,但最終將獲得難以維護且易于出錯的代碼。相反,選擇了Ragel將正則表達式轉換為有效的本機狀態機代碼。 Ragel除了遍歷狀態機外,不會嘗試緩沖或做其他任何事情。它提供的語法不僅可以描述模式,還可以將自定義操作(以宿主語言編寫的代碼)與任何給定狀態相關聯。
在本文的示例中,我們可以通過緩沖區,直到匹配電子郵件的開頭。如果我們隨后發現該模式不是電子郵件,則可以在該模式停止匹配后立即退出緩沖。否則,我們可以檢索匹配的電子郵件并將其替換為新內容。
要將模式轉換為流解析器,我們可以記住電子郵件的可能開頭的位置,除非已將其丟棄或由當前輸入的末尾替換,否則將未處理的部分存儲在永久緩沖區中。然后,當出現一個新塊時,我們可以對其進行單獨處理,從Ragel記住自己的狀態恢復,但隨后使用緩沖的塊和一個新塊來發出或進行模糊。
現在,我們已經解決了在文本中匹配電子郵件模式的問題,我們需要處理一個事實,即它們需要在頁面上進行模糊,這是第一次引入HTML“解析”的提示。
我將“解析”放在引號中,因為電子郵件過濾器(模塊的名稱)并沒有實現整個解析器,而是試圖復制整個HTML語法,而是添加了自定義的Ragel模式,僅用于跳過注釋以及不應模糊電子郵件的標簽。
這是一種合理的方法,尤其是在2010年,HTML5規范發布的四年之前,當時所有瀏覽器都有自己獨特的HTML處理方法。但是,你可以想象,這種方法無法很好地擴展。與此同時,新的特性開始添加,這也需要動態修改HTML(比如自動插入谷歌分析腳本),現有的模塊似乎是最好的地方。它發展到能夠處理越來越多的標記、操作和語法邊緣情況。
在2011年,Cloudflare決定還添加縮小功能,即使用戶自己沒有使用縮小功能也能加快其網站的速度。為此,我們決定使用現有的流量壓縮器——jitify。它已經具有NGINX綁定,這使其非常適合集成到現有管道中。
不幸的是,就像當時的大多數其他解析器以及我們上面描述的那樣,它具有自己的HTML,JavaScript和CSS處理規則,這些規則并不精確,而是試圖盡最大努力解析內容。這導致我們擁有兩個不兼容的獨立流解析器,并且可能單獨或組合生成漏洞。
v1:符合HTML5規范的解析器
多年來,工程師一直在向不斷增長的狀態機中添加新功能,同時修復了由于語法實現不精確,各種解析器之間的沖突以及功能本身存在的問題而引起的新漏洞。
接下來,我將描述研發者是如何從規范狀態機開始構建符合HTML5的解析器。僅使用此狀態機,就應該直接構建解析器。你可能已經知道,從歷史上看,HTML的解析并非十分嚴格,這意味著在不破壞現有實現的情況下,解析時需要構建實際的DOM。這對于流量重寫器是不可能的,因此開發了解析器反饋的模擬器。就性能而言,最好不要做任何事情。然后,我們描述了為什么重寫器在重寫HTML時可能會變得“懶惰”而不執行昂貴的文本編碼和解碼,然后詳細討論了判斷響應是否是HTML的難題。
HTML5
到2016年,HTML5已經為解析和兼容遺留內容和自定義瀏覽器實現定義了精確的語法規則,目前,所有瀏覽器和許多第三方實現都已經實現了它。
HTML5解析規范以狀態機的形式定義了基本的HTML語法,我們已經在Ragel上有過類似用例的經驗。盡管語法很復雜,但規范到Ragel語法的翻譯卻很簡單。由于能夠將regex語法與顯式轉換混合在一起,因此代碼看起來比狀態機的形式描述更簡單。
HTML5解析需要一個“DOM”
為了不破壞現有的實現,HTML5被指定為針對不正確的標簽嵌套、排序、未關閉的標簽、缺失的屬性和所有其他在舊瀏覽器中可能出現的問題的恢復過程。為了解決這些問題,該規范要求使用樹構建器來驅動詞法分析器。從本質上講,如果沒有DOM,就無法正確標記化HTML(分割為單獨的標簽)。
規范定義的HTML解析流程
因此,大多數解析器甚至不嘗試執行流解析,而是將輸入作為一個整體并生成一個文檔樹作為輸出。如果不給頁面加載增加明顯的延遲,我們就無法進行流轉換。
現有的HTML5 JavaScript解析器parse5已使用流量令牌生成器和重寫器實現了符合規范的樹解析。為了避免必須創建完整的DOM,引入了“解析器反饋模擬器(parser feedback simulator)”的概念。
樹生成器的反饋機制
你可以從名稱中猜到,該模塊旨在模擬完整解析器對令牌生成器的反饋,而無需實際構建整個DOM,而是僅保留正確驅動狀態機所需的必要信息和上下文。
在經過嚴格的測試并將測試運行器升級到parse5之后,我們發現這種技術適用于網絡上大多數編寫得很差的頁面,并將其應用到LazyHTML中。
LazyHTML架構
下一篇文章,我們繼續介紹可能出現的漏洞以及其中的原因,并就如何基于LazyHTML的思想構建新的流量重寫器。
特別聲明:以上文章內容僅代表作者本人觀點,不代表ESG跨境電商觀點或立場。如有關于作品內容、版權或其它問題請于作品發表后的30日內與ESG跨境電商聯系。
二維碼加載中...
使用微信掃一掃登錄
使用賬號密碼登錄
平臺顧問
微信掃一掃
馬上聯系在線顧問
小程序
ESG跨境小程序
手機入駐更便捷
返回頂部