前言
HTTP(HyperText Transfer Protocol)是 Web 世界的基礎協定,幾乎所有網頁、API、行動應用程式都靠它交換資料。理解 HTTP,不只是為了寫 API,更是為了看懂前後端到底在講什麼。
本文從 HTTP 的基本概念 開始,依序看 請求與回應的結構、常見方法與狀態碼、版本演進,最後用 Python + FastAPI 看一個請求實際上是怎麼被處理的。結尾附上這個網站本身用到的幾個例子。
一、HTTP 是什麼
HTTP 是一個 應用層協定,HTTP/1.x 與 HTTP/2 跑在 TCP 上,HTTP/3 跑在 QUIC(UDP)上。它採用 請求-回應模式,而且是 無狀態(stateless) 的。
- 請求-回應:客戶端送出請求,伺服器回一個回應,一來一往。
- 無狀態:伺服器不記得上一次請求發生過什麼,每個請求都是獨立的。「登入狀態」這類東西是靠 Cookie、Session、Token 另外疊在 HTTP 之上做出來的。
二、HTTP 的運作流程
- 建立連線:HTTP/1.x 與 HTTP/2 先完成 TCP 握手(HTTPS 再加 TLS 握手),HTTP/3 用 QUIC 把兩者合在一起。
- 客戶端送出 HTTP 請求。
- 伺服器處理並回應。
- 關閉連線,或保持連線(Keep-Alive)給下一個請求用。
┌────────┐ HTTP Request ┌────────┐
│ Client │ ─────────────────────────▶ │ Server │
└────────┘ └────────┘
▲ │
│ HTTP Response │
└─────────────────────────────────────┘
三、HTTP 訊息結構
HTTP 訊息分成 請求(Request) 與 回應(Response),兩者長得很像:第一行描述這則訊息是什麼,接著是零到多個標頭,一個空行之後是可有可無的主體。
1. 請求(Request)
請求行(Request Line):GET /index.html HTTP/1.1
請求標頭(Headers):Host、User-Agent、Accept…
(空行)
請求主體(Body):POST / PUT / PATCH 時攜帶資料(JSON、表單、檔案)
2. 回應(Response)
狀態行(Status Line):HTTP/1.1 200 OK
回應標頭(Headers):Content-Type、Content-Length、Cache-Control…
(空行)
回應主體(Body):HTML、JSON、圖片等
實際長這樣:
Request
┌──────────────────────────────────────────────┐
│ Request Line: GET /path?query=1 HTTP/1.1 │
│ Headers: │
│ Host: example.com │
│ User-Agent: curl/8.0 │
│ Accept: application/json │
│ │
│ Body (optional): │
│ { "name": "Alice" } │
└──────────────────────────────────────────────┘
Response
┌──────────────────────────────────────────────┐
│ Status Line: HTTP/1.1 200 OK │
│ Headers: │
│ Content-Type: application/json │
│ Content-Length: 27 │
│ │
│ Body: │
│ { "message": "Hello World" } │
└──────────────────────────────────────────────┘
四、HTTP 方法與用途
- GET:取得資源。通常不帶 Body,而且應該是安全的(不改變伺服器狀態)。
- POST:提交資料、建立資源。Body 帶資料。
- PUT:以整份內容取代指定網址的資源;資源不存在時建立。
- PATCH:部分更新資源,只送要改的欄位。
- DELETE:刪除資源。
- HEAD:和 GET 一樣,但只要標頭不要 Body。常用來確認檔案存在或看快取標頭。
- OPTIONS:詢問伺服器支援哪些方法;瀏覽器的 CORS 預檢(preflight)用的就是它。
五、HTTP 狀態碼分類
- 1xx 資訊:100 Continue。
- 2xx 成功:200 OK、201 Created(建立成功)、204 No Content(成功但沒有東西要回)。
- 3xx 重新導向:301 永久、302 暫時、304 Not Modified(快取還有效,不用重傳)。
- 4xx 客戶端錯誤:400 Bad Request、401 Unauthorized(沒登入)、403 Forbidden(登入了但沒權限)、404 Not Found、429 Too Many Requests(被限流)。
- 5xx 伺服器錯誤:500 Internal Server Error、502 Bad Gateway、503 Service Unavailable。
六、HTTP 與資料格式
HTTP/1.x 的訊息本身是純文字,所以可以用 curl -v 或 telnet 直接看;HTTP/2 起訊息改成二進位分幀,但請求、回應、標頭這些語意完全沒變。
Body 裡可以放任何東西,由 Content-Type 標頭說明是什麼:
- HTML(網頁)
- JSON(API 最常用)
- 二進位檔案(圖片、影片、PDF)
- 表單(
application/x-www-form-urlencoded、multipart/form-data)
七、HTTP 版本演進
- HTTP/1.0:預設每個請求開一條新的 TCP 連線,用完就關。
- HTTP/1.1:Keep-Alive 持久連線,一條連線可以連續送多個請求。規格裡也定義了管線化(pipelining),但因為回應必須照順序回、容易被前面的請求卡住,瀏覽器實務上幾乎沒有啟用;這個問題到 HTTP/2 才真正解決。
- HTTP/2:二進位分幀、同一條連線上多工(multiplexing)、HPACK 標頭壓縮。
- HTTP/3:改跑在 QUIC(UDP)上,連線建立更快,而且一個封包遺失不會卡住其他串流。
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| 傳輸層 | TCP | TCP | QUIC(UDP) |
| 訊息格式 | 純文字 | 二進位分幀 | 二進位分幀 |
| 一條連線多個請求 | 依序(Keep-Alive) | 多工並行 | 多工並行,串流互不影響 |
| 標頭壓縮 | 無 | HPACK | QPACK |
| 封包遺失的影響 | 整條連線等待 | 整條連線等待(TCP 層) | 只影響該串流 |
八、HTTP 與 Python(FastAPI)
HTTP 是協定,Python 是實作邏輯的語言。中間要有東西把網路上的位元組變成 Python 函式的呼叫,這就是 ASGI 伺服器(Uvicorn)與框架(FastAPI)的分工:
- Uvicorn 負責接 HTTP 連線、解析請求,轉成 ASGI 事件。
- FastAPI 在 ASGI 之上做路由比對、參數驗證、依賴注入,呼叫你寫的函式,再把回傳值變成 HTTP 回應。
FastAPI 範例
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.get("/hello")
def hello():
return {"message": "Hello World"}
@app.post("/items", status_code=201)
def create_item(item: Item):
return {"name": item.name, "price": item.price}
存成 main.py,啟動:
uvicorn main:app --host 0.0.0.0 --port 8080 --reload
開 http://localhost:8080/docs 就有自動產生的 Swagger UI。
一個請求的旅程
Client (Browser / CLI)
│ HTTP Request
▼
┌───────────────────┐ ┌──────────────────────────────┐
│ Uvicorn │ ASGI │ FastAPI │
│ (ASGI Server) │ ──────▶ │ 路由比對、參數驗證、依賴注入 │
└───────────────────┘ └──────────────────────────────┘
│
│ 呼叫 path operation 函式
▼
Python 應用邏輯 / I/O
- 商業邏輯
- DB / 外部 API
- 序列化回傳模型
│
│ 產生 Response(JSON / HTML…)
▼
┌───────────────────┐
│ Uvicorn │ 封裝成 HTTP Response
└───────────────────┘
│ HTTP Response
▼
Client 接收並呈現
九、為什麼不自己寫 socket
- Uvicorn 幫你處理底層:bind、listen、accept、recv,以及 HTTP 解析。
- FastAPI 幫你處理框架層:路由、驗證(Pydantic)、序列化、錯誤回應。
- 非同步:
async def的路由可以在等 I/O 時服務其他請求。 - 文件自動產生:OpenAPI 規格與 Swagger UI 不用手寫。
自己寫 socket 可以學到最多,但正式專案裡每一個「自己處理」的細節都是日後的坑。
十、這個網站上的 HTTP
以上是教材,下面是我自己這個站裡實際發生的事:
- 後台對
/api/*的每個請求都帶Authorization: Bearer <JWT>標頭。沒帶或過期,API Gateway 直接回 401,連 Lambda 都不會被叫起來;帶了但欄位不合法,Lambda 回 400 並在 JSON 裡說是哪個欄位。 - 照片成品走
/media/photos/<id>/<寬度>.webp,回應標頭是Cache-Control: public, max-age=31536000, immutable。檔名含 id,內容不會變,所以可以叫瀏覽器與 CloudFront 快取一年。 - 文章 HTML 的標頭是
Cache-Control: public, max-age=300, must-revalidate,因為網址固定、內容會變,所以只快取五分鐘,部署時再用 invalidation 清掉。 www.leo-bro.com回 301 到leo-bro.com,是 CloudFront Function 看Host標頭決定的。- 瀏覽器上傳照片到 S3 時,S3 回應沒有 CORS 標頭的話,檔案其實已經進去了,但瀏覽器會把整個 fetch 當成失敗。這是我踩過的坑:HTTP 層成功,瀏覽器層失敗,用 curl 永遠測不出來。
總結
- HTTP 是 Web 的語言,請求與回應是它的對話格式。
- 框架(如 FastAPI)讓你不用處理底層,專注在邏輯。
- 看得懂請求行、標頭、狀態碼,就看得懂瀏覽器開發者工具裡的每一行,也看得懂自己 API 的每一個錯誤。