天天看點

貓哥網絡程式設計系列:詳解 BAT 面試題

從産品上線前的接口開發和調試,到上線後的 bug 定位、性能優化,網絡程式設計知識貫穿着一個網際網路産品的整個生命周期。不論你是前後端的開發崗位,還是 SQA、運維等其他技術崗位,掌握網絡程式設計知識均是崗位的基礎要求,即使是産品、設計等非技術崗位,在灰階環境體驗産品時也需要了解頁面緩存、Host 切換等網絡基礎概念。

「貓哥網絡程式設計系列」一直是我想沉澱的一個技術知識點,因為我認為:網絡程式設計相關知識(尤其是 HTTP 協定),是網際網路産品開發當中最重要的基礎知識(沒有之一)。掌握這方面的基礎知識,對一個新手程式員來說至關重要。本系列将會在我的微信公衆号「貓哥學前班」上連載,并在 Github 上維護更新。

使用「詳解 BAT 面試題」作為本文的副标題,是為了吸引更多的技術新人浏覽此文,然而本文并非标題黨。掌握本文所提到的知識點必将大幅提升程式員的面試成功率,因為「網絡程式設計」方面的基礎知識,是 BAT 面試的必考項目。

2009 年我在支付寶做前端開發時,參與草拟了一份非正式的前端崗位招聘要求。

國内最大的第三方支付舞台,體驗億萬資金穿梭代碼的快感; 一群熱愛前端技術的夥伴,最快的成長經曆; 持續的教育訓練體系,完善的項目開發環境,最具潛力的UED團隊。 熱愛前端,熱愛設計,對新鮮事物充滿好奇心,喜歡搗鼓各種網際網路應用;(興趣、學習能力、創新能力) 自我管理能力強,健康的創業心态,樂于分享與溝通;(心态、分享、性格) 具備基本的前端素質,了解WEB标準化、性能優化方法,了解可用性、可通路性;(基本技能) 能和設計師談産品設計,和後端開發研讨技術實作方案,制定服務接口,崇尚團隊合作;(向前向後能力)

當時在面試時最流行問的前端技術問題是:Web 标準化、AJAX 與 YSlow。

問:一個 AJAX 請求從開始建立到最後的響應階段,在其整個生命周期中,使用到了哪些 JavaScript 對象與方法?

問:YSlow 的 34 條性能優化建議中,哪些與 HTTP 協定相關,請盡可能多的列舉出來,并說說你的了解?

2011 年我開始成為騰訊的前端開發面試官,負責騰訊電商的前端開發(網頁重構方向)筆試出題與面試工作。在 2012 年的校招過程中,我發現不論是我出的筆試題,還是其他面試官出的題目,HTTP 協定相關的知識都是必考項。例如,

問:HTTP 協定中與緩存相關的 HTTP Header 有哪些?

問:列舉出你所知道的 HTTP 狀态碼,并描述它們的含義與發生的場景?

後來我在學習百度 FIS 架構的過程中,無意間看到百度 FEX 團隊的這份開源前端開發面試題,不出所料,同樣有一道與網絡程式設計相關的題目:

一個頁面從輸入 URL 到頁面加載完的過程中都發生了什麼事情?越詳細越好

由此可見,網絡程式設計相關知識的确是 BAT 前端面試題中的必考項,而對于負責輸出 API 接口的背景開發崗位來說,更是如此:

問:一個 POST 請求的 Content-Type 有多少種,傳輸的資料格式有何差別?

問:什麼是 RESTful API,如何設計一個 Open API 的接口?

接下來我們一起探讨下具體的解題思路,浏覽完本文之後,你将會首先掌握 HTTP 協定相關的前後端基礎知識。

要掌握 HTTP ,就需要先看到 HTTP 到底長什麼樣?(不了解「網絡七層協定模型」和 TCP 的同學先不着急,本系列的後面幾篇會涉及到。)

在 Chrome 開發者工具下我們可以看到,打開一個網頁後,浏覽器會發起許多 HTTP 的請求(HTTP Request),這些請求經過伺服器端處理後會傳回對應的資料(HTTP Response),浏覽器會按照這些資料的類型将它們渲染出來。

貓哥網絡程式設計系列:詳解 BAT 面試題

Chrome 中看到的 Request/Response Header 是其格式化之後的形式,要看到它們的原始模樣(Raw Source),我們需要借助兩個 HTTP 接口調試利器。

其中 Windows 系統下使用 Fiddler,Mac 系統下使用 Charles。Fiddler 具體的安裝與使用教程,請自行百度(安裝 Fiddler4 還需同時安裝 .NET Framework 4),Charles 相關教程,推薦參考 iOS 大神唐巧的《Charles 從入門到精通》。使用 Linux 系統的說明已經是網絡程式設計方面的大牛了,不需要繼續往下看 :P

運作 Fiddler(或 Charles) 之後,使用 Chrome 浏覽器打開「貓哥學前班」的新浪微網誌首頁:http://weibo.com/mgxqb

在 Fiddler 左側面闆下選中該條 HTTP 請求,再将右側面闆的請求部分和響應部分都切換到 Raw 标簽頁。如下圖所示:

貓哥網絡程式設計系列:詳解 BAT 面試題

Charles 下的操作與 Fiddler 類似:

貓哥網絡程式設計系列:詳解 BAT 面試題

HTTP 協定規範由 W3C 制定,與具體的抓包工具無關,接下來我們主要以 Charles 為例,詳細講解下 HTTP 的封包格式,這對了解基于 HTTP 的 API 接口設計和網頁性能優化有很大幫助。

我們先看一下請求頭的源碼(Request Raw),為了防止隐私洩露,我已删除部分 Cookie 資訊:

仔細觀察以上源碼,我們能大概總結出 HTTP 協定的格式規範:

第一行定義了請求類型(<code>GET</code>)、請求路徑(<code>/mgxqb</code>)與協定類型及其版本号(<code>HTTP/1.1</code>),使用一個半角空格間隔這三塊資訊;

示例源碼的最後是兩個空行。由于 HTTP 規範中要求一個合法的 HTTP 封包至少包含有一個空行,其中第一個空行用來間隔封包的頭部資訊(HTTP Request/Response Header)和主體資訊(HTTP Request/Response Body)。在空行的下一行是封包的主體資訊,由于本例為 GET 類型請求,其主體(Body)資訊通常為空,這便是第二個空行的含義;

餘下的部分有着相同的格式,即 「HTTP Header 字段名+半角冒号+半角空格+值」,我們可以把它看成YAML 格式的簡易版。其中 HTTP Header 在規範中有着明确的定義,具體參見 HTTP頭字段清單。

這便是一個 HTTP 協定封包的源碼格式,以下我們簡單講解下最常見的 HTTP header 的含義。

User-Agent (以下簡稱 UA)字段記錄了通路目前網頁的使用者浏覽器的類型與版本、作業系統類型與版本。根據不同的 UA 資訊,提供不同的站點内容是使用 UA 的常見場景。例如,如果使用者使用手機通路魅族官網www.meizu.com,浏覽器會自動跳轉至魅族手機官網 m.meizu.com。這種跳轉實作既可以由前端 JavaScript 完成,也可以通過後端傳回 302 重定向來完成。

JavaScript 通路 <code>window.navigator.userAgent</code> 屬性即可擷取該資訊。雖然該屬性是隻讀的,但有很多前端手段可以僞造 UA 。如下圖,Chrome 開發者工具在模拟不同的手機機型時,也會改變浏覽器 UA 值。由此可見,通過檢測 HTTP User-Agent Header 來識别是否為爬蟲程式,不是一個有效的方法。

貓哥網絡程式設計系列:詳解 BAT 面試題

在 PHP 中,所有的 HTTP Header 字段資訊都儲存在 <code>$_SERVER</code> 對象中,通過通路<code>$_SERVER['HTTP_USER_AGENT']</code> 即可擷取 User-Agent 的值。

由于 HTTP 協定最初被設計成一種無狀态的資料傳輸協定,伺服器端無法判斷每次處理的請求互相之間以及與之前處理的請求之間的關系,Cookie 的設計就是為了解決這個問題。

