Định nghĩa: HTTP Headers là các metadata (dữ liệu về dữ liệu) được gửi kèm với HTTP request và response. Headers cung cấp thông tin bổ sung về request/response hoặc về dữ liệu được gửi trong message body.
Headers có dạng key-value pairs:
Header-Name: Header-Value
HTTP Request với Headers:
GET /api/users HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: application/json
Authorization: Bearer abc123token
Content-Type: application/json
HTTP Response với Headers:
HTTP/1.1 200 OK
Date: Mon, 15 Jan 2025 10:00:00 GMT
Content-Type: application/json
Content-Length: 348
Cache-Control: max-age=3600
Set-Cookie: session_id=xyz789; HttpOnly
{
"id": 1,
"name": "Nguyễn Văn A"
}
Loại | Mô tả | Ví dụ |
---|---|---|
Request | Chỉ xuất hiện trong request | Accept, User-Agent, Referer |
Response | Chỉ xuất hiện trong response | Set-Cookie, Server, ETag |
Both | Có thể xuất hiện ở cả hai | Content-Type, Cache-Control |
Entity Headers | Thông tin về body của message | Content-Length, Content-Encoding |
Content-Type
= content-type
Set-Cookie
)X-
(ví dụ: X-API-Key
)Metadata vs Data:
// Headers = metadata
Content-Type: application/json
Content-Length: 58
Authorization: Bearer token123
// Body = actual data
{
"name": "Nguyễn Văn A",
"email": "vana@example.com"
}
✓ Dễ đọc, dễ maintain, logic rõ ràng
Có thể thêm/sửa metadata mà không cần thay đổi format của body:
// Request 1: Chỉ gửi data
POST /api/users
Content-Type: application/json
{"name": "User A"}
// Request 2: Thêm authentication (không cần sửa body!)
POST /api/users
Content-Type: application/json
Authorization: Bearer abc123
{"name": "User A"}
// Request 3: Thêm tracking (vẫn không cần sửa body!)
POST /api/users
Content-Type: application/json
Authorization: Bearer abc123
X-Request-ID: req-12345
X-Client-Version: 2.0.1
{"name": "User A"}
Xử lý authentication và authorization riêng biệt:
// ✓ Đúng: Auth ở header
POST /api/orders
Authorization: Bearer token123
Content-Type: application/json
{
"product_id": 101,
"quantity": 2
}
// ✗ Sai: Đừng đặt auth token trong body
POST /api/orders
Content-Type: application/json
{
"token": "abc123", // ← Không nên!
"product_id": 101,
"quantity": 2
}
Lợi ích:
Caching thông qua headers:
// Server gửi response với cache headers
HTTP/1.1 200 OK
Cache-Control: max-age=3600
ETag: "v1-abc123"
Content-Type: application/json
{"products": [...]}
// Lần sau client gửi request
GET /api/products
If-None-Match: "v1-abc123"
// Server chỉ cần check header, không cần xử lý body
HTTP/1.1 304 Not Modified
ETag: "v1-abc123"
(không có body, tiết kiệm bandwidth!)
Compression thông qua headers:
// Client yêu cầu compressed data
GET /api/large-data
Accept-Encoding: gzip, deflate
// Server nén và báo trong header
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Length: 1024 // ← Kích thước sau khi nén
[compressed binary data] // ← Tiết kiệm 70-90% bandwidth
Dễ dàng thêm custom headers khi cần:
// Thêm tracking
X-Request-ID: req-abc-123
X-Correlation-ID: corr-xyz-789
// Thêm versioning
X-API-Version: 2.0
Accept-Version: 2.0
// Thêm debugging
X-Debug-Mode: true
X-Response-Time: 125ms
// Thêm business logic
X-Tenant-ID: company-123
X-Feature-Flags: feature-a,feature-b
✓ Không cần thay đổi API contract
✓ Backward compatible
✓ Dễ dàng A/B testing
HTTP được thiết kế theo nguyên tắc này từ đầu:
// Client 1 muốn JSON
GET /api/users/1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 1, "name": "User A"}
// Client 2 muốn XML
GET /api/users/1
Accept: application/xml
HTTP/1.1 200 OK
Content-Type: application/xml
<user><id>1</id><name>User A</name></user>
// Chỉ khác header Accept, không cần 2 endpoints!
// ✗ Không có headers (mọi thứ trong body)
POST /api/users
{
"auth_token": "abc123",
"request_id": "req-123",
"accept_format": "json",
"cache_control": "no-cache",
"user_agent": "MyApp/1.0",
"actual_data": {
"name": "User A",
"email": "usera@example.com"
}
}
// ✓ Có headers (tách biệt rõ ràng)
POST /api/users
Authorization: Bearer abc123
X-Request-ID: req-123
Accept: application/json
Cache-Control: no-cache
User-Agent: MyApp/1.0
{
"name": "User A",
"email": "usera@example.com"
}
Vấn đề khi không có headers:
Mục đích: Gửi credentials để xác thực với server.
Cú pháp: Authorization: <type> <credentials>
// 1. Basic Authentication
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
// (base64 encoded "username:password")
// 2. Bearer Token (JWT)
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// 3. API Key
Authorization: ApiKey abc123xyz789
// 4. Digest
Authorization: Digest username="user", realm="api@example.com", ...
// 5. OAuth 2.0
Authorization: Bearer ya29.a0AfH6SMBx...
import { test, expect } from '@playwright/test';
test('GET với Bearer token', async ({ request }) => {
const response = await request.get('https://api.example.com/users/me', {
headers: {
'Authorization': 'Bearer abc123token'
}
});
expect(response.status()).toBe(200);
});
test('GET với Basic Auth', async ({ request }) => {
const credentials = Buffer.from('username:password').toString('base64');
const response = await request.get('https://api.example.com/data', {
headers: {
'Authorization': `Basic ${credentials}`
}
});
expect(response.status()).toBe(200);
});
Alternative cách gửi API key (không theo chuẩn Authorization):
GET /api/weather
X-API-Key: your-api-key-here
Host: api.weather.com
Gửi cookies đã được lưu từ server:
GET /api/dashboard
Cookie: session_id=abc123; user_pref=dark_mode
Host: example.com
Cho server biết client muốn nhận dữ liệu dạng gì:
// Muốn JSON
Accept: application/json
// Muốn XML
Accept: application/xml
// Muốn HTML
Accept: text/html
// Chấp nhận nhiều loại (có priority)
Accept: application/json, application/xml;q=0.9, */*;q=0.8
Ngôn ngữ mà client muốn nhận:
// Muốn tiếng Việt
Accept-Language: vi-VN
// Muốn tiếng Anh hoặc tiếng Việt
Accept-Language: en-US, vi-VN;q=0.9
// Muốn tiếng Việt, tiếng Anh, hoặc bất kỳ
Accept-Language: vi, en;q=0.8, *;q=0.5
Compression algorithms mà client hỗ trợ:
Accept-Encoding: gzip, deflate, br
// br = Brotli (nén tốt nhất)
// gzip = phổ biến nhất
// deflate = cũ hơn
Cho server biết dữ liệu gửi lên có format gì:
// Gửi JSON
POST /api/users
Content-Type: application/json
{"name": "User A"}
// Gửi form data
POST /api/upload
Content-Type: application/x-www-form-urlencoded
name=UserA&email=user@example.com
// Upload file
POST /api/upload
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="photo.jpg"
...
import { test, expect } from '@playwright/test';
test('Request JSON response', async ({ request }) => {
const response = await request.get('https://api.example.com/users/1', {
headers: {
'Accept': 'application/json'
}
});
expect(response.headers()['content-type']).toContain('application/json');
const data = await response.json();
expect(data).toHaveProperty('id', 1);
});
test('Request XML response', async ({ request }) => {
const response = await request.get('https://api.example.com/users/1', {
headers: {
'Accept': 'application/xml'
}
});
expect(response.headers()['content-type']).toContain('application/xml');
const text = await response.text();
expect(text).toContain('<user>');
});
test('POST với Content-Type', async ({ request }) => {
const response = await request.post('https://api.example.com/users', {
headers: {
'Content-Type': 'application/json'
},
data: {
name: 'Nguyễn Văn A',
email: 'vana@example.com'
}
});
expect(response.status()).toBe(201);
});
Chỉ định mức độ ưu tiên (0.0 - 1.0, default = 1.0):
Accept: application/json;q=1.0, application/xml;q=0.8, text/html;q=0.5
// Nghĩa là:
// 1. Ưu tiên JSON nhất (q=1.0)
// 2. XML cũng ok (q=0.8)
// 3. HTML cũng được nhưng không thích lắm (q=0.5)
Thông tin về client (browser, app, bot, ...):
// Chrome browser
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
// Firefox browser
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
// Mobile Safari
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1
// Custom app
User-Agent: MyApp/1.0 (iOS 17.0; iPhone 15 Pro)
// Playwright
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
test('Custom User-Agent', async ({ request }) => {
const response = await request.get('https://api.example.com/data', {
headers: {
'User-Agent': 'MyTestBot/1.0'
}
});
expect(response.status()).toBe(200);
});
URL của trang trước đó mà user đến từ đó:
GET /api/products/123
Referer: https://example.com/products
Host: api.example.com
// Server biết user đang xem product 123 từ trang danh sách products
Domain name của server (bắt buộc trong HTTP/1.1):
GET /api/users
Host: api.example.com
// Nếu có port khác 80/443
GET /api/users
Host: api.example.com:8080
Origin của request (dùng cho CORS):
POST /api/users
Origin: https://myapp.com
Host: api.example.com
// Server sẽ check Origin để quyết định có cho phép CORS không
// User đang ở https://myapp.com, gọi API của https://api.example.com
POST https://api.example.com/users
Host: api.example.com // ← Server đích
Origin: https://myapp.com // ← Nơi xuất phát
Gửi ETag đã có, hỏi server có version mới không:
// Lần 1: GET và nhận ETag
GET /api/products/1
→ Response:
HTTP/1.1 200 OK
ETag: "v1-abc123"
{"id": 1, "name": "Product A"}
// Lần 2: Gửi lại với If-None-Match
GET /api/products/1
If-None-Match: "v1-abc123"
→ Response (nếu chưa thay đổi):
HTTP/1.1 304 Not Modified
ETag: "v1-abc123"
(không có body, tiết kiệm bandwidth!)
Chỉ lấy nếu đã thay đổi sau thời điểm này:
// Lần 1
GET /api/report.pdf
→ Response:
HTTP/1.1 200 OK
Last-Modified: Mon, 15 Jan 2025 10:00:00 GMT
[PDF data]
// Lần 2
GET /api/report.pdf
If-Modified-Since: Mon, 15 Jan 2025 10:00:00 GMT
→ Response (nếu chưa update):
HTTP/1.1 304 Not Modified
Last-Modified: Mon, 15 Jan 2025 10:00:00 GMT
(không có body)
Điều khiển caching behavior:
// Không muốn dùng cache
GET /api/latest-news
Cache-Control: no-cache
// Chỉ muốn fresh data (không cũ hơn 60s)
GET /api/stock-price
Cache-Control: max-age=60
// Không cache gì cả
GET /api/sensitive-data
Cache-Control: no-store
import { test, expect } from '@playwright/test';
test('Test ETag caching', async ({ request }) => {
// Lần 1: Lấy data và ETag
const response1 = await request.get('https://api.example.com/products/1');
expect(response1.status()).toBe(200);
const etag = response1.headers()['etag'];
console.log('ETag:', etag);
// Lần 2: Gửi với If-None-Match
const response2 = await request.get('https://api.example.com/products/1', {
headers: {
'If-None-Match': etag
}
});
// Nếu không đổi, nhận 304
if (response2.status() === 304) {
console.log('Cache hit! Data chưa thay đổi');
} else if (response2.status() === 200) {
console.log('Cache miss! Data đã thay đổi');
}
});
test('Force no-cache', async ({ request }) => {
const response = await request.get('https://api.example.com/latest-data', {
headers: {
'Cache-Control': 'no-cache'
}
});
expect(response.status()).toBe(200);
// Luôn nhận fresh data
});
Unique ID để tracking request qua nhiều services:
GET /api/orders/123
X-Request-ID: req-abc-123-xyz-789
Authorization: Bearer token123
// Server log:
[req-abc-123-xyz-789] Processing order 123
[req-abc-123-xyz-789] Calling payment service
[req-abc-123-xyz-789] Payment successful
[req-abc-123-xyz-789] Response sent
IP address gốc của client khi đi qua proxy/load balancer:
// Client → Proxy → Load Balancer → Server
X-Forwarded-For: 123.45.67.89, 10.0.0.1, 10.0.0.2
// 123.45.67.89 = Client IP gốc
// 10.0.0.1 = Proxy IP
// 10.0.0.2 = Load Balancer IP
Version của client app (useful cho mobile apps):
GET /api/features
X-Client-Version: 2.1.0
X-Platform: iOS
X-OS-Version: 17.0
// Server có thể:
// - Trả về features tương thích với version 2.1.0
// - Báo user update app nếu version quá cũ
// - Track bugs theo version
// Tenant ID cho multi-tenant apps
X-Tenant-ID: company-abc-123
// Feature flags
X-Feature-Flags: new-ui,beta-feature
// Debug mode
X-Debug-Mode: true
// Correlation ID (distributed tracing)
X-Correlation-ID: corr-xyz-789
// Rate limiting info
X-RateLimit-Remaining: 95
X-RateLimit-Limit: 100
import { test, expect } from '@playwright/test';
test('Send custom headers', async ({ request }) => {
const response = await request.get('https://api.example.com/data', {
headers: {
'X-Request-ID': `req-${Date.now()}`,
'X-Client-Version': '2.1.0',
'X-Platform': 'Web',
'X-Feature-Flags': 'new-ui,dark-mode'
}
});
expect(response.status()).toBe(200);
// Kiểm tra response có trả về request ID không
const requestId = response.headers()['x-request-id'];
console.log('Request ID:', requestId);
});
X-
(convention, không bắt buộc)Access-Control-Allow-Headers
nếu dùng cross-originLoại dữ liệu trong response body:
// JSON
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id": 1, "name": "User A"}
// HTML
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<html>...</html>
// Plain text
HTTP/1.1 200 OK
Content-Type: text/plain
Hello World
// PDF
HTTP/1.1 200 OK
Content-Type: application/pdf
[PDF binary data]
// Image
HTTP/1.1 200 OK
Content-Type: image/jpeg
[JPEG binary data]
Kích thước của response body (bytes):
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 58
{"id": 1, "name": "User A", "email": "usera@example.com"}
Compression algorithm đã dùng:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: gzip
Content-Length: 1024
[gzip compressed data]
// Client sẽ tự động decompress
Ngôn ngữ của content:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Language: vi-VN
<html>
<h1>Xin chào</h1>
</html>
import { test, expect } from '@playwright/test';
test('Verify Content headers', async ({ request }) => {
const response = await request.get('https://api.example.com/users/1');
// Kiểm tra Content-Type
expect(response.headers()['content-type']).toContain('application/json');
// Kiểm tra Content-Length
const contentLength = parseInt(response.headers()['content-length']);
expect(contentLength).toBeGreaterThan(0);
// Verify body size matches Content-Length
const body = await response.text();
expect(body.length).toBe(contentLength);
});
Điều khiển caching behavior:
// Cache trong 1 giờ
Cache-Control: max-age=3600
// Không cache
Cache-Control: no-store
// Cache nhưng phải revalidate
Cache-Control: no-cache
// Private cache (chỉ browser, không qua CDN)
Cache-Control: private, max-age=3600
// Public cache (browser + CDN)
Cache-Control: public, max-age=86400
// Kết hợp nhiều directives
Cache-Control: public, max-age=31536000, immutable
Identifier cho version của resource:
HTTP/1.1 200 OK
ETag: "v1-abc123"
Cache-Control: max-age=3600
Content-Type: application/json
{"id": 1, "name": "Product A"}
// Client sẽ lưu ETag và gửi lại lần sau với If-None-Match
Thời điểm cache hết hạn (HTTP/1.0 legacy):
HTTP/1.1 200 OK
Expires: Wed, 15 Jan 2025 11:00:00 GMT
Content-Type: application/json
// Cache-Control ưu tiên hơn Expires trong HTTP/1.1
Lần cuối resource được modified:
HTTP/1.1 200 OK
Last-Modified: Mon, 15 Jan 2025 10:00:00 GMT
Cache-Control: max-age=3600
// Client có thể dùng If-Modified-Since lần sau
// Static assets (CSS, JS, images) - cache 1 năm
HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
ETag: "v2-xyz789"
Content-Type: application/javascript
// API data - cache 5 phút, must revalidate
HTTP/1.1 200 OK
Cache-Control: private, max-age=300, must-revalidate
ETag: "data-v1-abc"
Content-Type: application/json
// Sensitive data - không cache
HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate
Content-Type: application/json
Directive | Ý nghĩa |
---|---|
max-age=<seconds> |
Cache trong bao lâu |
no-cache |
Phải revalidate trước khi dùng cache |
no-store |
Không cache gì cả |
public |
Có thể cache ở CDN, proxy |
private |
Chỉ cache ở browser |
must-revalidate |
Phải check với server khi stale |
immutable |
Không bao giờ thay đổi |
Bắt buộc dùng HTTPS:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
// Browser sẽ:
// - Chỉ dùng HTTPS cho domain này trong 1 năm
// - Áp dụng cho cả subdomains
// - Có thể thêm vào HSTS preload list
Ngăn MIME type sniffing:
X-Content-Type-Options: nosniff
// Browser sẽ không đoán Content-Type, phải tin header
Ngăn clickjacking (không cho embed trong iframe):
// Không cho embed trong iframe
X-Frame-Options: DENY
// Chỉ cho same-origin
X-Frame-Options: SAMEORIGIN
// Cho phép từ domain cụ thể
X-Frame-Options: ALLOW-FROM https://trusted-site.com
Kiểm soát nguồn tài nguyên được load:
// Chỉ cho phép resources từ same origin
Content-Security-Policy: default-src 'self'
// Cho phép scripts từ self và Google Analytics
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.google-analytics.com
// Chi tiết hơn
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
Bật XSS filter của browser (legacy):
X-XSS-Protection: 1; mode=block
// 1 = enable
// mode=block = block page nếu detect XSS
HTTP/1.1 200 OK
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
Cho phép origins nào được truy cập:
// Cho phép tất cả origins (không an toàn!)
Access-Control-Allow-Origin: *
// Cho phép một origin cụ thể
Access-Control-Allow-Origin: https://myapp.com
// Cho phép với credentials
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Methods được phép trong CORS:
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Headers được phép gửi:
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Thời gian cache preflight request (giây):
Access-Control-Max-Age: 86400
// Cache preflight trong 24 giờ
Headers nào client có thể đọc:
Access-Control-Expose-Headers: X-Request-ID, X-RateLimit-Remaining
// Client (https://myapp.com) gửi request đến API (https://api.example.com)
// 1. Preflight request (browser tự động gửi)
OPTIONS /api/users
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
// 2. Server response
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
// 3. Browser thấy OK, gửi actual request
POST /api/users
Origin: https://myapp.com
Authorization: Bearer token123
Content-Type: application/json
{"name": "User A"}
// 4. Server response
HTTP/1.1 201 Created
Access-Control-Allow-Origin: https://myapp.com
Content-Type: application/json
{"id": 1, "name": "User A"}
*
cho production APIs*
Access-Control-Max-Age
để giảm preflight requestsThông tin về web server:
Server: nginx/1.21.0
Server: Apache/2.4.41 (Ubuntu)
Server: Microsoft-IIS/10.0
Server: cloudflare
// ✗ Không tốt
Server: Apache/2.4.41 (Ubuntu)
// ✓ Tốt hơn
Server: Apache
// ✓ Tốt nhất
(không có Server header)
Thời gian server gửi response:
Date: Mon, 15 Jan 2025 10:00:00 GMT
URL redirect hoặc URL của resource mới tạo:
// Redirect (3xx)
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/new-url
// Resource mới tạo (201)
HTTP/1.1 201 Created
Location: /api/users/123
Content-Type: application/json
{"id": 123, "name": "User A"}
Gửi cookies cho client lưu:
// Cookie đơn giản
Set-Cookie: session_id=abc123
// Cookie với options
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
// Multiple cookies
Set-Cookie: session_id=abc123; HttpOnly
Set-Cookie: user_pref=dark_mode; Max-Age=2592000
Attribute | Ý nghĩa |
---|---|
HttpOnly |
JavaScript không đọc được (chống XSS) |
Secure |
Chỉ gửi qua HTTPS |
SameSite=Strict |
Chống CSRF, không gửi cross-site |
SameSite=Lax |
Gửi với top-level navigation |
Max-Age |
Thời gian sống (giây) |
Expires |
Thời điểm hết hạn |
Domain |
Domain nào nhận cookie |
Path |
Path nào nhận cookie |
Headers nào ảnh hưởng đến cache:
// Cache riêng cho mỗi Accept-Encoding
Vary: Accept-Encoding
// Cache riêng cho mỗi User-Agent và Accept-Language
Vary: User-Agent, Accept-Language
// Thường dùng với CORS
Vary: Origin
Category | Request Headers | Response Headers |
---|---|---|
Authentication | Authorization, Cookie | Set-Cookie, WWW-Authenticate |
Content | Content-Type, Accept | Content-Type, Content-Length, Content-Encoding |
Caching | If-None-Match, If-Modified-Since | ETag, Last-Modified, Cache-Control, Expires |
CORS | Origin | Access-Control-Allow-* |
Client Info | User-Agent, Referer, Host | Server, Date |
Security | - | Strict-Transport-Security, CSP, X-Frame-Options |
// ✗ Sai: Password trong custom header
X-User-Password: mypassword123
// ✓ Đúng: Dùng Authorization với HTTPS
Authorization: Bearer encrypted-token
// ✗ Sai: Không có Content-Type
POST /api/users
{"name": "User A"}
// ✓ Đúng
POST /api/users
Content-Type: application/json
{"name": "User A"}
// ✗ Sai: Không được phép!
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
// ✓ Đúng
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
// ✗ Thiếu security headers
HTTP/1.1 200 OK
Content-Type: text/html
<html>...</html>
// ✓ Đầy đủ security headers
HTTP/1.1 200 OK
Content-Type: text/html
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'
<html>...</html>
// ✗ Sai: Cache user private data
GET /api/users/me
→
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600 // ← Nguy hiểm!
// ✓ Đúng
GET /api/users/me
→
HTTP/1.1 200 OK
Cache-Control: private, no-store
Content-Type
khi có bodyAccept
để specify format mong muốnUser-Agent
để server biết client là gìAuthorization
header cho auth, không đặt trong bodyX-Request-ID
để trackingIf-None-Match
/ If-Modified-Since
để tận dụng cacheContent-Type
Cache-Control
, ETag
)Location
cho redirects và resources mới tạoContent-Length
khi biết sizeServer
headerStrict-Transport-Security
Content-Security-Policy
X-Frame-Options: DENY
hoặc SAMEORIGIN
X-Content-Type-Options: nosniff
HttpOnly
, Secure
, SameSite
Accept-Encoding
và Content-Encoding
ETag
và If-None-Match
Access-Control-Max-Age
để giảm preflight requestsVary
header đúng cách// CLIENT REQUEST
POST /api/orders HTTP/1.1
Host: api.example.com
User-Agent: MyApp/2.1.0 (iOS 17.0)
Accept: application/json
Accept-Language: vi-VN, en;q=0.9
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Request-ID: req-abc-123-xyz
X-Client-Version: 2.1.0
Origin: https://myapp.com
{
"product_id": 101,
"quantity": 2
}
// SERVER RESPONSE
HTTP/1.1 201 Created
Date: Mon, 15 Jan 2025 10:00:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 245
Content-Encoding: gzip
Cache-Control: private, no-cache
Location: /api/orders/789
ETag: "order-789-v1"
X-Request-ID: req-abc-123-xyz
X-Response-Time: 145ms
// Security Headers
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'
// CORS Headers
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Request-ID, X-Response-Time
Vary: Origin
{
"id": 789,
"product_id": 101,
"quantity": 2,
"total": 500000,
"status": "pending",
"created_at": "2025-01-15T10:00:00Z"
}
import { test, expect } from '@playwright/test';
test('Comprehensive headers test', async ({ request }) => {
const requestId = `req-${Date.now()}`;
const response = await request.post('https://api.example.com/orders', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Accept-Language': 'vi-VN',
'Accept-Encoding': 'gzip',
'Authorization': 'Bearer test-token',
'X-Request-ID': requestId,
'X-Client-Version': '2.1.0'
},
data: {
product_id: 101,
quantity: 2
}
});
// Verify status
expect(response.status()).toBe(201);
// Verify Content headers
expect(response.headers()['content-type']).toContain('application/json');
expect(response.headers()['content-length']).toBeTruthy();
// Verify Security headers
expect(response.headers()['strict-transport-security']).toBeTruthy();
expect(response.headers()['x-content-type-options']).toBe('nosniff');
expect(response.headers()['x-frame-options']).toBeTruthy();
// Verify CORS headers
expect(response.headers()['access-control-allow-origin']).toBeTruthy();
// Verify custom headers
expect(response.headers()['x-request-id']).toBe(requestId);
// Verify body
const data = await response.json();
expect(data).toHaveProperty('id');
expect(data.product_id).toBe(101);
});