在介紹 HTTP 之前,首先我們來思考幾個問題
- HTTP 到底是什麼?
- HTTP 在整個網絡通信中扮演着怎樣的角色?
- HTTP 是如何進行通信的呢?
帶着這 3 個問題 ,我們來開始今天的內容。(當然,如果大神對這3個問題比較清楚,可以直接跳到末文看總結)。
在理解 HTTP 之前,首先我們得先來看看 TCP/IP 協議族是什麼?
簡介TCP/IP
定義:
TCP/IP(Transmission Control Protocol/Internet Protocol,傳輸控制協議/網際協議)是指能夠在多個不同網絡間實現信息傳輸的協議簇。TCP/IP 協議不僅僅指的是 TCP 和 IP 兩個協議,而是指一個由 FTP、SMTP、TCP、UDP、IP 等協議構成的協議簇, 只是因爲在TCP/IP 協議中 TCP 協議和 IP 協議最具代表性,所以被稱爲TCP/IP協議。
看上面的描述一大堆,不需要死記硬背,我們只需要有一個概念,也就是 TCP/IP 的本質。
本質:
一系列協議所組成的一個網絡分層模型。
哦,,搞半天你是個協議族,還分層?分幾層 ?爲啥子要分層?
具體分層:
OSI 七層模型 | TCP/IP 模型 | 功能 | TCP/IP 具體包含的協議 |
---|---|---|---|
應用層 | 應用層 | 文件傳輸,電子郵件,文件服務,虛擬終端 | TFTP,HTTP,SMMP,FTP,SMTP,DNS,Telnet |
表示層 | 應用層 | 數據格式化,代碼轉換,數據加密 | 沒有協議 |
會話層 | 應用層 | 接觸或建立與別的接點的聯繫 | 沒有協議 |
傳輸層 | 傳輸層 | 提供端到端的接口 | TCP,UDP |
網絡層 | 網絡層 | 爲數據包選擇路由 | IP,ICMP,RIP,OSPF,BCP,ICMP |
數據鏈路層 | 鏈路層 | 傳輸由地址的幀以及錯誤功能檢測 | SLIP,CSLIP,PPP,ARP,RARP,MTU |
物理層 | 鏈路層 | 以二進制數據形式在物理媒體上傳輸數據 | ISO2110,IEEE802,IEEE802.2 |
簡單的看這個圖似乎好像有些複雜,這裏我們只需要瞭解三點:
- TCP/IP 的分層是從開放系統互聯 OSI (Open System Interconnection)模型演變而來的。
- TCP/IP 與 OSI 在分層模塊上有些區別,OSI 參考模型注重“通信協議必要的功能是什麼”,而 TCP/IP 則更強調“在計算機上實現協議應該開發哪種程序”。
- 在 TCP/IP 協議中,它們被簡化爲了四層.分別是應用層、傳輸層、網絡層、鏈路層
知道以上信息再結合我們實際的開發,其實上面這個表可再以優化一下,把我們常用的協議取出,如下:
TCP/IP 模型 | TCP/IP 具體包含的協議 |
---|---|
應用層 | HTTP,FTP,DNS |
傳輸層 | TCP,UDP |
網絡層 | IP |
數據鏈路層 | 路由,WIFI |
這樣看就簡潔多了吧,OK,回到之前我們的疑問,我們現在大致瞭解了 TCP/IP 協議族確實需要分層,並且是分四層,那麼,重點來了?到底爲什麼需要分層呢?
核心原因,主要是網絡的不可靠性,或者說是網絡的不穩定造成的。 假設我們的網絡是穩定可靠的,那麼應用層軟件其實可以直接和鏈路層設備進行通信,哪還需要中間這麼複雜的過程。其中 TCP 協議的三次握手和他的窗口(吞吐量)也主要是來處理網絡的不穩定和不可靠性的,後面的文章中會講到。另外分層的次要原因還考慮到安全、節約寬帶、方便管理等。
弄明白上面幾個問題後,我們對 HTTP 在網絡通信中扮演的角色已經有了一個初步的瞭解。讀到這裏我們可以對本文開頭的第二個問題進行回答了,哦!!!,原來 HTTP 在整個網絡通信(TCP/IP 協議族)中屬於應用層的一個協議,用於和傳輸層進行通信。
那麼到底什麼是 HTTP?
我們平時感受到 HTTP 比較直觀的是兩個場景
- 在我們遊覽器地址欄啪輸入“www.jackwaiting.com”,啪,一個界面出來了。
- 在我們編寫軟件程序的時候,拿到後臺的一個接口。例如:“https://www.jackwaiting.com?xxx=“xxx” ” 然後對這個 HTTP 請求做一系列的描述,什麼請求方法咯,GET/POST , 請求行,請求 Body,返回碼,XXXX 一大堆,對吧?
定義:
HTTP 是超文本傳輸協議(HyperText Transfer Protocol)。
注意:Hyper 這裏的“超”指的是“拓展”的意思,不是“超級”的意思。
超文本?
解釋:再我們屏幕前看到的文本中,含有可以跳轉到其它文本鏈接的文本。
怎麼去理解呢,例如我們這篇博客,在開頭定義了一個 “JackHttp 系列的介紹”的超鏈接,可以跳轉到另外一個鏈接對吧?那麼是不是就是說 “JackHttp 系列的介紹”就是超本文呢?可以這麼說,但是,,,,,我們這整篇文章也可以說成是超文本,編寫這篇博客的工具MarkDown 也能稱之爲超文本,甚至是顯示的代碼 HTML 也含有這種能力,也能稱之爲超文本,能明白嗎?也就是小到一個網絡請求的文本 “http://www.baidu.com” ,大到一個 HTML,凡是含有可以跳轉到其它文本鏈接的文本,都能稱之爲超文本。
爲什麼定義 HTTP,其目的是什麼?
我儘量用我的理解來解釋清楚:兩個陌生的機器要交換數據,就像我們人與人之間一樣,我們需要“租房、購房、吃飯,購物上網”等等行爲,“租房我們需要簽訂合同,購房不僅需要簽訂合同,風險比較大,還需要在政府備案網籤,HTTPS?吃飯商家也會提供一個菜單,購物我們用戶和淘寶其實也會簽訂《用戶協議》《用戶隱私協議》,有注意到吧?淘寶與商家也會遵循彼此簽訂的協議”。這些日常生活中的行爲視乎都有一個協議再約束我們,如果哪一方不按照彼此的協議做事情,就會有對應的懲罰,表現在計算機裏的就會給不同的端進行報錯。
那麼定義 HTTP 協議目的就是爲了規定客戶端和服務端 數據傳輸的格式 和 數據交互行爲 ,定義好了 HTTP 協議,客戶端與服務端之間溝通起來將會更加方便,省力,省帶寬。
HTTP 工作原理
饒了這麼大的圈,有點感覺了,哦!!!,客戶端想和某服務器進行通信,需要給服務器發送一個超本文?
萬丈高樓平地起! 我們按照 HTTP 協議說的方式在遊覽器中做一個簡單的 HTTP 請求試試,在地址欄輸入“http://jackwaiting.com”, 通過按 F12 追蹤看看發送的是一個怎樣的超文本。
從這個圖中,我們能挖出哪些信息呢,比較直觀的:
- 發送一個超文本似乎需要包含 Headers、Preview、Response、Cookies、Timing
- 在 Headers 中還包含 General, Response Headers , Request Headers
- 觀察地址欄發現會跳轉到 “https://blog.csdn.net/zhanggang740”, 並不是我們輸入的 “http://jackwaiting.com”, 細心點看你會發現在 Headers ->General 用一個黃色小圓球標記了一個 Status Code 301? 並且在 Headers -> Response Headers -> Location 中找到"https://blog.csdn.net/zhanggang740",此時我們隱隱的感覺到似乎跳轉到了"https://blog.csdn.net/zhanggang740"。
what fuck? 又來了個307?跳到了https? 還沒有結束?四連問,我們在往下看。
咦,狀態碼變綠了?返回 200?
看到這裏,如果此時你是一個 Android 程序猿,似乎找到一絲熟悉感,這不就是平時收到服務器返回給我的響應碼?但仔細一想平時我做網絡請求時也重來沒寫過這麼多的請求 Headers,也沒有收到過什麼 301,307 更不會察覺到還建立了 https 的連接,這很正常。
其實如果在我們 APP 中發送“http://jackwaiting.com”這個請求,同樣會經歷上面的所有過程,只是在 Android 中, okhttp 已經幫我們做了這些事情而已。
我簡單的說一下,暫時你只需要瞭解即可,當 okhttp 的攔截器接收到“http://jackwaiting.com”請求到來時,會逐步依次調用如下攔截器
RetryAndFollowUpInterceptor
BridgeInterceptor
CacheInterceptor
ConnectInterceptor
CallServerInterceptor
在我們這個例子中 RetryAndFollowUpInterceptor 會對我們的重定向異常 301、307 進行攔截,重新再次發送請求,BridgeInterceptor 會幫我們補充請求過程中所需要的 Headers 信息
ConnectInterceptor 會去建立 TCP Connect、SSL 連接(HTTPS 連接),然後通過CallServerInterceptor 發射請求,直到接收到響應 200 的狀態碼,此次 HTTP 請求結束,大概是如下流程:
所以作爲前端程序員可能只會感知到服務器返回的 200,中間的內容無法察覺,或者說不關心,那是因爲 Okhttp 幫我們實現了,這裏看不懂也沒關係,後期會詳解 Okhttp 的工作原理和源碼。
搞明白了這個過程,在來分析上面的那幾張圖剩下的內容:
- Preview 是這個請求後服務器返回給我們內容預覽,很直觀的感覺到,這不是協議所要求的。
- Response 是服務器返回的 “https://blog.csdn.net/zhanggang740” HTML 文本以此來展示網頁內容,是你,沒得跑。
- Cookies 保存着一些屬性值,暫時不知道是幹嘛的
- Timing 記錄的是一些連接時間,請求時間等。似乎也是一些可視化內容,我們不需要關心。
從這次請求我們似乎可以觀察到客戶端發送一個請,客戶端還會返回一個響應,中間會有一些Header,Cookes 等信息,如下圖
我們對上圖再次進行拆分,瞭解其本質
- 客戶端是如何把一個"http://jackwaiting.com" 轉換成一個請求的,轉換的標準是什麼?
- 一個轉換後的標準是如何發送到服務器的?
- 服務器返回的響應是怎樣的標準?
- 響應標準怎樣返回到客戶端?
URL -> HTTP 報文
報文(message)是網絡中交換與傳輸的數據單元,即站點一次性要發送的數據塊。報文包含了將要發送的完整的數據信息,其長短很不一致,長度不限且可變。
格式標準
在理解格式標準前,我先簡單說明一下,本文將使用 HTPP 1.1 的格式標準來講解,原因如下:
- HTTP 發展至今,已經衍生出了多個版本,從 HTTP 0.9 -> HTTP 1.0 -> HTTP 1.1 -> SPDY 協議 -> HTTP 2.0 。其中 HTTP 1.0 由於默認是短連接,每次與服務器交互,都需要新開一個連接,至今基本已經被棄用了。(1.0 都棄用了,0.9 不用說啦吧)。
- SPDY 協議是 HTTP 2.0 的基礎, HTTP 2.0 在市場上還沒有完全普及,在部分手機的兼容性上還沒有完全適配。
- HTTP 2.0 協議的格式和 HTTP 1.1 雖然完全不同了,但實際上 HTTP 2.0 並沒有改變 HTTP 1.1 的語義,只是把原來 HTTP 1.1 的 Header 和 Body 部分用 Frame 重新封裝了一層而已。
那麼一個 HTTP 1.1 報文具體長什麼樣子?
Request = Request-Line ; Section 5.1
*(( general-header ; Section 4.5
| request-header ; Section 5.3
| entity-header ) CRLF) ; Section 7.1
CRLF
[ message-body ] ; Section 4.3
這是一個典型的請求報文,從報文格式中可以看出,一個 Request 請求包含 4 部分
- 請求行(Request - Line)
- 請求頭 ( general-header| request-header| entity-header )
- 空行( CRLF)
- 請求體(message-body)
那麼,這 4 個部分具體都包含哪些內容呢?
請求行(Request - Line)
常用的請求行包含 請求資源的方法,請求資源的路徑(相對路徑、絕對路徑),使用的協議版本 3 個部分,如下:
1、Request-URI = "*" | absoluteURI | abs_path | authority
其中請求方法主要包含
GET:獲取資源(不帶Body)
POST:傳輸實體主體(帶Body)
PUT:傳輸資源文件
DELETE:刪除資源文件
HEAD:獲得此次請求的報文首部
TRACE:追蹤路徑
CONNECT:保留
OPTIONS:詢問支持的方法
例如我們本文的 URL “http://blog.csdn.net/zhanggang740” 轉換成請求行就是這個樣子,其中第一種更爲常見。
第一種格式
GET /zhanggang740 HTTP/1.1
Host: blog.csdn.net
第二種
GET http://blog.csdn.net/zhanggang740 HTTP/1.1
Host: www.haust.edu.cn
請求頭( Request-header)
我們接下來對一些重要的請求頭做一些分享。
Host
請求資源所在的服務器。
Accept
Accept 代表客戶端希望接受的數據類型。
常見的媒體格式類型如下:
媒體類型 | 格式 |
---|---|
text/html | HTML 格式 |
text/plain | 純文本格式 |
text/xml | XML 格式 |
image/gif | GIF 圖片格式 |
image/jpeg | JPG 圖片格式 |
image/png | PNG 圖片格式 |
application/xhtml+xml | XHTML 格式 |
application/xml | XML 數據格式 |
application/atom+xml | ATOM XML 聚合格式 |
application/json | JSON 數據格式 |
application/pdf | PDF 格式 |
application/msword | WORD 文檔格式 |
application/octet-stream | 二進制流數據(如常見的文件下載) |
application/x-www-form-urlencoded | 普通表單,Encoded URL 格式 ,只能傳文本,不能傳二進制數據 |
multipart/form-data | 多部分形式,一版用於傳輸包含二進制內容的多項內容 |
跟Accept 相關的幾個Header。
Header | 說明 |
---|---|
Accept-Charset | 聲明瀏覽器支持的字符集 |
Accept-Encoding | 聲明客戶端支持的編碼類型的 |
Accept-Langulage | 聲明客戶端支持的語言 |
Referer
HTTP Referer 是 Header 的一部分,當客戶端向服務器發送請求的時候,一般會帶 Referer 告訴服務器該請求是從哪裏鏈接過來的,服務器因此可以獲得一些信息用於處理。
Referrer Policy
Referrer 用於記錄這次請求的來源。
目前最新的 Referrer Policy 規定了五種 Referrer 策略:
- No Referrer When Downgrade
- 僅當發生協議降級(如 HTTPS 頁面引入 HTTP 資源,從 HTTPS 頁面跳到 HTTP 等)時不發送 Referrer 信息。
- Origin Only
- 發送只包含 Host 部分的 Referrer。啓用這個規則,無論是否發生協議降級,
無論是本站鏈接還是站外鏈接,都會發送 Referrer 信息,但是隻包含協議 + host 部分(不包含具體的路徑及參數等信息)。 - Origin When Cross-origin
- 僅在發生跨域訪問時發送只包含 Host 的 Referrer,同域下還是完整的。
它與 Origin Only 的區別是多判斷了是否 Cross-origin。需要注意的是協議、域名和端口都一致,纔會被瀏覽器認爲是同域。 - Unsafe URL。
- 無論是否發生協議降級,無論是本站鏈接還是站外鏈接,統統都發送 Referrer 信息。正如其名,這是最寬鬆而最不安全的策略。
- No Referrer :
- 任何情況下都不發送 Referrer 信息。
Pragma
Pragma 是 HTTP 1.1 之前版本的歷史遺留字段,僅作爲與 HTTP 的向後兼容而定義。
Origin
用於指明當前請求來自於哪個站點。
Cache-Control
Cache-Control 指定了請求和響應遵循的緩存機制。其中 Request Header 的 Cache-Control 包含如方式:
- no-cache
- 不要讀取緩存中的文件,要求向服務器重新請求。
- no-store
- 請求和響應都禁止被緩存。
- max-age
- 表示當訪問此網頁後的 max-age 秒內再次訪問不會去服務器請求,其功能與 Expires 類似。只是 Expires 是根據某個特定日期值做比較。一但緩存者自身的時間不準確.則結果可能就是錯誤的。而 max-age 沒有這種問題,所以使用過程中儘量優先使用 max-age。
- max-stale
- 允許讀取過期時間必須小於 max-stale 值的緩存對象。
- min-fresh
- 接受其 max-age 生命期大於其當前時間跟 min-fresh 值之和的緩存對象。
- only-if-cached
- 告知緩存者,我希望內容來自緩存,我並不關心被緩存響應,是否是新鮮的。
- no-transform
- 告知代理,不要更改媒體類型。
Connection
HTTP Connection 允許在請求處理結束之後將 TCP 連接保持在打開狀態,以便爲未來的HTTP 請求重用現存的連接。
- keep-alive
- 開啓 HTTP 持久連接,HTTP 1.1 默認值。
- close
- 關閉 HTTP 持久連接,HTTP 1.0 默認值。
- timeout
- 連接多久後關閉,關閉後,有請求,需要重新建立連接。
User-Agent
User-Agent 首部包含了一個特徵字符串,用來讓網絡協議的對端來識別發起請求的用戶代理軟件的應用類型、操作系統、軟件開發商、版本號。
空行(CRLF)
特別要注意:Request Head 後面必須有一個單獨的空白行 CLRF (Carriage-Return Line-Feed回車換行\r\n),否則格式會非法,或被服務器認爲不完整,等待CRLF發送過去。
請求體(message-body)
具體需要發送請求的 body 內容。
返回響應
服務器返回的響應報文,原理基本和請求時一致,包含狀態行、響應頭部、空行和響應包體,其中狀態行包含響應協議、狀態碼、描述 4 個部分組成。
我們重點關注響應頭和響應碼 2 個部分:
響應頭部(Response Header)
Content-Type
代表服務端返回的數據類型,格式跟請求頭 Accept 基本支持格式一致,就不詳細介紹了。
Content-Encoding
服務器發送的數據使用的壓縮格式。
Content-Length
服務器發送的數據的大小(單位:字節)。
Content-Language
服務器發送的數據使用的自然語言。
Location
配合狀態碼爲 3XX 時請求重定向。
Server
服務器的基本信息,如如服務器應用程序軟件的名稱、版本。
Last-Modified
被請求的當前資源最後修改時間
Cache-Control(重點)
- public
- 數據內容皆被儲存起來,就連有密碼保護的網頁也儲存,安全性很低。
- private
- 數據內容只能被儲存到私有的cache,僅對某個用戶有效,不能共享。
- no-cache
- 可以緩存,但是隻有跟服務器驗證了其有效後,才能返回給客戶端。
- no-store
- 請求和響應都禁止被緩存。
- max-age
- 本響應包含的對象的過期時間。
- max-stale
- 允許讀取過期時間必須小於 max-stale 值的緩存對象。
- must-revalidate
- 如果緩存過期了,會再次和原來的服務器確定是否爲最新數據。
- proxy-revalidate
- 與 must-revalidate 類似,區別在於:proxy-revalidate 要排除掉用戶代理的緩存的。即其規則並不應用於用戶代理的本地緩存上。
- s-maxage:
- 與 max-age 的唯一區別 是, s-maxage僅僅應用於共享緩存.而不應用於用戶代理的本地緩存。 一版情況下 s-maxage 的優先級要高於 max-age。
- no-transform
- 告知代理,不要更改媒體類型。
響應碼(狀態碼)
響應碼大全
響應碼大全份上,當然,如果你懶得點,我也幫你羅列席常見的響應碼。
狀態碼 | 原因短語 | 代表含義 | HTTP 版本 |
---|---|---|---|
200 | OK (成功) | 請求成功 | HTTP/0.9 可用 |
301 | Moved Permanently(永久移動) | 該狀態碼錶示所請求的 URI 資源路徑已經改變,新的 URL 會在響應的Location:頭字段裏找到. | HTTP/0.9 可用 |
307 | Temporary Redirect(臨時重定向) | 服務器發送該響應用來引導客戶端使用相同的方法訪問另外一個 URI 來獲取想要獲取的資源.新的 URL 會在響應的 Location :頭字段裏找到.與 302 狀態碼有相同的語義,且前後兩次訪問必須使用相同的方法 (GET POST). | HTTP/1.1 可用 |
404 | Not Found | (未找到) 服務器找不到所請求的資源.由於經常發生此種情況,所以該狀態碼在上網時是非常常見的. | HTTP/0.9 可用 |
500 | Internal Server Error | (內部服務器錯誤) 服務器遇到未知的無法解決的問題. | HTTP/0.9 可用 |
對拆分問題進行解答:
- 客戶端是如何把一個 “http://jackwaiting.com” 轉換成一個請求的,轉換的標準是什麼?
- 上面定義的報文就是我們我們 URL 轉換成 Request 請求的標準,也稱之爲轉換協議。
至於具體 http://jackwaiting.com 如何 轉換成一個 報文,在 Android 中,okHttp 的攔截器做了工作,在 Request 請求發射之前,對請求行,請求頭(可選),空行,請求body(可選) 進行的純代碼拼接,本文主要疏通 HTTP 部分,okhttp 源碼後續講解。 - 一個轉換後的標準是如何發送到服務器的?
- HTTP 在這個過程中其實什麼都沒做,僅僅只是提供了一個協議規範,保證我們的數據能正常的對接給 TCP,進行拆包傳輸。
- 服務器返回的響應是怎樣的標準?
- 報文嘛(有沒有發現解答越來越簡單?)
- 響應標準怎樣返回到客戶端?
- 逆向思維嘛,你自己想。
總結一句話:
HTTP 定義的是一個客戶端和服務器端通信的規範和標準,在整個網絡通信中其實沒有幹具體的實事,只是讓客戶端與服務端之間溝通起來更加方便,省力,省帶寬,就像是我們生活中的租房合同、購房合同,菜單 ^ . ^