使用者在浏覽器中首次通路一個站點時,會通過請求響應頭或頁面JS腳本生成一些用于辨別使用者身份的 Cookie 資訊,這些資訊會按照域名分類,存放在浏覽器本地緩存檔案當中。例如 Windows 系統下通過通路 「C:\Users&lt;使用者名&gt;\AppData\Local\Microsoft\Windows\Temporary Internet Files」 目錄可以檢視到 IE 浏覽器儲存在本地的 Cookie 檔案。當使用者再次通路該站點時,這些 Cookie 資訊會被浏覽器自動添加到 HTTP Request Header 的 Cookie 字段中,伺服器通過讀取這些資訊,來區分目前請求的使用者身份與狀态。

浏覽器可以通過讀寫 document.cookie 屬性來添加或删除 Cookie 資訊,伺服器端可以通過 HTTP Response Header(響應頭)中的 Set-Cookie 來改寫用戶端的 Cookie 資訊。每一條 Cookie 屬性通常都會設定一個過期時間,過期之後的 Cookie 浏覽器将會自動清理它們,不會再被攜帶在 HTTP Request Header(請求頭)中。

例如,以下 PHP 語句可以通過設定 Cookie 過期時間為前一個小時來觸發用戶端 Cookie 過期,達到删除 Cookie 的目的:

由于 Cookie 通常用于記錄使用者「帳号資訊」和使用者的「操作記錄」,是以洩露 Cookie 會帶來個人帳号與隐私洩露的風險。這也是為什麼你在百度上搜尋「貸款」的關鍵詞之後,通路其他網站時就能看到相關的推薦廣告,甚至第二天就會有各種放貸電話找上門來。

又由于 Cookie 可以随意被用戶端修改(通過修改 document.cookie 屬性),是以浏覽器廠商們一起制定了HttpOnly 的 Cookie 機制。伺服器端在 setcookie 時,通過設定 HttpOnly 的辨別,可以防止用戶端通過 JavaScript 修改 Cookie 的資訊。不過這種方法對于基于 HTTP 協定進行篡改的方法來說無法防範,在之後的貓哥網絡程式設計系列中,我将會介紹如何通過監控 Wi-Fi 流量來截取、僞造使用者身份。

在 YSlow 性能優化最佳實踐中,有兩條與 Cookie 相關的建議:

Reduce Cookie Size

Use Cookie-free Domains for Components

雖然浏覽器對 Cookie 的大小與數量有着較為嚴格的限制,但很多網站(尤其是包含登入态的)的 Cookie 資訊量通常比其他所有 HTTP Header 加起來的還要多。為了減少不必要的 HTTP 資料傳輸量,YSlow 給出了以上兩條優化建議。由于網頁的靜态資源(圖檔、CSS、JS)檔案無需記錄使用者狀态,是以通常會使用一個額外的域名(Cookie 是按域名來分類存儲)來存放靜态資源檔案。

我們使用 Chrome 開發者工具檢視「貓哥學前班」新浪微網誌首頁,可以看到新浪微網誌使用了 img.t.sinajs.cn的域名來存放它的 CSS 檔案,這個域名發起的 HTTP Request Header 中沒有自動帶上 Cookie 字段資訊 (因為前後端腳本都沒有在這個域名上設定 Cookie,而是設定在了 weibo.com 域名上):

貓哥網絡程式設計系列:詳解 BAT 面試題

這裡還需要引申一個知識點:Session,它和 Cookie 有什麼關系?由于 Session 與本文所講的 HTTP 協定關系不大,相關知識點請自行百度。

網站性能優化中,最為關鍵的是緩存機制(又是沒有之一)。在伺服器端通常會使用 Memcached、Redis 等服務來緩存經常通路的資料。例如在一個電商網站中,使用者經常通路的熱賣商品資料會被緩存在記憶體中,使用者在一定時間内通路商品詳情頁時,背景程式直接從緩存服務中擷取這段資料,這種方法可以大幅降低資料庫的通路壓力。

在使用者端,浏覽器會有一系列機制通過緩存來提升頁面加載速度。例如 IE/Chrome 都會緩存 GET 類型的 AJAX 請求,IE 甚至會緩存 POST 類型的請求,需要通過增加時間戳參數的方式來強制清除緩存。對于所有的靜态資源檔案來說,最佳實踐是為它們增加一個 「Never Expires」(永不過期)的強(長)緩存,以下是一個強緩存靜态資源伺服器的 Nginx 配置示例:

通過配置 「expires 365d」,HTTP Response Header(響應頭)中會傳回 「Cache-Control: max-age=31536000」 的頭字段,配合 Last-Modified 頭字段。浏覽器便可以自動完成資源的強緩存。

Cache-Control 是浏覽器緩存機制中最為重要的一個配置,以下是浏覽器加載靜态資源檔案時的緩存檢查機制流程:

貓哥網絡程式設計系列:詳解 BAT 面試題

由此可見,靜态資源緩存優化的最佳狀态是:直接從本地緩存中讀取 &gt; 304 狀态 &gt; 200 狀态。關于 HTTP 狀态碼,與網站性能優化有關的主要是以下幾個。

盡量減少 200 狀态碼的請求。200 表示是一個正常的請求傳回,此條優化規則要求盡可能多的減少頁面的 HTTP Request 數量。常見的方法有:合并打包靜态資源、使用 CSS Sprite 雪碧圖合并、緩存 AJAX、使用 LocalStorage/UserData/Manifest 等本地緩存技術。

清理傳回 301/302 狀态碼的入口連結。301 表示永久重定向,302 表示臨時重定向。伺服器端使用重定向傳回通常是為了相容一個舊的入口連結。我們能做的優化是,将調用舊入口的場景進行清理,直接調用重定向之後的新 URL 位址。

304 表示靜态資源未更新,浏覽器可直接使用本地緩存檔案。通常 304 的産生與浏覽器的處理機制以及伺服器緩存頭配置有一定的關系。304 雖然未傳輸檔案主體内容,但 HTTP 請求的建立依然是一個可以避免的性能損耗。騰訊 KM(内部知識分享平台)上有一篇文章通過在真實海量業務場景(沒記錯的話是 Qzone 業務)中,正交驗證 HTTP 1.0 與 1.1 協定中與緩存相關的 HTTP Header 配置,結合日志分析得出了一個最佳實踐:關閉 Etag 配置,隻啟用 Cache-Control 與 Last-Modified 響應頭。為了相容老浏覽器,可保留 Expires。因為 Etag 的緩存方案,在經過 CDN 及網關代理伺服器後,會導緻緩存命中率下降。從以上「浏覽器緩存檢查機制流程」圖上可以看出,使用強緩存(Cache-Control max-age 設定為一年)後浏覽器在資源過期前不會發起 HTTP 請求,那如何保證靜态資源在伺服器上更新後本地的緩存也能同步更新呢?可參考百度 FIS 的「檔案指紋」方案。

清理傳回 404 狀态碼的入口連結。靜态資源檔案的 404 調用需嚴格避免,而入口頁面的 404 則在所難免。通過在全站 404 頁面進行産品引導與體驗優化,并結合資料上報記錄來源頁(HTTP Referer Header 或 <code>document.referrer</code>),可以找到并清理 404 來源入口。對于由搜尋引擎進入的來源,可通過主動送出新索引至搜尋引擎,或使用 301/302 重定向的方式,有效利用起這些「被浪費的流量」。

502 伺服器出錯。如果是 Nginx + FastCGI 的常見架構,通常是由于 Nginx 緩沖區溢出或伺服器資源被耗盡引起,針對不同的業務場景進行 Nginx 的配置優化能顯著提升伺服器抗壓性能。

如果你對上文提及的「網絡性能優化」的知識點十分感興趣,建議你通讀 Steve Souders 的《高性能網站建設指南》與《高性能網站建設進階指南》,Steve Souders 的個人網站上積累了很多性能優化的方法與案例。

如果你能看到這裡,相信你已經知道如何解答前文提到的幾道 BAT 網絡程式設計面試題中,關于 「HTTP 協定、狀态碼、緩存與性能優化」相關的問題。

在下一章節中,我們會繼續 HTTP 協定的話題,并詳細講解 HTTP POST 相關的網絡程式設計細節與調試技巧,相信看完之後你将能輕松定位并解決所有接口聯調的問題與 Bug:)

歡迎關注我的微信公衆号「貓哥學前班」

貓哥網絡程式設計系列:詳解 BAT 面試題

繼續閱讀