Skip to content

HTTP 狀態碼

狀態碼是伺服器自己對「它拿這個請求做了什麼」所下的陳述。它是關於伺服器處理過程的證據,不是對請求內容的判決:它說明哪一層回答了你、目標有沒有被辨識出來,以及做出這個決定時身分是否已經建立。真正有用的部分幾乎都在相近碼之間的差別,因為那才是會改變你對伺服器認知的東西。

面板怎麼呈現狀態碼

位置呈現形式
Network 記錄的 Status只有數字,依類別上色
Network 記錄的 Response 子分頁起始行為 HTTP/1.1 <狀態碼>,沒有狀態文字
Repeater 的回應區起始行為 HTTP/1.1 <狀態碼>,狀態文字的位置永遠是空的

兩處都不會顯示狀態文字。Repeater 的起始行留了一個位置給它,但這個實驗室從來不填,所以你看到的是狀態碼後面跟著一個空白;Network 明細則連那個位置都沒有。在協定上,狀態文字由伺服器自行決定,屬於輔助說明,不是客戶端據以行動的狀態本身——但這裡的每一筆回應都沒有帶它。

Network 記錄的顏色分帶:

類別顏色
500 以上紅色
400–499橘色
300–399黃色
300 以下的一切,含 2xx 與 1xx綠色

顏色是拿數字比大小推出來的,所以沒人見過的狀態碼一樣會落進某一帶,而 1xx 與 2xx 共用綠色帶、沒有自己的顏色。這些控制項在面板上的位置寫在 Network 面板說明

2xx —— 請求抵達了處理器,而處理器回答了

2xx 表示請求的格式足以被路由、伺服器要求的授權已經滿足,而且伺服器認為自己這一側的處理已經完成。

2xx 代表的事:應用程式同意請求的內容。拒絕密碼的登入表單、找不到東西的搜尋、記下驗證錯誤的 API,都可以回 200 並把不同意寫在回應內容裡。狀態碼類別與應用程式層級的結果是兩條各自獨立的通道,而許多應用程式只用後面那一條。

狀態碼伺服器陳述了什麼它排除了什麼
200 OK有處理器執行過並產生了內容該路徑未被路由;處理器前面擋著一道授權關卡
201 Created有處理器執行過,並宣告一份新資源已經存在這是一次唯讀操作
202 Accepted請求被收下,但處理尚未結束回應內容就是最終結果
204 No Content有處理器執行過,並刻意不產生內容回應區空白就代表請求失敗
206 Partial Content伺服器接受了範圍請求內容是整份資源

在只顯示回應內容的地方,204 與內容為空的 200 完全一樣。唯一能把「伺服器選擇不送內容」和「伺服器該送的內容沒送出來」分開的,就是那一行狀態行。

3xx —— 伺服器把客戶端導去別處,而不是直接回答

3xx 表示伺服器認得這個請求,並指名另一個位置,或斷定客戶端手上的快取仍然有效。路徑有被路由到。至於決定要轉址之前有沒有先檢查授權,狀態碼沒有說。

狀態碼伺服器陳述了什麼它排除了什麼
301 Moved Permanently資源有了新的正規位置,客戶端可以快取這次搬遷原路徑仍是現行位置
302 Found這一次導向另一個位置,原路徑仍是正規位置沒有排除任何關於搬遷是否長久的事
303 See Other不管這次用什麼方法,去那個位置時用 GET那個位置是同一次操作的延續
304 Not Modified條件式請求的驗證值相符,後面沒有內容內容為空代表資源是空的
307 Temporary Redirect302 相同,但方法與內容必須保留轉址會把 POST 悄悄降成 GET
308 Permanent Redirect301 相同,但方法與內容必須保留同一種降級,用於長久搬遷的情形

301 與 302 與 307 的差別

這三個碼把兩個互相獨立的軸擠在一起,搞混它們會讓整條轉址鏈看起來毫無規律。

搬遷是長久的搬遷是暫時的
方法可以被改寫成 GET301302
方法與內容必須保留308307

301302 的規格早於「方法是否保留」被釘死的時間,而客戶端最後一致採取把後續請求改寫成 GET 的做法。307308 的存在就是為了把相反的意思明講出來。所以 POST 收到 302POST 收到 307,產生的第二個請求並不相同——前者送出去時是沒有內容的 GET,後者是帶著原本內容的 POST

