一次排查頁面重複請求的經歷

前段時間重構一個頁面,頁面中存在通過第三方JavaScript代碼插入的動態廣告(正常的產品需求),上線後發現第三方的廣告資源存在重複請求的問題。由於控制廣告插入的JavaScript代碼由第三方提供,我們只負責按照他們要求的方式引入即可,所以對JavaScript代碼內容並不瞭解,在這種情況下開始了艱難的排查過程。雖困難重重,但最終還是找到了原因,在此過程中有些收穫,現將排查過程抽象如下:

注:以下過程和截圖皆在Chrome瀏覽器中進行。

一、代碼

<div id="container">
    <iframe src="/iframe-1" frameborder="0"></iframe>
    <iframe src="/iframe-2" frameborder="0"></iframe>
    <iframe src="/iframe-3" frameborder="0"></iframe>
</div>

<script>
    document.getElementById('container').innerHTML += '<p>上面是iframe</p>';
</script>

代碼大意:頁面上先渲染3個iframe(目前頁面插入廣告仍然以iframe作爲主要實現形式),然後在最後一個iframe後面追加一個p元素

二、現象

1.頁面:渲染正常

圖片描述

2.Network:存在重複的異常請求(Status是canceled)

圖片描述

三、排查過程

1.重複請求從何而來?

既然是解決重複請求的問題,那麼重複請求從何而來是我們要解決的第一問題。
由於請求是從第三方的JavaScript代碼中發出的,去讀第三方壓縮後的JavaScript代碼更像無頭蒼蠅。整個過程就像在圍城之外徘徊,心急如焚。後來靜下心來發現Chrome的devtools中一個很關鍵的排查助力神器:Network下的Initiator

圖片描述

此列是什麼意思呢?通俗地說就是觸發請求的位置。
通過對比發現,同一個重複的請求發起的位置並不相同,以/iframe-1爲例:
點擊第一個請求的Initiator,跳轉的位置(標黃位置):
圖片描述

點擊第二個請求的Initiator,跳轉的位置(標黃位置):
圖片描述

通過觀察可以發現,第一個/iframe-1請求是由於正常渲染iframe元素自動觸發的,第二個/iframe-1請求是在執行JavaScript代碼(作用拼接DOM節點)時觸發的,然而對於觸發第二個/iframe-1請求的那行JavaScript代碼,其真實意圖僅僅是拼接一個p元素而已,並不期望其他額外的事情(比如觸發新的請求)發生。另外,對於/iframe-2和/iframe-3的第二次請求的觸發點都是那段拼接DOM節點的JavaScript代碼,至此,產生問題的罪魁禍首已經浮出水面,接下來我們分析下產生重複請求的原因。

2.爲什麼會重複請求?

產生重複請求的JavaScript代碼

document.getElementById('container').innerHTML += '<p>上面是iframe</p>';

翻譯成:

document.getElementById('container').innerHTML = document.getElementById('container').innerHTML + '<p>上面是iframe</p>';

意思就明瞭多了:先獲取id爲container的div元素的所有內部HTML,將其拼接p元素後,再賦值給container的innerHTML。

這個過程會導致iframe元素的重新渲染,也就會引發iframe對應的請求重新觸發。

所以,同一個請求會觸發兩次的原因:頁面加載時渲染iframe元素會觸發第一次請求,執行JavaScript代碼導致iframe重新渲染觸發第二次請求。

找到了問題的原因,解決問題的辦法也就水到渠成了,將

document.getElementById('container').innerHTML += '<p>上面是iframe</p>';

改爲:

var div = document.createElement('div');
var text = document.createTextNode('廣告');
div.appendChild(text);

document.getElementById('container').appendChild(div);

問題解決了,不過,還有一個疑問:爲什麼渲染iframe產生的第一個請求的狀態是canceled?

3.爲什麼重複的請求的Status是canceled?

首先Status是canceled代表什麼意思呢?
從其字面意思理解,代表此請求被取消了,即此請求在發給服務器端之前就被瀏覽器取消了,也就是說此請求根本就沒有從瀏覽器發出去,更不可能到達服務器,所以狀態是canceled而不是HTTP狀態碼也就不難理解了。

那第一次的請求爲什麼會被瀏覽器取消呢?
用關鍵詞“chrome calcel request”谷歌了一下,在stack overflow上找到了一個比較全面的解答,截圖如下:
圖片描述

其中紅色標註即爲我們要尋找的答案。

根據截圖大概梳理一下,Chrome瀏覽器會取消請求的幾種場景:

  • 觸發請求的DOM元素被刪除了(比如img元素還沒有加載完就被刪除了)
  • 做了一些不必要的數據加載(比如開始加載iframe後改變其src或重寫其內容)
  • 大量的請求指向同一個服務器,並且前面請求的網絡問題表明後續的請求也走不通(DNS查詢錯誤,前面的請求報400)

至此,整個過程中的疑問點一一解開了。

四、總結

現在再回顧此bug,產生的原因並不高深,但整個排查過程確實值得總結。小結一下:

1.對於第三方庫報錯,切莫妄圖通過通讀並熟悉整個庫後解決問題,通讀代碼只會浪費解決問題的時間,弄明白調用關係纔是王道

2.Chrome開發者工具中的Network > Initiator代表請求是從哪裏觸發的,對於定位請求非常有用,尤其是對於一些第三方庫中發出的請求

3.請求狀態爲canceled,表示請求被瀏覽器取消了,並沒有從瀏覽器發出去,更不可能進到服務器

4.Chrome瀏覽器取消請求的幾種情景,見上圖

5.element.innerHTML += HTMLStr 表示將原有的子節點和新的節點拼接後再重新賦值,會導致節點元素重新渲染,節點內容中含有iframe時慎用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章