貓哥網(wǎng)絡(luò)編程系列:詳解 BAT 面試題

從產(chǎn)品上線前的接口開發(fā)和調(diào)試,到上線后的 bug 定位、性能優(yōu)化,網(wǎng)絡(luò)編程知識貫穿著一個互聯(lián)網(wǎng)產(chǎn)品的整個生命周期。不論你是前后端的開發(fā)崗位,還是 SQA、運維等其他技術(shù)崗位,掌握網(wǎng)絡(luò)編程知識均是崗位的基礎(chǔ)要求,即使是產(chǎn)品、設(shè)計等非技術(shù)崗位,在灰度環(huán)境體驗產(chǎn)品時也需要理解頁面緩存、Host 切換等網(wǎng)絡(luò)基礎(chǔ)概念。

「貓哥網(wǎng)絡(luò)編程系列」一直是我想沉淀的一個技術(shù)知識點,因為我認(rèn)為:網(wǎng)絡(luò)編程相關(guān)知識(尤其是 HTTP 協(xié)議),是互聯(lián)網(wǎng)產(chǎn)品開發(fā)當(dāng)中最重要的基礎(chǔ)知識(沒有之一)。掌握這方面的基礎(chǔ)知識,對一個新手程序員來說至關(guān)重要。本系列將會在我的微信公眾號「貓哥學(xué)前班」上連載,并在 Github 上維護更新

使用「詳解 BAT 面試題」作為本文的副標(biāo)題,是為了吸引更多的技術(shù)新人瀏覽此文,然而本文并非標(biāo)題黨。掌握本文所提到的知識點必將大幅提升程序員的面試成功率,因為「網(wǎng)絡(luò)編程」方面的基礎(chǔ)知識,是 BAT 面試的必考項目。

從 BAT 面試題說起

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

這里有:

  1. 國內(nèi)最大的第三方支付舞臺,體驗億萬資金穿梭代碼的快感;
  2. 一群熱愛前端技術(shù)的伙伴,最快的成長經(jīng)歷;
  3. 持續(xù)的培訓(xùn)體系,完善的項目開發(fā)環(huán)境,最具潛力的UED團隊。

你需要:

  1. 熱愛前端,熱愛設(shè)計,對新鮮事物充滿好奇心,喜歡搗鼓各種互聯(lián)網(wǎng)應(yīng)用;(興趣、學(xué)習(xí)能力、創(chuàng)新能力)
  2. 自我管理能力強,健康的創(chuàng)業(yè)心態(tài),樂于分享與溝通;(心態(tài)、分享、性格)
  3. 具備基本的前端素質(zhì),了解WEB標(biāo)準(zhǔn)化、性能優(yōu)化方法,了解可用性、可訪問性;(基本技能)
  4. 能和設(shè)計師談產(chǎn)品設(shè)計,和后端開發(fā)研討技術(shù)實現(xiàn)方案,制定服務(wù)接口,崇尚團隊合作;(向前向后能力)

當(dāng)時在面試時最流行問的前端技術(shù)問題是:Web 標(biāo)準(zhǔn)化、AJAX 與 YSlow

  • 問:一個 AJAX 請求從開始創(chuàng)建到最后的響應(yīng)階段,在其整個生命周期中,使用到了哪些 JavaScript 對象與方法?
  • 問:YSlow 的 34 條性能優(yōu)化建議中,哪些與 HTTP 協(xié)議相關(guān),請盡可能多的列舉出來,并說說你的理解?

2011 年我開始成為騰訊的前端開發(fā)面試官,負(fù)責(zé)騰訊電商的前端開發(fā)(網(wǎng)頁重構(gòu)方向)筆試出題與面試工作。在 2012 年的校招過程中,我發(fā)現(xiàn)不論是我出的筆試題,還是其他面試官出的題目,HTTP 協(xié)議相關(guān)的知識都是必考項。例如,

  • 問:HTTP 協(xié)議中與緩存相關(guān)的 HTTP Header 有哪些?
  • 問:列舉出你所知道的 HTTP 狀態(tài)碼,并描述它們的含義與發(fā)生的場景?