另一個後果是快取:把 301 當真的客戶端,之後再訪時可能整個跳過原網址,於是一個你以為自己送出去的請求根本不會出現。

表單送出後收到 302 代表什麼

對憑證送出所回的轉址,成立的是「處理器執行過,並決定把客戶端移走」。它不成立「認證成功」——應用程式同樣可以在失敗時把人導回登入頁。能分辨這兩者的證據是 Location 標頭裡的目標,以及該筆轉址回應上的 Set-Cookie,不是狀態碼。

Browser 面板怎麼處理轉址

面板最多自動跟隨連續五個 3xx 回應。每一跳都是各自送出的請求,因此一條轉址鏈在 Network 記錄裡每一跳留下一列,而整條鏈在歷史紀錄裡只收斂成最終網址的一筆。

每一個被跟隨的跳轉一律以 GET 重新送出,與觸發它的狀態碼無關,也與原本的方法無關。面板的行為因此分辨不出 302307;狀態碼本身,也就是 Network 記錄的 Status 欄,是唯一看得到這個差別的地方。

有兩種情形會讓鏈條中止而且不顯示任何錯誤訊息:

  • 沒有 Location 標頭的 3xx 不會被跟隨。它被當成一般回應直接呈現,而一個沒有內容的轉址呈現出來就是空白頁。
  • 第六個連續的 3xx 也被這樣呈現——不顯示任何錯誤,而且網址列停在產生該次跳轉的那個網址,不會往前走。

4xx —— 伺服器拒絕了請求,並把責任歸給請求

4xx 表示伺服器把請求處理到足以做出「不給」這個決定的程度。這個決定本身就是資訊:它代表那個路徑上有東西在聽,也代表這次拒絕是刻意的,不是崩潰。

狀態碼伺服器陳述了什麼它排除了什麼
400 Bad Request請求無法被解析,或違反某項語法要求請求以可用的形式抵達了應用程式邏輯
401 Unauthorized需要憑證,而憑證不存在或不被接受資源是開放的;身分已經建立
403 Forbidden請求被理解了,然後被拒絕已被接受的身分足以取得這份資源
404 Not Found伺服器對這個目標沒有任何可回應的內容沒有排除「目標存在於某道授權層後面」
405 Method Not Allowed路徑有被路由,但不是給這個方法用的路徑不存在:它對其他方法是存在的
409 Conflict請求與資源目前的狀態相衝突請求格式有誤
413 Content Too Large內容超過了設定的上限內容曾經被解析
415 Unsupported Media TypeContent-Type 不在這個處理器接受的範圍內內容本身曾經被檢視
422 Unprocessable Content語法解析成功,但語意被拒絕這是解析失敗:它是一次驗證決定
429 Too Many Requests撞到了流量限制這一筆請求本身無效

401 與 403 的差別

差別在於做出決定的當下,身分是否已經建立。

401403
決定當下的身分不存在,或不被接受已建立,或被視為無關
伺服器實際在說「我不知道你是誰」「我知道的夠多了,答案還是不行」
WWW-Authenticate 標頭規格要求要有不預期出現

401 成立的是「這條路徑在一道認證關卡後面」,而這件事同時代表路徑存在、伺服器知道怎麼路由它。403 成立的是「缺的不是身分」——同一個請求換上更好的憑證,仍然會撞到同一條規則。

應用程式很常把這兩者用混,所以一個該回 401 卻回 403 的應用程式,這件事本身是對它自身一致性的觀察,而不是關於它存取模型的事實。

404 與 403 的差別

兩者都在說「這個你拿不到」,差別在於伺服器願意透露多少。

  • 403 確認了:就路由層而言目標是存在的,而且有一條規則拒絕了它。
  • 404 對存在與否什麼都沒說。伺服器會對從未被路由的路徑回 404,也會對存在但被設定藏起來、不給未授權客戶端看的路徑回 404,而後者是披著 404 外皮的 403 語意。

後果是不對稱的:403 是存在的正面證據,而 404 除了「沒有可供回應的內容」之外什麼都證明不了。把回應的大小、標頭與耗時拿去跟一條已知未被路由的路徑相比,才是分開這兩種情形的東西;光看狀態碼分不出來。

