前兩講中,我們學習了 HTTP 報文里請求行的組成部分,包括請求方法和 URI。有了請求行,加上后面的頭字段就形成了請求頭,可以通過 TCP/IP 協議發送給服務器。
服務器收到請求報文,解析后需要進行處理,具體的業務邏輯多種多樣,但最后必定是拼出一個響應報文發回客戶端。
響應報文由響應頭加響應體數據組成,響應頭又由狀態行和頭字段構成。
我們先來復習一下狀態行的結構,有三部分:
開頭的 Version 部分是 HTTP 協議的版本號,通常是 HTTP/1.1,用處不是很大。
后面的 Reason 部分是原因短語,是狀態碼的簡短文字描述,例如“OK”“Not Found”等等,也可以自定義。但它只是為了兼容早期的文本客戶端而存在,提供的信息很有限,目前的大多數客戶端都會忽略它。
所以,狀態行里有用的就只剩下中間的狀態碼(Status Code)了。它是一個十進制數字,以代碼的形式表示服務器對請求的處理結果,就像我們通常編寫程序時函數返回的錯誤碼一樣。
不過你要注意,它的名字是“狀態碼”而不是“錯誤碼”。也就是說,它的含義不僅是錯誤,更重要的意義在于表達 HTTP 數據處理的“狀態”,客戶端可以依據代碼適時轉換處理狀態,例如繼續發送請求、切換協議,重定向跳轉等,有那么點 TCP 狀態轉換的意思。
狀態碼
目前 RFC 標準里規定的狀態碼是三位數,所以取值范圍就是從 000 到 999。但如果把代碼簡單地從 000 開始順序編下去就顯得有點太“low”,不靈活、不利于擴展,所以狀態碼也被設計成有一定的格式。
RFC 標準把狀態碼分成了五類,用數字的第一位表示分類,而 0~99 不用,這樣狀態碼的實際可用范圍就大大縮小了,由 000~999 變成了 100~599。
這五類的具體含義是:
- 1××:提示信息,表示目前是協議處理的中間狀態,還需要后續的操作;
- 2××:成功,報文已經收到并被正確處理;
- 3××:重定向,資源位置發生變動,需要客戶端重新發送請求;
- 4××:客戶端錯誤,請求報文有誤,服務器無法處理;
- 5××:服務器錯誤,服務器在處理請求時內部發生了錯誤。
在 HTTP 協議中,正確地理解并應用這些狀態碼不是客戶端或服務器單方的責任,而是雙方共同的責任。
客戶端作為請求的發起方,獲取響應報文后,需要通過狀態碼知道請求是否被正確處理,是否要再次發送請求,如果出錯了原因又是什么。這樣才能進行下一步的動作,要么發送新請求,要么改正錯誤重發請求。
服務器端作為請求的接收方,也應該很好地運用狀態碼。在處理請求時,選擇最恰當的狀態碼回復客戶端,告知客戶端處理的結果,指示客戶端下一步應該如何行動。特別是在出錯的時候,盡量不要簡單地返 400、500 這樣意思含糊不清的狀態碼。
目前 RFC 標準里總共有 41 個狀態碼,但狀態碼的定義是開放的,允許自行擴展。所以 Apache、Nginx 等 Web 服務器都定義了一些專有的狀態碼。如果你自己開發 Web 應用,也完全可以在不沖突的前提下定義新的代碼。
在我們的實驗環境里也可以對這些狀態碼做測試驗證,訪問 URI“/12-1”,用查詢參數“code=xxx”來檢查這些狀態碼的效果,服務器不僅會在狀態行里顯示狀態碼,還會在響應頭里用自定義的“Expect-Code”字段輸出這個代碼。
例如,在 Chrome 里訪問“http://www.chrono.com/12-1?code=405”的結果如下圖。
接下來我就挑一些實際開發中比較有價值的狀態碼逐個詳細介紹。
1××
1××類狀態碼屬于提示信息,是協議處理的中間狀態,實際能夠用到的時候很少。
我們偶爾能夠見到的是“101 Switching Protocols”。它的意思是客戶端使用 Upgrade 頭字段,要求在 HTTP 協議的基礎上改成其他的協議繼續通信,比如 WebSocket。而如果服務器也同意變更協議,就會發送狀態碼 101,但這之后的數據傳輸就不會再使用 HTTP 了。
2××
2××類狀態碼表示服務器收到并成功處理了客戶端的請求,這也是客戶端最愿意看到的狀態碼。
“200 OK”是最常見的成功狀態碼,表示一切正常,服務器如客戶端所期望的那樣返回了處理結果,如果是非 HEAD 請求,通常在響應頭后都會有 body 數據。
“204 No Content”是另一個很常見的成功狀態碼,它的含義與“200 OK”基本相同,但響應頭后沒有 body 數據。所以對于 Web 服務器來說,正確地區分 200 和 204 是很必要的。
“206 Partial Content”是 HTTP 分塊下載或斷點續傳的基礎,在客戶端發送“范圍請求”、要求獲取資源的部分數據時出現,它與 200 一樣,也是服務器成功處理了請求,但 body 里的數據不是資源的全部,而是其中的一部分。
狀態碼 206 通常還會伴隨著頭字段“Content-Range”,表示響應報文里 body 數據的具體范圍,供客戶端確認,例如“Content-Range: bytes 0-99/2000”,意思是此次獲取的是總計 2000 個字節的前 100 個字節。
3××
3××類狀態碼表示客戶端請求的資源發生了變動,客戶端必須用新的 URI 重新發送請求獲取資源,也就是通常所說的“重定向”,包括著名的 301、302 跳轉。
“301 Moved Permanently”俗稱“永久重定向”,含義是此次請求的資源已經不存在了,需要改用改用新的 URI 再次訪問。
與它類似的是“302 Found”,曾經的描述短語是“Moved Temporarily”,俗稱“臨時重定向”,意思是請求的資源還在,但需要暫時用另一個 URI 來訪問。
301 和 302 都會在響應頭里使用字段Location指明后續要跳轉的 URI,最終的效果很相似,瀏覽器都會重定向到新的 URI。兩者的根本區別在于語義,一個是“永久”,一個是“臨時”,所以在場景、用法上差距很大。
比如,你的網站升級到了 HTTPS,原來的 HTTP 不打算用了,這就是“永久”的,所以要配置 301 跳轉,把所有的 HTTP 流量都切換到 HTTPS。
再比如,今天夜里網站后臺要系統維護,服務暫時不可用,這就屬于“臨時”的,可以配置成 302 跳轉,把流量臨時切換到一個靜態通知頁面,瀏覽器看到這個 302 就知道這只是暫時的情況,不會做緩存優化,第二天還會訪問原來的地址。
“304 Not Modified” 是一個比較有意思的狀態碼,它用于 If-Modified-Since 等條件請求,表示資源未修改,用于緩存控制。它不具有通常的跳轉含義,但可以理解成“重定向已到緩存的文件”(即“緩存重定向”)。
301、302 和 304 分別涉及了 HTTP 協議里重要的“重定向跳轉”和“緩存控制”,在之后的課程中我還會細講。
4××
4××類狀態碼表示客戶端發送的請求報文有誤,服務器無法處理,它就是真正的“錯誤碼”含義了。
“400 Bad Request”是一個通用的錯誤碼,表示請求報文有錯誤,但具體是數據格式錯誤、缺少請求頭還是 URI 超長它沒有明確說,只是一個籠統的錯誤,客戶端看到 400 只會是“一頭霧水”“不知所措”。所以,在開發 Web 應用時應當盡量避免給客戶端返回 400,而是要用其他更有明確含義的狀態碼。
“403 Forbidden”實際上不是客戶端的請求出錯,而是表示服務器禁止訪問資源。原因可能多種多樣,例如信息敏感、法律禁止等,如果服務器友好一點,可以在 body 里詳細說明拒絕請求的原因,不過現實中通常都是直接給一個“閉門羹”。
“404 Not Found”可能是我們最常看見也是最不愿意看到的一個狀態碼,它的原意是資源在本服務器上未找到,所以無法提供給客戶端。但現在已經被“用濫了”,只要服務器“不高興”就可以給出個 404,而我們也無從得知后面到底是真的未找到,還是有什么別的原因,某種程度上它比 403 還要令人討厭。
4××里剩下的一些代碼較明確地說明了錯誤的原因,都很好理解,開發中常用的有:
- 405 Method Not Allowed:不允許使用某些方法操作資源,例如不允許 POST 只能 GET;
- 406 Not Acceptable:資源無法滿足客戶端請求的條件,例如請求中文但只有英文;
- 408 Request Timeout:請求超時,服務器等待了過長的時間;
- 409 Conflict:多個請求發生了沖突,可以理解為多線程并發時的競態;
- 413 Request Entity Too Large:請求報文里的 body 太大;
- 414 Request-URI Too Long:請求行里的 URI 太大;
- 429 Too Many Requests:客戶端發送了太多的請求,通常是由于服務器的限連策略;
- 431 Request Header Fields Too Large:請求頭某個字段或總體太大;
5××
5××類狀態碼表示客戶端請求報文正確,但服務器在處理時內部發生了錯誤,無法返回應有的響應數據,是服務器端的“錯誤碼”。
“500 Internal Server Error”與 400 類似,也是一個通用的錯誤碼,服務器究竟發生了什么錯誤我們是不知道的。不過對于服務器來說這應該算是好事,通常不應該把服務器內部的詳細信息,例如出錯的函數調用棧告訴外界。雖然不利于調試,但能夠防止黑客的窺探或者分析。
“501 Not Implemented”表示客戶端請求的功能還不支持,這個錯誤碼比 500 要“溫和”一些,和“即將開業,敬請期待”的意思差不多,不過具體什么時候“開業”就不好說了。
“502 Bad Gateway”通常是服務器作為網關或者代理時返回的錯誤碼,表示服務器自身工作正常,訪問后端服務器時發生了錯誤,但具體的錯誤原因也是不知道的。
“503 Service Unavailable”表示服務器當前很忙,暫時無法響應服務,我們上網時有時候遇到的“網絡服務正忙,請稍后重試”的提示信息就是狀態碼 503。
503 是一個“臨時”的狀態,很可能過幾秒鐘后服務器就不那么忙了,可以繼續提供服務,所以 503 響應報文里通常還會有一個“Retry-After”字段,指示客戶端可以在多久以后再次嘗試發送請求。
小結
- 狀態碼在響應報文里表示了服務器對請求的處理結果;
- 狀態碼后的原因短語是簡單的文字描述,可以自定義;
- 狀態碼是十進制的三位數,分為五類,從 100 到 599;
- 2××類狀態碼表示成功,常用的有 200、204、206;
- 3××類狀態碼表示重定向,常用的有 301、302、304;
- 4××類狀態碼表示客戶端錯誤,常用的有 400、403、404;
- 5××類狀態碼表示服務器錯誤,常用的有 500、501、502、503。
課下作業
- 你在開發 HTTP 客戶端,收到了一個非標準的狀態碼,比如 4××、5××,應當如何應對呢?
- 你在開發 HTTP 服務器,處理請求時發現報文里缺了一個必需的 query 參數,應該如何告知客戶端錯誤原因呢?
歡迎你把自己的答案寫在留言區,與我和其他同學一起討論。如果你覺得有所收獲,歡迎你把文章分享給你的朋友。