目錄
02-5. HTTP Headers 詳解
⏱️ 閱讀時間: 12 分鐘 🎯 難度: ⭐⭐ (簡單)
🎯 本篇重點
深入理解 HTTP Headers 的分類、常用 Headers 的用途、安全相關 Headers,以及如何在實際開發中正確使用。
🤔 什麼是 HTTP Headers?
HTTP Headers = HTTP 請求和回應的「元資料」
一句話解釋: HTTP Headers 就像是包裹上的標籤,告訴對方關於這個包裹的重要資訊(內容類型、大小、來源、目的地等)。
📦 用快遞包裹來比喻 Headers
包裹標籤(Headers) HTTP Headers
寄件人 User-Agent(客戶端資訊)
收件人 Host(目標伺服器)
包裹內容類型 Content-Type(內容類型)
包裹重量 Content-Length(內容大小)
寄送方式 Connection(連線方式)
到期日 Expires(過期時間)
特殊處理說明 Cache-Control(快取控制)
簽名證明 Authorization(認證資訊)📋 Headers 的分類
四大類
1. 通用標頭(General Headers)
├─ 請求和回應都可以使用
├─ Connection
├─ Date
└─ Cache-Control
2. 請求標頭(Request Headers)
├─ 客戶端發送給伺服器
├─ Host
├─ User-Agent
├─ Accept
└─ Authorization
3. 回應標頭(Response Headers)
├─ 伺服器回傳給客戶端
├─ Server
├─ Set-Cookie
└─ Location
4. 實體標頭(Entity Headers)
├─ 描述訊息主體
├─ Content-Type
├─ Content-Length
└─ Content-Encoding🔍 常用 Headers 詳解
📍 Host(請求標頭)
Host: www.example.com
說明:
- 指定目標伺服器的域名
- HTTP/1.1 必須包含
- 支援虛擬主機(一個 IP 多個網站)
為什麼需要 Host?
一個伺服器(一個 IP)可以架設多個網站:
伺服器 IP: 192.168.1.100
├─ www.site1.com
├─ www.site2.com
└─ www.site3.com
請求:
GET / HTTP/1.1
Host: www.site1.com
→ 伺服器知道要回傳 site1 的內容
請求:
GET / HTTP/1.1
Host: www.site2.com
→ 伺服器知道要回傳 site2 的內容🌐 User-Agent(請求標頭)
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 Chrome/120.0.0.0
說明:
- 客戶端的軟體資訊
- 包含作業系統、瀏覽器版本等
用途:
1. 判斷裝置類型(桌面版 vs 行動版)
2. 瀏覽器相容性處理
3. 統計分析
常見 User-Agent:
// Chrome(Windows)
Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 Chrome/120.0.0.0
// Safari(iPhone)
Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)
AppleWebKit/605.1.15 Safari/604.1
// curl
curl/7.68.0
// 爬蟲
Googlebot/2.1
實際應用:
// Python - 判斷是否為行動裝置
user_agent = request.headers.get('User-Agent', '')
is_mobile = 'Mobile' in user_agent or 'Android' in user_agent
if is_mobile:
return render_template('mobile.html')
else:
return render_template('desktop.html')🎯 Accept(請求標頭)
Accept: text/html, application/json, */*
說明:
- 客戶端可以接受的內容類型
- 伺服器根據此決定回傳格式
內容類型:
application/json - JSON 資料
text/html - HTML 網頁
text/plain - 純文字
application/xml - XML 資料
image/* - 任何圖片
*/* - 任何類型
優先順序(品質因子):
Accept: text/html;q=1.0, application/json;q=0.9, */*;q=0.8
q=1.0:最優先(HTML)
q=0.9:次優先(JSON)
q=0.8:最低優先(其他)
內容協商(Content Negotiation):
【請求】
GET /api/users/123 HTTP/1.1
Accept: application/json
【回應】
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 123, "name": "John"}
【請求】
GET /api/users/123 HTTP/1.1
Accept: application/xml
【回應】
HTTP/1.1 200 OK
Content-Type: application/xml
<user><id>123</id><name>John</name></user>
相關 Headers:
- Accept-Language: zh-TW, en-US(語言)
- Accept-Encoding: gzip, deflate(編碼)
- Accept-Charset: utf-8(字元集)🔐 Authorization(請求標頭)
Authorization: Bearer eyJhbGci0iJIUzI1NiIsInR5cCI6IkpXVCJ9...
說明:
- 身份驗證資訊
- 證明客戶端的身份
類型:
1. Basic(基本認證)
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
編碼方式:Base64(username:password)
範例:
username: john
password: secret123
→ "john:secret123"
→ Base64 編碼
→ "am9objpzZWNyZXQxMjM="
Authorization: Basic am9objpzZWNyZXQxMjM=
2. Bearer(Token 認證)
Authorization: Bearer ACCESS_TOKEN
通常是 JWT(JSON Web Token)
範例:
Authorization: Bearer eyJhbGci0iJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT 結構:
Header.Payload.Signature
3. Digest(摘要認證)
Authorization: Digest username="john",
realm="API",
nonce="...",
response="..."
4. OAuth 2.0
Authorization: Bearer ya29.a0AfH6SMBx...
實際應用:
【登入】
POST /api/login
{"username": "john", "password": "123456"}
→ 200 OK
{"token": "eyJhbGci..."}
【後續請求】
GET /api/profile
Authorization: Bearer eyJhbGci...
→ 200 OK
{"id": 123, "name": "John"}📝 Content-Type(實體標頭)
Content-Type: application/json; charset=utf-8
說明:
- 訊息主體的內容類型
- 告訴對方如何解析內容
常見類型:
1. JSON 資料
Content-Type: application/json
{"name": "John", "age": 30}
2. 表單資料(URL 編碼)
Content-Type: application/x-www-form-urlencoded
name=John&age=30&city=Taipei
3. 表單資料(多部分,用於檔案上傳)
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg
[二進位圖片資料]
------Boundary--
4. 純文字
Content-Type: text/plain
Hello World
5. HTML
Content-Type: text/html; charset=utf-8
<!DOCTYPE html><html>...</html>
6. XML
Content-Type: application/xml
<user><name>John</name></user>
7. 圖片
Content-Type: image/jpeg
Content-Type: image/png
Content-Type: image/gif
8. PDF
Content-Type: application/pdf
實際範例:
// JavaScript - 發送 JSON
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({name: 'John'})
});
// JavaScript - 發送表單
fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'username=john&password=123'
});
// JavaScript - 上傳檔案
const formData = new FormData();
formData.append('file', fileInput.files[0]);
fetch('/api/upload', {
method: 'POST',
body: formData // 自動設定 Content-Type: multipart/form-data
});📏 Content-Length(實體標頭)
Content-Length: 1234
說明:
- 訊息主體的位元組數
- 客戶端知道要讀取多少資料
範例:
POST /api/users HTTP/1.1
Content-Type: application/json
Content-Length: 45
{"name": "John", "email": "john@example.com"}
為什麼需要 Content-Length?
1. 知道資料大小
2. 知道何時停止讀取
3. 支援持久連線(Keep-Alive)
沒有 Content-Length 時:
- 使用分塊傳輸(Transfer-Encoding: chunked)
- 或者關閉連線表示結束🍪 Cookie / Set-Cookie
【Set-Cookie(回應標頭)】
Set-Cookie: sessionid=abc123; HttpOnly; Secure; Max-Age=3600
說明:
- 伺服器設定 Cookie
- 客戶端儲存在瀏覽器
屬性:
Set-Cookie: name=value;
Domain=.example.com; - 有效域名
Path=/; - 有效路徑
Expires=Wed, 09 Jun 2025 10:18:14 GMT; - 過期時間
Max-Age=3600; - 存活秒數(優先於 Expires)
HttpOnly; - 禁止 JavaScript 存取
Secure; - 只在 HTTPS 傳送
SameSite=Strict - 防 CSRF
範例:
【登入成功】
HTTP/1.1 200 OK
Set-Cookie: sessionid=abc123; HttpOnly; Secure; Max-Age=3600
Set-Cookie: user_pref=dark_mode; Max-Age=31536000
【Cookie(請求標頭)】
Cookie: sessionid=abc123; user_pref=dark_mode
說明:
- 客戶端發送 Cookie
- 每次請求自動帶上(相同 Domain 和 Path)
範例:
GET /api/profile HTTP/1.1
Host: api.example.com
Cookie: sessionid=abc123; user_pref=dark_mode
SameSite 屬性(防 CSRF):
1. SameSite=Strict(嚴格)
- 只在同站請求傳送
- 最安全但可能影響用戶體驗
2. SameSite=Lax(寬鬆,預設)
- 大部分跨站請求不傳送
- 頂層導航(點連結)會傳送
3. SameSite=None(無限制)
- 所有請求都傳送
- 必須搭配 Secure
範例:
Set-Cookie: sessionid=abc123; SameSite=Strict; Secure; HttpOnly💾 Cache-Control(通用標頭)
Cache-Control: max-age=3600, public
說明:
- 控制快取行為
- 減少重複請求
常見指令:
1. max-age(最大快取時間)
Cache-Control: max-age=3600
→ 快取 1 小時(3600 秒)
2. no-cache(需要重新驗證)
Cache-Control: no-cache
→ 每次都要向伺服器驗證
3. no-store(完全不快取)
Cache-Control: no-store
→ 不儲存快取(適用於敏感資料)
4. public(公共快取)
Cache-Control: public
→ 可被 CDN、代理伺服器快取
5. private(私有快取)
Cache-Control: private
→ 只能被瀏覽器快取,不能被 CDN 快取
6. must-revalidate(必須重新驗證)
Cache-Control: must-revalidate
→ 過期後必須重新驗證
組合使用:
Cache-Control: public, max-age=31536000, immutable
→ 公共快取,快取 1 年,內容不會改變
Cache-Control: private, max-age=0, must-revalidate
→ 私有快取,立即過期,必須驗證
Cache-Control: no-store
→ 完全不快取(用於敏感資料)
實際應用:
// 靜態資源(CSS、JS、圖片)
Cache-Control: public, max-age=31536000
→ 快取 1 年
// API 回應
Cache-Control: private, max-age=300
→ 快取 5 分鐘
// 敏感資料
Cache-Control: no-store
→ 不快取🔄 ETag / If-None-Match(快取驗證)
【ETag(回應標頭)】
ETag: "abc123"
說明:
- 資源的唯一識別碼
- 用於快取驗證
【第一次請求】
GET /api/articles/123 HTTP/1.1
HTTP/1.1 200 OK
ETag: "abc123"
Cache-Control: max-age=3600
{文章內容...}
【第二次請求(快取過期後)】
GET /api/articles/123 HTTP/1.1
If-None-Match: "abc123"
如果資源未改變:
HTTP/1.1 304 Not Modified
ETag: "abc123"
→ 客戶端使用快取
如果資源已改變:
HTTP/1.1 200 OK
ETag: "def456"
{新的文章內容...}
ETag 生成方式:
1. 內容的 Hash 值
ETag: "5d41402abc4b2a76b9719d911017c592"
2. 版本號
ETag: "v123"
3. 最後修改時間
ETag: "1704542400"
強 ETag vs 弱 ETag:
- 強 ETag: "abc123"(位元組完全相同)
- 弱 ETag: W/"abc123"(語義相同即可)📅 Last-Modified / If-Modified-Since(快取驗證)
【Last-Modified(回應標頭)】
Last-Modified: Mon, 06 Jan 2025 12:00:00 GMT
說明:
- 資源最後修改時間
【第一次請求】
GET /api/articles/123 HTTP/1.1
HTTP/1.1 200 OK
Last-Modified: Mon, 06 Jan 2025 12:00:00 GMT
Cache-Control: max-age=3600
{文章內容...}
【第二次請求(快取過期後)】
GET /api/articles/123 HTTP/1.1
If-Modified-Since: Mon, 06 Jan 2025 12:00:00 GMT
如果資源未修改:
HTTP/1.1 304 Not Modified
→ 客戶端使用快取
如果資源已修改:
HTTP/1.1 200 OK
Last-Modified: Mon, 06 Jan 2025 15:00:00 GMT
{新的文章內容...}
ETag vs Last-Modified:
ETag:
✅ 更精確(內容改變就改變)
✅ 支援弱驗證
❌ 需要計算(可能耗資源)
Last-Modified:
✅ 簡單(只需記錄時間)
❌ 精度只到秒(1 秒內多次修改無法區分)
❌ 可能不準確(複製檔案會改變修改時間)
建議:
- 兩者都提供(伺服器會優先使用 ETag)🔗 Location(回應標頭)
Location: https://www.example.com/new-page
說明:
- 重新導向的目標 URL
- 或新建立資源的 URL
使用場景:
1. 重新導向(3xx)
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/new-url
HTTP/1.1 302 Found
Location: https://www.example.com/temp-url
2. 建立資源(201)
POST /api/users HTTP/1.1
{"name": "John"}
HTTP/1.1 201 Created
Location: /api/users/123
{"id": 123, "name": "John"}
→ Location 指向新建立的資源🌍 Access-Control-Allow-Origin(CORS)
Access-Control-Allow-Origin: https://www.example.com
說明:
- 跨來源資源共用(CORS)
- 允許哪些域名存取
CORS Headers:
1. Access-Control-Allow-Origin
- 允許的來源
Access-Control-Allow-Origin: *
→ 允許所有域名
Access-Control-Allow-Origin: https://www.example.com
→ 只允許特定域名
2. Access-Control-Allow-Methods
- 允許的 HTTP 方法
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
3. Access-Control-Allow-Headers
- 允許的請求標頭
Access-Control-Allow-Headers: Content-Type, Authorization
4. Access-Control-Allow-Credentials
- 是否允許傳送 Cookie
Access-Control-Allow-Credentials: true
5. Access-Control-Max-Age
- 預檢請求的快取時間
Access-Control-Max-Age: 86400
→ 快取 24 小時
完整範例:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
CORS 預檢請求(Preflight):
【預檢請求】
OPTIONS /api/users HTTP/1.1
Origin: https://www.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
【預檢回應】
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type
【實際請求】
POST /api/users HTTP/1.1
Origin: https://www.example.com
Content-Type: application/json
{"name": "John"}🔒 安全相關 Headers
Strict-Transport-Security(HSTS)
Strict-Transport-Security: max-age=31536000; includeSubDomains
說明:
- 強制使用 HTTPS
- 防止降級攻擊
範例:
HTTP/1.1 200 OK
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000:有效期 1 年
includeSubDomains:包含所有子域名
preload:加入瀏覽器預載清單
效果:
- 瀏覽器記住這個設定
- 未來所有請求自動用 HTTPS
- 即使用戶輸入 http://,也會自動轉為 https://X-Content-Type-Options
X-Content-Type-Options: nosniff
說明:
- 防止 MIME 類型嗅探
- 強制使用宣告的 Content-Type
為什麼需要?
瀏覽器可能猜測檔案類型:
- 伺服器說是 text/plain
- 但瀏覽器發現內容像 JavaScript
- 瀏覽器可能當成 JavaScript 執行(危險)
範例:
HTTP/1.1 200 OK
Content-Type: text/plain
X-Content-Type-Options: nosniff
console.log('evil code');
→ 瀏覽器不會執行,只會顯示為文字X-Frame-Options
X-Frame-Options: DENY
說明:
- 防止點擊劫持(Clickjacking)
- 控制頁面是否可被嵌入 iframe
選項:
1. DENY - 完全禁止
X-Frame-Options: DENY
2. SAMEORIGIN - 只允許同源
X-Frame-Options: SAMEORIGIN
3. ALLOW-FROM - 允許特定來源(已棄用)
X-Frame-Options: ALLOW-FROM https://www.example.com
替代方案(推薦):
Content-Security-Policy: frame-ancestors 'self'
Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors https://www.example.comContent-Security-Policy(CSP)
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com
說明:
- 內容安全政策
- 防止 XSS 攻擊
常用指令:
1. default-src(預設來源)
Content-Security-Policy: default-src 'self'
→ 所有資源只能從同源載入
2. script-src(JavaScript 來源)
Content-Security-Policy: script-src 'self' https://cdn.example.com
→ JavaScript 只能從自己或 CDN 載入
3. style-src(CSS 來源)
Content-Security-Policy: style-src 'self' 'unsafe-inline'
4. img-src(圖片來源)
Content-Security-Policy: img-src 'self' data: https:
5. frame-ancestors(iframe 嵌入)
Content-Security-Policy: frame-ancestors 'none'
完整範例:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.googleapis.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'💻 實戰範例
Python Flask 設定 Headers
from flask import Flask, jsonify, make_response
from functools import wraps
import hashlib
from datetime import datetime, timedelta
app = Flask(__name__)
# 設定安全 Headers
@app.after_request
def set_security_headers(response):
# HSTS - 強制 HTTPS
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
# 防止 MIME 嗅探
response.headers['X-Content-Type-Options'] = 'nosniff'
# 防止點擊劫持
response.headers['X-Frame-Options'] = 'DENY'
# CSP
response.headers['Content-Security-Policy'] = "default-src 'self'"
# 其他安全標頭
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
return response
# 設定 CORS
@app.after_request
def set_cors_headers(response):
response.headers['Access-Control-Allow-Origin'] = 'https://www.example.com'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
# 設定快取
@app.route('/api/static-data')
def static_data():
response = make_response(jsonify({"data": "static"}))
# 快取 1 年
response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
# ETag
content = '{"data": "static"}'
etag = hashlib.md5(content.encode()).hexdigest()
response.headers['ETag'] = f'"{etag}"'
return response
# 快取驗證
@app.route('/api/articles/<int:article_id>')
def get_article(article_id):
# 模擬取得文章
article = {"id": article_id, "title": "Article Title", "updated_at": "2025-01-06T12:00:00Z"}
# 檢查 If-None-Match
client_etag = request.headers.get('If-None-Match')
# 計算 ETag
import json
content = json.dumps(article)
server_etag = hashlib.md5(content.encode()).hexdigest()
server_etag = f'"{server_etag}"'
if client_etag == server_etag:
# 資源未改變
return '', 304, {'ETag': server_etag}
# 資源已改變,回傳新內容
response = make_response(jsonify(article))
response.headers['ETag'] = server_etag
response.headers['Cache-Control'] = 'private, max-age=300'
return response
# 認證
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({"error": "Authentication required"}), 401, {
'WWW-Authenticate': 'Bearer realm="API"'
}
if not auth_header.startswith('Bearer '):
return jsonify({"error": "Invalid token format"}), 401
token = auth_header[7:] # 移除 "Bearer "
# 驗證 token...
if not verify_token(token):
return jsonify({"error": "Invalid token"}), 401
return f(*args, **kwargs)
return decorated
@app.route('/api/profile')
@require_auth
def get_profile():
return jsonify({"name": "John", "email": "john@example.com"})
# 設定 Cookie
@app.route('/api/login', methods=['POST'])
def login():
# 驗證用戶...
response = make_response(jsonify({"status": "success"}))
# 設定 Session Cookie
response.set_cookie(
'sessionid',
'abc123',
max_age=3600, # 1 小時
secure=True, # 只在 HTTPS 傳送
httponly=True, # 禁止 JavaScript 存取
samesite='Strict' # 防 CSRF
)
return response
if __name__ == '__main__':
app.run(ssl_context='adhoc') # 使用 HTTPS✅ 重點回顧
Headers 分類:
- 通用標頭 - Connection, Date, Cache-Control
- 請求標頭 - Host, User-Agent, Accept, Authorization
- 回應標頭 - Server, Set-Cookie, Location
- 實體標頭 - Content-Type, Content-Length
常用 Headers:
- Host - 目標伺服器(必須)
- User-Agent - 客戶端資訊
- Authorization - 認證資訊(Basic, Bearer, OAuth)
- Content-Type - 內容類型
- Cookie / Set-Cookie - 狀態管理
- Cache-Control - 快取控制
- ETag / If-None-Match - 快取驗證
- Location - 重新導向或新資源位置
安全 Headers:
- Strict-Transport-Security - 強制 HTTPS
- X-Content-Type-Options - 防止 MIME 嗅探
- X-Frame-Options - 防止點擊劫持
- Content-Security-Policy - 防止 XSS
CORS Headers:
- Access-Control-Allow-Origin - 允許的來源
- Access-Control-Allow-Methods - 允許的方法
- Access-Control-Allow-Headers - 允許的標頭
快取相關:
- Cache-Control - 快取策略
- ETag - 資源識別碼
- Last-Modified - 最後修改時間
最佳實踐:
- ✅ 設定安全 Headers(HSTS, CSP, X-Frame-Options)
- ✅ 使用 HTTPS(Secure Cookie)
- ✅ 設定適當的快取策略
- ✅ 正確處理 CORS
- ✅ 使用 ETag 減少資料傳輸
上一篇: 02-4. HTTP 狀態碼完整指南 下一篇: 03-1. HTTPS 是什麼?
最後更新:2025-01-06