405 作為存在訊號

405 是這四個裡面歧義最少的。它只可能由「路徑比對成功、然後找不到對應這個方法的處理器」的路由器產生,所以它同時成立兩件事:路徑有被路由,而且那裡至少有另一個方法是被處理的。Allow 標頭若有出現,會直接把那些方法列出來。

5xx —— 伺服器壞了,而且它說出來了

5xx 表示請求已被認定為「客戶端該負的責任都盡到了」,然後伺服器這一側有東西失敗。類別邊界很重要:4xx 是一次決定,5xx 是一次故障。這個區別正是 5xx 之所以有價值的原因——它通常代表某個輸入抵達了一段當初沒被寫來處理它的程式碼。

狀態碼伺服器陳述了什麼它排除了什麼
500 Internal Server Error應用程式本身拋出了未被處理的狀況請求是被某條驗證規則擋掉的
501 Not Implemented伺服器根本不支援這個方法失敗只發生在這條路徑上
502 Bad Gateway代理層從上游收到無效的回答,或根本沒有回答失敗發生在回答你的那一層
503 Service Unavailable回答你的那一層活著,但宣告自己無法服務請求曾被處理
504 Gateway Timeout代理層等了上游,然後放棄上游拒絕了:它根本沒有回答

500 與 502 與 504 的差別

三者都代表「沒有得到有用的回答」,但它們指名的是三個不同的失敗者。

誰失敗了抵達了什麼
500處理這個請求的應用程式應用程式執行了,並拋出了東西
502上游某一跳,回答得很糟代理層拿到一個它用不了的回覆
504上游某一跳,沒有回答代理層在時限內沒拿到任何回覆

三者之中只有 500 成立「應用程式執行過你的輸入」。502504 成立的是:某個東西前面站著一層代理,而後面那個東西的狀態與你正在對話的那一層並不相同。兩者之間,502 代表有回答送到而被判定不可用,504 代表等待時間用完了——後者由逾時界定,所以 Network 記錄 Time 欄的耗時本身就是證據的一部分。

500 的回應內容帶著什麼

伺服器端未被處理的例外,經常會把例外本身序列化進回應內容:堆疊追蹤、檔案路徑、樣板名稱,或資料庫驅動的錯誤文字。那段內容陳述的是應用程式壞掉當下正在做什麼,而資料庫錯誤訊息更進一步陳述了輸入抵達了一次查詢——辨識它的判準寫在 SQL injection

回應內容為空或只有制式文字的 500,成立的是故障本身而沒有指名故障內容。細節的缺席是錯誤處理器的性質,不是故障的性質。

這個實驗室自己產生的狀態碼

Network 記錄裡有些回應根本不是題目應用程式送的。它們產生在實驗室自己的 dispatch 邊界上,把它們讀成應用程式的回答,會讓你去找一個並不存在的行為。

狀態碼回應內容代表什麼
503{"error": "runtime not ready"}頁面的 dispatch 層當下還沒有 runtime。這個請求從未抵達題目應用程式
503{"error": "<host 自己的錯誤訊息>"}runtime host 沒有 worker 可以接手這個請求。與另外三列不同,error 的值不是固定字串,而是 host 當下拋出的訊息。這份回應是被刻意合成出來的,好讓這次嘗試仍以一列的形式留在記錄裡,而不是整筆消失
508{"error": "challenge runtime execution exceeded the time limit"}題目應用程式收到了請求,但在單一請求十秒的上限內沒有回答
502{"error": "the challenge runtime could not be reached"}在 Code 面板由 requests 收到:runtime 連不上。它以回應物件的形式抵達,不是拋出例外

上面這些之外還有另一個訊號:只有 PHP 執行環境會在展開後的 Network 明細上方掛一塊紅框,開頭寫著 Runtime diagnostic:。它寫的是這個執行環境在處理這份回應時丟掉了什麼——例如附在不能帶主體的狀態碼上的輸出、或是被移除的保留標頭——所以它不代表直譯器出錯。本 repo 現存題目的後端全是 Python,因此目前沒有任何一題看得到它。

由此有兩個後果。第一,記錄裡的一列紅字不會自動成為關於題目應用程式的證據。第二,在 Code 面板看到的 502 與題目應用程式送出的 502 意思不同,而分開兩者的是回應內容的文字。