后來我在學(xué)習(xí)百度 FIS 框架的過程中,無意間看到百度 FEX 團隊的這份開源前端開發(fā)面試題,不出所料,同樣有一道與網(wǎng)絡(luò)編程相關(guān)的題目:

一個頁面從輸入 URL 到頁面加載完的過程中都發(fā)生了什么事情?越詳細(xì)越好

由此可見,網(wǎng)絡(luò)編程相關(guān)知識的確是 BAT 前端面試題中的必考項,而對于負(fù)責(zé)輸出 API 接口的后臺開發(fā)崗位來說,更是如此:

  • 問:一個 POST 請求的 Content-Type 有多少種,傳輸?shù)臄?shù)據(jù)格式有何區(qū)別?
  • 問:什么是 RESTful API,如何設(shè)計一個 Open API 的接口?

解題思路

接下來我們一起探討下具體的解題思路,瀏覽完本文之后,你將會首先掌握 HTTP 協(xié)議相關(guān)的前后端基礎(chǔ)知識。

要掌握 HTTP ,就需要先看到 HTTP 到底長什么樣?(不了解「網(wǎng)絡(luò)七層協(xié)議模型」和 TCP 的同學(xué)先不著急,本系列的后面幾篇會涉及到。)

1、安裝 HTTP 抓包工具

在 Chrome 開發(fā)者工具下我們可以看到,打開一個網(wǎng)頁后,瀏覽器會發(fā)起許多 HTTP 的請求(HTTP Request),這些請求經(jīng)過服務(wù)器端處理后會返回對應(yīng)的數(shù)據(jù)(HTTP Response),瀏覽器會按照這些數(shù)據(jù)的類型將它們渲染出來。

Chrome Network Panel

Chrome 中看到的 Request/Response Header 是其格式化之后的形式,要看到它們的原始模樣(Raw Source),我們需要借助兩個 HTTP 接口調(diào)試?yán)鳌?/p>

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

2、查看 HTTP 詳細(xì)報文

運行 Fiddler(或 Charles) 之后,使用 Chrome 瀏覽器打開「貓哥學(xué)前班」的新浪微博主頁:http://weibo.com/mgxqb

在 Fiddler 左側(cè)面板下選中該條 HTTP 請求,再將右側(cè)面板的請求部分和響應(yīng)部分都切換到 Raw 標(biāo)簽頁。如下圖所示:

Fiddler Panel

Charles 下的操作與 Fiddler 類似:

Charles Panel

HTTP 協(xié)議規(guī)范由 W3C 制定,與具體的抓包工具無關(guān),接下來我們主要以 Charles 為例,詳細(xì)講解下 HTTP 的報文格式,這對理解基于 HTTP 的 API 接口設(shè)計和網(wǎng)頁性能優(yōu)化有很大幫助。

我們先看一下請求頭的源碼(Request Raw),為了防止隱私泄露,我已刪除部分 Cookie 信息:

GET /mgxqb HTTP/1.1
Host: weibo.com
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4
Cookie: YF-Page-G0=f70469e0b5607cacf38b47457e34254f; _s_tentry=passport.weibo.com


仔細(xì)觀察以上源碼,我們能大概總結(jié)出 HTTP 協(xié)議的格式規(guī)范:

  • 第一行定義了請求類型(GET)、請求路徑(/mgxqb)與協(xié)議類型及其版本號(HTTP/1.1),使用一個半角空格間隔這三塊信息;
  • 示例源碼的最后是兩個空行。由于 HTTP 規(guī)范中要求一個合法的 HTTP 報文至少包含有一個空行,其中第一個空行用來間隔報文的頭部信息(HTTP Request/Response Header)和主體信息(HTTP Request/Response Body)。在空行的下一行是報文的主體信息,由于本例為 GET 類型請求,其主體(Body)信息通常為空,這便是第二個空行的含義;
  • 余下的部分有著相同的格式,即 「HTTP Header 字段名+半角冒號+半角空格+值」,我們可以把它看成 YAML 格式的簡易版。其中 HTTP Header 在規(guī)范中有著明確的定義,具體參見 HTTP頭字段列表。

