HTTP 報文簡介
用於 HTTP 協議交互的信息被稱為 HTTP 報文,請求端(客戶端)發起的 HTTP 報文叫做請求報文,響應端(服務器端)發出的 HTTP 報文叫做響應報文。請求報文會向 Web 服務器請求一個動作,響應報文會將請求的處理結果返回給客戶端。
HTTP 報文本身是由多行(用CR+LF作換行符,即\r\n
)數據構成的字符串文本,這些文本信息描述了報文的內容及含義,后面跟着可選的數據部分。
HTTP 報文大致可分為報文首部和報文主體兩塊,兩者由首次出現的空行來分隔,通常,並不一定要有報文主體,比如 GET 請求就沒有:
而 POST 請求則包含報文主體:
正常的響應報文通常都包含報文主體:
HTTP 報文組成結構
了解了 HTTP 報文的基本概念后,下面我們具體分析 HTTP 報文的組成結構。
HTTP 報文是簡單的格式化數據塊:
報文分為請求報文和響應報文,二者結構類似,都由三部分構成 —— 對報文進行描述的起始行、包含屬性的首部字段、以及可選的包含數據的報文主體部分:
通常我們可以將請求報文的起始行稱作請求行,對應的報文主體稱作請求主體;將響應報文的起始行稱作狀態行(包含狀態碼),對應的報文主體稱作響應主體。首部字段又可以細分為請求首部字段、響應首部字段、通用首部字段和實體首部字段,關於首部字段的含義及使用后面我們會詳細介紹。
我們以 http://laravel58.test/
為例,其請求報文的組成結構如下:
對應的響應報文組成結構如下:
起始行與首部字段之間通過一個換行符分隔(CR+LF),多個首部字段之間也是通過換行符分隔,報文首部與報文主體之間則通過一個一個空行分隔(兩個CR+LF),報文首部有純文本格式的字符串組成,報文主體則可以包含多種格式數據。
請求報文可以抽象為如下格式:
<method> <request-URL> <version>(請求行)
<headers>
<entity-body>
響應報文可以抽象如下格式:
<version> <status-code> <reason-phrase>(響應行)
<headers>
<entity-body>
下面是對各部分的簡單描述:
- 方法(method):客戶端希望服務器對資源執行的動作,比如 GET、POST、PUT、DELETE 等;
- 請求 URL(request-URL):命名了所請求資源,或者 URL 路徑組成的完整 URL,比如
http://laravel58.test/
這個示例中是 /; - 版本(version):HTTP 版本,目前主流版本是 1.1;
- 狀態碼(status-code):三個數字,描述了請求過程所發生的情況,例如 200、404、500 等;
- 原因短語(reason-phrase):數字狀態碼的可讀版本;
- 首部字段(headers):可以有零個或多個首部,每個首部都包含一個名字,后面跟着一個冒號,然后是一個可選的空格,接着是一個值,最后是一個 CR+LF(即\r\n),報文首部是由一個空行結束的,表示了首部列表的結束和實體主體部分的開始;
- 主體(entity-body):包含一個由任意數據組成的數據塊,並不是所有報文都包含實體的主體部分。
HTTP 請求方法
在請求報文的第一行第一個元素就是 HTTP 方法,HTTP 協議主要支持的方法包括以下這些:
今天我們將就這些常見的 HTTP 方法的使用和注意事項進行介紹。在整篇教程中,我們將基於 TELNET 工具模擬 HTTP 通信或者通過抓包工具分析請求報文和響應報文,以便讓你有更直觀的感受。
GET
GET 方法用於從服務器上獲取指定的 URL 資源:
這是我們日常最常見的 HTTP 方法了,由於 GET 請求不會對服務器資源產生副作用(只是獲取資源,不會對資源進行變更,但是這一點需要開發者保證,通過 GET 請求變更服務器資源有重大安全隱患,在開發過程中國要避免這么做),所以我們通常將其稱為安全的 HTTP 方法,而與之相對的,將 PUT、POST、DELETE、PATCH 這些會對服務器資源造成變更的方法稱作不安全的 HTTP 方法。
HEAD
HEAD 方法和 GET 方法類似,但只返回 HTTP 首部,不返回報文主體部分,通常用於確認 URL 的有效性及資源更新的日期時間等:
顯然,和 GET 方法一樣,HEAD 方法也是安全的 HTTP 方法。
POST
POST 方法用來向服務器發送數據,通常用它來支持 HTML 的表單,POST 請求會對服務器資源進行變更(這一點需要開發者保證,HTTP 協議本身沒有強制要求這么做,你完全可以通過 GET 請求發送數據,但是從安全角度說,強烈建議這么做,即用 POST 方法發送變更資源的請求),是不安全的 HTTP 方法。
我們以 Laravel 框架自帶的注冊頁面為例:
點擊注冊按鈕,即可提交表單,這里使用的是 POST 方法:
PUT
PUT 方法設計的初衷是用來傳輸文件,就像 FTP 協議的文件上傳一樣,要求在報文的主體中包含文件內容,然后保存到請求 URI 指定的位置。但是,由於 PUT 方法自身不帶驗證機制,任何人都可以上傳文件,存在安全性問題,因此一般 Web 網站不會通過該方法來上傳文件。若配合 Web 應用程序的驗證機制,則可能會開放使用 PUT 方法。
PUT 方法的語義就是讓服務器用請求主體部分來創建一個由所請求的 URL 命令的新文檔,或者,如果那個 URL 已經存在的話,就用這個主體來替代它,因此,我們會看到在 RESTful 架構中,我們一般使用 PUT 方法來更新資源,Laravel 框架中的資源控制器就是這么做的。
與 PUT 方法類似的還有一個 PATCH 方法,用於對指定資源進行部分修改(PUT 方法用於整體覆蓋),但是日常開發中,我們不會分的這么細,通常,可以用 PATCH 的地方,往往就可以用 PUT 方法替代。
和 POST 方法一樣,PUT 方法也是不安全的 HTTP 方法。具體請求和響應報文格式和 POST 類似,這里不再重復演示了。
需要注意的是,在 Nginx 中,默認只支持對靜態資源進行 GET、POST、HEAD 請求,其他 HTTP 方法都會返回 405 Method Not Allowed,要支持的其他 HTTP 方法的話需要配置。所以你會看到在 Laravel 框架中都是通過方法偽造來實現 PUT、DELETE 請求。
DELETE
DELETE 方法設計的初衷是用來刪除文件,是與 PUT 請求相對的方法,DELETE 方法按請求 URI 刪除指定的資源。但是,HTTP/1.1 的 DELETE 方法和 PUT 方法一樣不帶驗證機制,所以一般 Web 網站也不使用 DELETE 方法進行文件刪除,當配合 Web 應用程序的驗證機制,或遵守 REST 標准時還是有可能會開放使用的。
目前我們會在 RESTful 架構中,通過 DELETE 方法來刪除資源(不一定是文件),Laravel 框架中的資源控制器就是這么做的。DELETE 請求報文中不需要報文實體,只需要通過 URL 來定位資源即可。和 PUT 方法一樣,我們需要在 HTML 表單中偽造 DELETE 方法才能讓 Nginx 支持 DELETE 請求。
和 POST 方法一樣,DELETE 方法也是不安全的 HTTP 方法。
OPTIONS
OPTIONS 方法用來查詢針對請求 URL 指定的資源支持的所有 HTTP 方法。Nginx 默認也不支持對資源發起 OPTIONS 請求,我們可以通過在 Laravel 項目中 PHP 內置的 Web 服務器來模擬 OPTIONS 請求,首先通過 php artisan serve
啟動內置服務器,然后通過 TELNET 進行模擬通信:
可以看到,在指定 URL 資源 /api/task/1
上支持多個 HTTP 方法。
TRACE
客戶端發起一個請求時,這個請求可能要穿過防火牆、代理、網關或其他一些應用程序,每個中間節點都可能會修改原始的 HTTP 請求,TRACE 方法允許客戶端在最終請求發送給服務器時,看看它變成了什么樣子。該命令主要用於 HTTP 通信的診斷和調試。
TRACE 請求會在目標服務器端發起一個「環回」診斷,行程最后一站的服務器會返回一條 TRACE 響應,並在響應主體中攜帶它收到的原始請求報文,這樣,客戶端就可以查看在所有中間 HTTP 應用程序組成的請求/響應鏈上,原始報文是否以及如何被修改過。
發送 TRACE 請求時,可以在 Max-Forwards 首部字段中填入數值,每經過一個服務器/中間節點該數值就會減1,當數值剛好減到0時,就停止繼續傳輸,最后接收到請求的服務器/節點則返回狀態碼 200 OK 的響應(不管是否到達最終目標服務器都會返回,不會再繼續傳遞請求)。
但是,TRACE 方法並不怎么常用,再加上容易引發 XST(Cross-Site Tracing,跨站追蹤)攻擊,就更不會用到了。
HTTP 響應狀態碼
狀態碼概述
當目前為止,我們已經介紹完了 HTTP 請求報文起始行的所有元素,第一個是請求方法,第二個是標識請求資源的 URL(一般來說是相對於域名的相對 URL,Web 服務器會將其和請求首部里的 Host 字段組合拼接成完整 URL),第三個是客戶端 HTTP 協議的版本,關於報文首部我們放到下一篇統一介紹,現在我們跳到響應報文的起始行,前面學院君已經簡單介紹過,響應報文的起始行也由三部分組成,分別是服務器 HTTP 協議的版本,響應狀態碼以及原因短語,HTTP 協議的版本我們已經討論過,響應短語主要是響應狀態碼的可讀版本,所以我們重點關注響應狀態碼。
狀態碼的職責是當客戶端向服務器端發送請求時,描述返回的處理結果,借助狀態碼,用戶可以知道服務器端是正常處理了請求還是出現了錯誤。
狀態碼由3位數字組成,按照首數字可以將響應分類如下:
RFC2616 上記錄的 HTTP 狀態碼有 40 種,再加上 WebDAV(基於萬維網的分布式創作和版本控制,RFC4918、5842)和附加 HTTP 狀態碼(RFC6585)等擴展,數量達60余種,但常用的大概只有14種。下面我們就常見的狀態碼進行介紹。
1XX:信息性狀態碼
信息性狀態碼日常很少見,主要有如下兩個:
- 100:說明收到請求的初始部分,請客戶端繼續,比如客戶端要發送一個請求實體給服務器,但想在發送之前查看下服務器是否會接收這個實體;
- 101:說明服務器正在根據客戶端的指定,將協議切換成 Upgrade 首部所列的協議。
2XX:成功狀態碼
2XX 的響應結果表明請求被服務器正常處理了。常見的 2XX 狀態碼如下所示:
- 200:表明從客戶端發來的請求在服務器端被正常處理了;
- 201:用於創建服務器對象的請求(如 PUT 請求);
- 202:請求已被接收,但服務器還未對其執行任何動作;
- 203:表明實體部分包含的信息不是來自源端服務器,而是來自資源的一份副本;
- 204:該狀態碼代表服務器接收的請求已經成功處理,但在返回的響應報文中不含實體的主體部分;
- 205:負責告知瀏覽器清除當前頁面中的所有 HTML 表單元素;
- 206:該狀態碼表示客戶端進行了范圍請求,而服務器成功執行了這部分的 GET 請求,響應報文中由 Content-Range 指定范圍的實體內容。
3XX:重定向狀態碼
3XX 響應結果表明瀏覽器需要執行某些特殊的處理以正確處理請求,比如對請求進行重定向,常見的 3XX 狀態碼如下所示:
- 300:客戶端請求一個實際指向多個資源的 URL 時返回這個狀態碼;
- 301:永久性重定向,該狀態碼表示請求的資源已經被分配了新的 URL,以后應使用資源現在所指的 URL;
- 302:臨時性重定向,該狀態碼表示請求的資源已被分配了新的 URL,希望用戶能使用新的 URL 訪問,和 - 301 狀態碼類似,但 302 狀態碼代表的資源不是被永久移動,只是臨時性質的;
- 303:該狀態碼表示由於請求對應的資源存在着另一個 URL,應使用 GET 方法定向獲取請求的資源,303 狀態碼和 302 狀態碼有着相同的功能,但 303 狀態碼明確表示客戶端應當采用 GET 方法獲取資源,這點和 302 狀態碼有所區別;
- 304:該狀態碼表示客戶端發送附帶條件的請求(If-Match、If-Modified-Since、If-None-Match、If-Range、If-Unmodified-Since)時,服務器端允許請求訪問資源,但未滿足條件的情況下,304 狀態碼返回時,不包含任何響應的主體部分,常用做客戶端緩存。304 雖然被划分在 3XX 類別中,但是與重定向沒有任何關系;
- 305:用來說明必須通過一個代理來訪問資源,代理的位置由 Location 首部指定;
- 307:臨時重定向,該狀態碼與 302 有着相同的含義,盡管 302 標准禁止 POST 變成 GET,但實際使用時大家都不遵守。307 會遵照瀏覽器標准,不會從 POST 變成 GET,但是對於處理響應時的行為,每種瀏覽器都有可能出現不同的情況。
注:當 301、302、303 響應狀態碼返回時,幾乎所有瀏覽器都會把 POST 改成 GET,並刪除請求報文內的主體,之后請求會自動再次發送。
4XX:客戶端錯誤狀態碼
4XX 的響應結果表示客戶端是發生錯誤的原因所在,常見的 4XX 狀態碼列舉如下:
- 400:該狀態碼表示請求報文中存在語法錯誤,當錯誤發生時,需修改請求的內容后再次發送請求;
- 401:該狀態碼表示發送的請求需要有通過 HTTP 認證的認證信息,另外若之前已進行過1次請求,則表示用戶認證失敗;
- 402:該狀態碼暫時還未啟用;
- 403:該狀態碼表明對請求資源的訪問被服務器拒絕了,服務器端沒有必要給出拒絕的詳細理由,但如果想做說明的話,可以在實體的主體部分對原因進行描述,這樣用戶就能看到了;
- 404:該狀態碼表明服務器上無法找到請求的資源,除此之外,也可以在服務器端拒絕請求且不想說明原因時使用;
- 405:發起的請求中帶有請求 URL 所不支持的請求方法時返回該狀態碼,上一篇分享中我們已經提及;
- 406:客戶端可以指定參數來說明它們願意接收什么類型的實體,服務器沒有與客戶端可接受的 URL 相匹配的資源時,返回此狀態碼;
- 407:與 401 狀態碼類似,但用於要求對資源進行認證的代理服務器;
- 408:客戶端請求超時,服務器返回此狀態碼並關閉連接;
- 409:說明請求可能在資源上引發沖突時返回此狀態碼;
- 410:與 404 類似,只是服務器曾經擁有過此資源,現在被移除了;
- 411:服務器要求請求報文中包含 Content-Length 首部時使用;
- 412:客戶端發起條件請求,但其中一個條件失敗時使用;
- 413:客戶端發送的實體主體部分超過服務器處理的上限,一般上傳大文件時會出現這種情況;
- 414:客戶端發送請求中的請求 URL 超過服務器能處理的長度上限,返回此狀態碼;
- 415:服務器無法理解或無法支持客戶端所發送實體的內容類型。
5XX:服務器錯誤狀態碼
5XX 的響應結果表明服務器本身發生錯誤,常見的 5XX 狀態碼如下所示:
- 500:該狀態碼表明服務器端在執行請求時發生了錯誤,也可能是 Web 應用程序存在的 bug 或某些臨時的故障;
- 501:客戶端發起的請求超出服務器能力的范圍;
- 502:作為代理或網關使用的服務器從請求響應鏈的下一條鏈路上收到了一條偽響應時返回此狀態碼;
- 503:該狀態碼表明服務器暫時處於超負載或正在進行停機維護,現在無法處理請求,如果事先得知解除以上狀況需要的時間,最好寫入 Retry-After 首部字段再返回給客戶端;
- 504:與狀態碼 408 相似,這是這里的響應來自網關或代理,它們在等待另一個服務器對其請求進行響應的時候超時了,比如 php-fpm 故障或沒有啟動,通過 nginx 訪問應用的時候會返回此狀態碼;
- 505:服務器收到的請求使用了它無法支持的協議版本時,使用此狀態碼。