Skip to content

HTTP 速查表

用于客户端-服务器通信的超文本传输协议。

01

入门

HTTP 请求与响应

HTTP 是请求-响应协议。客户端发送请求(方法、路径、头、可选的请求体),服务器以状态、头和响应体回应。HTTP/1.1 基于文本,HTTP/2 和 HTTP/3 使用二进制分帧。

http
# HTTP Request
GET /api/users HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: application/json

# HTTP Response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 45

{"id": 1, "name": "Alice", "email": "[email protected]"}

URL 结构

URL 标识资源。scheme 选择协议。host 通过 DNS 解析。省略时使用默认端口。fragment(#) 仅在客户端使用,在发送到服务器之前会被剥离。查询字符串通常用于过滤/分页。

http
# Generic URL format
scheme://userinfo@host:port/path?query#fragment

# Example
https://user:[email protected]:8443/api/v1/users?page=2#section

# Components
# scheme    - https (protocol)
# userinfo  - user:pass (rare, deprecated)
# host      - example.com (domain or IP)
# port      - 8443 (default: 80 for http, 443 for https)
# path      - /api/v1/users (resource location)
# query     - page=2 (key=value pairs, & separated)
# fragment  - section (client-only, never sent to server)

HTTP 版本概览

HTTP/0.9 是一个最小的原型。HTTP/1.0 增加了头和内容类型,但每个请求都打开一个新连接。HTTP/1.1 引入了持久连接、必需的 Host 头(启用虚拟主机)和管道化。HTTP/2 增加了二进制多路复用。HTTP/3 运行在 QUIC 之上,消除了传输层的队头阻塞。

http
# HTTP/0.9 (1991) - single GET, no headers
GET /page.html

# HTTP/1.0 (1996) - headers, status codes, multiple types
GET /page.html HTTP/1.0

# HTTP/1.1 (1997) - persistent connections, Host header, pipelining
GET /page.html HTTP/1.1
Host: example.com

# HTTP/2 (2015) - binary, multiplexing, header compression
# (negotiated via ALPN or h2c upgrade)

# HTTP/3 (2022) - over QUIC (UDP-based)
# (negotiated via Alt-Svc header)

连接生命周期

HTTP/1.1 默认保持 TCP 连接打开(keep-alive),避免了重复握手的开销。多个请求可以通过一个连接发送。Connection: close 头标志最后一个请求。HTTP/2 在单个连接上多路复用多个流。Keep-Alive 超时和最大值可在服务器上配置。

http
# HTTP/1.1 persistent connection (keep-alive)
GET /page1 HTTP/1.1
Host: example.com
Connection: keep-alive

# (server responds, connection stays open)
HTTP/1.1 200 OK
Content-Length: 128
Connection: keep-alive

# Client reuses same TCP connection for next request
GET /page2 HTTP/1.1
Host: example.com

# Closing the connection
GET /final HTTP/1.1
Host: example.com
Connection: close

消息格式

HTTP 消息以起始行(请求行或状态行)开始,后跟头、空行和可选的请求体。行使用 CRLF 行尾。头名称不区分大小写。空行是必需的,标志请求体的开始。请求体可以是任意长度(分块)或固定长度(Content-Length)。

http
# Request format
<METHOD> <PATH> <VERSION>

<Header-Name>: <value>

...



<optional body>

# Response format
<VERSION> <STATUS> <REASON>

<Header-Name>: <value>

...



<optional body>

# Lines end with CRLF (
). Headers are case-insensitive.
# A blank line (
) separates headers from body.

Telnet / 原始 HTTP 调试

可以使用 nc 或 telnet(端口 80)或 openssl s_client(HTTPS)在 TCP 上直接发送原始 HTTP。这对调试非常有价值。在 HTTP/1.1+ 上必须包含 Host 头。两个空行终止请求。curl -v 显示包括头在内的网络格式对话。

http
# Manual HTTP request via netcat/nc
nc example.com 80
GET / HTTP/1.1
Host: example.com

# (press Enter twice to send blank line)

# Using openssl for HTTPS
openssl s_client -connect example.com:443
GET / HTTP/1.1
Host: example.com

# curl with --http1.1 to force version
curl -v --http1.1 https://example.com
curl -v --http2 https://example.com
curl -v --http3 https://example.com
02

HTTP 方法

GET — 获取资源

GET 检索资源的表示。它必须是安全的(无副作用)且幂等的(可重复)。GET 请求可缓存、可加入书签。虽然规范允许请求体,但不建议使用——代理和 CDN 可能会剥离它。改用查询参数进行过滤。

http
# Basic GET
GET /api/users/42 HTTP/1.1
Host: api.example.com
Accept: application/json

# GET with query parameters
GET /api/users?role=admin&active=true HTTP/1.1
Host: api.example.com

# GET should be safe & idempotent
# - safe: no server state change
# - idempotent: repeating yields same result
# - cacheable: yes
# - body allowed but discouraged (some clients/servers reject)

POST — 创建资源

POST 提交数据以进行处理。它用于创建由服务器分配 URI 的新资源(例如 /api/users → /api/users/43)。POST 既不安全也不幂等——提交两次可能创建两个资源。201 Created 响应应包含指向新资源的 Location 头。

http
# POST creating a new resource
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 52

{"name": "Bob", "email": "[email protected]", "age": 30}

# Response typically 201 Created with Location header
HTTP/1.1 201 Created
Location: /api/users/43
Content-Type: application/json

{"id": 43, "name": "Bob"}

# POST is neither safe nor idempotent.
# Repeating may create duplicate resources.

PUT — 替换资源

PUT 用请求体替换目标 URI 处的整个资源。它是幂等的——重复相同的 PUT 产生相同的最终状态。PUT 针对特定 URI(例如 /users/43),而 POST 针对集合。如果在 PUT 中省略某个字段,通常会将其删除(完全替换)。

http
# PUT replaces the entire resource at the given URI
PUT /api/users/43 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 60

{"id": 43, "name": "Robert", "email": "[email protected]", "age": 31}

# Idempotent: sending the same PUT again yields the same state.
# If the resource doesn't exist, PUT may create it (server-dependent).

# Response
HTTP/1.1 200 OK
# or 204 No Content if no body returned

PATCH — 部分更新

PATCH 对资源应用部分更新,与 PUT 完全替换不同。最简单的形式是 JSON Merge Patch(RFC 7396):仅发送要更改的字段。JSON Patch(RFC 6902)使用操作数组(add、remove、replace、move、copy、test)。PATCH 默认不幂等,但 merge-patch 是幂等的。

http
# PATCH applies partial modifications
PATCH /api/users/43 HTTP/1.1
Host: api.example.com
Content-Type: application/merge-patch+json
Content-Length: 22

{"email": "[email protected]"}

# Only the email field is updated; other fields remain.

# JSON Patch (RFC 6902) - structured operations
PATCH /api/users/43 HTTP/1.1
Content-Type: application/json-patch+json

[
  {"op": "replace", "path": "/email", "value": "[email protected]"},
  {"op": "remove", "path": "/age"}
]

DELETE — 删除资源

DELETE 删除目标资源。它是幂等的——两次删除同一资源应使服务器保持相同状态。常见的成功代码是 204(无请求体)、200(带有确认的请求体)或 202(异步删除)。大多数 API 需要认证。对于缺失的资源是否返回 404 存在争议——204 对幂等更安全。

http
# Delete a resource
DELETE /api/users/43 HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>

# Successful responses:
HTTP/1.1 204 No Content        # most common
HTTP/1.1 200 OK                 # if body returned
HTTP/1.1 202 Accepted           # async deletion queued

# Idempotent: deleting an already-deleted resource
# should still return 204 (or 404 — debatable).

HEAD 与 OPTIONS

HEAD 与 GET 相同,但服务器只返回头(无请求体)——适合在不下载请求体的情况下检查 Content-Length、Content-Type 或 Last-Modified。OPTIONS 询问允许哪些方法(Allow 头),并被浏览器用于 CORS 预检。两者都是安全且幂等的。

http
# HEAD: like GET but returns headers only (no body)
HEAD /api/users/42 HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 128
# (no body)

# OPTIONS: describe communication options
OPTIONS /api/users HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Access-Control-Allow-Methods: GET, POST
03

状态码

1xx 信息性

1xx 代码是信息性的,表示请求已收到,处理继续进行。100 Continue 让客户端检查是否发送大型请求体(与 Expect: 100-continue 头配合使用)。101 Switching Protocols 启用 WebSocket。103 Early Hints 让服务器在最终响应准备好之前提示浏览器预加载资源。

http
# 100 Continue - server received headers, client may send body
POST /upload HTTP/1.1
Host: example.com
Expect: 100-continue
Content-Length: 1048576

# Server replies:
HTTP/1.1 100 Continue
# (client now sends the request body)

# 101 Switching Protocols - used for WebSocket upgrade
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

# 103 Early Hints - preload resources before final response
HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style

2xx 成功

2xx 代码表示成功。200 OK 是通用成功。201 Created 表示创建了新资源(带有 Location 头)。202 Accepted 表示请求已排队进行异步处理(例如长任务)。204 No Content 表示成功但无请求体——常用于 DELETE/PUT。206 Partial Content 返回资源的一部分(Range 请求)。

http
# 200 OK - standard success
HTTP/1.1 200 OK

# 201 Created - new resource created (POST/PUT)
HTTP/1.1 201 Created
Location: /api/users/43

# 202 Accepted - request queued for async processing
HTTP/1.1 202 Accepted

# 204 No Content - success, no body (DELETE, PUT)
HTTP/1.1 204 No Content

# 206 Partial Content - Range request result
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/2048

3xx 重定向

3xx 代码表示重定向。301/308 是永久的(可缓存,SEO 链接权重传递);302/307 是临时的。关键区别:301 和 302 历史上允许 POST→GET 转换(导致数据丢失),而 307 和 308 严格保留方法。304 Not Modified 在缓存有效时的条件请求中返回——不发送请求体。

http
# 301 Moved Permanently - cacheable, change bookmarks
HTTP/1.1 301 Moved Permanently
Location: https://newsite.com/page

# 302 Found - temporary redirect (don't update bookmarks)
HTTP/1.1 302 Found
Location: /login

# 304 Not Modified - cache is still valid (conditional GET)
HTTP/1.1 304 Not Modified
ETag: "abc123"

# 307 Temporary Redirect - preserves method (POST stays POST)
HTTP/1.1 307 Temporary Redirect
Location: /new-endpoint

# 308 Permanent Redirect - permanent, preserves method
HTTP/1.1 308 Permanent Redirect
Location: https://newsite.com/api

4xx 客户端错误

4xx 代码表示客户端出错。400 Bad Request 是格式错误的输入。401 表示无/无效认证(必须包含 WWW-Authenticate)。403 表示已认证但缺少权限。404 是缺失资源。429 表示速率限制(包含 Retry-After)。其他常见代码:405 Method Not Allowed、409 Conflict、422 Unprocessable Entity。

http
# 400 Bad Request - malformed syntax
HTTP/1.1 400 Bad Request
Content-Type: application/json

{"error": "Invalid JSON in request body"}

# 401 Unauthorized - authentication required
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"

# 403 Forbidden - authenticated but not allowed
HTTP/1.1 403 Forbidden

# 404 Not Found - resource doesn't exist
HTTP/1.1 404 Not Found

# 429 Too Many Requests - rate limit hit
HTTP/1.1 429 Too Many Requests
Retry-After: 60

5xx 服务器错误

5xx 代码表示服务器失败。500 是未处理错误的统称(检查服务器日志)。501 表示服务器未实现该方法。502 表示代理/网关从上游收到错误响应。503 表示临时过载(使用 Retry-After)。504 表示上游超时。客户端可以用退避策略重试 502/503/504。

http
# 500 Internal Server Error - generic server failure
HTTP/1.1 500 Internal Server Error

# 501 Not Implemented - server doesn't support the method
HTTP/1.1 501 Not Implemented

# 502 Bad Gateway - upstream server returned invalid response
HTTP/1.1 502 Bad Gateway

# 503 Service Unavailable - temporary overload or maintenance
HTTP/1.1 503 Service Unavailable
Retry-After: 300

# 504 Gateway Timeout - upstream didn't respond in time
HTTP/1.1 504 Gateway Timeout

状态码模式

一致的状态码映射使 API 可预测。使用 2xx 表示成功,并注意语义精确性(201 表示创建,204 表示无请求体)。使用 4xx 表示客户端错误,并选择最具体的代码(409 表示冲突,422 表示验证失败)。401 与 403 是最常见的混淆:401 = '你是谁?',403 = '我知道你是谁,但你不能做这件事'。

http
# REST API common mapping
GET     /users      -> 200
POST    /users      -> 201 (Created)
GET     /users/42   -> 200 | 404
PUT     /users/42   -> 200 | 201 (if created) | 404
PATCH   /users/42   -> 200 | 404
DELETE  /users/42   -> 204 | 404

# Validation errors
400 -> malformed JSON, missing required field
409 -> duplicate resource / version conflict
422 -> semantic validation failure (some APIs)

# Auth flow
401 -> no/invalid token (login required)
403 -> logged in but lacking permission
404 -> hide existence from unauthorized users

# Use 418 I'm a teapot for fun (RFC 2324 joke).
04

请求头

常见请求头

常见请求头传达客户端身份(User-Agent)、所需响应格式(Accept 系列)、连接控制(Connection)、认证(Authorization)、状态(Cookie)和来源(Referer/Origin)。Host 在 HTTP/1.1+ 中是必需的,用于虚拟主机。Content-Type 和 Content-Length 描述任何请求体。

http
GET /api/data HTTP/1.1
Host: api.example.com          # target host (mandatory in HTTP/1.1)
User-Agent: Mozilla/5.0        # client identity
Accept: application/json        # desired response format
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Authorization: Bearer <token>
Cookie: session=abc123; theme=dark
Referer: https://example.com/
Origin: https://example.com
Content-Type: application/json
Content-Length: 42

Accept 头(内容协商)

Accept 系列驱动内容协商。质量值(q,0-1)表示偏好——值越高越偏好。*/* 是通配符,匹配所有类型。服务器从其支持的类型中选择最佳匹配;如果都不匹配,则返回 406 Not Acceptable(尽管大多数服务器会回退到默认值而不是 406)。Brotli(br)和 zstd 提供比 gzip 更好的压缩。

http
# Accept: desired media types with quality (q) values
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8

# Accept-Language: preferred languages
Accept-Language: en-US,en;q=0.9, zh-CN;q=0.8, fr;q=0.7

# Accept-Encoding: accepted compression algorithms
Accept-Encoding: gzip, deflate, br, zstd;q=0.9

# q=1.0 is default. Higher q = more preferred.
# */* matches any type.
# Server responds with 406 Not Acceptable if it can't satisfy.

Authorization 与认证头

Authorization 携带凭据。Basic auth 在每个请求上发送 base64 编码的用户名:密码(始终使用 HTTPS)。Bearer 令牌是不透明字符串(通常是 JWT),用于 OAuth 2.0。自定义认证方案(X-API-Key)在 API 网关中很常见。Basic auth 的 base64 是编码,不是加密——没有 TLS 它不提供任何安全性。

http
# Basic Authentication (base64 of user:pass)
Authorization: Basic dXNlcjpwYXNz
# dXNlcjpwYXNz = base64("user:pass")

# Bearer Token (OAuth 2.0 / JWT)
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

# API Key in header (custom)
X-API-Key: abc123def456

# Proxy authentication
Proxy-Authorization: Basic dXNlcjpwYXNz

# WARNING: Basic auth sends credentials on every request.
# Always use HTTPS to prevent eavesdropping.

User-Agent 与 Referer

User-Agent 标识客户端,但大多数浏览器为了向后兼容(历史嗅探)伪造 Mozilla/5.0。Referer(规范中拼写错误,从未更正)告诉服务器哪个页面链接到此请求——用于分析和防盗链保护。Origin 是 scheme+host+port,由浏览器在 CORS 请求和 POST 请求中发送。

http
# User-Agent identifies the client software
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

# Common browsers prepend "Mozilla/5.0" for historical compatibility
# (servers used to serve different content to different browsers)

# Referer: page that linked to this request
Referer: https://example.com/search?q=hello

# Origin: scheme + host + port (used by CORS)
Origin: https://example.com

# Note: "Referer" is the official spelling (misspelled in the original spec)

条件请求头

条件头启用高效缓存和并发控制。If-None-Match(与 ETag 一起)和 If-Modified-Since(与 Last-Modified 一起)使 GET 成为条件请求——如果缓存有效,服务器返回 304。If-Match 和 If-Unmodified-Since 保护 PUT/PATCH 免受丢失更新(乐观并发)。If-Range 启用原子范围请求。

http
# Conditional GET - only fetch if changed
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT
If-None-Match: "abc123"

# If-Match / If-Unmodified-Since - for safe PUT/PATCH
If-Match: "abc123"
If-Unmodified-Since: Wed, 21 Oct 2023 07:28:00 GMT

# If-Range - for range requests (all-or-nothing)
If-Range: "abc123"
Range: bytes=0-1023

# Typical flow:
# 1. Server sends ETag/Last-Modified with response
# 2. Client sends If-None-Match/If-Modified-Since on next request
# 3. Server replies 304 if unchanged, 200 if changed

自定义头与 X- 头

自定义头历史上使用 X- 前缀,但 RFC 6648 在 2012 年废弃了此约定。新头应去掉 X-(例如 Request-ID 而不是 X-Request-ID)。X-Forwarded-For 和 X-Forwarded-Proto 通过代理携带客户端信息(注意:可被伪造)。标准化替代方案是 Forwarded(RFC 7239)。Traceparent/Tracestate 启用分布式追踪(W3C)。

http
# X- prefixed headers (deprecated convention, but widely used)
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
X-Correlation-ID: abc-123
X-Forwarded-For: 203.0.113.1, 70.41.3.18
X-Forwarded-Proto: https
X-Real-IP: 203.0.113.1
X-RateLimit-Remaining: 42

# Modern recommendation: avoid X- prefix (RFC 6648)
# Use a namespace instead:
Request-ID: 550e8400-e29b-41d4-a716-446655440000
Traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01

# Forwarded (RFC 7239) - standardized proxy info
Forwarded: for=203.0.113.1; proto=https; host=example.com
05

响应头

常见响应头

常见响应头描述服务器(Server)、响应体(Content-Type、Content-Length)、连接控制(Connection)、缓存(Cache-Control、ETag、Last-Modified)、内容协商(Vary)、状态(Set-Cookie)和 CORS(Access-Control-*)。Date 是必需的,使用 GMT 的 RFC 1123 格式。

http
HTTP/1.1 200 OK
Server: nginx/1.25.0
Date: Wed, 21 Oct 2023 07:28:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 128
Connection: keep-alive
Cache-Control: public, max-age=3600
ETag: "abc123"
Last-Modified: Mon, 20 Oct 2023 10:00:00 GMT
Vary: Accept-Encoding
Set-Cookie: session=xyz; HttpOnly; Secure
Access-Control-Allow-Origin: https://example.com

内容头

Content-* 头描述请求体。Content-Type 是 MIME 类型,至关重要。Content-Length 给出准确的字节计数(除非使用分块编码,否则必需)。Content-Encoding 指示压缩(gzip、br)。Content-Disposition 强制下载并指定文件名。Content-Range 伴随 206 Partial Content 响应。

http
# Body metadata
Content-Type: application/json; charset=utf-8
Content-Length: 128                # exact byte count
Content-Encoding: gzip             # applied encoding
Content-Language: en-US
Content-Location: /api/users/42.json
Content-Disposition: attachment; filename="report.pdf"
Content-Range: bytes 0-1023/2048   # for partial content

# For chunked transfer (no Content-Length known upfront)
Transfer-Encoding: chunked

# MIME type format: type/subtype; parameter=value
# e.g., text/html; charset=UTF-8

安全头

安全头加固浏览器以抵御常见攻击。HSTS 强制使用 HTTPS 并防止 SSL 剥离。X-Content-Type-Options: nosniff 防止 MIME 嗅探。X-Frame-Options(或 CSP frame-ancestors)阻止点击劫持。CSP 是最强大的——它控制哪些资源可以加载和执行。Referrer-Policy 限制泄露的 referer 信息。在每个响应上设置这些头。

http
# Force HTTPS for 6 months (incl. subdomains)
Strict-Transport-Security: max-age=15768000; includeSubDomains; preload

# Prevent MIME-type sniffing
X-Content-Type-Options: nosniff

# Prevent clickjacking
X-Frame-Options: DENY
# Or modern CSP frame-ancestors:
Content-Security-Policy: frame-ancestors 'none'

# Referrer policy
Referrer-Policy: strict-origin-when-cross-origin

# Permissions policy (feature lockdown)
Permissions-Policy: geolocation=(), camera=()

# Full Content-Security-Policy
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com

CORS 响应头

CORS(跨源资源共享)头让服务器选择性地接受跨源请求。任何跨源读取都需要 Access-Control-Allow-Origin;可以是特定源或 *。如果涉及凭据,需要 Allow-Credentials: true 且特定源(不能用通配符)。预检(OPTIONS)响应包括 Allow-Methods/Headers。Expose-Headers 让 JS 读取非默认响应头。

http
# Allow a specific origin
Access-Control-Allow-Origin: https://example.com
# Or wildcard (but credentials won't work with *)
Access-Control-Allow-Origin: *

# Allow credentials (cookies, Authorization)
Access-Control-Allow-Credentials: true

# Allowed methods (preflight response)
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

# Allowed headers (preflight response)
Access-Control-Allow-Headers: Content-Type, Authorization

# How long browser can cache preflight (seconds)
Access-Control-Max-Age: 86400

# Headers the JS can read from the response
Access-Control-Expose-Headers: X-Request-ID, X-Total-Count

缓存头

Cache-Control 是现代(HTTP/1.1)缓存头,带有指令:public/private(谁可以缓存)、max-age(以秒为单位的鲜度)、no-cache(必须重新验证)、no-store(从不缓存)、immutable(URL 从不更改)。ETag 和 Last-Modified 是与条件请求一起使用的验证器。Expires(HTTP/1.0)是后备方案。Age 告诉响应已缓存多长时间。

http
# Cache-Control directives (HTTP/1.1 - preferred)
Cache-Control: public, max-age=3600
Cache-Control: private, max-age=600
Cache-Control: no-cache              # revalidate before use
Cache-Control: no-store              # never cache
Cache-Control: must-revalidate
Cache-Control: immutable

# Validators
ETag: "abc123"                       # opaque version tag
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

# HTTP/1.0 fallbacks (deprecated)
Expires: Wed, 21 Oct 2023 08:28:00 GMT
Pragma: no-cache

# Age: time a response has been in a cache (seconds)
Age: 120
06

Cookie 与 Set-Cookie

设置 Cookie(Set-Cookie)

Set-Cookie 指示浏览器存储 cookie。每个属性控制范围和生命周期:Domain/Path 限制发送位置;Max-Age(秒)或 Expires 设置生命周期;Secure 限制为 HTTPS;HttpOnly 阻止 JS 访问;SameSite 控制跨站发送。要删除 cookie,使用相同的 Path/Domain 设置 Max-Age=0。

http
# Basic cookie
HTTP/1.1 200 OK
Set-Cookie: session=abc123

# With attributes
Set-Cookie: session=abc123; Path=/; Domain=example.com; Max-Age=3600; HttpOnly; Secure; SameSite=Lax

# Multiple cookies = multiple Set-Cookie headers
Set-Cookie: session=abc123; HttpOnly; Secure
Set-Cookie: theme=dark; Max-Age=86400
Set-Cookie: lang=en-US; Path=/

# Deleting a cookie: set Max-Age=0 or expired Expires
Set-Cookie: session=; Max-Age=0; Path=/

Cookie 属性

Cookie 属性控制安全性和范围。Secure + HttpOnly + SameSite 是现代基线。SameSite=Lax 是浏览器默认值——它允许顶级导航上的 cookie,但在第三方上下文中阻止它们(击败大多数 CSRF)。__Host- 前缀强制 Path=/、无 Domain 和 Secure——防止子域 cookie 注入。__Secure- 需要 Secure。

http
# Security attributes
Secure           # only sent over HTTPS
HttpOnly         # not accessible via document.cookie (XSS protection)
SameSite=Strict  # never sent on cross-site requests
SameSite=Lax     # sent on top-level navigation (default in modern browsers)
SameSite=None    # sent cross-site (requires Secure)

# Scope attributes
Domain=example.com    # visible to subdomains too
Path=/                # visible to all paths
Path=/api             # only /api and below

# Lifetime attributes
Max-Age=3600          # seconds (preferred)
Expires=Wed, 21 Oct 2025 07:28:00 GMT  # absolute date

# __Host- and __Secure- prefixes (extra protection)
Set-Cookie: __Host-session=abc; Path=/; Secure; HttpOnly; SameSite=Lax

发送 Cookie(Cookie)

Cookie 请求头将所有匹配的 cookie 作为 name=value 对发送(无属性)。浏览器在每个匹配的请求上自动发送 cookie。对于带 cookie 的跨源 fetch,客户端必须设置 credentials: 'include' 且服务器必须用 Access-Control-Allow-Credentials: true 回显特定源(不是 *)。

http
# Browser sends stored cookies back to the server
GET /api/profile HTTP/1.1
Host: example.com
Cookie: session=abc123; theme=dark; lang=en-US

# Format: name=value pairs separated by "; "
# No attributes are sent back - only name=value

# Server reads cookies (Express.js example)
# const sessionId = req.headers.cookie
#   .split(';').find(c => c.trim().startsWith('session='))
#   .split('=')[1];

# Cross-site requests (with credentials)
fetch('https://api.example.com/data', {
  credentials: 'include'  # sends cookies cross-origin
});
# Server must respond with:
# Access-Control-Allow-Origin: https://app.example.com (specific, no *)
# Access-Control-Allow-Credentials: true

Cookie 与令牌

Cookie 由浏览器存储并自动发送;令牌(JWT)存储在 JS 中并手动发送。Cookie 易受 CSRF 攻击(通过 SameSite 缓解)。令牌易受 XSS 攻击(通过短生命周期和安全存储缓解)。Cookie 适合同源 Web 应用;令牌适合 API 和 SPA。两者的最佳实践:将刷新令牌存储在 HttpOnly cookie 中,访问令牌存储在内存中。

http
# Cookie-based auth (stateful server-side session)
POST /login HTTP/1.1
Host: example.com
Content-Type: application/json

{"user": "alice", "password": "secret"}

# Server response
HTTP/1.1 200 OK
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax

# Subsequent requests carry cookie automatically
GET /api/profile HTTP/1.1
Cookie: session=abc123

# Token-based auth (stateless, e.g., JWT)
# Server response
HTTP/1.1 200 OK
Content-Type: application/json

{"token": "eyJhbGc..."}

# Subsequent requests carry token in header (manual)
GET /api/profile HTTP/1.1
Authorization: Bearer eyJhbGc...

第三方 Cookie 与 SameSite

第三方 cookie 由与用户访问页面不同的域设置——长期用于跟踪和广告。它们需要 SameSite=None+Secure。现代浏览器(Chrome、Firefox、Safari)正在逐步淘汰第三方 cookie。替代方案包括 CHIPS(分区 cookie,按顶级站点划分)、Storage Access API 和服务器端跟踪。ITP(Safari)直接阻止它们。

http
# First-party cookie: domain matches the page
Set-Cookie: session=abc; Domain=example.com
# Visiting example.com -> sent to example.com (first-party)

# Third-party cookie: embedded in another site's page
# e.g., analytics.com cookie loaded via <img> on shop.com
Set-Cookie: tracker=xyz; Domain=analytics.com; SameSite=None; Secure

# SameSite behavior:
# Strict  -> NOT sent on shop.com -> analytics.com requests
# Lax     -> NOT sent on embedded cross-site requests
#            (sent on top-level navigation)
# None    -> sent cross-site (requires Secure)

# Browsers are phasing out third-party cookies (2024+).
# Alternatives: Partitioned cookies (CHIPS)
Set-Cookie: tracker=xyz; Partitioned; Secure; SameSite=None
07

认证

基本认证

Basic auth 在每个请求上发送 base64 编码的凭据。它简单且被普遍支持,但如果没有 HTTPS 则不提供任何安全性——base64 很容易被解码。WWW-Authenticate 头挑战客户端(浏览器显示原生登录对话框)。Basic auth 在现代 Web 应用中很少见,但在 API 令牌和内部工具中很常见。

http
# Basic auth: base64(user:pass) sent on every request
GET /api/data HTTP/1.1
Host: example.com
Authorization: Basic dXNlcjpwYXNz

# dXNlcjpwYXNz = base64("user:pass")
# In curl: curl -u user:pass https://example.com/api/data

# Server challenge (401 with WWW-Authenticate)
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Restricted Area", charset="UTF-8"

# WARNING: base64 is encoding, NOT encryption.
# Always pair Basic auth with HTTPS.

Bearer 令牌(OAuth 2.0)

Bearer 令牌是作为授权证明呈现的不透明字符串。服务器只需验证它们——如果是自包含的(JWT)则是无状态的,或在存储中查找。OAuth 2.0 定义了获取令牌的流程(授权码、客户端凭据、刷新令牌)。始终通过 HTTPS 发送 Bearer 令牌。令牌具有有限的生命周期;使用刷新令牌获取新令牌。

http
# Bearer token: opaque string proving authorization
GET /api/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer SlAV32hkKG...

# Token obtained from OAuth 2.0 flow:
POST /oauth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=alice&password=secret&client_id=...

# Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "access_token": "SlAV32hkKG...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "GmR..."
}

JWT 结构

JWT 由点分隔的三部分 base64url 编码组成:头部(算法)、载荷(声明)和签名。签名证明令牌未被篡改。常见声明:sub(主题)、iat(签发时间)、exp(过期时间)、iss(签发者)、aud(受众)。载荷未加密——切勿在其中放置机密。签名算法:HS256(HMAC)、RS256(RSA)、ES256(ECDSA)。

http
# JWT = header.payload.signature (base64url-encoded)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

# Decoded:
# Header
{"alg": "HS256", "typ": "JWT"}

# Payload (claims)
{
  "sub": "1234567890",        # subject (user id)
  "name": "Alice",
  "iat": 1516239022,          # issued at
  "exp": 1516242622,          # expiration
  "iss": "example.com",       # issuer
  "aud": "api.example.com"    # audience
}

# Signature (HMAC-SHA256 of header.payload with secret)
HMAC-SHA256(secret, "eyJhbGc...")

JWT 验证

JWT 验证:验证签名(捕获篡改),然后检查声明(exp、iss、aud)。exp 最重要——用 401 拒绝过期令牌。JWT 是无状态的:一旦签发,在过期之前无法撤销。对于撤销,使用短生命周期访问令牌(5-15 分钟)+ 服务器端存储的刷新令牌,或维护黑名单(破坏无状态性)。

http
# Server-side JWT validation steps:
# 1. Parse three parts (header.payload.signature)
# 2. Verify signature using secret (HS256) or public key (RS256)
# 3. Check claims:
#    - exp (must be in future)
#    - iat (must be in past, optional)
#    - iss (must match expected issuer)
#    - aud (must match expected audience)
# 4. Check revocation list (if using short-lived + refresh)

# Common errors:
# 401 - invalid signature, expired, wrong issuer
# 401 - "Bearer" prefix missing
# 403 - valid token but lacking permission (scope)

# Token expired response
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="The token has expired"

OAuth 2.0 授权码流程

授权码流程是服务器端 Web 应用的标准。第 1 步将用户重定向到认证服务器。第 2 步将短期代码返回到重定向 URI。第 3 步服务器对服务器交换代码(加上 client_secret)获取令牌。PKCE(代码交换证明密钥)用代码挑战/验证器对替换密钥——对于无法安全存储密钥的 SPA 和移动应用至关重要。

http
# Step 1: Redirect user to authorization endpoint
GET /authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https://app.com/callback&scope=read&state=random123 HTTP/1.1
Host: auth.example.com

# Step 2: User logs in & grants consent, redirected back with code
HTTP/1.1 302 Found
Location: https://app.com/callback?code=AUTH_CODE&state=random123

# Step 3: Server exchanges code for token (server-to-server)
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

grant_type=authorization_code&code=AUTH_CODE&redirect_uri=https://app.com/callback

# Step 4: Token response
{"access_token": "...", "refresh_token": "...", "expires_in": 3600}

# Use PKCE for SPAs/mobile (no client_secret)
# code_challenge = base64url(SHA256(code_verifier))

API 密钥

API 密钥是标识客户端的简单不透明机密。它们在头中发送(首选)或在查询字符串中发送(避免——记录在 URL 和代理日志中)。API 密钥是有状态的(服务器查找它们)且可撤销。与 JWT 不同,它们不携带声明——服务器存储权限。最佳实践:静态哈希密钥、严格限定范围、定期轮换,并提供自助轮换 UI。

http
# API key in header (most common)
GET /api/data HTTP/1.1
X-API-Key: abc123def456

# API key as Bearer token
Authorization: Bearer abc123def456

# API key in query string (less secure - logged in URLs)
GET /api/data?api_key=abc123def456 HTTP/1.1

# API key in custom header with prefix
Authorization: ApiKey abc123def456

# Best practices:
# - Rotate keys periodically
# - Scope keys to specific permissions
# - Store keys hashed at rest (like passwords)
# - Never commit keys to source control
08

内容协商

Accept 头

Accept 头列出客户端可以处理的媒体类型,质量值(q)表示偏好。通配符(text/*、*/*)扩大匹配范围。服务器选择最佳匹配并以所选 Content-Type 响应。如果没有媒体类型匹配,服务器理想情况下返回 406,但实际上许多 API 会回退到默认格式。

http
# Single type
Accept: application/json

# Multiple types with preferences (q values, 0-1)
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8

# Wildcard media types
Accept: text/*           # any text/* subtype
Accept: */*              # anything
Accept: image/png, image/*;q=0.8

# Parameters
Accept: text/html;charset=utf-8

# Server picks best match and returns it in Content-Type.
# If no match, server returns 406 Not Acceptable (or a default).

Accept-Language

Accept-Language 告诉服务器用户偏好的语言,质量值用于排名。RFC 5646 定义语言标签(language[-script][-region])。服务器以 Content-Language 响应。浏览器根据用户的 OS/区域设置进行设置。对于 i18n,优雅回退:如果请求的语言不可用,返回默认值。CDN 有时按 Accept-Language 变化缓存——使用 Vary 头。

http
# Single language
Accept-Language: en-US

# Multiple languages with preferences
Accept-Language: en-US,en;q=0.9, zh-CN;q=0.8, fr;q=0.7, de;q=0.5

# Wildcard
Accept-Language: *

# Server responds with the selected language:
Content-Language: en-US

# RFC 5646 language tags:
# en       - language
# en-US    - language + region
# zh-Hans  - language + script (Simplified Chinese)
# es-419   - language + region (Latin America)

Accept-Encoding

Accept-Encoding 列出客户端可以解码的压缩算法。服务器选择一个并应用它,返回 Content-Encoding。Brotli(br)比 gzip 更好地压缩文本,并在所有现代浏览器中受支持。zstd 正在兴起,速度/比率更好。identity 表示无压缩。始终对文本响应(HTML、CSS、JS、JSON)启用压缩——大幅节省带宽,但跳过已压缩的格式(图像、视频)。

http
# Accepted compression algorithms
Accept-Encoding: gzip, deflate, br, zstd

# With preferences
Accept-Encoding: br;q=1.0, gzip;q=0.8, identity;q=0.5, *;q=0

# identity = no encoding (raw)
# * = any encoding not listed

# Server response
Content-Encoding: br

# Common algorithms:
# gzip     - widespread, RFC 1952
# deflate  - zlib, RFC 1950
# br       - Brotli, better for text (modern browsers)
# zstd     - Zstandard, fast + good ratio (newer)
# identity - no compression

Vary 头

Vary 头告诉缓存(浏览器、CDN、代理)哪些请求头影响响应。如果服务器基于 Accept-Encoding 返回不同内容,必须 Vary: Accept-Encoding,以便缓存不会向只接受 br 的客户端提供 gzip 响应。过度 Vary 损害缓存命中率;Vary 不足导致缓存中毒(提供错误内容)。Vary: * 实际上禁用缓存。

http
# Server response telling caches to vary by Accept-Encoding
HTTP/1.1 200 OK
Content-Type: text/html
Content-Encoding: gzip
Vary: Accept-Encoding

# Vary on multiple headers
Vary: Accept-Encoding, Accept-Language

# Vary on Accept (full content negotiation)
Vary: Accept

# Vary: * means "varies on everything" (effectively no-cache)
Vary: *

# CDN/cache key includes the Vary header values.
# A gzip+en response is cached separately from a br+fr response.

质量值(q)

质量值(q,0-1)在 Accept 系列头中表示偏好强度。q=1.0 是默认值(最偏好);q=0 表示'不可接受'。服务器列出它可以生成的内容,将每个与客户端列表匹配,选择 q 值最高的匹配。特异性打破平局:application/json 即使在相同 q 值下也胜过 */*。允许三位小数,但很少需要。

http
# q values appear in Accept, Accept-Language, Accept-Encoding, Accept-Charset
# Format: ;q=0.000 to ;q=1.000 (default is 1.0)

Accept: text/html;q=1.0, application/json;q=0.9, */*;q=0.1

# Meaning:
# q=1.0    - strongly preferred
# q=0.5    - acceptable
# q=0      - NOT acceptable (refuse)

# Server algorithm (RFC 7231):
# 1. List media types the server can produce
# 2. Match each against the client's Accept
# 3. Pick the match with the highest q value
# 4. Ties broken by specificity (concrete > wildcard)

# Example: client prefers JSON over XML
Accept: application/json, application/xml;q=0.5
09

缓存

Cache-Control 指令

Cache-Control 是带有指令的现代缓存头。public/private 控制谁可以缓存;max-age 设置以秒为单位的鲜度;no-cache 强制重新验证;no-store 禁止缓存;must-revalidate 禁止提供过期内容;s-maxage 为共享缓存(CDN)设置单独的 TTL。immutable 告诉浏览器资源从不更改——重新加载时跳过重新验证。Stale-while-revalidate 和 stale-if-error 启用优雅降级。

http
# Response directives (server -> cache)
Cache-Control: public, max-age=3600           # cacheable by all, fresh 1h
Cache-Control: private, max-age=600           # browser only, fresh 10min
Cache-Control: no-cache                       # revalidate before use
Cache-Control: no-store                       # never cache (sensitive data)
Cache-Control: must-revalidate                # never serve stale
Cache-Control: proxy-revalidate               # proxies must revalidate
Cache-Control: immutable                      # URL never changes
Cache-Control: max-age=0, must-revalidate     # always revalidate
Cache-Control: s-maxage=3600, max-age=600     # CDN caches 1h, browser 10min

# Request directives (client -> cache)
Cache-Control: no-cache                       # send request to origin
Cache-Control: no-store                       # don't store the response
Cache-Control: only-if-cached                 # only from cache

ETag 与 If-None-Match

ETag 是附加到响应的不透明版本标签(通常是内容的哈希)。在下一个请求上,客户端发送带有保存的 ETag 的 If-None-Match。如果资源未更改,服务器返回 304 Not Modified,无请求体——节省带宽。强 ETag 保证字节相同的内容;弱 ETag(W/)允许语义等效的响应(例如,空白差异)。

http
# Server tags a response with an ETag
HTTP/1.1 200 OK
ETag: "abc123"
Content-Type: application/json

{"data": "..."}

# Client sends conditional request on next fetch
GET /api/data HTTP/1.1
If-None-Match: "abc123"

# Server compares:
# - If matches: 304 Not Modified (no body)
HTTP/1.1 304 Not Modified
ETag: "abc123"

# - If changed: 200 OK with new body + new ETag
HTTP/1.1 200 OK
ETag: "def456"

# Strong vs weak ETags:
# "abc123"      - strong (byte-identical)
# W/"abc123"    - weak (semantically equivalent)

Last-Modified 与 If-Modified-Since

Last-Modified 是基于时间戳的验证器。它具有 1 秒精度——亚秒级更改可能被遗漏。If-Modified-Since 在未更改时触发 304。If-Unmodified-Since 防止丢失更新:如果另一个客户端修改了资源,服务器返回 412 Precondition Failed。可用时优先使用 ETag(更精确),但 Last-Modified 是有用的后备。

http
# Server includes Last-Modified
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

# Client sends conditional request
GET /api/data HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT

# Server: if not modified since that time -> 304
HTTP/1.1 304 Not Modified

# Server: if modified since -> 200 with new content
HTTP/1.1 200 OK
Last-Modified: Thu, 22 Oct 2023 09:00:00 GMT

# If-Unmodified-Since (for safe updates - optimistic locking)
PUT /api/users/42 HTTP/1.1
If-Unmodified-Since: Wed, 21 Oct 2023 07:28:00 GMT
# 412 Precondition Failed if modified elsewhere

缓存验证流程

缓存流程:新鲜(在 max-age 内)→从缓存提供,无网络。过期(max-age 后)→带验证器的条件请求。304 刷新缓存计时器而不重新下载请求体。200 替换缓存条目。这大幅减少了带宽和源负载。对于经常更改但你可以容忍轻微过期的情况,使用 stale-while-revalidate。

http
# 1. First request - server sends validators
GET /api/users HTTP/1.1

HTTP/1.1 200 OK
Cache-Control: max-age=60
ETag: "v1"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

# 2. Within max-age (60s): cache serves directly, no network
GET /api/users  (served from cache)

# 3. After max-age: stale - revalidate (conditional GET)
GET /api/users HTTP/1.1
If-None-Match: "v1"
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT

# 4a. Unchanged -> 304 (revalidate resets the cache timer)
HTTP/1.1 304 Not Modified
Cache-Control: max-age=60
ETag: "v1"

# 4b. Changed -> 200 with new body
HTTP/1.1 200 OK
Cache-Control: max-age=60
ETag: "v2"

缓存破坏与失效

缓存破坏:给不可变资产(JS、CSS、图像)版本化的 URL(app.v1a2b3c.js),并用 immutable 缓存一年。部署时,更改 URL——浏览器获取新文件。HTML 入口点使用 no-cache,以便用户始终重新验证并获取新资产 URL。此模式既提供即时更新又提供长期缓存。避免查询字符串版本控制(?v=123)——某些代理不缓存它。

http
# Immutable assets with long cache + versioned URLs
GET /assets/app.v1a2b3c.js HTTP/1.1
HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable

# When deploying a new version, the URL changes:
GET /assets/app.v4d5e6f.js HTTP/1.1
# (browser fetches new file; old one stays cached but unused)

# HTML entry point: short cache, must revalidate
GET /index.html HTTP/1.1
HTTP/1.1 200 OK
Cache-Control: no-cache
# (revalidate every time, but 304 if unchanged)

# Anti-pattern: caching HTML forever (users see stale entry)
# Anti-pattern: querying JS with ?ts=123 (defeats CDN caching)

# Manual cache purge via CDN API (vendor-specific)
10

CORS(跨源)

同源策略与 CORS

同源策略(SOP)是一种浏览器安全模型,默认阻止网页读取跨源响应。CORS(跨源资源共享)是退出机制:服务器声明哪些源可以读取其响应。Origin 定义为 scheme+host+port——其中任何一个不同都意味着跨源。CORS 仅对基于浏览器的请求有意义;服务器到服务器的调用不受限制。

http
# Same-Origin Policy (SOP): browsers block cross-origin reads by default
# Origin = scheme + host + port
# https://app.com  vs  https://api.com  -> DIFFERENT origins
# http://app.com   vs  https://app.com  -> DIFFERENT (scheme)
# https://app.com  vs  https://app.com:8443 -> DIFFERENT (port)

# CORS lets a server OPT IN to cross-origin requests
# via Access-Control-Allow-Origin response header.

# Simple cross-origin GET (no preflight)
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com

# Server allows the origin
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com

简单请求(无预检)

简单请求跳过预检步骤,直接发送到服务器。限制是保守的:只有 GET/HEAD/POST,只有安全列表中的头,以及只有简单的 Content-Type。任何偏差(例如 Content-Type: application/json、自定义头、PUT/DELETE)都会使请求'非简单'并触发预检。服务器仍必须在响应中包含 CORS 头,否则浏览器会阻止读取它。

http
# A "simple" request is sent directly (no preflight):
# - Method: GET, HEAD, or POST
# - Only CORS-safelisted headers:
#     Accept, Accept-Language, Content-Language, Content-Type
# - Content-Type limited to:
#     application/x-www-form-urlencoded
#     multipart/form-data
#     text/plain
# - No event listeners on upload
# - No ReadableStream

# Example: simple cross-origin POST form submit
POST /api/login HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Content-Type: application/x-www-form-urlencoded

user=alice&pass=secret

# Server must respond with Allow-Origin for browser to expose response

预检请求(OPTIONS)

预检是浏览器在非简单跨源请求之前发送的 OPTIONS 请求。它检查实际方法和头是否被允许。服务器以 Allow-Methods 和 Allow-Headers 响应。Access-Control-Max-Age 缓存预检结果(默认 5s,浏览器上限为 24h)。预检增加延迟——尽可能坚持使用简单请求,避免在热点路径上使用预检。

http
# Browser sends preflight OPTIONS before non-simple requests
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

# Server preflight response
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400  # cache preflight for 24h

# Only then does the browser send the actual request
PUT /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Content-Type: application/json
Authorization: Bearer ...

{"key": "value"}

带凭据的 CORS

默认情况下,跨源 fetch 不发送 cookie。要包含它们,客户端设置 credentials: 'include'。服务器必须以 Access-Control-Allow-Credentials: true 响应并回显特定源(不是通配符 *)。始终包含 Vary: Origin,以便缓存不会向另一个源提供为一个源协商的响应。错误配置的 CORS+凭据是一个安全漏洞——将源限制为已知允许列表。

http
# Client: opt in to sending cookies/auth
fetch('https://api.example.com/data', {
  credentials: 'include'   # send cookies cross-origin
});

# Browser sends:
GET /data HTTP/1.1
Origin: https://app.example.com
Cookie: session=abc123

# Server response MUST:
# 1. Echo the SPECIFIC origin (NOT *)
# 2. Set Allow-Credentials: true
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin   # important: caches must vary by Origin

# Wildcard origin + credentials = browser blocks response
# (Access-Control-Allow-Origin: * with credentials fails)

常见 CORS 错误

CORS 错误出现在浏览器控制台中。最常见的是缺少 Allow-Origin(服务器未选择加入)、通配符加凭据(禁止组合)以及预检响应中缺少 Allow-Headers/Methods。CORS 由浏览器而不是服务器强制执行——请求仍然到达服务器,但浏览器对 JS 隐藏响应。检查 Network 选项卡:如果 OPTIONS 预检失败,实际请求永远不会发送。

http
# Error: No 'Access-Control-Allow-Origin' header
# Cause: server didn't send CORS header, or origin not allowed
# Fix: add Access-Control-Allow-Origin on the server

# Error: Credentials flag is true, but Allow-Origin is *
# Cause: wildcard not allowed with credentials
# Fix: echo the specific request Origin instead of *

# Error: preflight missing Allow-Methods / Allow-Headers
# Cause: server didn't allow the method/header
# Fix: add them to Access-Control-Allow-Methods/Headers

# Error: Allow-Origin doesn't match Origin
# Cause: server echoed a different origin (or scheme mismatch)
# Fix: dynamically echo req.headers.origin if allowlisted

# Debug:
# - Check browser DevTools Console for exact error
# - Check Network tab -> see the OPTIONS preflight response
# - Verify headers in the response
11

HTTPS 与 SSL/TLS

TLS 握手(HTTPS)

HTTPS 用 TLS 加密包装 HTTP。TLS 握手认证服务器(证书)、协商加密并交换密钥。TLS 1.2 需要 2 次往返;TLS 1.3 需要 1 次(或恢复时 0 次)。SNI(服务器名称指示)以明文形式发送目标主机名,以便服务器可以选择正确的证书——这将主机名泄露给窃听者;ECH(加密客户端 Hello)对其加密。

http
# HTTPS = HTTP over TLS
# 1. TCP connection (port 443)
# 2. TLS handshake:
ClientHello -> server
  - supported TLS versions, cipher suites, extensions
  - SNI: hostname (example.com)
ServerHello + Certificate + ServerHelloDone <- server
  - selected cipher, certificate (public key)
ClientKeyExchange + ChangeCipherSpec -> server
  - encrypted pre-master secret (RSA) or ECDHE params
ChangeCipherSpec + Finished <- server
# 3. Encrypted HTTP exchange begins
GET /page HTTP/1.1
Host: example.com

# TLS 1.3 collapses this to 1 round trip (1-RTT)
# 0-RTT resumption for returning connections

HTTPS 连接

HTTPS 是基于 TLS 的 HTTP,通常在端口 443 上。HTTP 消息本身未更改——TLS 只是加密底层 TCP 流量。要强制使用 HTTPS,重定向 HTTP→HTTPS(301)并设置 HSTS 头,以便浏览器记住在指定持续时间内使用 HTTPS。HSTS 预加载列表(由浏览器维护)硬编码此行为,因此即使是第一个请求也通过 HTTPS。

http
# HTTP on port 80 (plaintext)
GET /page HTTP/1.1
Host: example.com

# HTTPS on port 443 (TLS-encrypted)
# Same HTTP messages, but the wire traffic is encrypted.
GET /page HTTP/1.1
Host: example.com

# Force HTTPS via redirect
HTTP/1.1 301 Moved Permanently
Location: https://example.com/page

# Force HTTPS via HSTS (browser remembers)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# Modern browsers default to https:// for bare domains
# (some even refuse plain HTTP for sensitive endpoints)

证书

X.509 证书将公钥绑定到域,由证书颁发机构(CA)签名。信任链从服务器证书向上通过中间证书到达浏览器中预安装的根 CA。浏览器验证链、过期日期以及 SAN(主题备用名称)与请求的主机名匹配。通配符证书(*.example.com)和多域证书(多个 SAN)覆盖多个主机名。

http
# X.509 certificate contains:
# - Subject: example.com (CN is deprecated; use SAN)
# - Subject Alternative Name (SAN): example.com, www.example.com
# - Issuer: Let's Encrypt / DigiCert (CA)
# - Validity: Not Before / Not After
# - Public key (RSA or ECDSA)
# - Signature by CA

# Chain of trust:
# Root CA (self-signed, in browser trust store)
#   -> Intermediate CA
#     -> Server cert (your domain)

# Server sends leaf + intermediate during handshake.
# Browser verifies: signature chain back to a trusted root.

# Inspect a cert:
openssl s_client -connect example.com:443 -showcerts
openssl x509 -in cert.pem -text -noout

HSTS(HTTP 严格传输安全)

HSTS 告诉浏览器始终对域使用 HTTPS,防止 SSL 剥离攻击。max-age 以秒为单位(1 年 = 31536000)。includeSubDomains 将其扩展到所有子域。preload 将域提交到硬编码的浏览器列表,因此即使是第一次访问也是 HTTPS——强保护但难以撤销(删除需要数月)。始终先重定向 HTTP→HTTPS,然后添加 HSTS,最后可选地预加载。

http
# Tell browser to use HTTPS for 6 months
Strict-Transport-Security: max-age=15768000

# Include subdomains
Strict-Transport-Security: max-age=15768000; includeSubDomains

# Submit to preload list (browsers hardcode the domain)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# Effects:
# - http://example.com -> browser rewrites to https://
# - Self-signed cert -> browser blocks (no override)
# - prevents SSL stripping attacks (downgrade to HTTP)

# Preload list: https://hstspreload.org
# Once preloaded, removal is slow and difficult.

TLS 版本与密码套件

禁用 SSLv2/v3 和 TLS 1.0/1.1——它们有已知漏洞(POODLE、BEAST)。TLS 1.3(2018)是现代标准:更快的握手、删除弱密码、强制前向保密。带有 ECDHE + AES-GCM 的 TLS 1.2 是可接受的。使用 SSL Labs 等工具为你的配置评分。前向保密(ECDHE)确保即使私钥稍后泄露,过去的流量也保持加密。

http
# TLS versions (deprecate old ones!)
# SSLv2, SSLv3  - BROKEN (POODLE), disable
# TLS 1.0, 1.1  - deprecated (2020), disable
# TLS 1.2       - widely supported, OK
# TLS 1.3       - fastest, most secure (preferred)

# Modern cipher suites (TLS 1.3):
# TLS_AES_256_GCM_SHA384
# TLS_CHACHA20_POLY1305_SHA256
# TLS_AES_128_GCM_SHA256

# TLS 1.2 recommended:
# ECDHE-ECDSA-AES256-GCM-SHA384 (forward secrecy + AEAD)
# ECDHE-RSA-AES256-GCM-SHA384

# Server config (nginx example)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

# Test: https://www.ssllabs.com/ssltest/
12

HTTP/2

HTTP/2 特性

HTTP/2 保留 HTTP 语义(方法、状态码、头),但将网络格式更改为二进制帧。主要优势:在一个 TCP 连接上多路复用多个请求(HTTP 层无队头阻塞)、HPACK 头压缩和流优先级。通过 TLS 上的 ALPN(最常见)或明文上的 h2c 升级进行协商。由于实际性能不佳,服务器推送在 2022 年被弃用。

http
# HTTP/2 (RFC 9113, 2015) - binary, multiplexed
# - Binary framing (not text)
# - Multiplexing: many streams over 1 TCP connection
# - Header compression (HPACK)
# - Server Push (deprecated in 2022)
# - Stream prioritization
# - Single connection per origin

# Negotiation:
# - Over TLS: ALPN extension advertises "h2"
# - Over plaintext: h2c upgrade (rare in practice)

#curl with HTTP/2:
curl -v --http2 https://example.com
# (Connection header shows HTTP/2)

# Backward compatible: same methods, status codes, headers, semantics
# Only the wire format changes.

多路复用

多路复用是 HTTP/2 的招牌功能。许多流共享一个 TCP 连接,帧交错——不再有每个源 6 个连接的限制,HTTP 层无队头阻塞。这消除了 HTTP/1.1 的黑客技巧,如域分片、文件合并和图像内联。注意:TCP 级别的队头阻塞仍然存在(丢失的数据包会阻塞所有流直到重新传输)——这就是 HTTP/3 用 QUIC 修复的问题。

http
# HTTP/1.1: 6 parallel connections per origin, each request blocks its connection
# (head-of-line blocking at the HTTP level)

# HTTP/2: 1 connection, many parallel streams
# Each request/response is a stream with a unique ID.

# Frames carry data for multiple streams interleaved:
# Stream 1: HEADERS, DATA (request 1)
# Stream 3: HEADERS, DATA (request 2)
# Stream 5: HEADERS, DATA (request 3)
# Server responds with interleaved frames on each stream.

# Browser opens one TCP+TLS connection per origin,
# then sends all requests as concurrent streams.

# Eliminates: domain sharding, concatenation, inlining
# (those were HTTP/1.1 optimization hacks)

服务器推送(已弃用)

服务器推送让服务器在客户端请求之前发送资源。理论上,这节省了关键资产的往返时间。实际上,它在 2022 年被弃用:服务器无法知道客户端的缓存状态,因此它过度推送,经常浪费带宽。浏览器现在忽略 PUSH_PROMISE 帧。现代替代方案:HTML 中的 <link rel=preload>,或 103 Early Hints(服务器在最终响应之前发送预加载提示)。

http
# Server proactively sends resources the client will need
GET /index.html HTTP/2
Host: example.com

HTTP/2 200 OK
Content-Type: text/html
Link: </style.css>; rel=preload; as=style

# Server then pushes style.css on a new stream
PUSH_PROMISE (stream 2, /style.css)
<headers + body of /style.css>

# Then the actual /index.html body follows.

# DEPRECATED (2022): browsers ignore it because:
# - Client cache might already have the asset
# - Server can't know what client has cached
# - Net performance negative in real deployments

# Modern alternative: <link rel="preload" href="...">
# or 103 Early Hints

流优先级

HTTP/2 让客户端通过带权重的依赖树表达流优先级。服务器使用它来分配带宽:例如,CSS 优先于图像。RFC 9113(2022)删除了显式 PRIORITY 帧,将调度留给服务器——许多实现了自己的调度。可扩展优先级方案(RFC 9218)是可选的现代替代方案。优先级在慢速连接上最重要。

http
# HTTP/2 streams have priority (weight + dependency tree)
# Originally: client sends PRIORITY frames
# Client says: "CSS (stream 3) is more important than image (stream 5)"

# PRIORITY frame (original model):
# - stream dependency (parent)
# - weight (1-256)

# RFC 9113 (2022) removed explicit prioritization
# - Leaves it to the server
# - Many servers implement their own scheduler

# Practical effect: server allocates bandwidth
# - HTML, CSS, JS -> high priority
# - Images, fonts -> lower priority
# - Critical render path optimized

# Clients can still signal via Extensible Prioritization Scheme
# (RFC 9218) - optional.

HPACK 头压缩

HPACK 使用常见头的静态表、学习最近发送值的每连接动态表和霍夫曼编码来压缩 HTTP 头。这大幅减少了开销——每个请求上发送的 1KB cookie 变成一个小的索引引用。HPACK 经过精心设计,以避免困扰早期 TLS 压缩的 CRIME/BREACH 压缩预言攻击。HTTP/3 使用类似的方案 QPACK。

http
# HTTP/1.1: headers sent as plaintext on every request (redundant)
# Cookies alone can exceed 1KB per request.

# HTTP/2: HPACK compresses headers
# - Static table: 61 common headers (e.g., :method GET)
# - Dynamic table: per-connection, learns recently sent headers
# - Huffman coding for strings

# Example: first request sends full User-Agent (200 bytes)
# Subsequent requests reference the dynamic table entry (2 bytes)

# Indexed header representation:
# 1 bit set + index -> "use entry N"

# Literal with incremental indexing:
# "add this to the dynamic table for future reuse"

# Security: CRIME attack exploited compression oracles.
# HPACK mitigates by never compressing sensitive + attacker-controlled
# data together.
13

HTTP/3 与 QUIC

QUIC 协议

QUIC 是一种基于 UDP 的传输协议,集成了 TLS 1.3 并提供可靠的、多路复用的流。转向 UDP 让 QUIC 比内核 TCP 发展得更快。主要优势:无 TCP 级别的队头阻塞(丢失的数据包只阻塞一个流)、返回连接的 0-RTT 恢复和连接迁移(从 Wi-Fi 切换到蜂窝网络的手机保持连接)。HTTP/3 运行在 QUIC 之上。

http
# QUIC = Quick UDP Internet Connections (Google, then IETF)
# - Transport protocol over UDP (not TCP)
# - Built-in TLS 1.3 encryption
# - Multiple streams, independent per-stream delivery
# - 0-RTT connection resumption
# - Connection migration (survives IP changes)
# - Better loss recovery than TCP

# Why UDP? TCP is in the kernel and hard to evolve.
# QUIC moves reliability + congestion control to user space.

# Wire format: UDP packets containing QUIC packets
# - Initial, Handshake, 1-RTT, 0-RTT packets
# - Frames carry stream data, acks, flow control

# Negotiation:
# Server advertises HTTP/3 via Alt-Svc header on HTTP/2 response:
Alt-Svc: h3=":443"; ma=86400
# Browser caches this and uses HTTP/3 next time.

HTTP/3 特性

HTTP/3 保留 HTTP 语义,但运行在 QUIC 而不是 TCP 之上。最大优势:无队头阻塞。在 HTTP/2 中,单个丢失的 TCP 数据包会阻塞所有多路复用流直到重新传输;在 HTTP/3 中,只有受影响的流等待。加上 0-RTT 恢复和连接迁移。代价:更多 CPU(用户空间中的 QUIC)和 UDP 有时被网络阻止/限制。采用率正在增长——大多数主要站点支持它。

http
# HTTP/3 (RFC 9114, 2022) - HTTP semantics over QUIC
# Same methods, status codes, headers as HTTP/2
# - Binary framing (similar to HTTP/2)
# - Multiplexing over QUIC streams (no HoL blocking!)
# - QPACK header compression (HPACK variant)
# - 0-RTT for returning clients
# - Connection migration

# Loss recovery: per-stream, not per-connection
# A lost packet stalls only that stream, others continue.

# Head-of-line blocking eliminated at the transport layer:
# HTTP/2: lost TCP packet blocks ALL streams (kernel retransmit)
# HTTP/3: lost QUIC packet blocks only that stream

# curl with HTTP/3 (requires special build):
curl -v --http3 https://example.com

0-RTT 连接恢复

0-RTT 让返回的客户端在第一次发送中发送数据(请求之前无往返)。这节省了数十到数百毫秒的重复访问。问题:早期数据可能被攻击者重放,因此必须限于幂等操作(GET、HEAD、PUT/DELETE 如果幂等)。服务器应拒绝 POST 和其他状态更改方法的 0-RTT。防重放机制增加了复杂性。

http
# TLS 1.3 + QUIC: 0-RTT resumption for returning clients
# First visit: full handshake (1-RTT in TLS 1.3)
Client -> Server: ClientHello + key share
Server -> Client: ServerHello + key share + Finished
Client -> Server: Finished + first HTTP request

# Returning visit (0-RTT):
Client -> Server: ClientHello + early data (HTTP request)
# Server can respond immediately, no round trip!

# Trade-offs:
# + Saves 1 RTT on subsequent visits (faster page loads)
# - Vulnerable to replay attacks on early data
# - Only safe for idempotent methods (GET, HEAD)
# - Server should reject 0-RTT for non-idempotent requests

# Configure carefully:
# - Limit early data to safe methods
# - Use anti-replay mechanisms

连接迁移

QUIC 连接由连接 ID 而不是 IP/端口 4 元组标识。这意味着连接在 IP 更改后仍然存活——从 Wi-Fi 切换到蜂窝网络的手机,或在不同网络之间移动的笔记本电脑,保持连接活动。服务器用 PATH_CHALLENGE/PATH_RESPONSE 验证新路径。TCP 无法做到这一点,因为内核通过 4 元组标识连接。对移动设备意义重大。

http
# TCP connection identified by 4-tuple:
# (src IP, src port, dst IP, dst port)
# If your phone switches Wi-Fi -> cellular, IP changes ->
# the TCP connection dies. Browser must reconnect.

# QUIC uses a connection ID (random, in every packet).
# The 4-tuple can change mid-connection; the connection ID
# identifies the connection.

# Scenario:
# 1. Phone on Wi-Fi, downloading a large file over HTTP/3
# 2. Phone walks out, switches to cellular
# 3. Packets now arrive from a new IP, same connection ID
# 4. Server continues serving the same connection!
# 5. No reconnect, no re-handshake, no failed download.

# Validation: server sends PATH_CHALLENGE, client replies PATH_RESPONSE.

HTTP/1.1 vs HTTP/2 vs HTTP/3

HTTP/1.1 是基于 TCP 的文本,有 6 个并行连接。HTTP/2 是基于 TCP 的二进制,带有多路复用(但有 TCP 级别的队头阻塞)。HTTP/3 是基于 QUIC 的二进制,无队头阻塞、强制加密、0-RTT 和连接迁移。采用是渐进的:服务器通过 HTTP/2 响应上的 Alt-Svc 头通告 HTTP/3,浏览器在下一次访问时升级。三者共享 HTTP 语义。

http
# Feature comparison
# Property         | HTTP/1.1     | HTTP/2       | HTTP/3
# -----------------+--------------+--------------+--------------
# Transport        | TCP          | TCP          | QUIC (UDP)
# Format           | Text         | Binary       | Binary
# Multiplexing     | No (6 conns) | Yes          | Yes
# HoL blocking     | HTTP-level   | TCP-level    | None
# Header compress  | No           | HPACK        | QPACK
# Server Push      | No           | Yes (deprecated) | No
# Encryption       | Optional     | Optional     | Mandatory (TLS 1.3)
# 0-RTT            | No           | No           | Yes
# Connection migration | No       | No           | Yes
# Handshake RTT    | 1+           | 1+           | 0-1 (with resumption)

# Most sites today: HTTP/2 over TLS, with HTTP/3 available via Alt-Svc
14

重定向(3xx)

301 与 302

301 表示永久:浏览器缓存重定向,书签更新,SEO 链接权重传递到新 URL。302 表示临时:不缓存任何内容,SEO 保留在原始 URL。历史上,两者都将 POST→GET 转换(一种规范违规,成为事实上的标准)。要严格保留方法,使用 307(临时)或 308(永久)。对于 SEO 迁移使用 301;对于登录重定向使用 302 或 307。

http
# 301 Moved Permanently
HTTP/1.1 301 Moved Permanently
Location: https://newsite.com/page
# - Permanent; browser & SEO caches it
# - Link juice transfers to new URL (SEO)
# - Bookmark updates
# - HTTP/1.1 spec: should preserve method, but historically
#   POST -> GET (causing data loss)

# 302 Found
HTTP/1.1 302 Found
Location: /login
# - Temporary
# - Don't update bookmarks
# - SEO link juice stays with original URL
# - HTTP/1.1 spec: should preserve method, but historically
#   POST -> GET

# Use 301 for permanent moves (site migrations).
# Use 302 for temporary redirects (login flows, A/B tests).

307 与 308

307 和 308 是 302 和 301 的保留方法对应物。它们修复了历史上的 POST→GET 转换错误。307 是临时的,308 是永久的——两者都保留原始方法和请求体。在重定向非 GET 请求时使用它们,特别是在 POST→GET 转换会静默丢弃请求体的 API 中。308 是永久 API 端点移动的现代选择。

http
# 307 Temporary Redirect (HTTP/1.1, method-preserving)
HTTP/1.1 307 Temporary Redirect
Location: /new-endpoint
# - Temporary
# - Method PRESERVED: POST stays POST, body stays intact
# - Browser prompts before re-issuing POST to different host

# 308 Permanent Redirect (RFC 7538)
HTTP/1.1 308 Permanent Redirect
Location: https://newsite.com/api
# - Permanent
# - Method PRESERVED
# - Like 301 but without the POST->GET conversion

# When to use:
# - 307: temporary redirect that must preserve method
# - 308: permanent redirect that must preserve method
#   (e.g., API endpoint moved, clients still POSTing)

# Most browsers support both since ~2015.

303 See Other(PRG 模式)

303 See Other 明确告诉浏览器用 GET 跟进——Post/Redirect/Get(PRG)模式的正确工具。处理 POST 后,用 303 重定向到 GET 页面。如果用户刷新,他们会重新发出安全的 GET(不是 POST),避免重复表单提交。PRG 使用 303,需要保留原始方法时使用 307/308,纯导航使用 301/302。

http
# 303 See Other - explicitly converts to GET
HTTP/1.1 303 See Other
Location: /success
# - Always converts to GET (no body)
# - Used in Post/Redirect/Get (PRG) pattern

# PRG pattern: avoid double-submit on refresh
# 1. User submits form
POST /checkout HTTP/1.1
Host: shop.com
Content-Type: application/x-www-form-urlencoded

item=book&qty=1

# 2. Server processes, redirects to a GET page
HTTP/1.1 303 See Other
Location: /order/123/confirmation

# 3. Browser issues GET (safe to refresh)
GET /order/123/confirmation HTTP/1.1
Host: shop.com

HTTP/1.1 200 OK
# "Order confirmed!"

重定向链与循环

重定向链增加延迟(每一跳 = 往返),应该扁平化——将原始 URL 直接指向最终目的地。重定向循环是致命的:浏览器在几跳后检测到它们并显示错误页面。常见原因:负载均衡器后面的 HTTPS↔HTTP 配置错误(使用 X-Forwarded-Proto),或尾部斜杠冲突。HSTS 通过让浏览器内部重写来避免 HTTP→HTTPS 重定向循环。

http
# Redirect chain: A -> B -> C
GET /a  -> 301 -> /b
GET /b  -> 301 -> /c
GET /c  -> 200 OK

# Each hop adds a round trip; chains hurt performance.
# Browsers cap redirects (typically 20), then error.

# Redirect loop (fatal):
GET /a -> 302 -> /b
GET /b -> 302 -> /a
GET /a -> 302 -> /b  (browser detects loop, shows error)

# Common cause: HTTPS <-> HTTP loop
# Server A redirects HTTP -> HTTPS
# Server B (behind TLS terminator) sees HTTP internally,
# redirects to HTTPS again -> loop

# Fix: trust X-Forwarded-Proto, or use HSTS instead of redirects

# Common cause: trailing slash mismatch
# /api/users -> 301 -> /api/users/ -> 301 -> /api/users/
# (server config bug)

常见重定向用例

常见重定向用例:HTTP→HTTPS(301)、apex↔www(301)、旧→新 URL 迁移(301)、登录重定向(302/303)、PRG 模式(303)、API 端点移动(308)和 CDN 源回退。对于永久移动,始终优先使用 301/308,以便缓存和 SEO 更新。在可能的情况下,使用 HSTS 而不是 HTTP→HTTPS 重定向——它避免了后续访问时的重定向往返。

http
# 1. HTTP -> HTTPS upgrade
HTTP/1.1 301 Moved Permanently
Location: https://example.com/

# 2. www -> apex (or vice versa)
HTTP/1.1 301 Moved Permanently
Location: https://example.com/

# 3. Old URL -> new URL (site migration)
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page

# 4. Login required (temporary)
HTTP/1.1 302 Found
Location: /login?return=/dashboard

# 5. POST form -> confirmation (PRG)
HTTP/1.1 303 See Other
Location: /success

# 6. API endpoint moved (preserve method)
HTTP/1.1 308 Permanent Redirect
Location: /v2/users

# 7. CDN origin fallback (when content not cached)
HTTP/1.1 302 Found
Location: https://origin.example.com/asset
15

内容类型(MIME)

常见 MIME 类型

MIME 类型(媒体类型)描述请求体的格式。它们由 type/subtype 和可选参数(charset、boundary)组成。常见类型:text/html 用于页面,application/json 用于 API,image/* 和 video/* 用于媒体。application/octet-stream 是未知二进制数据的后备——浏览器将下载而不是尝试渲染它。IANA 维护官方注册表。

http
# Text
text/html                    # HTML pages
text/plain                   # plain text
text/css                     # stylesheets
text/csv                     # comma-separated values
text/javascript              # JavaScript (legacy, was application/javascript)

# Application
application/json             # JSON
application/xml              # XML
application/javascript       # JavaScript (modern, text/javascript also OK)
application/pdf              # PDF
application/zip              # ZIP archive
application/octet-stream     # binary (unknown type, forces download)
application/x-www-form-urlencoded  # form data
application/ld+json          # JSON-LD (linked data)

# Image
image/jpeg, image/png, image/gif, image/webp, image/svg+xml, image/avif

# Audio/Video
audio/mpeg, audio/ogg, video/mp4, video/webm

# Multipart
multipart/form-data          # file uploads
multipart/byteranges         # partial content

Content-Type 头

Content-Type 头声明请求体的 MIME 类型。对于文本类型,charset 指定编码(text/* 的默认值是 ISO-8859-1;始终显式设置 UTF-8)。对于多部分请求体,boundary 分隔各部分。如果缺少或不明确,浏览器可能会'嗅探'类型——用 X-Content-Type-Options: nosniff 防止这种情况,这强制它们遵守声明的类型(针对通过文件上传的 XSS 的安全加固)。

http
# Response Content-Type
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

# Request Content-Type (for bodies)
POST /api/users HTTP/1.1
Content-Type: application/json

{"name": "Alice"}

# charset parameter (text types)
Content-Type: text/html; charset=UTF-8

# boundary parameter (multipart)
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

# X-Content-Type-Options: nosniff
# tells browser not to sniff/override the declared type

multipart/form-data

multipart/form-data 在一个请求体中编码多个部分(字段和文件),由 boundary 分隔。每个部分有自己的 Content-Disposition(带有可选文件名)和 Content-Type。这是浏览器提交带文件输入的表单时使用的格式。这是通过 HTML 表单上传文件的唯一实用方法,但对于 API,base64-in-JSON 或直接 PUT 到存储 URL 通常更简单。

http
# File upload form
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----boundary123

------boundary123
Content-Disposition: form-data; name="title"

My Photo
------boundary123
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg

<binary JPEG bytes>
------boundary123--

# Each part has its own headers (Content-Disposition, Content-Type).
# Final boundary has trailing --.
# Used for file uploads (browsers' <input type="file">).
# Less efficient than JSON for plain fields - overhead per part.

application/json

application/json 是占主导地位的 API 格式——轻量级、人类可读,并在每种语言中原生支持。JSON 默认是 UTF-8(charset 参数是冗余的但无害)。特定于供应商的媒体类型(application/vnd.api+json、application/vnd.github.v3+json)启用版本控制和配置文件协商。RFC 7807(application/problem+json)标准化错误响应。

http
# Most common API Content-Type
POST /api/users HTTP/1.1
Content-Type: application/json
Accept: application/json

{
  "name": "Alice",
  "age": 30,
  "tags": ["admin", "user"],
  "metadata": null
}

# Variants:
# application/json             - standard
# application/json; charset=utf-8  - JSON is UTF-8 by default, charset is redundant
# application/ld+json          - JSON-LD (linked data with @context)
# application/vnd.api+json     - JSON:API spec
# application/problem+json     - RFC 7807 problem details for errors

# Vendor types: application/vnd.company.v1+json
# (custom versioned JSON)

charset 参数

charset 参数声明文本编码。对于 text/* 类型,始终设置它——默认的 ISO-8859-1 会导致非 ASCII 字符的乱码。UTF-8 是通用的最佳选择。对于 JSON,charset 是冗余的(JSON 按规范是 UTF-8)。对于 XML,规范默认为 UTF-8,但显式声明可避免 BOM 嗅探开销。不匹配的字符集会产生乱码字符。

http
# charset specifies the text encoding
Content-Type: text/html; charset=UTF-8      # preferred
Content-Type: text/html; charset=iso-8859-1 # legacy

# For JSON, charset is redundant (JSON spec mandates UTF-8)
Content-Type: application/json              # correct
Content-Type: application/json; charset=utf-8  # harmless but redundant

# Default charsets (when omitted):
# text/*                  -> ISO-8859-1 (legacy, problematic)
# application/json        -> UTF-8 (mandatory per spec)
# application/xml         -> UTF-8 (per XML spec)

# Always declare charset for text types to avoid mojibake.
# Mismatched charset = broken characters.

# In HTML, also declare in the document:
<meta charset="UTF-8">
16

分块传输

Transfer-Encoding: chunked

分块传输编码让服务器在不知道总长度的情况下流式传输请求体。每个块以其十六进制长度为前缀,后跟 CRLF、数据和 CRLF。零长度块标志结束。这对于在生成数据之前不知道大小的动态生成响应(模板渲染、数据库查询)至关重要。HTTP/2 和 HTTP/3 不使用分块编码——它们有自己的分帧。

http
# When the server doesn't know the final Content-Length
# (e.g., streaming, dynamically generated content),
# it sends the body in chunks.

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

4

Wiki

6

pedia 

E

in chunks.

0




# Each chunk: <hex-length>
<data>

# Final chunk: 0
 (zero length)
# Then optional trailer headers + final 

流式响应

流式响应使用分块编码增量推送数据。服务器发送事件(SSE)是典型模式:服务器保持连接打开并将事件写为 'data: <payload>\n\n'。浏览器的 EventSource API 在它们到达时接收它们。其他用途:渐进式 HTML 渲染(边生成边刷新)、大型 JSON 数组、日志跟踪。确保没有中间代理缓冲响应——为 nginx 设置 X-Accel-Buffering: no。

http
# Server-Sent Events (SSE): one-way streaming over HTTP
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Transfer-Encoding: chunked

data: {"msg": "hello"}


data: {"msg": "world"}


data: {"msg": "done"}



# Each event: "data: <payload>

"
# Browser keeps the connection open via EventSource API.

# Other streaming patterns:
# - Server-side rendering: flush HTML as it's generated
# - Large JSON: stream array elements
# - Logs tailing: continuous chunk stream

# Don't buffer: set X-Accel-Buffering: no (nginx)

Trailer 头

Trailer 头在使用分块编码时在请求体之后发送。它们对于在流式传输期间计算的汇总统计信息很有用——例如,行数、校验和或处理持续时间。Trailer 头通告将出现哪些 trailer。某些头被禁止作为 trailer(Host、Content-Length、Transfer-Encoding、认证头),因为它们影响连接分帧。客户端支持参差不齐——许多客户端忽略 trailer。

http
# Trailer headers are sent AFTER the body (with chunked encoding)
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
Trailer: X-Total-Count, X-Duration

5

{"a":1

5

,"b":2

0

X-Total-Count: 2

X-Duration: 0.045s




# Use case: compute summary stats during streaming
# (e.g., total count, checksum) and send at the end.

# The "Trailer" header declares which trailers to expect.
# Some headers are forbidden as trailers:
# - Transfer-Encoding, Content-Length, Host
# - Authentication-related headers

Content-Length 与分块

Content-Length 预先告诉客户端准确的请求体大小——简单、启用进度条,并允许连接干净地重用。分块编码让你在不知道大小的情况下流式传输。你必须使用其中一个(或在结束时关闭连接)。同时发送两者是模棱两可的;按规范,分块优先,但旧客户端可能行为异常。对于静态文件,始终使用 Content-Length;对于动态/流式,使用分块。

http
# Content-Length: known body size up front
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 11

Hello World

# Pros: simple, allows progress bars, enables persistent conn reuse
# Cons: requires knowing the size before sending

# Transfer-Encoding: chunked: stream without knowing size
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

5
Hello

6
 World

0



# Pros: can stream as data is produced
# Cons: no progress bar, slightly more overhead per chunk

# Rule: you must use ONE of these (or close the connection).
# Using both is ambiguous - chunked wins per spec but is buggy in old clients.

HTTP/2 流式(无分块)

HTTP/2 和 HTTP/3 不使用分块编码——它们有自己的二进制分帧。DATA 帧携带请求体字节;END_STREAM 标志标志结束。这提供了相同的流式功能,而没有十六进制长度分帧的每块开销。Transfer-Encoding 头在 HTTP/2 中是非法的(RFC 9113)。Content-Length 仍然允许,但可选。流式的语义得到保留;只有网络机制更改。

http
# HTTP/2 and HTTP/3 don't use Transfer-Encoding: chunked.
# They have their own framing: DATA frames carry body bytes,
# END_STREAM flag marks the end.

# In HTTP/2, "streaming" means sending multiple DATA frames
# on a single stream, with the END_STREAM flag on the last.

# Server push of partial response:
# HEADERS frame (status, headers)
# DATA frame (chunk 1)
# DATA frame (chunk 2)
# DATA frame + END_STREAM (chunk 3, final)

# Effect: same streaming behavior as chunked,
# but at the framing layer, not the encoding layer.

# Transfer-Encoding header is illegal in HTTP/2.
# Content-Length is allowed but optional.
17

范围请求

Range 头

Range 请求让客户端获取资源的一部分——对于可恢复下载、视频定位和并行分块下载至关重要。Range 头指定字节范围。服务器以 206 Partial Content 和显示返回内容的 Content-Range 头响应。如果服务器不支持范围,它会忽略头并返回带有完整请求体的 200。始终先检查 Accept-Ranges。

http
# Request a portion of a resource (e.g., resume downloads, video seeking)
GET /video.mp4 HTTP/1.1
Host: example.com
Range: bytes=0-1023        # first 1024 bytes

# Server response: 206 Partial Content
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/1048576
Content-Length: 1024
Content-Type: video/mp4

<1024 bytes of video>

# Range formats:
Range: bytes=0-1023        # bytes 0 through 1023
Range: bytes=1024-         # from byte 1024 to end
Range: bytes=-1024         # last 1024 bytes
Range: bytes=0-1023,2048-3071  # multiple ranges (multipart response)

Accept-Ranges 头

Accept-Ranges 头通告服务器是否支持范围请求。bytes 表示是;none(或缺失)表示否。客户端在依赖 Range 之前应检查此项——忽略 Range 的服务器将返回带有 200 的完整请求体。浏览器自动将此用于 <video> 定位(跳转到时间戳 = 范围请求)和可恢复下载。大多数 CDN 和静态服务器支持范围;动态端点通常不支持。

http
# Server advertises range support
HTTP/1.1 200 OK
Accept-Ranges: bytes        # supports byte-range requests
Content-Length: 1048576

# Server doesn't support ranges
HTTP/1.1 200 OK
Accept-Ranges: none

# (or omit the header entirely - same as "none")

# Client checks before sending Range:
# 1. HEAD or GET request to inspect Accept-Ranges
# 2. If "bytes", send Range: bytes=...
# 3. If "none" or missing, expect 200 with full body

# Browsers handle this transparently for video/audio,
# resumable downloads, and PDF preview.

部分内容响应(206)

206 Partial Content 是成功范围请求的响应。Content-Range 显示单位(字节)、返回范围和总大小。如果请求的范围超过资源,服务器返回 416 Range Not Satisfiable。If-Range 使范围成为条件:如果验证器(ETag 或日期)匹配,返回 206;如果资源已更改,返回带有完整请求体的 200——防止部分损坏问题。

http
# Single range request
GET /file.bin HTTP/1.1
Range: bytes=100-199

# 206 Partial Content
HTTP/1.1 206 Partial Content
Content-Range: bytes 100-199/1024
Content-Length: 100
Content-Type: application/octet-stream

<100 bytes>

# Content-Range format: <unit> <start>-<end>/<total>
# total can be "*" if unknown (streaming)

# If range is out of bounds:
# Range: bytes=1000-2000 on a 500-byte file ->
HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */500

# If-Range: only return range if validator still matches
GET /file.bin HTTP/1.1
Range: bytes=0-1023
If-Range: "etag123"
# If etag matches: 206 (partial)
# If etag differs: 200 (full body, resource changed)

多范围请求

多范围请求在一个请求中请求多个字节范围。服务器以 multipart/byteranges 响应——每个部分有自己的 Content-Type 和 Content-Range。实际上,多范围很少使用:客户端通常改为发出并行单范围请求(更简单、更缓存友好、更容易从错误中恢复)。某些服务器限制范围数量或合并重叠范围。避免在关键路径上依赖多范围。

http
# Request multiple ranges in one request
GET /file.bin HTTP/1.1
Range: bytes=0-99,200-299,400-499

# Server responds with multipart/byteranges
HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=THIS_STRING_SEPARATES

--THIS_STRING_SEPARATES
Content-Type: application/octet-stream
Content-Range: bytes 0-99/1024

<100 bytes>
--THIS_STRING_SEPARATES
Content-Type: application/octet-stream
Content-Range: bytes 200-299/1024

<100 bytes>
--THIS_STRING_SEPARATES
Content-Type: application/octet-stream
Content-Range: bytes 400-499/1024

<100 bytes>
--THIS_STRING_SEPARATES--

# Most clients use single-range; multi-range is rare in practice.

Range 用例

Range 请求支持视频定位(跳转到时间戳)、可恢复下载(暂停后继续)、并行下载(下载管理器将文件拆分为块)、PDF 页面预览和读取存储在文件末尾的元数据(ID3 标签)。对于所有这些,服务器必须支持范围并通过 Accept-Ranges: bytes 通告。没有范围支持,视频定位降级为完整下载,可恢复下载是不可能的。

http
# 1. Video seeking (HTML5 <video>)
# Browser sends Range when user jumps to a timestamp
GET /video.mp4 HTTP/1.1
Range: bytes=5242880-    # seek to ~5MB offset

# 2. Resumable downloads
# Pause at 50%, resume later
GET /bigfile.zip HTTP/1.1
Range: bytes=524288000-  # continue from 500MB

# 3. Parallel downloads (download managers)
# Split file into N chunks, fetch concurrently
GET /bigfile.zip HTTP/1.1
Range: bytes=0-10485759      # chunk 1 (first 10MB)
# (parallel connection)
GET /bigfile.zip HTTP/1.1
Range: bytes=10485760-20971519  # chunk 2

# 4. PDF preview (load only visible pages)
GET /doc.pdf HTTP/1.1
Range: bytes=102400-204800   # specific page region

# 5. Streaming audio with metadata at the end
# Fetch ID3 tag without downloading the whole file
18

会话管理

基于 Cookie 的会话

基于 Cookie 的会话存储由随机会话 ID 作为键的服务器端会话(在内存、Redis 或数据库中)。cookie 只保存 ID。优点:可撤销(删除会话)、小 cookie、成熟。缺点:有状态(服务器必须存储会话)、水平扩展需要共享会话存储(Redis)、易受 CSRF 攻击(用 SameSite 缓解)。最适合带服务器渲染页面的传统 Web 应用。

http
# 1. User logs in
POST /login HTTP/1.1
Host: example.com
Content-Type: application/json

{"username": "alice", "password": "secret"}

# 2. Server validates, creates session, stores in DB/Redis,
#    returns session ID in a cookie
HTTP/1.1 200 OK
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax

# 3. Subsequent requests carry the cookie
GET /dashboard HTTP/1.1
Cookie: session=abc123

# 4. Server looks up session ID, finds user, serves request
# 5. Logout: delete session server-side, expire cookie
POST /logout HTTP/1.1
Cookie: session=abc123

HTTP/1.1 200 OK
Set-Cookie: session=; Max-Age=0; Path=/

基于令牌的会话(JWT)

基于令牌的会话(通常是 JWT)将会话状态存储在令牌中。服务器验证签名而无需数据库查找——无状态。优点:可扩展(无会话存储)、跨服务工作、移动友好。缺点:在过期之前无法撤销(用短访问令牌 + 服务器端存储的刷新令牌缓解)、比会话 ID 大、如果存储在 localStorage 中易受 XSS 攻击。最适合 API 和 SPA。

http
# 1. Login returns a JWT (no server-side state)
POST /login HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json
{"token": "eyJhbGciOiJIUzI1NiJ9..."}

# 2. Client stores token (memory, sessionStorage)
# 3. Each request includes the token in a header
GET /api/profile HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

# 4. Server validates signature + claims, no DB lookup needed
# 5. Logout: client discards token (no server-side invalidation
#    until exp - this is the stateless tradeoff)

# Refresh token flow:
# - Short-lived access token (5-15 min)
# - Long-lived refresh token (days), stored server-side, revocable
# - When access token expires, exchange refresh token for new one

头中的会话

通过头(Authorization、X-API-Key)的会话是无状态的,适用于任何客户端(浏览器、移动、CLI、服务器到服务器)。刷新令牌流程处理过期:短生命周期访问令牌限制被盗令牌的影响范围,而刷新令牌(安全存储,理想情况下在 HttpOnly cookie 中)让客户端无需重新认证即可获取新访问令牌。通过轮换检测被盗的刷新令牌。

http
# Bearer token in Authorization header
GET /api/data HTTP/1.1
Authorization: Bearer eyJhbGc...

# Custom header (e.g., API gateway)
GET /api/data HTTP/1.1
X-API-Key: abc123def456

# Basic auth (per-request credentials)
GET /api/data HTTP/1.1
Authorization: Basic dXNlcjpwYXNz

# Bearer + refresh flow:
# 1. Access token expires -> API returns 401
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token"

# 2. Client uses refresh token to get a new access token
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=GmR...

# 3. New access token returned
{"access_token": "...", "expires_in": 900}

会话过期与滑动

会话过期策略:绝对(从登录开始的固定生命周期)、滑动(活动时重置)或组合(先到者为准)。组合最安全——限制总会话长度,同时允许合理的空闲时间。Cookie 使用 Max-Age;服务器端会话有单独的 TTL。JWT 无法滑动——exp 声明在签发时固定。要'延长' JWT,签发新的(刷新令牌流程),而不是更改旧的。

http
# Absolute expiration: session dies after fixed time from login
# e.g., 8 hours after login, regardless of activity
session.expires_at = session.created_at + 8h

# Sliding/Idle expiration: resets on each activity
# e.g., session dies after 30 min of inactivity
session.expires_at = NOW() + 30min   # updated on each request

# Combined (most secure):
# - Absolute cap (e.g., 24h max from login)
# - Idle timeout (e.g., 30min inactivity)
# Whichever comes first.

# Set-Cookie with Max-Age sets cookie lifetime,
# but server-side session may expire earlier (DB cleanup).
Set-Cookie: session=abc; Max-Age=1800  # 30 min

# JWT exp claim (absolute):
# {"exp": 1516242622}  # hardcoded, can't slide

CSRF 防护

CSRF 攻击利用浏览器自动发送 cookie 的事实。SameSite cookie(现代浏览器中默认 Lax)通过拒绝在跨站 POST 上发送 cookie 来阻止大多数 CSRF。CSRF 令牌模式在表单中添加服务器生成的令牌,并在提交时验证它——由于 SOP,攻击者无法读取令牌。双重提交和自定义头防御增加深度防御。使用 Bearer 令牌(无 cookie)的 API 本质上不受 CSRF 影响。

http
# CSRF (Cross-Site Request Forgery): attacker tricks the user's
# browser into sending a state-changing request using their cookies.

# Defense 1: SameSite cookies (modern default)
Set-Cookie: session=abc; SameSite=Lax   # blocks most CSRF
Set-Cookie: session=abc; SameSite=Strict # blocks all cross-site

# Defense 2: CSRF token (synchronizer pattern)
# Server embeds a random token in the form
<form action="/transfer" method="POST">
  <input type="hidden" name="csrf" value="randomtoken123">
  ...
</form>
# Server validates the token on POST

# Defense 3: Double-submit cookie
Set-Cookie: csrf=xyz
# JS reads cookie, includes value in a custom header
X-CSRF-Token: xyz
# (custom headers can't be sent cross-site without preflight)

# Defense 4: Require custom header on state-changing requests
# (browsers block cross-site custom headers without CORS preflight)
19

WebSocket

WebSocket 升级

WebSocket 以带有 Upgrade 头的 HTTP 请求开始。如果服务器同意,它返回 101 Switching Protocols。之后,同一 TCP 连接使用 WebSocket 二进制协议——全双工、低开销、每条消息无 HTTP 开销。Sec-WebSocket-Accept 值(客户端密钥加上固定 GUID 的 SHA-1)证明服务器有意升级。Origin 让服务器授权升级源。

http
# Client initiates HTTP upgrade
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com

# Server accepts upgrade
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

# After 101, the TCP connection is "upgraded" - HTTP is done.
# Both sides send WebSocket frames (binary protocol).

# Sec-WebSocket-Accept = base64(SHA1(key + magic GUID))
# Server must compute this to prove it understood the upgrade.

WebSocket 握手(JS 客户端)

浏览器 WebSocket API 是事件驱动的:升级成功后触发 onopen,每个传入帧触发 onmessage,断开连接时触发 onclose,失败时触发 onerror。发送字符串或二进制(ArrayBuffer、Blob)。用代码(1000 = 正常)和原因关闭。连接是全双工的——双方都可以随时发送。WSS(WebSocket Secure)使用 TLS,等效于 HTTPS。

http
# Browser client
const ws = new WebSocket('wss://example.com/ws');

ws.onopen = () => {
  console.log('connected');
  ws.send('Hello Server');
};

ws.onmessage = (event) => {
  console.log('received:', event.data);
};

ws.onclose = (event) => {
  console.log('closed:', event.code, event.reason);
};

ws.onerror = (error) => {
  console.error('error:', error);
};

# Send binary
ws.send(new ArrayBuffer(8));

# Close
ws.close(1000, 'normal closure');

WebSocket 帧

WebSocket 帧是二进制的:FIN 位(消息完成)、操作码(text/binary/close/ping/pong)、掩码位(客户端帧必须被掩码以防止中间人缓存中毒)、有效载荷长度和数据。关闭代码传达连接结束的原因——1000 是正常的,1006 是异常的(无关闭帧,通常是网络断开)。Ping/pong 帧保持连接活动并检测死对等方。帧可以跨多个帧分片。

http
# After the handshake, communication is via binary frames:
# - FIN bit (1 = final fragment of a message)
# - Opcode (4 bits): 0x1 text, 0x2 binary, 0x8 close,
#                    0x9 ping, 0xA pong
# - Mask bit (client->server frames are masked)
# - Payload length (7, 7+16, or 7+64 bits)
# - Masking key (4 bytes, client->server only)
# - Payload data

# Close codes:
# 1000  Normal closure
# 1001  Endpoint going away
# 1006  Abnormal closure (no close frame)
# 1008  Policy violation
# 1011  Internal server error
# 4000-4999  Application-defined

# Ping/Pong: keep-alive and liveness check
# Server: ping -> Client: pong (within timeout)

# Frames can be fragmented (FIN=0) and reassembled.

WebSocket 与 HTTP

WebSocket 是全双工和双向的,非常适合实时应用(聊天、游戏、实时仪表板、协作编辑)。HTTP 是请求-响应——客户端询问,服务器回答。对于单向服务器到客户端更新,SSE 更简单(基于 HTTP、自动重新连接、与代理配合良好)。对于无状态 CRUD,HTTP/REST 是正确的。WebSocket 每条消息的开销最低,但设置成本最高(升级、分帧)。

http
# HTTP: request-response, half-duplex (client initiates)
# WebSocket: full-duplex, either side can send anytime

# Use HTTP when:
# - Client asks, server answers (CRUD APIs)
# - Stateless, cacheable resources
# - Occasional data fetches

# Use WebSocket when:
# - Real-time updates (chat, live sports, dashboards)
# - Bidirectional streaming (collaborative editing, gaming)
# - High-frequency, low-latency messaging
# - Server pushes events without client polling

# Alternatives:
# - SSE (Server-Sent Events): one-way server->client over HTTP
# - HTTP/2 streaming: server can push via DATA frames
# - Long polling: HTTP hack (client polls, server holds open)

# WebSocket has lower per-message overhead than HTTP.

WebSocket 用例

WebSocket 在实时双向应用中大放异彩:聊天、实时仪表板、多人游戏、协作编辑、通知和 WebRTC 信令。挑战是扩展:客户端连接到一个特定的服务器,因此向所有客户端广播需要发布/订阅后端(Redis、Kafka)在服务器实例之间分发。在负载均衡器上使用粘性会话,或设计通过发布/订阅层路由消息的无状态服务器。

http
# 1. Chat application
# Client sends: {"type": "message", "text": "hi"}
# Server broadcasts to all connected clients

# 2. Live dashboard (stock prices, sports scores)
# Server pushes updates every second
# Client just listens

# 3. Multiplayer game
# Low-latency bidirectional: position, actions, events

# 4. Collaborative editing (Google Docs style)
# Operational Transform / CRDT messages both ways

# 5. Live notifications
# Server pushes: "you have a new message"

# 6. Voice/video signaling (WebRTC)
# WS carries SDP offers/answers before media flows over UDP

# Scaling WebSockets: sticky sessions or a pub/sub backend
# (Redis Pub/Sub, Kafka) so any server can reach any client.
20

REST API 设计

资源命名

REST 资源是名词(复数:/users 而不是 /user),HTTP 方法表达动作。不要在 URL 中使用动词(/createUser 是错误的——POST /users 是正确的)。子资源表达关系(/users/42/orders)。查询参数处理过滤、排序、分页——永远不要将它们烘焙到路径中。多词路径段使用 kebab-case(/password-resets,而不是 /passwordResets)。一致性是最重要的规则。

http
# Resources are nouns, pluralized
GET /api/users            # list users
POST /api/users           # create user
GET /api/users/42         # get user 42
PUT /api/users/42         # replace user 42
PATCH /api/users/42       # update user 42
DELETE /api/users/42      # delete user 42

# Sub-resources for relationships
GET /api/users/42/orders              # user 42's orders
GET /api/users/42/orders/1001         # specific order

# Use query params for filtering/sorting/pagination
GET /api/users?role=admin&sort=-created_at&page=2&limit=20

# Bad (verbs in URL):
POST /api/createUser     # should be POST /api/users
GET /api/getUser/42      # should be GET /api/users/42

# Good: nouns + HTTP methods express the action.

REST 中的 HTTP 方法

标准 REST CRUD 映射:POST=创建、GET=读取、PUT=替换、PATCH=更新、DELETE=删除。GET/HEAD/OPTIONS 是安全的(无副作用)。PUT 和 DELETE 是幂等的。POST 不是(重复会创建重复项)。对于不适合 CRUD 的动作(锁定、归档、发送电子邮件),使用带 POST 的子资源:/users/42/lock。这种'RPC 逃生舱'是务实的——纯 REST 不能干净地建模每个操作。

http
# Standard CRUD mapping:
POST    /users         # Create (server assigns ID)
GET     /users         # List (with filtering/pagination)
GET     /users/:id     # Read one
PUT     /users/:id     # Replace (full update)
PATCH   /users/:id     # Update (partial)
DELETE  /users/:id     # Delete

# Safe (no side effects): GET, HEAD, OPTIONS
# Idempotent (repeatable): GET, HEAD, OPTIONS, PUT, DELETE
# Neither: POST (creates new resource each time)
# PATCH: idempotency depends on the patch format

# Custom "actions" on a resource (RPC-style escape hatch):
POST /users/42/lock      # lock the user (not pure REST)
POST /users/42/unlock
POST /users/42:lock      # Google AIP style
POST /users/42/actions/lock  # explicit escape hatch

REST 中的状态码

语义化使用状态码。200 是通用成功;201 表示创建(带 Location);204 表示成功但无请求体。400 是格式错误的输入;422(WebDAV)是格式良好但语义无效(验证)。401 与 403 是最容易混淆的一对:401 = '你是谁?',403 = '我知道你是谁,但你不能做这件事'。5xx 表示服务器出错了——客户端可以用退避策略重试。在你的 API 中保持一致。

http
# Success (2xx)
200 OK              # generic success, returns data
201 Created         # POST/PUT created a resource (Location header)
202 Accepted        # async job queued (return job URL)
204 No Content      # success, no body (DELETE, PUT)

# Client errors (4xx)
400 Bad Request     # malformed input, validation failure
401 Unauthorized    # not authenticated (needs login)
403 Forbidden       # authenticated but lacks permission
404 Not Found       # resource doesn't exist
409 Conflict        # version conflict, duplicate
422 Unprocessable   # semantic error (well-formed but invalid)
429 Too Many Requests  # rate limited (Retry-After)

# Server errors (5xx)
500 Internal Server Error  # unexpected failure
503 Service Unavailable    # maintenance, overload

# Idempotency: 204 for DELETE/PUT is conventional.
# 201 vs 200 for POST: 201 if a resource was created.

分页

基于页面的分页(offset/limit)简单,但在大型数据集上效率低下(OFFSET 扫描过去的行)且不稳定(新插入会移动行)。基于游标的分页使用指向最后一项的不透明令牌——在并发插入下稳定且快速(在游标列上索引查找)。无限滚动和大型信息流使用游标;需要'跳转到第 N 页'的管理 UI 使用基于页面的分页。Link 头(RFC 5988)是公开分页 URL 的 HATEOAS 风格方式。

http
# Page-based (offset/limit)
GET /api/users?page=2&limit=20
# Response:
{
  "data": [...],
  "page": 2,
  "limit": 20,
  "total": 1000,
  "total_pages": 50
}

# Cursor-based (token, better for large/realtime datasets)
GET /api/users?cursor=abc123&limit=20
# Response:
{
  "data": [...],
  "next_cursor": "def456",
  "has_more": true
}

# Link header (RFC 5988):
Link: <https://api.example.com/users?page=3>; rel="next",
      <https://api.example.com/users?page=50>; rel="last"

# Pros of cursor: stable under inserts/deletes,
# faster on large datasets (no OFFSET scan).
# Cons: can't jump to arbitrary page.

API 版本控制

API 版本控制防止破坏性更改伤害现有客户端。URI 版本控制(/v1/、/v2/)是最常见和明确的方法——易于调试、路由和缓存。头版本控制保持 URL 干净,但在浏览器中更难测试。弃用时,使用 Sunset 和 Deprecation 头(RFC 8594)宣布生命终止日期,给客户端充足的时间(6-24 个月),并记录迁移路径。非破坏性更改(添加可选字段)不需要版本提升。

http
# 1. URI versioning (most common, explicit)
GET /api/v1/users
GET /api/v2/users

# 2. Header versioning (cleaner URLs)
GET /api/users
Accept: application/vnd.example.v2+json

# 3. Custom header
X-API-Version: 2

# 4. Query parameter
GET /api/users?version=2

# Deprecation flow:
# - Mark old version as deprecated (Sunset header)
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
# - Document migration guide
# - Maintain old version for a transition period (6-24 months)
# - Notify clients via email, response headers, logs
# - Eventually shut down the old version

# Breaking changes require a new major version.
# Non-breaking changes (adding fields) don't.

REST 最佳实践

REST 最佳实践:复数名词、方法表达动作、语义状态码、默认分页、通过查询字符串过滤、一致的错误格式(RFC 7807 problem+json 是标准)和用于可发现性的超媒体链接(HATEOAS)。对于不是自然幂等的 POST 端点(支付、订单创建),接受 Idempotency-Key 头,以便客户端可以安全重试而不会重复效果——服务器存储以 UUID 为键的响应并在重试时重放它。

http
# 1. Use plural nouns: /users not /user
# 2. HTTP methods express actions (no verbs in URLs)
# 3. Status codes are semantic (201 for create, 404 for missing)
# 4. Pagination on list endpoints (default limit, max cap)
# 5. Filtering via query params: ?role=admin&active=true
# 6. Consistent error format (RFC 7807 problem+json):
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json

{
  "type": "https://example.com/errors/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "Email is required",
  "instance": "/api/users",
  "errors": [{"field": "email", "message": "required"}]
}

# 7. Hypermedia (HATEOAS) links in responses (optional):
{"data": {...}, "_links": {"self": "/users/42", "orders": "/users/42/orders"}}
# 8. Idempotency keys for safe POST retries (Stripe pattern)
Idempotency-Key: client-generated-uuid

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。