這便是一個 HTTP 協(xié)議報文的源碼格式,以下我們簡單講解下最常見的 HTTP header 的含義。

3、常見 HTTP header

User-Agent:客戶端身份標(biāo)識

User-Agent (以下簡稱 UA)字段記錄了訪問當(dāng)前網(wǎng)頁的用戶瀏覽器的類型與版本、操作系統(tǒng)類型與版本。根據(jù)不同的 UA 信息,提供不同的站點內(nèi)容是使用 UA 的常見場景。例如,如果用戶使用手機訪問魅族官網(wǎng) www.meizu.com,瀏覽器會自動跳轉(zhuǎn)至魅族手機官網(wǎng) m.meizu.com。這種跳轉(zhuǎn)實現(xiàn)既可以由前端 JavaScript 完成,也可以通過后端返回 302 重定向來完成。

JavaScript 訪問 window.navigator.userAgent 屬性即可獲取該信息。雖然該屬性是只讀的,但有很多前端手段可以偽造 UA 。如下圖,Chrome 開發(fā)者工具在模擬不同的手機機型時,也會改變?yōu)g覽器 UA 值。由此可見,通過檢測 HTTP User-Agent Header 來識別是否為爬蟲程序,不是一個有效的方法。

使用 Chrome 開發(fā)者工具模擬不同手機設(shè)備 UA

在 PHP 中,所有的 HTTP Header 字段信息都保存在 $_SERVER 對象中,通過訪問 $_SERVER['HTTP_USER_AGENT'] 即可獲取 User-Agent 的值。

Cookie:用戶身份標(biāo)識

由于 HTTP 協(xié)議最初被設(shè)計成一種無狀態(tài)的數(shù)據(jù)傳輸協(xié)議,服務(wù)器端無法判斷每次處理的請求相互之間以及與之前處理的請求之間的關(guān)系,Cookie 的設(shè)計就是為了解決這個問題。

用戶在瀏覽器中首次訪問一個站點時,會通過請求響應(yīng)頭或頁面JS腳本生成一些用于標(biāo)識用戶身份的 Cookie 信息,這些信息會按照域名分類,存放在瀏覽器本地緩存文件當(dāng)中。例如 Windows 系統(tǒng)下通過訪問 「C:\Users<用戶名>\AppData\Local\Microsoft\Windows\Temporary Internet Files」 目錄可以查看到 IE 瀏覽器保存在本地的 Cookie 文件。當(dāng)用戶再次訪問該站點時,這些 Cookie 信息會被瀏覽器自動添加到 HTTP Request Header 的 Cookie 字段中,服務(wù)器通過讀取這些信息,來區(qū)分當(dāng)前請求的用戶身份與狀態(tài)。

瀏覽器可以通過讀寫 document.cookie 屬性來添加或刪除 Cookie 信息,服務(wù)器端可以通過 HTTP Response Header(響應(yīng)頭)中的 Set-Cookie 來改寫客戶端的 Cookie 信息。每一條 Cookie 屬性通常都會設(shè)置一個過期時間,過期之后的 Cookie 瀏覽器將會自動清理它們,不會再被攜帶在 HTTP Request Header(請求頭)中。

例如,以下 PHP 語句可以通過設(shè)置 Cookie 過期時間為前一個小時來觸發(fā)客戶端 Cookie 過期,達到刪除 Cookie 的目的:

setcookie('key', '', time() - 3600, '/'); 

由于 Cookie 通常用于記錄用戶「帳號信息」和用戶的「操作記錄」,所以泄露 Cookie 會帶來個人帳號與隱私泄露的風(fēng)險。這也是為什么你在百度上搜索「貸款」的關(guān)鍵詞之后,訪問其他網(wǎng)站時就能看到相關(guān)的推薦廣告,甚至第二天就會有各種放貸電話找上門來。

