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