TCP, HTTP & Web cho Backend/HTTP anatomy — method, status code đọc cho đúng
12/29
Bài 12 / 29~22 phútHTTP & WebMiễn phí lượt xem

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èmBody (chỉ với POST/PUT/PATCH)
Phúc đáp: kết quả + mã phân loạiStatus line + response headers
💡 Cách nhớ

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 lineHTTP/<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...).
📌 HTTP là stateless — không có bộ nhớ giữa các request

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 logiccaching.

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.

MethodSafeIdempotentGiải thích
GETChỉ đọc, lặp lại vô hại
HEADNhư GET nhưng không có body
DELETEKhôngXoá lần 1 thành công, lần 2 trả 404 nhưng trạng thái server không đổi thêm
PUTKhôngGhi đè toàn bộ resource — lần 2 ghi đè lại giá trị như cũ
POSTKhôngKhôngMỗi lần tạo một resource mới — gọi 2 lần = 2 bản ghi
PATCHKhôngThường khôngTuỳ implementation; patch {"count": 5} idempotent, patch {"inc": 1} thì không
⚠️ POST không idempotent — retry nguy hiểm

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)"]
    end

4. Status code — 5 class và ý nghĩa thật

Status code là số 3 chữ số, nhóm theo chữ số đầu:

1xxInformational — server đang xử lý, chưa xong100 Continue · 101 Switching Protocols
2xxSuccess — request được nhận và xử lý thành công200 OK · 201 Created · 204 No Content
3xxRedirection — client cần action thêm để hoàn thành301 Moved · 302 Found · 304 Not Modified
4xxClient Error — request có lỗi từ phía client400 Bad Request · 401 Unauth · 403 Forbidden · 404 Not Found
5xxServer Error — server gặp lỗi khi xử lý request hợp lệ500 Internal Error · 502 Bad Gateway · 503 Unavailable · 504 Timeout

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-Authenticate chỉ 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:

CodeNghĩaMethod sau redirect
301 Moved PermanentlyURL thay đổi vĩnh viễnBrowser thường đổi POST → GET (legacy)
302 FoundRedirect tạm thờiBrowser thường đổi POST → GET (legacy)
307 Temporary RedirectRedirect tạm thời, giữ nguyên methodPOST vẫn là POST
308 Permanent RedirectRedirect vĩnh viễn, giữ nguyên methodPOST 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

7. 📚 Deep Dive — tài liệu gốc

📚 RFC chính thứ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

Tự kiểm tra
Q1
Tại sao HTTP được gọi là stateless? Điều này có lợi gì khi scale hệ thống?
HTTP stateless nghĩa là mỗi request hoàn toàn độc lập — server không giữ bộ nhớ về request trước. Thiết kế này có lợi cho scale ngang: bất kỳ server nào trong pool cũng xử lý được request tiếp theo mà không cần biết lịch sử của client. Nếu HTTP có state, mọi request từ cùng client buộc phải đi về đúng một server (sticky session), làm phức tạp load balancer và tăng rủi ro single point of failure. Session mà ta thấy trong app thực tế là lớp được build bên trên HTTP, qua cookie hay token.
Q2
Phâ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.
Safe: không thay đổi server state (chỉ đọc). Idempotent: gọi N lần kết quả như gọi 1 lần. Mọi safe method đều idempotent, nhưng không phải ngược lại. GET vừa safe vừa idempotent — chỉ đọc dữ liệu, lặp lại vô hại. DELETE idempotent nhưng không safe — xoá resource lần đầu thành công (200/204), lần 2 trả 404 nhưng trạng thái server không thay đổi thêm nữa; tuy nhiên nó đã thay đổi state (xoá resource) nên không phải safe.
Q3
Vì sao retry POST không có idempotency key nguy hiểm? Kịch bản lỗi cụ thể là gì?
Giả sử client POST tạo đơn hàng. Server xử lý thành công — tạo đơn và trừ kho — nhưng mạng rớt trước khi response về tới client. Client không biết thành công hay thất bại nên retry. Nếu server không có cơ chế dedup, nó tạo thêm một đơn hàng nữa, trừ kho thêm lần nữa. Kết quả: khách mua một nhưng bị tính hai. Giải pháp: client gửi kèm 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.
Q4
User đã đă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?
403 Forbidden cho biết server đã xác thực được danh tính (biết bạn là ai) nhưng từ chối vì không đủ quyền. Vậy cần debug phía phân quyền (authorization) — kiểm tra role/permission của user trong DB hoặc trong JWT claims, và kiểm tra policy của endpoint /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).
Q5
Nginx 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?
502 Bad Gateway nghĩa là Nginx (gateway) đã nhận được phản hồi từ upstream nhưng phản hồi đó không hợp lệ hoặc không parse được — upstream đang chạy nhưng có vấn đề (crash sau khi kết nối, trả về malformed response, tắt kết nối giữa chừng). 504 Gateway Timeout nghĩa là Nginx không nhận được phản hồi trong thời gian timeout — upstream quá tải, bị treo, hoặc đã chết hoàn toàn. Khi debug: 502 kiểm tra log upstream xem nó crash hay trả gì sai; 504 kiểm tra CPU/memory upstream và tăng timeout nếu cần.
Q6
Bạ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?
Dùng 307 Temporary Redirect (nếu redirect tạm thời) hoặc 308 Permanent Redirect (nếu vĩnh viễn). Cả hai đều bắt buộc client giữ nguyên method và body khi follow redirect. 301 và 302 không có bảo đảm này — browser legacy thường tự đổi POST thành GET (theo hành vi thực tế, không phải spec gốc). Nếu bạn redirect API endpoint từ HTTP sang HTTPS với method POST, 308 là lựa chọn đúng.
Q7
Vì 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?
GET được kỳ vọng là safe — các tầng infrastructure (browser prefetch, CDN, proxy cache, crawler, monitoring tool) thoải mái gọi lại GET bất kỳ lúc nào mà không lo side effect. Nếu GET thực sự xoá dữ liệu: browser có thể prefetch link và xoá dữ liệu, Google bot crawler có thể gọi endpoint xoá, proxy có thể serve cached version của response mà bỏ qua lần gọi thực. Ngoài ra URL đầy đủ (bao gồm query parameter) bị log vào access log — data nhạy cảm bị lộ. Dùng DELETE/POST đúng ngữ nghĩa là contract với toàn bộ hệ sinh thái infrastructure.

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

Đặt 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

Bài tiếp theo

HTTP headers quan trọng — Content-Type, Cache-Control, Authorization