HTTP anatomy — method, status code đọc cho đúng
Mổ cấu trúc thật của HTTP request/response (request line, headers, body), tính chất safe/idempotent của method ảnh hưởng tới retry, và ý nghĩa thật của từng status code class — bao gồm các cặp hay nhầm: 401 vs 403, 502 vs 504, 301 vs 302 vs 307.
TL;DR: HTTP là giao thức text-based, stateless — mỗi request độc lập, không có bộ nhớ session bên trong giao thức. Một request gồm request line (method + path + version), headers (metadata), và tuỳ chọn body; response gồm status line (version + status code + reason), headers, và body. Method có hai tính chất quan trọng: safe (GET/HEAD không thay đổi server state) và idempotent (GET/PUT/DELETE gọi nhiều lần kết quả như một lần — POST thì không). Status code chia 5 class; cặp hay nhầm nhất là 401 (chưa xác thực) vs 403 (đã xác thực nhưng thiếu quyền), và 502 (upstream trả rác) vs 504 (upstream không trả kịp).
Bạn đã biết từ Module 1 TCP rằng HTTP chạy sau khi TCP handshake xong. Giờ ta phóng to đúng phần HTTP — không phải "gửi request nhận response" theo kiểu hộp đen, mà nhìn từng byte thật sự bay qua lại.
1. Analogy — Thư tín chính thức với tiêu đề và phong bì
HTTP request giống một bức thư nghiệp vụ gửi qua bưu điện:
- Dòng đầu tiên (request line) là tiêu đề bức thư: "Tôi yêu cầu GET tài liệu
/users/42, theo chuẩn HTTP/1.1". - Phần tiêu đề (headers) là thông tin phong bì và metadata: "Gửi từ đâu (Host), tôi chấp nhận format gì (Accept), token xác thực của tôi là gì (Authorization)...".
- Thân thư (body) là nội dung đính kèm — chỉ có khi gửi dữ liệu (POST/PUT).
Phía server trả lời cũng đúng format đó:
- Dòng đầu (status line): "HTTP/1.1 200 OK — mọi thứ ổn, đây là kết quả."
- Headers: "Tôi trả JSON (Content-Type), dữ liệu này cache được bao lâu (Cache-Control)...".
- Body: dữ liệu thật sự (JSON, HTML...).
| Thư nghiệp vụ | HTTP |
|---|---|
| Tiêu đề thư (Action + tài liệu) | Request line: GET /users/42 HTTP/1.1 |
| Metadata phong bì | Headers |
| Nội dung đính kèm | Body (chỉ với POST/PUT/PATCH) |
| Phúc đáp: kết quả + mã phân loại | Status line + response headers |
HTTP là text-based: mọi thứ (request line, headers) đều là ký tự ASCII đọc được — bạn có thể curl -v xem nguyên văn mà không cần tool đặc biệt. Body mới có thể binary (ảnh, file).
2. Cấu trúc thật của request và response
Chạy lệnh này để xem raw HTTP:
curl -v https://httpbin.org/get 2>&1 | head -40
Bạn sẽ thấy (phần > là request, < là response):
> GET /get HTTP/2
> Host: httpbin.org
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/2 200
< content-type: application/json
< content-length: 312
<
{ ... json body ... }
2.1 Request structure
Một HTTP/1.1 request raw (trước khi HTTP/2 nén header):
GET /users/42?fields=name,email HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
User-Agent: MyApp/1.0
- Request line — dòng đầu tiên:
<METHOD> <path+query> HTTP/<version>. Kết thúc bằng CRLF (\r\n). - Headers — mỗi dòng là
Name: Value, không phân biệt hoa thường tên header. Kết thúc header section bằng dòng trống (CRLF kép). - Body — xuất hiện sau dòng trống. GET/HEAD/DELETE không có body; POST/PUT/PATCH thường có.
2.2 Response structure
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 85
Cache-Control: private, max-age=0
{"id":42,"name":"Nguyen Van A","email":"[email protected]"}
- Status line —
HTTP/<version> <status-code> <reason-phrase>. Reason phrase (OK,Not Found...) chỉ là text dành cho người đọc — code mới là machine-readable. - Headers — metadata về response.
- Body — dữ liệu trả về; có thể vắng mặt (status 204, 304...).
Mỗi HTTP request hoàn toàn độc lập. Server không nhớ request trước của bạn là gì — không có "session" nào trong bản thân giao thức HTTP. Stateless là thiết kế cố ý: giúp scale ngang dễ dàng (bất kỳ server nào cũng xử lý được request tiếp theo). Session mà bạn biết (login rồi nhớ) được implement bên trên HTTP — qua cookie hoặc token, không phải tính năng HTTP gốc. Bài 03 giải thích cơ chế này.
3. HTTP method — safe và idempotent, tại sao quan trọng
HTTP định nghĩa 9 method trong RFC 9110, nhưng backend dev chủ yếu dùng 5: GET, POST, PUT, PATCH, DELETE. Hiểu đúng method không chỉ là convention API — nó ảnh hưởng trực tiếp đến retry logic và caching.
3.1 Safe — method không đổi server state
Safe (RFC 9110 §9.2.1): method được gọi là safe nếu nó không thay đổi trạng thái server. Gọi safe method bao nhiêu lần cũng không tạo side effect.
Method safe: GET, HEAD, OPTIONS, TRACE.
Vì sao quan trọng? Vì browser, proxy, CDN, và HTTP client tự động tái dùng và cache safe method. Nếu bạn thiết kế một endpoint GET mà nó xoá dữ liệu khi được gọi, proxy hay browser sẽ thoải mái gọi lại — và bạn mất dữ liệu.
3.2 Idempotent — gọi nhiều lần kết quả như một lần
Idempotent (RFC 9110 §9.2.2): gọi method N lần cho cùng request, trạng thái server sau lần 1 và sau lần N là như nhau.
| Method | Safe | Idempotent | Giải thích |
|---|---|---|---|
| GET | Có | Có | Chỉ đọc, lặp lại vô hại |
| HEAD | Có | Có | Như GET nhưng không có body |
| DELETE | Không | Có | Xoá lần 1 thành công, lần 2 trả 404 nhưng trạng thái server không đổi thêm |
| PUT | Không | Có | Ghi đè toàn bộ resource — lần 2 ghi đè lại giá trị như cũ |
| POST | Không | Không | Mỗi lần tạo một resource mới — gọi 2 lần = 2 bản ghi |
| PATCH | Không | Thường không | Tuỳ implementation; patch {"count": 5} idempotent, patch {"inc": 1} thì không |
Khi mạng bị ngắt giữa chừng sau khi server đã xử lý POST nhưng chưa trả response, client không biết thành công hay thất bại. Nếu retry mù quáng, bạn có thể tạo hai đơn hàng, hai giao dịch, hoặc gửi hai email. Với GET/PUT/DELETE — retry an toàn vì idempotent. Đây là lý do các payment gateway thường yêu cầu idempotency-key header để làm POST an toàn hơn.
3.3 Sơ đồ tính chất method
flowchart LR
subgraph Safe["Safe (khong doi state)"]
GET
HEAD
end
subgraph Idempotent["Idempotent (lap lai an toan)"]
GET2["GET"]
HEAD2["HEAD"]
PUT
DELETE
end
subgraph Neither["Khong safe, khong idempotent"]
POST
PATCH2["PATCH (thuong)"]
end4. Status code — 5 class và ý nghĩa thật
Status code là số 3 chữ số, nhóm theo chữ số đầu:
4.1 Các cặp hay nhầm
401 vs 403:
- 401 Unauthorized — client chưa xác thực (chưa gửi credentials, hoặc credentials không hợp lệ). Tên "Unauthorized" trong spec là lỗi lịch sử — thực ra nghĩa là "Unauthenticated". Server thường kèm header
WWW-Authenticatechỉ cách xác thực. - 403 Forbidden — client đã xác thực nhưng không có quyền truy cập resource. Gọi lại với credentials khác cũng không giúp được — vấn đề là quyền, không phải danh tính.
Ví dụ: user thường gọi API /admin/users → trả 403 (đã đăng nhập, nhưng không phải admin). User chưa đăng nhập gọi API có bảo vệ → trả 401.
502 vs 504:
- 502 Bad Gateway — server đóng vai gateway/proxy (Nginx, load balancer) nhận được phản hồi không hợp lệ từ upstream. Upstream trả lời nhưng nội dung sai định dạng — upstream đang chạy nhưng có vấn đề.
- 504 Gateway Timeout — gateway/proxy không nhận được phản hồi kịp thời từ upstream. Upstream không trả lời trong thời gian timeout — upstream có thể đang quá tải hoặc đã chết.
Cách nhớ: 502 = upstream trả nhưng trả rác; 504 = upstream không trả kịp.
301 vs 302 vs 307 vs 308:
| Code | Nghĩa | Method sau redirect |
|---|---|---|
| 301 Moved Permanently | URL thay đổi vĩnh viễn | Browser thường đổi POST → GET (legacy) |
| 302 Found | Redirect tạm thời | Browser thường đổi POST → GET (legacy) |
| 307 Temporary Redirect | Redirect tạm thời, giữ nguyên method | POST vẫn là POST |
| 308 Permanent Redirect | Redirect vĩnh viễn, giữ nguyên method | POST vẫn là POST |
Khi redirect API và muốn giữ method nguyên vẹn (POST không tự thành GET), dùng 307 hoặc 308.
5. Pitfall — lỗi thường gặp
❌ Nhầm 1: Trả 200 kèm body {"error": "Not found"}.
✅ Đây là anti-pattern phổ biến ở một số API cũ. Monitoring tool, retry logic, và load balancer đều nhìn vào status code — không phải body. Trả 200 với body lỗi khiến chúng không phát hiện được lỗi. Dùng đúng status code: 404 nếu không tìm thấy, 400 nếu input sai, 500 nếu lỗi server.
❌ Nhầm 2: Dùng GET để thực hiện thao tác thay đổi dữ liệu (vd GET /deleteUser?id=5).
✅ Browser, proxy, CDN thoải mái cache và tái gọi GET. Thao tác delete qua GET có thể bị prefetch (browser prefetch link), bị gọi lại khi retry, hoặc bị log vào access log với URL đầy đủ. Dùng DELETE/POST cho thao tác có side effect.
❌ Nhầm 3: Retry POST khi mạng timeout mà không có idempotency key.
✅ Nếu server đã xử lý POST trước khi response bị mất, retry tạo ra bản ghi trùng. Giải pháp: thêm Idempotency-Key: <uuid> header (nhiều payment API và service hỗ trợ) — server dùng key này để deduplicate.
6. Liên hệ các bài khác
- Bài 02 — HTTP headers quan trọng: headers như
Authorization,Content-Type,Cache-Controlxuất hiện trong request/response structure bài này — bài 02 đào sâu từng nhóm. - Bài 03 — Cookie, session & CORS: 401 vs 403 liên quan trực tiếp tới cookie/session; CORS preflight dùng method OPTIONS.
- Bài 07 — Caching, CDN, proxy: safe method (GET/HEAD) là điều kiện để CDN cache response — bài 07 giải thích cơ chế đó.
- Bài 06 — REST, gRPC, WebSocket, SSE: cách thiết kế API REST dùng đúng method và status code là nền tảng của bài này.
- Khoá Foundations — điều gì xảy ra khi gõ URL: HTTP là chặng thứ 4 trong 5 chặng — bài này phóng to chặng đó.
7. 📚 Deep Dive — tài liệu gốc
Đọc khi muốn hiểu tận gốc:
- RFC 9110 — HTTP Semantics — định nghĩa chính thức method, status code, header semantics cho tất cả phiên bản HTTP (2022). Mục §9 về method semantics, §15 về status code.
- RFC 9112 — HTTP/1.1 — cú pháp message format (request line, header fields, chunked encoding).
Ghi chú: RFC 9110 là bản hợp nhất thay thế RFC 7231. Khi muốn tra cứu "HTTP đúng ra phải làm gì", đây là nguồn chuẩn. Không cần đọc hết — mục §9.2 (safe/idempotent) và §15 (status codes) là phần khớp trực tiếp với bài này.
8. Tóm tắt
- HTTP request gồm request line (method + path + version), headers, và tuỳ chọn body; response gồm status line, headers, body.
- HTTP là text-based (đọc được bằng
curl -v) và stateless (mỗi request độc lập — session là lớp bên trên). - Safe method (GET/HEAD): không đổi server state — tự động an toàn để cache và retry.
- Idempotent method (GET/HEAD/PUT/DELETE): gọi nhiều lần kết quả như một lần — an toàn để retry khi network fail.
- POST không idempotent — retry mà không có idempotency key có thể tạo bản ghi trùng.
- Status class: 1xx informational, 2xx success, 3xx redirect, 4xx client error, 5xx server error.
- 401 = chưa xác thực; 403 = đã xác thực nhưng thiếu quyền.
- 502 = upstream trả rác (Bad Gateway); 504 = upstream không trả kịp (Gateway Timeout).
- 307/308 giữ nguyên method qua redirect; 301/302 legacy có thể đổi POST thành GET.
9. Tự kiểm tra
Q1Tại sao HTTP được gọi là stateless? Điều này có lợi gì khi scale hệ thống?▸
Q2Phân biệt safe và idempotent. Cho ví dụ một method vừa safe vừa idempotent, và một method idempotent nhưng không safe.▸
Q3Vì sao retry POST không có idempotency key nguy hiểm? Kịch bản lỗi cụ thể là gì?▸
Idempotency-Key: <uuid> duy nhất cho mỗi "ý định" — server lưu key đó, lần retry giống key thì trả lại kết quả cũ thay vì xử lý lại.Q4User đã đăng nhập nhưng gọi API /admin/delete-user và nhận 403. Nên debug ở đâu: phía xác thực hay phía phân quyền?▸
/admin/delete-user yêu cầu role gì. Nếu nhận 401 thì mới debug phía xác thực (token hết hạn, sai token, thiếu header Authorization).Q5Nginx trả 502. Điều này gợi ý gì về trạng thái của upstream service, và khác 504 thế nào?▸
Q6Bạn cần redirect từ URL cũ sang URL mới nhưng muốn giữ nguyên POST body và method. Dùng status code nào?▸
Q7Vì sao không nên dùng GET để thực hiện thao tác xoá hay tạo dữ liệu, dù API vẫn hoạt động đúng?▸
Bài tiếp theo: HTTP headers quan trọng — Content-Type, Cache-Control...
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên