HTTP headers quan trọng — Content-Type, Cache-Control, Authorization
Đào sâu các nhóm header backend cần nắm: content negotiation (Content-Type, Accept, Content-Encoding), caching sơ bộ (Cache-Control, ETag, 304), xác thực (Authorization, WWW-Authenticate), và headers bảo mật (HSTS, X-Content-Type-Options). Bài nền để hiểu caching/CDN và cookie/CORS.
TL;DR: HTTP header là metadata đi kèm mọi request/response — backend dev đụng vào chúng mỗi ngày nhưng ít khi hiểu rõ tại sao. Bài này đào sâu bốn nhóm quan trọng nhất: content negotiation (client và server thương lượng format, ngôn ngữ, encoding nào), Content-Encoding (nén body để giảm băng thông, đánh đổi CPU), caching (Cache-Control, ETag, 304 Not Modified — giới thiệu để nối vào bài 07), và Authorization (Bearer token, Basic auth). Pitfall lớn nhất: thiếu hoặc sai Content-Type khiến server hiểu nhầm body, và no-store đặt sai chỗ giết cache toàn bộ.
Bạn đã thấy header xuất hiện trong bài 01 — HTTP anatomy: mỗi request/response đều có section headers. Giờ ta phóng to từng nhóm và hiểu cơ chế vận hành bên dưới.
1. Analogy — Phiếu đặt hàng và phiếu giao hàng
Header giống phiếu đặt hàng đính kèm gói hàng:
- Content-Type là nhãn trên gói: "nội dung bên trong là JSON" hay "là HTML" hay "là ảnh PNG".
- Accept là yêu cầu của người đặt: "tôi chỉ nhận được JSON, đừng gửi XML".
- Content-Encoding là ghi chú đóng gói: "tôi nén gzip, bạn phải giải nén trước khi dùng".
- Cache-Control là hạn sử dụng: "dùng được 60 giây, sau đó phải lấy hàng mới".
- Authorization là thẻ thành viên kèm theo: "đây là token chứng minh tôi có quyền nhận hàng này".
| Đặt hàng/giao hàng | HTTP header |
|---|---|
| Nhãn "nội dung là JSON" | Content-Type: application/json |
| Yêu cầu "chỉ gửi JSON" | Accept: application/json |
| Ghi chú "đã nén gzip" | Content-Encoding: gzip |
| Hạn dùng 60 giây | Cache-Control: max-age=60 |
| Thẻ thành viên | Authorization: Bearer <token> |
Headers đi theo hai hướng: request header (client gửi lên, khai báo nhu cầu và danh tính), response header (server gửi xuống, mô tả dữ liệu trả về và cách xử lý). Một số tên header dùng cho cả hai, một số chỉ một chiều.
2. Content-Type và content negotiation
2.1 Content-Type — nhãn của body
Content-Type khai báo loại dữ liệu trong body của request hoặc response. Format là MIME type: type/subtype[; parameter].
Content-Type: application/json; charset=utf-8
Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Content-Type: application/x-www-form-urlencoded
Server đọc Content-Type của request để biết cách parse body. Nếu bạn gửi JSON nhưng thiếu hoặc sai Content-Type, Spring Boot / Express / Django có thể từ chối hoặc parse nhầm sang form data.
2.2 Content negotiation — client và server thương lượng
Content negotiation là cơ chế cho phép cùng một URL phục vụ nhiều format khác nhau tuỳ client. Client khai báo nhu cầu qua ba header Accept:
Accept: application/json, text/html;q=0.9, */*;q=0.8
Accept-Encoding: gzip, br, deflate
Accept-Language: vi-VN, vi;q=0.9, en;q=0.8
Accept: format body client chấp nhận (JSON, HTML, XML...). Giá trịq=là quality factor — ưu tiên từ cao xuống thấp.*/*= chấp nhận bất kỳ.Accept-Encoding: thuật toán nén client hỗ trợ.Accept-Language: ngôn ngữ ưu tiên.
Server phản hồi bằng Content-Type tương ứng và header Vary để proxy biết cache theo chiều nào:
Content-Type: application/json
Vary: Accept, Accept-Encoding
Proxy và CDN cache response theo URL. Nếu cùng URL có thể trả JSON hoặc HTML tuỳ Accept, cache phải lưu riêng cho từng giá trị Accept. Header Vary: Accept báo cho cache biết điều đó — phân biệt cache entry theo header được list trong Vary. Thiếu Vary khi dùng content negotiation → một user nhận HTML, cache lưu lại, user khác nhận JSON thì bị trả lầm HTML.
3. Content-Encoding — nén body để giảm băng thông
Content-Encoding khai báo body đã được nén bằng thuật toán nào. Khác với Content-Type (loại dữ liệu logic), Content-Encoding mô tả biến đổi vật lý trên byte stream.
Content-Encoding: gzip
Content-Encoding: br
Quy trình nén:
sequenceDiagram
participant C as Client
participant S as Server
C->>S: Request + Accept-Encoding: gzip, br
Note over S: Chon br (uu tien cao nhat ho tro)
S-->>C: Content-Encoding: br + body nen br
Note over C: Giai nen br -> nhan duoc body gocHai thuật toán phổ biến:
| Thuật toán | Tỉ lệ nén | CPU nén | CPU giải nén | Khi nào dùng |
|---|---|---|---|---|
| gzip | Tốt (~70% text) | Trung bình | Thấp | Mặc định, hỗ trợ rộng |
| Brotli (br) | Tốt hơn gzip ~20% | Cao hơn | Tương đương | Modern browser, static file |
Đánh đổi của nén: tốn CPU server để nén, đổi lấy bandwidth thấp hơn. Với file tĩnh (JS, CSS), Brotli pre-compress một lần và serve mãi. Với response động (JSON API), gzip thường là lựa chọn tốt vì nhanh hơn.
Content-Encoding mô tả nén của payload (tồn tại trong storage/cache). Transfer-Encoding mô tả cách truyền qua mạng (ví dụ chunked transfer — phổ biến với streaming). Trong HTTP/2 chunked encoding bị deprecated, nhưng Content-Encoding vẫn dùng cho nén payload.
4. Cache-Control và ETag — kiểm soát freshness
4.1 Cache-Control
Cache-Control là header quan trọng nhất để kiểm soát caching. Xuất hiện cả trong response (server nói cache được bao lâu) và request (client bảo cache đừng dùng bản cũ).
Các directive thường gặp trong response:
| Directive | Nghĩa |
|---|---|
max-age=N | Cache hợp lệ trong N giây kể từ khi nhận |
s-maxage=N | Như max-age nhưng chỉ cho shared cache (CDN/proxy), không phải browser cache |
no-cache | Cache được lưu nhưng phải validate với server trước khi dùng (không phải "đừng cache") |
no-store | Không lưu vào cache nào — dữ liệu nhạy cảm như banking |
private | Chỉ browser cache, không lưu ở CDN/proxy |
public | CDN và proxy được phép cache |
must-revalidate | Sau khi hết max-age, phải validate trước khi dùng lại |
Ví dụ thực tế:
# Static asset (JS/CSS) - cache 1 nam, versioned URL
Cache-Control: public, max-age=31536000, immutable
# API response user-specific - cache browser 60s, khong CDN
Cache-Control: private, max-age=60
# Du lieu nhay cam - khong cache bat cu dau
Cache-Control: no-store
# Trang HTML - cache nhung phai validate truoc khi dung
Cache-Control: no-cache
4.2 ETag và 304 Not Modified — validation
ETag (Entity Tag) là một "fingerprint" của resource — thường là hash của content. Server gửi ETag cùng response; lần sau client gửi lại ETag trong header If-None-Match:
sequenceDiagram
participant C as Client
participant S as Server
C->>S: GET /users/1
S-->>C: 200 OK + ETag: "abc123" + body
Note over C: Luu ETag va body vao cache
C->>S: GET /users/1 + If-None-Match: "abc123"
Note over S: Kiem tra: resource van la "abc123"?
S-->>C: 304 Not Modified (khong co body!)
Note over C: Dung body cu tu cacheKhi server trả 304 Not Modified, không có body — client dùng lại bản đã cache. Tiết kiệm toàn bộ bandwidth truyền body. Bài 07 — Caching, CDN, proxy sẽ đào sâu strategy caching toàn hệ thống.
Trước ETag, server dùng Last-Modified (timestamp) và client gửi lại If-Modified-Since. Nhược điểm: timestamp chỉ chính xác đến giây, không phát hiện được content thay đổi rồi đổi lại trong 1 giây. ETag (hash-based) chính xác hơn và là cách khuyến nghị.
5. Authorization và xác thực
5.1 Header Authorization
Authorization gửi credentials lên server trong mỗi request. HTTP định nghĩa scheme <type> <credentials>:
# Bearer token (OAuth2, JWT)
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
# Basic auth: base64(username:password) - chi dung voi HTTPS
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
# API Key (custom scheme, khong chuan nhung pho bien)
Authorization: ApiKey sk-live-abc123
Khi server từ chối vì thiếu/sai credentials, nó trả 401 kèm header WWW-Authenticate hướng dẫn cách xác thực:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api.example.com", error="invalid_token"
5.2 Header Host — virtual hosting
Host là header bắt buộc trong HTTP/1.1 — server cần nó để biết virtual host nào xử lý request. Một IP có thể chạy hàng trăm domain; header Host là cách phân biệt:
GET / HTTP/1.1
Host: api.example.com
Nginx dùng Host để route tới đúng server block. Thiếu Host trong HTTP/1.1 → server trả 400 Bad Request.
6. Headers bảo mật cơ bản
Một số header bảo mật quan trọng mà backend nên set:
| Header | Mục đích |
|---|---|
Strict-Transport-Security: max-age=31536000 | Bắt browser dùng HTTPS (HSTS), không downgrade xuống HTTP |
X-Content-Type-Options: nosniff | Ngăn browser đoán MIME type — đọc đúng Content-Type |
X-Frame-Options: DENY | Ngăn embed trong iframe — chống clickjacking |
Referrer-Policy: strict-origin-when-cross-origin | Kiểm soát thông tin Referer gửi đi |
Bài 03 — Cookie, session & CORS sẽ giải thích sâu hơn các header bảo mật liên quan đến cookie và cross-origin.
7. Bảng tổng hợp header theo nhóm
| Nhóm | Request header | Response header |
|---|---|---|
| Content | Content-Type, Content-Length | Content-Type, Content-Length |
| Negotiation | Accept, Accept-Encoding, Accept-Language | Vary |
| Encoding | — | Content-Encoding |
| Cache | If-None-Match, If-Modified-Since, Cache-Control | Cache-Control, ETag, Last-Modified |
| Auth | Authorization | WWW-Authenticate |
| Routing | Host | — |
| Bảo mật | — | Strict-Transport-Security, X-Content-Type-Options |
8. Pitfall — lỗi thường gặp
❌ Nhầm 1: Gửi JSON body nhưng thiếu Content-Type: application/json.
✅ Framework phía server (Spring, Express, FastAPI) đọc Content-Type để chọn deserializer. Thiếu header → server có thể xử lý body như form-urlencoded hoặc từ chối với 415 Unsupported Media Type. Với curl, luôn thêm -H "Content-Type: application/json" khi gửi JSON.
❌ Nhầm 2: Set Cache-Control: no-store cho toàn bộ API response "cho chắc chắn".
✅ no-store ngăn cache ở mọi tầng — browser, proxy, CDN đều không cache. Với resource tĩnh (icon, JS bundle) điều này làm tăng latency và băng thông mỗi request. Chỉ dùng no-store cho dữ liệu thực sự nhạy cảm (thông tin tài khoản, giao dịch tài chính). Public resource dùng public, max-age=N; user-specific resource dùng private, max-age=N.
❌ Nhầm 3: Nhầm no-cache với "đừng cache".
✅ Cache-Control: no-cache không có nghĩa "đừng cache". Nó có nghĩa "lưu vào cache nhưng phải validate với server trước khi dùng" — tức là vẫn dùng ETag/304 flow. Nếu muốn không cache hẳn, dùng no-store. Đây là naming confusing nhất trong HTTP.
9. Liên hệ các bài khác
- Bài 01 — HTTP anatomy: headers xuất hiện trong cấu trúc request/response — bài này đào sâu từng nhóm.
- Bài 03 — Cookie, session & CORS: cookie được gửi qua
Cookieheader (request) vàSet-Cookieheader (response) — là header đặc biệt với nhiều attribute bảo mật. - Bài 07 — Caching, CDN, proxy:
Cache-ControlvàETagtrong bài này là nền tảng — bài 07 xây dựng strategy caching toàn hệ thống trên đó. - Bài 08 — Reverse proxy & Nginx: Nginx đọc
Hostđể route, set các security header, và forward các header nhưX-Forwarded-For.
10. 📚 Deep Dive — tài liệu gốc
Đọc khi muốn hiểu tận gốc:
- RFC 9110 §12 — Content Negotiation — định nghĩa chính thức
Accept,Accept-Encoding,Accept-Language, và cơ chế negotiation. - RFC 9111 — HTTP Caching — toàn bộ spec về
Cache-Control,ETag,Expires,Vary, và validation semantics. - RFC 7235 — HTTP Authentication —
Authorization,WWW-Authenticate, scheme framework. RFC 7617 (Basic), RFC 6750 (Bearer).
Ghi chú: RFC 9111 (Caching) và RFC 9110 §12 (Content Negotiation) là hai mục bạn sẽ tra cứu nhiều nhất khi thiết kế API. Không cần đọc hết — bookmark để reference khi cần.
11. Tóm tắt
Content-Typekhai báo loại dữ liệu body — thiếu hoặc sai khiến server hiểu nhầm và fail parse.- Content negotiation: client dùng
Accept/Accept-Encoding/Accept-Languageđể thương lượng format, server dùngVaryđể báo cache phân biệt theo header nào. Content-Encoding(gzip/br) nén body để giảm băng thông, đánh đổi CPU server; Brotli nén tốt hơn gzip nhưng tốn CPU hơn.Cache-Control:no-store= không lưu gì;no-cache= lưu nhưng validate trước khi dùng;max-age=N= cache N giây;privatevspublic= chỉ browser hay cả CDN.ETag+If-None-Matchcho phép validation: server trả 304 Not Modified (không body) khi resource chưa đổi — tiết kiệm bandwidth.Authorization: Bearer <token>là cách gửi JWT/OAuth token; server từ chối trả 401 kèmWWW-Authenticate.Hostbắt buộc trong HTTP/1.1 cho virtual hosting; thiếu → 400.- Header bảo mật (
Strict-Transport-Security,X-Content-Type-Options) nên set mặc định cho mọi response.
12. Tự kiểm tra
Q1Vì sao gửi JSON body mà thiếu Content-Type: application/json có thể gây lỗi, dù body đúng format?▸
Content-Type để quyết định dùng deserializer nào để parse body. Thiếu header → server không biết body là JSON, có thể thử parse như application/x-www-form-urlencoded (dạng key=value&key2=value2) hoặc trả 415 Unsupported Media Type. Framework như Spring Boot mặc định yêu cầu Content-Type: application/json cho endpoint nhận JSON — thiếu header là thiếu contract, dù payload đúng.Q2Cache-Control: no-cache và Cache-Control: no-store khác nhau thế nào? Khi nào dùng cái nào?▸
no-cache vẫn cho phép cache lưu response, nhưng cache phải gửi request validate lên server (dùng ETag/If-None-Match) trước khi dùng bản đã lưu. Nếu server xác nhận không đổi (304), cache mới dùng bản cũ. no-store ngăn cache lưu hoàn toàn — mỗi request phải lấy dữ liệu mới từ server. Dùng no-cache khi muốn đảm bảo luôn dùng bản mới nhất nhưng vẫn tiết kiệm bandwidth nếu không đổi. Dùng no-store cho dữ liệu nhạy cảm (thông tin tài chính, thông tin y tế) mà không được lưu trong bất kỳ cache nào.Q3ETag và 304 Not Modified hoạt động thế nào? Lợi ích so với luôn trả 200 với đầy đủ body?▸
ETag: "abc123" trong response. Lần sau client gửi If-None-Match: "abc123". Server kiểm tra: nếu resource vẫn có ETag đó (chưa thay đổi), trả 304 Not Modified với không có body. Client dùng lại bản cache. Lợi ích: tiết kiệm toàn bộ bandwidth truyền body — với JSON lớn hay ảnh, đây là tối ưu đáng kể. Client vẫn tốn 1 RTT cho request validate, nhưng không tốn băng thông truyền body khi data chưa đổi.Q4Tại sao response có content negotiation cần set header Vary?▸
Accept header. Nếu không có Vary: Accept, cache lưu bản JSON rồi serve nó cho browser cũng đang hỏi cùng URL — browser nhận JSON thay vì HTML. Vary: Accept báo cache phải tạo cache entry riêng cho từng giá trị Accept. Tương tự với Vary: Accept-Encoding khi serve cả gzip và brotli.Q5Khi nào nên dùng Brotli (br) thay vì gzip? Đánh đổi là gì?▸
Accept-Encoding: gzip, br, server chọn thuật toán tốt nhất nó hỗ trợ.Q6Authorization: Bearer token khác Basic auth thế nào về bảo mật?▸
base64(username:password) mỗi request — base64 không phải mã hoá, chỉ encode. Nếu dùng không qua HTTPS, credentials bị lộ nguyên văn. Dùng Basic auth an toàn duy nhất khi kết hợp HTTPS bắt buộc. Bearer token (JWT hoặc opaque token) không chứa password — chỉ chứa assertion "tôi đã xác thực, đây là claims của tôi". Token có thể expire, revoke, và scope giới hạn quyền. Ngay cả khi token bị intercept, attacker không biết password gốc. Mọi API public hiện đại nên dùng Bearer token (OAuth2/JWT), không phải Basic auth.Bài tiếp theo: Cookie, session & CORS — vì sao request bị chặn
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