又由于 Cookie 可以隨意被客戶端修改(通過修改 document.cookie 屬性),因此瀏覽器廠商們一起制定了 HttpOnly 的 Cookie 機制。服務(wù)器端在 setcookie 時,通過設(shè)置 HttpOnly 的標(biāo)識,可以防止客戶端通過 JavaScript 修改 Cookie 的信息。不過這種方法對于基于 HTTP 協(xié)議進行篡改的方法來說無法防范,在之后的貓哥網(wǎng)絡(luò)編程系列中,我將會介紹如何通過監(jiān)控 Wi-Fi 流量來截取、偽造用戶身份。

在 YSlow 性能優(yōu)化最佳實踐中,有兩條與 Cookie 相關(guān)的建議:

雖然瀏覽器對 Cookie 的大小與數(shù)量有著較為嚴(yán)格的限制,但很多網(wǎng)站(尤其是包含登錄態(tài)的)的 Cookie 信息量通常比其他所有 HTTP Header 加起來的還要多。為了減少不必要的 HTTP 數(shù)據(jù)傳輸量,YSlow 給出了以上兩條優(yōu)化建議。由于網(wǎng)頁的靜態(tài)資源(圖片、CSS、JS)文件無需記錄用戶狀態(tài),因此通常會使用一個額外的域名(Cookie 是按域名來分類存儲)來存放靜態(tài)資源文件。

我們使用 Chrome 開發(fā)者工具查看「貓哥學(xué)前班」新浪微博主頁,可以看到新浪微博使用了 img.t.sinajs.cn 的域名來存放它的 CSS 文件,這個域名發(fā)起的 HTTP Request Header 中沒有自動帶上 Cookie 字段信息 (因為前后端腳本都沒有在這個域名上設(shè)置 Cookie,而是設(shè)置在了 weibo.com 域名上):

Use Cookie-free Domains

這里還需要引申一個知識點:Session,它和 Cookie 有什么關(guān)系?由于 Session 與本文所講的 HTTP 協(xié)議關(guān)系不大,相關(guān)知識點請自行百度。

Cache-Control:瀏覽器資源緩存標(biāo)識

網(wǎng)站性能優(yōu)化中,最為關(guān)鍵的是緩存機制(又是沒有之一)。在服務(wù)器端通常會使用 Memcached、Redis 等服務(wù)來緩存經(jīng)常訪問的數(shù)據(jù)。例如在一個電商網(wǎng)站中,用戶經(jīng)常訪問的熱賣商品數(shù)據(jù)會被緩存在內(nèi)存中,用戶在一定時間內(nèi)訪問商品詳情頁時,后臺程序直接從緩存服務(wù)中獲取這段數(shù)據(jù),這種方法可以大幅降低數(shù)據(jù)庫的訪問壓力。

在用戶端,瀏覽器會有一系列機制通過緩存來提升頁面加載速度。例如 IE/Chrome 都會緩存 GET 類型的 AJAX 請求,IE 甚至?xí)彺?POST 類型的請求,需要通過增加時間戳參數(shù)的方式來強制清除緩存。對于所有的靜態(tài)資源文件來說,最佳實踐是為它們增加一個 「Never Expires」(永不過期)的強(長)緩存,以下是一個強緩存靜態(tài)資源服務(wù)器的 Nginx 配置示例:

server {
    listen 80;
    server_name yekai.net;
    root /var/www/yekai.net;

    location / {
        index index.html index.htm;
    }
    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|js|css)$ {  
        expires 365d;  
    }
}

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

Cache-Control 是瀏覽器緩存機制中最為重要的一個配置,以下是瀏覽器加載靜態(tài)資源文件時的緩存檢查機制流程:

瀏覽器緩存檢查機制流程

由此可見,靜態(tài)資源緩存優(yōu)化的最佳狀態(tài)是:直接從本地緩存中讀取 > 304 狀態(tài) > 200 狀態(tài)。關(guān)于 HTTP 狀態(tài)碼,與網(wǎng)站性能優(yōu)化有關(guān)的主要是以下幾個。

  • 盡量減少 200 狀態(tài)碼的請求。200 表示是一個正常的請求返回,此條優(yōu)化規(guī)則要求盡可能多的減少頁面的 HTTP Request 數(shù)量。常見的方法有:合并打包靜態(tài)資源、使用 CSS Sprite 雪碧圖合并、緩存 AJAX、使用 LocalStorage/UserData/Manifest 等本地緩存技術(shù)。
  • 清理返回 301/302 狀態(tài)碼的入口鏈接。301 表示永久重定向,302 表示臨時重定向。服務(wù)器端使用重定向返回通常是為了兼容一個舊的入口鏈接。我們能做的優(yōu)化是,將調(diào)用舊入口的場景進行清理,直接調(diào)用重定向之后的新 URL 地址。
  • 304 表示靜態(tài)資源未更新,瀏覽器可直接使用本地緩存文件。通常 304 的產(chǎn)生與瀏覽器的處理機制以及服務(wù)器緩存頭配置有一定的關(guān)系。304 雖然未傳輸文件主體內(nèi)容,但 HTTP 請求的建立依然是一個可以避免的性能損耗。騰訊 KM(內(nèi)部知識分享平臺)上有一篇文章通過在真實海量業(yè)務(wù)場景(沒記錯的話是 Qzone 業(yè)務(wù))中,正交驗證 HTTP 1.0 與 1.1 協(xié)議中與緩存相關(guān)的 HTTP Header 配置,結(jié)合日志分析得出了一個最佳實踐:關(guān)閉 Etag 配置,只啟用 Cache-Control 與 Last-Modified 響應(yīng)頭。為了兼容老瀏覽器,可保留 Expires。因為 Etag 的緩存方案,在經(jīng)過 CDN 及網(wǎng)關(guān)代理服務(wù)器后,會導(dǎo)致緩存命中率下降。從以上「瀏覽器緩存檢查機制流程」圖上可以看出,使用強緩存(Cache-Control max-age 設(shè)置為一年)后瀏覽器在資源過期前不會發(fā)起 HTTP 請求,那如何保證靜態(tài)資源在服務(wù)器上更新后本地的緩存也能同步更新呢?可參考百度 FIS 的「文件指紋」方案。
  • 清理返回 404 狀態(tài)碼的入口鏈接。靜態(tài)資源文件的 404 調(diào)用需嚴(yán)格避免,而入口頁面的 404 則在所難免。通過在全站 404 頁面進行產(chǎn)品引導(dǎo)與體驗優(yōu)化,并結(jié)合數(shù)據(jù)上報記錄來源頁(HTTP Referer Headerdocument.referrer),可以找到并清理 404 來源入口。對于由搜索引擎進入的來源,可通過主動提交新索引至搜索引擎,或使用 301/302 重定向的方式,有效利用起這些「被浪費的流量」。
  • 502 服務(wù)器出錯。如果是 Nginx + FastCGI 的常見架構(gòu),通常是由于 Nginx 緩沖區(qū)溢出或服務(wù)器資源被耗盡引起,針對不同的業(yè)務(wù)場景進行 Nginx 的配置優(yōu)化能顯著提升服務(wù)器抗壓性能。

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

如果你能看到這里,相信你已經(jīng)知道如何解答前文提到的幾道 BAT 網(wǎng)絡(luò)編程面試題中,關(guān)于 「HTTP 協(xié)議、狀態(tài)碼、緩存與性能優(yōu)化」相關(guān)的問題。

在下一章節(jié)中,我們會繼續(xù) HTTP 協(xié)議的話題,并詳細(xì)講解 HTTP POST 相關(guān)的網(wǎng)絡(luò)編程細(xì)節(jié)與調(diào)試技巧,相信看完之后你將能輕松定位并解決所有接口聯(lián)調(diào)的問題與 Bug:)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,039評論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,555評論 19 139
  • <a name='html'>HTML</a> Doctype作用?標(biāo)準(zhǔn)模式與兼容模式各有什么區(qū)別? (1)、<...
    clark124閱讀 3,827評論 1 19
  • 清流。 遠(yuǎn)山如黛。 游鴨。 碧水。 陽朔的魅力,可能在于某個早醒的清晨或日落的傍晚,想在這個小城住下來,一輩子。
    貓besos閱讀 276評論 2 3

友情鏈接更多精彩內(nèi)容