HTTP 協議基礎
HTTP 介紹
HTTP 是 HyperText Transfer Protocol(超文本傳輸協議)的簡寫,它是 TCP/IP 協議集中的一個應用層協議,是客戶端與服務端進行交互時必須遵循的規則。它用於定義 Web 瀏覽器與 Web 服務器之間交換數據的過程以及數據本身的格式,底層是靠 TCP 進行可靠地信息傳輸。
客戶端向服務端發送 HTTP 請求返回的完整過程:
網絡模型介紹
應用層
應用層決定了向用戶提供應用服務時通信的活動,它構建於 TCP 協議之上,HTTP協議也處於該層。
TCP/IP 協議族內預存了各類通用的應用服務。比如,FTP(File Transfer Protocol,文件傳輸協議)和 DNS(Domain Name System,域名系統)服務就是其中兩類。
傳輸層
傳輸層對上層應用層,提供處於網絡連接中的兩臺計算機之間的數據傳輸,向用戶提供可靠的端到端服務,並且傳輸層向上層屏蔽了下層數據通信的細節。
傳輸層主要有兩個協議: TCP (Transmission Control
Protocol,傳輸控制協議)和 UDP(User Data Protocol,用戶數據
報協議)。
在更多的情況下,使用的是 TCP 協議,因爲它是一個可靠的傳輸協議。
網絡層
網絡層,爲數據在結點之間傳輸創建邏輯鏈路,用來處理在網絡上流動的數據包。數據包是網絡傳輸的最小數據單位。該層規定了通過怎樣的路徑(所謂的傳輸路線)到達對方計算機,並把數據包傳送給對方。與對方計算機之間通過多臺計算機或網絡設備進行傳輸時,網絡層所起的作用就是在衆多的選項內選擇一條傳輸路線。
數據鏈路層
數據鏈路層,在通信的實體間建立數據鏈路連接。簡單地說,就是將兩臺物理設備通過軟件服務(如操作系統,設備驅動)建立起電路上的連接,使兩臺設備可以傳輸數據。
物理層
物理層,主要作用是定義物理設備如何傳輸數據。這裏的物理設備,包括電腦硬件,網線等。
HTTP 協議發展歷史
HTTP/0.9
HTTP 於 1990 年問世。那時的 HTTP 並沒有作爲正式的標準被建立。
現在的 HTTP 其實含有 HTTP1.0 之前版本的意思,因此被稱爲 HTTP/0.9。
該版本極其簡單:
- 只有一個命令 GET;
- 沒有 Header 等描述數據的信息;
- 服務器在發送數據完畢後,就關閉 TCP 連接。
HTTP/1.0
HTTP/1.0 版本與 HTTP/0.9 相比,主要有:
- 增加了很多命令,如 POST,PUT,HEAD;
- 增加了 Status Code 和 Header 相關內容;
- 增加了多字符集支持、多部分發送(multi-part type)、權限(authorization)、緩存(cache)、內容編碼(content encoding)等。
HTTP/1.1
HTTP/1.1 是目前主流的 HTTP 版本,有比較完善的功能。
-
增加了 PATCH OPTIONS、DELETE 命令。
-
持久連接,即 TCP 連接默認不關閉,可以被多個請求複用,提高了請求性能。
-
管道機制(pipeline),即在同一個 TCP 連接裏面,客戶端可以同時發送多個請求。例如,瀏覽器同時發出 A 請求和 B 請求,但是服務器還是按照順序,先回應 A 請求,完成後再回應 B 請求。
-
增加 Host 字段,可以將請求發往同一個服務器的不同網站,爲虛擬主機打下了基礎。這個字段增加的好處就是在同一個物理服務器中可以同時部署多個 Web 服務,這樣可以提高物理服務器的使用效率。
HTTP2
HTTP2 目前還沒有普及,但肯定是未來的主流。HTTP2 主要解決了傳輸性能的問題。
-
所有數據以二進制傳輸。在 HTTP/1.1 版本中大部分數據是以文本形式傳輸,在 HTTP2 版本中,所有數據以二進制傳輸,統稱爲“幀”。
-
多工。因爲有了以二進制傳輸的好處,同一個連接裏面發送多個請求不再需要按照順序來進行返回處理,而是同時返回。在返回第一個請求的同時也可以返回第二個請求,這樣它就是一個並行的效率,可以更大限度地讓整個 Web 應用的傳輸效率有一個質的提升。
-
頭信息壓縮。在 HTTP/1.1 中,每次發送請求和返回請求,它的 HTTP 頭信息總是要完整發送和返回,而這部分頭信息內容是以字符串形式保存,所以它佔用的帶寬量是很大的。而 HTTP2 中,對頭信息進行了壓縮,減少了對帶寬的佔用。
-
服務器推送。HTTP/2 允許服務器未經請求,主動向客戶端發送資源。常見場景是客戶端請求一個網頁,這個網頁裏面包含很多靜態資源。正常情況下,客戶端必須收到網頁後,解析 HTML 源碼,發現有靜態資源,再發出靜態資源請求。其實,服務器可以預期到客戶端請求網頁後,很可能會再請求靜態資源,所以就主動把這些靜態資源隨着網頁一起發給客戶端了。
HTTP 三次握手
爲了準確無誤地將數據送達目標處,TCP 協議採用了三次握手 (three-way handshaking) 策略。用 TCP 協議把數據包送出去後,TCP 不會對傳送後的情況置之不理,它一定會向對方確認是否成功送達。握手過程中使用了 TCP 的標誌——SYN(synchronize) 和 ACK(acknowledgement)。
發送端首先發送一個帶 SYN 標誌的數據包給對方。接收端收到後,回傳一個帶有 SYN/ACK 標誌的數據包以示傳達確認信息。最後,發送端再回傳一個帶 ACK 標誌的數據包,代表“握手”結束。
通俗點講,發送端先發送一個數據包給接收端,當接收端收到數據包後就會知道發送端的發送數據功能正常;然後接收端又返回一個數據包給發送端,當發送端接收到數據包後就會知道接收端的發送和接收數據的功能正常;最後發送端再先接收端發送一個數據包,當接收端收到數據包後就會知道發送端的接收數據的功能也正常。這樣發送端和接收端的發送和接收數據的功能都正常,就可以可靠地進行數據交互了。
若在握手過程中某個階段莫名中斷,TCP 協議會再次以相同的順序發送相同的數據包。
URI、URL 和 URN
URI
URI(統一資源標識符)是 Uniform Resource Identifier 的縮寫。它主要用於定位某一類特定的資源而設計,用來唯一標識互聯網上的信息資源。它包括 URL 和 URN。
URL
URL(統一資源定位符)是 Uniform Resource Locator 的縮寫。它用來找到資源所在的位置,並且去訪問和得到資源。
URL 格式:
- 協議
獲取資源時要指定協議類型。比如,http、https、ftp 等協議。
- 登錄信息(認證)
指定用戶名和密碼作爲從服務器端獲取資源時必要的登錄信息(身份認證)。這種方式在現在的 Web 應用開發中不太會使用到。如果用戶每次訪問資源,都需要在 URL 中填寫用戶名和密碼,是很不方便的,也是很安全的做法。
- 服務器地址
指定資源所在服務器在互聯網中的位置。它可以是 ip 地址,也可以是 DNS 可解析的地址。
- 服務器端口號
指定服務器連接的網絡端口號。每一臺服務器都有很多的端口,在這臺服務器上可以運行很多軟件的 Web 服務,這些 Web 服務可以監聽不同的端口。如果我們要找這臺服務器上某一個 Web 服務裏面的資源,就要指定要找的是哪個 Web 服務,也就是說端口是用來定位服務器上的某個 Web 服務的。
- 資源路徑
指定服務器上的文件路徑來定位特指的資源。這與 UNIX 系統的文件目錄結構相似。
- 查詢字符串
針對已指定的文件路徑內的資源,可以使用查詢字符串傳入任意參數。
- 片段標識符
使用片段標識符通常可標記出已獲取資源中的子資源(文檔內的某個位置)。
URN
URN(永久統一資源定位符)是 Uniform Resource Name 的縮寫。作爲 HTTP 服務,如果某一類資源改變了位置,導致它的 URL 鏈接無法訪問到資源,那麼 URN 就解決了這個問題。也就是說,即便是資源改變了位置,通過 URN 還是可以訪問到。
HTTP 報文格式
用於 HTTP 協議交互的信息被稱爲 HTTP 報文。請求端(客戶端)的 HTTP 報文叫做請求報文,響應端(服務器端)的叫做響應報文。HTTP 報文本身是由多行(用 CR+LF 作換行符)數據構成的字符串文本。HTTP 報文大致可分爲報文首部和報文主體兩塊。兩者由最初出現的空行(CR+LF)來劃分。通常,並不一定要有報文主體。
請求報文
請求報文是由請求方法、請求 URL、協議版本、可選的請求首部字段和內容實體構成的。
響應報文
響應報文基本上由協議版本、狀態碼(表示請求成功或失敗的數字代碼)、用以解釋狀態碼的原因短語、可選的響應首部字段以及實體主體構成。
HTTP 方法
HTTP 方法是用來定義對資源的操作,從定義上講有各自的語義。注意,語義是定義上的,具體的操作需要根據實際情況來。
HTTP/1.0 和 HRTTP/1.1 支持的方法:
方法 | 說明 | 支持的 HTTP 協議版本 |
---|---|---|
GET | 獲取資源 | 1.0、1.1 |
POST | 傳輸實體主體 | 1.0、1.1 |
PUT | 傳輸文件 | 1.0、1.1 |
HEAD | 獲得報文首部 | 1.0、1.1 |
DELETE | 刪除文件 | 1.0、1.1 |
OPTIONS | 詢問支持的方法 | 1.1 |
TRACE | 追蹤路徑 | 1.1 |
CONNECT | 要求用隧道協議連接代理 | 1.1 |
LINK | 建立和資源之間的聯繫 | 1.0 |
UNLINE | 斷開連接關係 | 1.0 |
其中,LINK 和 UNLINK 已被 HTTP/1.1 廢棄,不再使用。
GET: 獲取資源
GET 方法用來請求訪問已被 URL 識別的資源。指定的資源經服務器端解析後返回響應內容。
POST: 傳輸實體主體
POST 方法用來傳輸實體的主體。雖然用 GET 方法也可以傳輸實體的主體,但一般不用 GET 方法進行傳輸,而是用 POST 方法。雖說POST 的功能與 GET 很相似,但 POST 的主要目的並不是獲取響應的主體內容。
PUT: 傳輸文件
PUT 方法用來傳輸文件。就像 FTP 協議的文件,上傳一樣,要求在請求報文的主體中包含文件內容,然後保存到請求 URL 指定的位置。
但是,鑑於 HTTP/1.1 的 PUT 方法自身不帶驗證機制,任何人都可以上傳文件,存在安全性問題,因此一般的 Web 網站不使用該方法。若配合 Web 應用程序的驗證機制,或架構設計採用 REST(REpresentationalState Transfer, 表徵狀態轉移) 標準的同類Web 網站,就可能會開放使用 PUT 方法。
HEAD: 獲得報文首部
HEAD 方法和 GET 方法一樣,只是不返回報文主體部分。用於確認 URL 的有效性及資源更新的日期時間等。
DELETE: 刪除文件
DELETE 方法用來刪除文件,是與 PUT 相反的方法。DELETE 方法按請求 URL 刪除指定的資源。
但是,HTTP/1.1 的 DELETE 方法和 PUT 方法一樣不帶驗證機制,所以一般的 Web 網站也不使用 DELETE 方法。當配合 Web 應用程序的驗證機制,或遵守 REST 標準時還是有可能會開放使用的。
OPTIONS: 詢問支持的方法
OPTIONS 方法用來查詢針對請求 URL 指定的資源支持的方法。就是我們常說的,預請求。
TRACE: 追蹤路徑
TRACE 方法是讓 Web 服務器端將之前的請求通信環回給客戶端的方法。
但是,TRACE 方法本來就不怎麼常用,再加上它容易引發 XST(Cross-Site Tracing,跨站追蹤)攻擊,通常就更不會用到了。
CONNECT: 要求用隧道協議連接代理
CONNECT 方法要求在與代理服務器通信時建立隧道,實現用隧道協議進行 TCP 通信。主要使用 SSL (Secure Sockets Layer,安全套接層)和 TLS(Transport Layer Security,傳輸層安全)協議把通信內容加密後經網絡隧道傳輸。
CONNECT 的格式:
CONNECT 代理服務器名稱 端口號 HTTP版本
HTTP 訪問控制(CORS)
CORS
跨域資源共享(CORS)是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個服務器上的 Web 應用被准許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器不同的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。
另外,規範要求,對那些可能對服務器數據產生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預請求(preflight request),從而獲知服務端是否允許該跨域請求。服務器確認允許之後,才發起實際的 HTTP 請求。在預請求的返回中,服務器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關數據)。
跨域:指不同域名之間相互訪問,瀏覽器不能執行其他網站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器施加的安全機制。
同域:指同一協議、同一 ip 地址、同一端口,其中有一個不同就會產生跨域。
預請求
預請求必須首先使用 OPTIONS
方法發起一個請求到服務器,以獲知服務器是否允許該實際請求。預請求的使用,可以避免跨域請求對服務器的用戶數據產生未知的影響。
當滿足以下條件時,可以不發送預請求:
-
HTTP 方法
- GET
- HEAD
- POST
-
Fetch Standard 定義的 CORS-safelisted request-header。安全的 request-header 如下:
- Accept
- Accept-Language
- Content-Language
- Content-Type(需要注意額外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
-
Content-Type 的值僅限於下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
-
XMLHttpRequestUpload 對象均沒有註冊任何事件監聽器(不常用)。
-
請求中沒有使用 ReadableStream 對象(不常用)。
這些跨域請求與瀏覽器發出的其他跨域請求一樣。如果服務器未返回正確的響應首部,則請求方不會收到任何數據。因此,那些不允許跨域請求的網站無需爲這一新的 HTTP 訪問控制特性擔心。
預請求相關的響應首部字段
- Access-Control-Allow-Origin
Access-Control-Allow-Origin 響應首部字段,表明服務器允許訪問資源的域。其語法如下:
Access-Control-Allow-Origin: [origin] | *
其中,origin 參數的值指定了允許訪問該資源的外域 URI。對於不需要攜帶身份憑證的請求,服務器可以指定該字段的值爲 “*”,表示允許來自所有域的請求。
- Access-Control-Allow-Headers
Access-Control-Allow-Headers 響應首部字段用於預請求的響應,指明瞭實際請求中允許攜帶的首部字段。其語法如下:
Access-Control-Allow-Headers: [field-name](自定義的請求頭) | *
當有多個請求頭時,使用逗號分隔。
- Access-Control-Expose-Headers
在跨域訪問時,XMLHttpRequest 對象的 getResponseHeader() 方法只能拿到一些最基本的響應頭,如 Cache-Control、Content-Language、Content-Type、Expires、Last-Modified 等,如果要訪問其他頭,則需要服務器設置響應頭。
Access-Control-Expose-Headers 響應首部字段讓服務器把允許瀏覽器訪問的頭放入白名單,例如:
Access-Control-Expose-Headers: X-My-Count, X-My-Title
- Access-Control-Allow-Methods
Access-Control-Allow-Methods 響應首部字段用於預請求的響應,指明瞭實際請求所允許使用的 HTTP 方法。其語法如下:
Access-Control-Allow-Methods: [method] | *
當有多個方法時,用逗號分隔。
- Access-Control-Max-Age
Access-Control-Max-Age 響應首部字段指定了預請求的結果能夠被緩存多久。在有效時間內,瀏覽器無須爲同一請求再次發起預請求。請注意,瀏覽器自身維護了一個最大有效時間,如果該首部字段的值超過了最大有效時間,將不會生效。其語法如下:
Access-Control-Max-Age: [detla-seconds]
detla-seconds 參數表示預請求的結果在多少秒內有效。
- Access-Control-Allow-Credentials
Access-Control-Allow-Credentials 響應首部字段指定了當瀏覽器的 credentials 設置爲 true 時是否允許瀏覽器讀取 response 的內容。當用在對預請求的響應中時,它指定了實際的請求是否可以使用 credentials。請注意:簡單 GET 請求不會被預檢;如果對此類請求的響應中不包含該字段,這個響應將被忽略掉,並且瀏覽器也不會將相應內容返回給網頁。
可緩存性
HTTP/1.1 定義的 Cache-Control
頭用來區分對緩存機制的支持情況,請求頭和響應頭都支持這個屬性。通過它提供的不同的值來定義緩存策略。
公共緩存 public
Cache-Control: public
“public” 表示 HTTP 請求在返回的過程中,所經過的任何路徑(比如中間代理、CDN等)都可以對響應的內容進行緩存。
私有緩存 private
Cache-Control: private
“private” 表示響應內容只有發起請求的那個瀏覽器可緩存。
強制確認緩存 no-cache
Cache-Control: no-cache
“no-cache” 表示每次有請求發出時,緩存會將此請求發到服務器,服務器端會驗證請求中所描述的緩存是否過期,若未過期,則緩存才使用本地緩存副本。使用 no-cache 指令的目的是爲了防止從緩存中返回過期的資源。
禁止進行緩存 no-store
Cache-Control: no-store
“no-store” 表示緩存中不得存儲任何關於客戶端請求和服務端響應的內容。每次由客戶端發起的請求都會下載完整的響應內容。
緩存過期機制 max-age
Cache-Control: max-age=[seconds]
過期機制中,最重要的指令是 “max-age=”,表示資源能夠被緩存的最大時間。
緩存驗證確認 must-revalidate
“must-revalidate” 表示在設置了 max-age 的緩存中,必須先從服務端重新請求資源,以驗證緩存是否真的過期了,而不能直接使用本地的緩存。
緩存驗證
當緩存的文檔過期後,需要進行緩存驗證或者重新獲取資源。只有在服務器返回強校驗器或者弱校驗器時纔會進行驗證。
Last Modified
Last-Modified(上次修改時間)響應頭可以作爲一種弱校驗器,通過對比上次修改時間以驗證資源是否需要更新。說它弱是因爲它只能精確到一秒。如果響應頭裏含有這個信息,客戶端可以在後續的請求中帶上 If-Modified-Since
或 If-Unmodified-Since
來驗證緩存。
ETag
ETag 是緩存的一種強校驗器,主要通過數據簽名進行資源驗證,最典型的做法是對資源進行哈希值計算。如果資源請求的響應頭裏含有 ETag,客戶端可以在後續的請求的頭中帶上 If-Match
或 If-None-Match
頭來驗證緩存。
最後,當客戶端向服務端發起緩存校驗的請求時,服務端會返回 200 ok
表示返回正常的結果或者 304 Not Modified
(不返回 body)表示瀏覽器可以使用本地緩存文件。304 的響應頭也可以同時更新緩存文檔的過期時間。
HTTP Cookie
HTTP Cookie(也叫 Web Cookie 或瀏覽器 Cookie)是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下一次向同一服務器再發起請求時被攜帶併發送到服務器上。通常,它用於告知服務端兩個請求是否來自同一瀏覽器,如保持用戶的登錄狀態。Cookie 使基於無狀態的 HTTP 協議記錄穩定的狀態信息成爲了可能。
Cookie 主要用於以下三個方面:
- 會話狀態管理(如用戶登錄狀態、購物車、遊戲分數或其它需要記錄的信息)
- 個性化設置(如用戶自定義設置、主題等)
- 瀏覽器行爲跟蹤(如跟蹤分析用戶行爲等)
Cookie 曾一度用於客戶端數據的存儲,因當時並沒有其它合適的存儲辦法而作爲唯一的存儲手段,但現在隨着現代瀏覽器開始支持各種各樣的存儲方式,Cookie 漸漸被淘汰。由於服務器指定 Cookie 後,瀏覽器的每次請求都會攜帶 Cookie 數據,會帶來額外的性能開銷。
設置 Cookie
當服務器收到 HTTP 請求時,服務器可以在響應頭裏面添加一個 Set-Cookie 選項。瀏覽器收到響應後通常會保存下 Cookie,之後對該服務器每一次請求中都通過 Cookie 請求頭部將 Cookie 信息發送給服務器。另外,Cookie 的過期時間、域、路徑、有效期、適用站點都可以根據需要來指定。
服務器使用 Set-Cookie 響應頭部向用戶代理(一般是瀏覽器)發送Cookie 信息,告知客戶端保存 Cookie 信息。其語法如下:
Set-Cookie: [cookie name]= [value]
Cookie 是以鍵值對形式存在的,可以同時設置多個,以逗號分隔。
Cookie 屬性
- max-age
設置 Cookie 有效期,單位秒。例如:
Set-Cookie: id=123; max-age=200
- expires
設置 Cookie 過期時間。例如:
Set-Cookie: id=123; expires=Wed, 21 Oct 2015 07:28:00 GMT;
- secure
Cookie 的 secure 屬性用於限制 Web 頁面僅在 HTTPS 安全連接時,纔可以發送Cookie。其語法如下:
Set-Cookie: id=123; secure
- HttpOnly
Cookie 的 HttpOnly 屬性是 Cookie 的擴展功能,它使 JavaScript 腳本的 Document.cookie API 無法獲得 Cookie。其主要目的爲防止跨站腳本攻擊(Cross-sitescripting,XSS)對 Cookie 的信息竊取。其語法如下:
Set-Cookie: id=123; HttpOnly
- domain
domain 標識指定了哪些主機可以接受 Cookie。如果不指定,默認爲當前文檔的主機(不包含子域名)。比如,當指定 example.com 後,除 example.com 以外,www.example.com 或 www2.example.com 等都可以發送 Cookie。其語法如下:
Set-Cookie: id=123; domain=example.com
HTTP 持久連接
爲解決上述 TCP 連接的問題,HTTP/1.1 和一部分的 HTTP/1.0 想出了持久連接(HTTP Persistent Connections,也稱爲 HTTP keep-alive 或 HTTP connection reuse)的方法。持久連接的特點是,只要任意一端沒有明確提出斷開連接,則保持 TCP 連接狀態。
持久連接的好處在於減少了 TCP 連接的重複建立和斷開所造成的額外開銷,減輕了服務器端的負載。另外,減少開銷的那部分時間,使 HTTP 請求和響應能夠更早地結束,這樣 Web 頁面的顯示速度也就相應提高了。
在 HTTP/1.1 中,所有的連接默認都是持久連接。響應頭格式如下:
Connection: keep-alive | close
數據協商
數據協商機制是指客戶端和服務器端就響應的資源內容進行交涉,然後提供給客戶端最爲適合的資源。數據協商會以響應資源的語言、字符集、編碼方式等作爲判斷的基準。
請求報文和響應報文中的某些首部字段可以作爲判斷基準,字段分爲請求和返回兩類。
請求類
-
Accept:用戶代理(瀏覽器)可處理的數據類型。
-
Accept-Charset:用戶代理(瀏覽器)可接受的字符集。
-
Accept-Encoding:用戶代理(瀏覽器)可接受的數據編碼方式,主要限制服務端如何進行數據壓縮。
-
Accept-Language:用戶代理(瀏覽器)可接受的自然語言類型。
-
User-Agent:HTTP 客戶端程序的信息
返回類
-
Content-Type:響應內容的數據類型。
-
Content-Encoding:響應內容的編碼方式。
-
Content-Language:響應內容的自然語言類型。