TCP, HTTP & Web cho Backend/HTTP headers quan trọng — Content-Type, Cache-Control, Authorization
13/29
Bài 13 / 29~20 phútHTTP & WebMiễn phí lượt xem

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àngHTTP 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âyCache-Control: max-age=60
Thẻ thành viênAuthorization: Bearer <token>
💡 Cách nhớ

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
📌 Tại sao cần Vary?

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 goc

Hai thuật toán phổ biến:

Thuật toánTỉ lệ nénCPU nénCPU giải nénKhi nào dùng
gzipTốt (~70% text)Trung bìnhThấpMặc định, hỗ trợ rộng
Brotli (br)Tốt hơn gzip ~20%Cao hơnTương đươngModern 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 vs Transfer-Encoding

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:

DirectiveNghĩa
max-age=NCache hợp lệ trong N giây kể từ khi nhận
s-maxage=NNhư max-age nhưng chỉ cho shared cache (CDN/proxy), không phải browser cache
no-cacheCache được lưu nhưng phải validate với server trước khi dùng (không phải "đừng cache")
no-storeKhông lưu vào cache nào — dữ liệu nhạy cảm như banking
privateChỉ browser cache, không lưu ở CDN/proxy
publicCDN và proxy được phép cache
must-revalidateSau 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 cache

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

📌 Last-Modified — alternative cũ hơn ETag

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:

HeaderMục đích
Strict-Transport-Security: max-age=31536000Bắt browser dùng HTTPS (HSTS), không downgrade xuống HTTP
X-Content-Type-Options: nosniffNgăn browser đoán MIME type — đọc đúng Content-Type
X-Frame-Options: DENYNgăn embed trong iframe — chống clickjacking
Referrer-Policy: strict-origin-when-cross-originKiể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ómRequest headerResponse header
ContentContent-Type, Content-LengthContent-Type, Content-Length
NegotiationAccept, Accept-Encoding, Accept-LanguageVary
EncodingContent-Encoding
CacheIf-None-Match, If-Modified-Since, Cache-ControlCache-Control, ETag, Last-Modified
AuthAuthorizationWWW-Authenticate
RoutingHost
Bảo mậtStrict-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

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

📚 RFC chính thức

Đọc khi muốn hiểu tận gốc:

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-Type khai 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ùng Vary để 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; private vs public = chỉ browser hay cả CDN.
  • ETag + If-None-Match cho 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èm WWW-Authenticate.
  • Host bắ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

Tự kiểm tra
Q1
Vì sao gửi JSON body mà thiếu Content-Type: application/json có thể gây lỗi, dù body đúng format?
Server đọc 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.
Q2
Cache-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.
Q3
ETag và 304 Not Modified hoạt động thế nào? Lợi ích so với luôn trả 200 với đầy đủ body?
Server gửi kèm 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.
Q4
Tại sao response có content negotiation cần set header Vary?
CDN và proxy cache response theo URL key mặc định. Nhưng cùng URL có thể trả JSON (cho API client) hoặc HTML (cho browser), tùy 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.
Q5
Khi nào nên dùng Brotli (br) thay vì gzip? Đánh đổi là gì?
Brotli nén tốt hơn gzip khoảng 15-20% với text, đặc biệt tốt với JS/CSS/HTML lớn — nhưng tốn CPU hơn để nén. Dùng Brotli tốt nhất cho static asset: pre-compress một lần, serve mãi (tốn CPU một lần, tiết kiệm bandwidth mãi). Với response động (JSON API tạo realtime), gzip thường được ưu tiên vì nhanh hơn. Ngoài ra Brotli chỉ làm việc trên HTTPS (browser policy) và được hỗ trợ bởi tất cả modern browser. Kiểm tra: client gửi Accept-Encoding: gzip, br, server chọn thuật toán tốt nhất nó hỗ trợ.
Q6
Authorization: Bearer token khác Basic auth thế nào về bảo mật?
Basic auth gửi 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

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

Cookie, session & CORS — vì sao request bị chặn