導入 — 閱讀瀏覽器與伺服器的 HTTP 對話
確認 HTTP 在輸入 URL 之後所發生的事(DNS → TCP → TLS → HTTP)中的位置,掌握請求與回應原始文字的骨架,以及無狀態(stateless)的意義。
打開了 Network 分頁,卻不知道該看哪裡
頁面顯示不出來、API 不照預期運作 — 向前輩求助,得到的回答是「看一下開發者工具的 Network 分頁」。打開一看,一排排的行與數字並列著,不知道該從哪裡讀起。剛開始開發的時候,每個人都會經歷這個場面。
其實,那個畫面上映出的東西,本質非常樸素。瀏覽器與伺服器只是在一來一往地交換用文字寫成的信件而已。瀏覽器送出「請給我這個」的信(請求),伺服器回覆「好的,結果是這樣」的信(回應)。只要知道這往返書信的書寫格式,Network 分頁也好、curl 的輸出也好,都能用同一副骨架讀懂。
在本課程中,不管看什麼輸出,都確認相同的 3 點。
在 HTTP 開始之前 — 輸入 URL 後發生的事
在瀏覽器輸入 https://example.com/ 並按下 Enter 之後,在 HTTP 的信件被送出之前,會層層疊上幾項準備。
example.com 這個名稱查出伺服器的 IP 位址。不知道收件地址就寄不出信http:// 則省略這一段,信件以明文流動重要的是各層的分工。DNS 是地址組,TCP 是運輸組,TLS 是信封組,而 HTTP 就是信件的本文。即使變成 HTTPS,信件的書寫格式(本課程所學的一切)也不會改變 — 差別只在要不要裝進信封。
請求與回應只是方向相反,骨架同樣是「第 1 行 + 標頭 + 空行 + 內文」。只有第 1 行會依角色改變形態 — 提出請求的一方是請求行,回答的一方是狀態行。
試著閱讀原始文字
把實際的一來一往,不加工地直接看一看。首先是瀏覽器送出的請求。
GET /docs/index.html HTTP/1.1
Host: example.com
Accept: text/html
第 1 行是請求行。以空白分隔並列 GET(想做什麼 = 方法)、/docs/index.html(對哪一個對象 = 路徑)、HTTP/1.1(用哪個版本交談 = 版本)這 3 項。從第 2 行起是 名稱: 值 形式的標頭,而空行是「抬頭到此為止」的信號。GET 的請求只是提出請託,所以空行之後的內文是空的。
來自伺服器的回應,也以相同的骨架回來。
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1256
<!DOCTYPE html>
<html>...
第 1 行是狀態行。200 是給機器的 3 位數結果代碼,OK 是給人看的補充短語。接下來的標頭附上「是 HTML・共 1256 位元組」的補充,空行之後,被請求的東西(HTML 本文)就作為內文送達。
這裡是整個課程的地基:請求與回應都是「第 1 行 + 標頭 + 空行 + 內文」。讀第 1 行就知道「請求了什麼」「結果如何」,讀標頭就知道「條件與補充」。
練習 1-1 — 從輸入 URL 到 HTTP 的路程
確認 HTTP 開始之前發生了什麼,以及請求第 1 行的讀法。
Q1. 在瀏覽器輸入 https://example.com/ 之後,到 HTTP 請求被送出為止,發生的事情順序正確的是哪一個?
要寄信,首先需要地址(用 DNS 查出 IP 位址),接著是道路(TCP 連線),如果是 HTTPS 還需要封上信封(TLS),HTTP 的對話就在這之上開始。HTTP 請求無法最先送出(因為還不知道要送到哪裡),而 TLS 建立在 TCP 之上,所以不會排在 TCP 之前。本課程要閱讀的,就是這疊層中最上面的那一層。
Q2. 請求的第 1 行 GET /docs/index.html HTTP/1.1 的正確讀法是哪一個?
請求行是「動詞 + 受詞 + 使用語言的版本」。GET 是想做什麼(想取得),/docs/index.html 是對哪一個對象,HTTP/1.1 是用哪個版本的 HTTP 交談。狀態碼是出現在回應側第 1 行的東西,不包含在請求行裡。只要能讀這 3 個一組,「請求了什麼」就一定看得懂。
Q3. 在 HTTP 的請求與回應中,把標頭的結尾與內文的開頭分隔開來的是哪一個?
把標頭一行一行寫完之後,放上 1 個空行,就是「從這裡開始是內文」的信號。以書信來比喻,就和抬頭之後空一行的作用相同。並不存在 END 之類的結尾記號或特殊的二進位 — HTTP/1.1 的訊息從頭到尾都能靠文字的約定讀完。
無狀態 — 伺服器不記得「剛才的後續」
這套往返書信有一個最讓初學者驚訝的性質:伺服器原則上不記得前一個請求。就算第 1 封信看了商品頁、第 2 封信把商品放進購物車,對伺服器來說第 2 封仍是「來自陌生人的新信件」。這稱為無狀態(stateless,不持有狀態)。
看起來不方便,但這是設計上的抉擇。正因為每個請求各自完結,伺服器才能一封一封獨立處理,並且能分派給任意多台機器。世界級規模的服務得以成立,靠的就是這個取捨。
那麼,登入狀態這種「連續劇」要怎麼實現?答案是在每一封信上附上名牌 — 伺服器發行的識別標記(Cookie),由瀏覽器在之後的每個請求上寫上去。機制會在第 4 章閱讀,這裡請先帶著「在無狀態的往返書信之上,用附註創造狀態」這個構圖前進。
練習 1-2 — 回應的讀法與無狀態
確認回覆這一側的第 1 行,以及 HTTP 的重要性質「無狀態(stateless)」。
Q4. 回應的第 1 行 HTTP/1.1 200 OK 中的 200 與 OK,正確的說明是哪一個?
狀態行是「版本 + 3 位數狀態碼 + 給人看的短語」。程式用來判定的是 200 這個數字,OK 只不過是給讀者的附註。結果的判定就靠這 3 位數 — 這正是 3 點組合中第 2 點「結果如何」的讀法,第 3 章會按位數深入探討。
Q5. HTTP 常被說是「無狀態(stateless)」。正確的說明是哪一個?
無狀態(state-less)就是「不持有狀態」— 每個請求各自完結,伺服器不會自動記得「剛才的後續」。正因如此,每個請求都必須每次附上必要的資訊;要實現登入狀態這種「連續劇」,就要使用第 4 章介紹的 Cookie 這種附註機制。這不是加密或同時連線數的話題。
本章帶回家的重點
- HTTP 是在 DNS → TCP → TLS 的準備之上交換的文字往返書信
- 請求與回應的骨架相同:「第 1 行 + 標頭 + 空行 + 內文」。第 1 行是請求行(方法・路徑・版本)或狀態行(版本・3 位數代碼・短語)
- HTTP 是無狀態的 — 每個請求都是獨立的一封信。「連續劇」要用 Cookie 等附註來製作
- 讀法永遠是 3 點組合 — 請求了什麼・結果如何・條件與補充是什麼
下一章將深入 3 點組合的第 1 點「請求了什麼」。GET 與 POST 有什麼不同、哪些請託「可以再送一次」— 一起來閱讀方法的約定。