HTTP 协议详解
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最广泛的应用层协议。从浏览网页到调用 API,从流媒体传输到物联网通信,HTTP 无处不在。理解 HTTP 的工作原理对于 Web 开发、API 设计、性能优化和安全防护都至关重要。
HTTP 协议概述
HTTP 是一种无状态的应用层协议,基于请求-响应模型工作。客户端发送请求,服务器返回响应,每次请求相互独立,服务器不保留之前请求的状态信息。
HTTP 的设计目标
HTTP 最初的设计目的是传输 HTML 文档,但随着互联网的发展,它已经演变为一种通用的数据传输协议:
简单快速:HTTP 协议设计简洁,请求和响应都是可读的文本格式(HTTP/2 之后改为二进制帧)。客户端只需发送请求方法和路径,即可获取资源。
灵活可扩展:HTTP 允许传输任意类型的数据,通过 Content-Type 头部指明数据类型。协议本身也可以通过添加新的方法、头部和状态码来扩展。
无状态:每个请求都是独立的,服务器不保存客户端的状态。这简化了服务器设计,但也意味着需要在应用层实现状态管理(如 Cookie、Session)。
无连接:早期 HTTP 每次请求都建立新的 TCP 连接。HTTP/1.1 引入持久连接(Keep-Alive),允许在一个连接上发送多个请求。
HTTP 版本演进
HTTP 协议经历了多次重大演进,每个版本都解决了前一版本的性能瓶颈:
| 版本 | 年份 | 核心特性 | 主要改进 |
|---|---|---|---|
| HTTP/0.9 | 1991 | 单行协议 | 仅支持 GET,无头部,响应只有 HTML |
| HTTP/1.0 | 1996 | 头部、状态码 | 支持多种内容类型,添加 Content-Type |
| HTTP/1.1 | 1997 | 持久连接、管道化 | 连接复用、分块传输、Host 头部 |
| HTTP/2 | 2015 | 二进制分帧、多路复用 | 头部压缩、服务器推送、消除队头阻塞 |
| HTTP/3 | 2022 | 基于 QUIC | 0-RTT 连接、连接迁移、彻底解决队头阻塞 |
HTTP/1.0 的局限
HTTP/1.0 每次请求都需要建立新的 TCP 连接,三次握手增加了显著的延迟。一个典型的网页可能需要加载几十个资源(HTML、CSS、JavaScript、图片等),每个资源都需要单独的连接,效率极低。
HTTP/1.1 的改进
HTTP/1.1 引入了持久连接(Persistent Connection),允许在一个 TCP 连接上发送多个请求。还支持管道化(Pipelining),客户端可以连续发送多个请求而不必等待响应。但管道化存在队头阻塞问题:如果第一个请求处理时间很长,后续请求即使已经准备好也必须等待。
HTTP/2 的突破
HTTP/2 将数据分解为独立的帧,交错传输,实现了真正的多路复用。即使某个请求耗时很长,其他请求也可以并行处理。同时引入 HPACK 压缩算法,大幅减少头部传输量。
HTTP/3 的革命
HTTP/3 彻底抛弃了 TCP,基于 QUIC 协议(UDP)实现。解决了 TCP 层面的队头阻塞问题,支持 0-RTT 连接恢复,更适合现代移动网络环境。
HTTP 消息格式
HTTP 消息分为请求消息和响应消息,两者结构相似但内容不同。
请求消息格式
HTTP 请求由三部分组成:请求行、请求头部、请求体。
POST /api/users HTTP/1.1 <- 请求行
Host: example.com <- 请求头部
Content-Type: application/json
Content-Length: 42
User-Agent: Mozilla/5.0
<- 空行
{"name": "张三", "age": 25} <- 请求体
请求行包含三个部分:
- 请求方法:指明要对资源执行的操作,如 GET、POST、PUT、DELETE
- 请求目标:通常是 URL 路径,或完整 URL(代理场景)
- HTTP 版本:格式为 HTTP/x.x
响应消息格式
HTTP 响应也由三部分组成:状态行、响应头部、响应体。
HTTP/1.1 200 OK <- 状态行
Content-Type: application/json <- 响应头部
Content-Length: 58
Cache-Control: max-age=3600
<- 空行
{"id": 1, "name": "张三", "age": 25} <- 响应体
状态行包含三个部分:
- HTTP 版本:服务器使用的 HTTP 版本
- 状态码:三位数字,表示请求的处理结果
- 原因短语:状态码的简短文字描述
消息体的处理
消息体是可选的,GET 和 HEAD 请求通常没有消息体。当存在消息体时,需要通过头部指明:
- Content-Type:消息体的媒体类型
- Content-Length:消息体的字节长度
- Transfer-Encoding: chunked:分块传输编码,用于动态生成的内容
HTTP 请求方法
HTTP 定义了一组请求方法(也称为 HTTP 动词),每个方法有不同的语义和行为特性。
方法的语义
| 方法 | 语义 | 是否安全 | 是否幂等 | 是否可缓存 |
|---|---|---|---|---|
| GET | 获取资源 | 是 | 是 | 是 |
| HEAD | 获取资源元信息 | 是 | 是 | 是 |
| POST | 创建资源或处理数据 | 否 | 否 | 条件性 |
| PUT | 替换资源 | 否 | 是 | 否 |
| DELETE | 删除资源 | 否 | 是 | 否 |
| PATCH | 部分更新资源 | 否 | 否 | 条件性 |
| OPTIONS | 查询支持的方法 | 是 | 是 | 否 |
| CONNECT | 建立隧道 | 否 | 否 | 否 |
| TRACE | 消息环路测试 | 是 | 是 | 否 |
安全方法:指只读方法,不会改变服务器状态。GET、HEAD、OPTIONS、TRACE 是安全的。安全方法可以被缓存、预取,不会产生副作用。
幂等方法:多次执行相同请求,效果与执行一次相同。GET、HEAD、PUT、DELETE、OPTIONS、TRACE 是幂等的。幂等性在分布式系统中很重要,允许安全重试。
GET 方法
GET 方法请求获取指定资源的表示。这是最常见的 HTTP 方法。
特点:
- 只用于获取数据,不应有副作用
- 请求参数放在 URL 的查询字符串中
- 可以被缓存、收藏为书签
- 有 URL 长度限制(浏览器和服务器限制,通常 2KB-8KB)
请求示例:
GET /users?page=1&size=20 HTTP/1.1
Host: api.example.com
Accept: application/json
使用场景:
- 网页浏览
- API 数据查询
- 搜索请求
- 静态资源获取
注意事项:
- 不要用 GET 请求执行写操作(如删除数据)
- 敏感信息不应放在 URL 中(会被记录在访问日志中)
- GET 请求可以被缓存,注意缓存策略
POST 方法
POST 方法向指定资源提交数据,通常用于创建新资源或触发处理流程。
特点:
- 可以包含请求体,数据量无限制
- 请求体数据类型通过 Content-Type 指定
- 不是幂等的(多次提交会创建多个资源)
- 默认不缓存
请求示例:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 42
{"name": "张三", "email": "[email protected]"}
常见 Content-Type:
| 类型 | 用途 |
|---|---|
| application/json | JSON 数据(最常用) |
| application/x-www-form-urlencoded | 表单默认编码 |
| multipart/form-data | 文件上传 |
| text/plain | 纯文本 |
| application/xml | XML 数据 |
使用场景:
- 创建新资源
- 提交表单数据
- 文件上传
- 复杂查询条件
PUT 方法
PUT 方法用请求体中的数据完全替换目标资源。如果资源不存在,通常会创建新资源。
特点:
- 幂等:多次执行相同 PUT 请求,资源状态相同
- 需要发送完整资源数据
- 适合整体替换
请求示例:
PUT /api/users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 62
{"name": "李四", "email": "[email protected]", "age": 30}
PUT vs POST:
| 特性 | PUT | POST |
|---|---|---|
| 幂等性 | 是 | 否 |
| 语义 | 替换资源 | 创建或处理 |
| URL 含义 | 资源的具体地址 | 资源集合或处理器 |
| 多次请求 | 结果相同 | 可能创建多个资源 |
DELETE 方法
DELETE 方法请求删除指定资源。
特点:
- 幂等:删除一个已删除的资源,结果仍然是资源不存在
- 通常没有请求体
- 成功响应通常是 200 或 204
请求示例:
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
PATCH 方法
PATCH 方法对资源进行部分更新,只修改指定的字段。
特点:
- 不是幂等的(取决于实现方式)
- 只发送需要修改的字段
- 比 PUT 更节省带宽
请求示例:
PATCH /api/users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json-patch+json
[
{"op": "replace", "path": "/email", "value": "[email protected]"}
]
PATCH 格式:
- JSON Patch(application/json-patch+json):标准化的补丁格式
- JSON Merge Patch(application/merge-patch+json):简单的合并格式
OPTIONS 方法
OPTIONS 方法查询服务器支持的 HTTP 方法,常用于 CORS 预检请求。
请求示例:
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://client.example.com
Access-Control-Request-Method: POST
响应示例:
HTTP/1.1 200 OK
Allow: GET, POST, PUT, DELETE
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Origin: https://client.example.com
HEAD 方法
HEAD 方法与 GET 相同,但服务器只返回头部,不返回消息体。
用途:
- 检查资源是否存在(通过状态码)
- 检查资源是否更新(通过 Last-Modified 或 ETag)
- 获取资源大小(通过 Content-Length)
请求示例:
HEAD /images/logo.png HTTP/1.1
Host: example.com
CONNECT 方法
CONNECT 方法建立到目标资源的隧道,主要用于 HTTPS 代理。
工作原理:
客户端 -> 代理服务器: CONNECT target.com:443 HTTP/1.1
代理服务器 -> 客户端: HTTP/1.1 200 Connection Established
[此时建立了隧道,后续通信透明转发]
客户端 <-> 代理服务器 <-> 目标服务器
HTTP 头部字段
HTTP 头部字段是键值对形式,提供请求或响应的元信息。头部字段不区分大小写,格式为 Name: Value。
头部分类
根据使用场景,HTTP 头部可以分为以下几类:
通用头部:既可以出现在请求中,也可以出现在响应中。
| 头部 | 说明 |
|---|---|
| Cache-Control | 缓存控制指令 |
| Connection | 连接管理,如 Keep-Alive |
| Date | 消息生成的日期时间 |
| Transfer-Encoding | 传输编码方式 |
| Upgrade | 升级到其他协议 |
请求头部:只在请求中出现,提供关于请求或客户端的信息。
| 头部 | 说明 | 示例 |
|---|---|---|
| Host | 目标主机名和端口 | example.com:8080 |
| User-Agent | 客户端信息 | Mozilla/5.0 (Windows NT 10.0) |
| Accept | 可接受的响应内容类型 | text/html, application/json |
| Accept-Language | 可接受的语言 | zh-CN, en;q=0.9 |
| Accept-Encoding | 可接受的编码方式 | gzip, deflate, br |
| Authorization | 认证信息 | Bearer token123 |
| Cookie | 发送给服务器的 Cookie | sessionid=abc123 |
| Referer | 请求来源页面的 URL | https://example.com/page |
| Content-Type | 请求体的媒体类型 | application/json |
| Content-Length | 请求体的字节长度 | 1024 |
响应头部:只在响应中出现,提供关于响应或服务器的信息。
| 头部 | 说明 | 示例 |
|---|---|---|
| Server | 服务器软件信息 | nginx/1.20.1 |
| Set-Cookie | 设置客户端 Cookie | sessionid=xyz; Path=/; HttpOnly |
| Content-Type | 响应体的媒体类型 | text/html; charset=utf-8 |
| Content-Length | 响应体的字节长度 | 2048 |
| Content-Encoding | 响应体的编码方式 | gzip |
| Content-Disposition | 建议的文件名 | attachment; filename="report.pdf" |
| Location | 重定向目标 URL | https://example.com/new |
| WWW-Authenticate | 认证要求 | Basic realm="Admin Area" |
| ETag | 资源版本标识 | "33a64df551425fcc" |
| Last-Modified | 资源最后修改时间 | Wed, 21 Oct 2025 07:28:00 GMT |
实体头部:描述消息体的内容,可在请求或响应中使用。
| 头部 | 说明 |
|---|---|
| Content-Type | 实体的媒体类型 |
| Content-Length | 实体的字节长度 |
| Content-Encoding | 实体的编码方式 |
| Content-Language | 实体的自然语言 |
| Content-Location | 实体的备用位置 |
重要头部详解
Content-Type
Content-Type 指定消息体的媒体类型(MIME 类型),格式为 type/subtype; parameters。
常用 MIME 类型:
| 类型 | MIME 类型 | 说明 |
|---|---|---|
| 文本 | text/html | HTML 文档 |
| 文本 | text/plain | 纯文本 |
| 文本 | text/css | CSS 样式表 |
| 文本 | text/javascript | JavaScript 脚本 |
| 图片 | image/jpeg | JPEG 图片 |
| 图片 | image/png | PNG 图片 |
| 图片 | image/gif | GIF 图片 |
| 图片 | image/svg+xml | SVG 图片 |
| 音视频 | audio/mpeg | MP3 音频 |
| 音视频 | video/mp4 | MP4 视频 |
| 应用 | application/json | JSON 数据 |
| 应用 | application/xml | XML 数据 |
| 应用 | application/pdf | PDF 文档 |
| 应用 | application/zip | ZIP 压缩包 |
| 表单 | application/x-www-form-urlencoded | URL 编码表单 |
| 表单 | multipart/form-data | 多部分表单(文件上传) |
Accept 系列头部
Accept 系列头部用于内容协商,客户端告诉服务器自己能够处理的内容类型。
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
Accept-Language: zh-CN, zh;q=0.9, en;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Charset: utf-8, iso-8859-1;q=0.5
q 值:表示优先级,范围 0-1,默认为 1。值越大优先级越高。
Authorization
Authorization 头部用于传递认证信息。
Basic 认证:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
# Base64 编码的 username:password
Bearer Token(OAuth 2.0):
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Cache-Control
Cache-Control 头部控制缓存行为,可出现在请求或响应中。
请求指令:
Cache-Control: max-age=0 # 要求重新验证
Cache-Control: no-cache # 要求验证缓存
Cache-Control: no-store # 不使用缓存
Cache-Control: max-stale=3600 # 接受过期缓存
Cache-Control: only-if-cached # 只使用缓存
响应指令:
Cache-Control: public # 可被任何缓存存储
Cache-Control: private # 只能被浏览器缓存
Cache-Control: max-age=3600 # 缓存有效期(秒)
Cache-Control: no-cache # 使用前必须验证
Cache-Control: no-store # 不缓存
Cache-Control: must-revalidate # 过期后必须验证
Cache-Control: immutable # 资源永不过期
HTTP 状态码
HTTP 状态码是三位数字,表示请求的处理结果。状态码分为五类:
状态码分类
| 类别 | 范围 | 含义 |
|---|---|---|
| 1xx | 100-199 | 信息性响应:请求已接收,继续处理 |
| 2xx | 200-299 | 成功:请求已成功接收、理解、接受 |
| 3xx | 300-399 | 重定向:需要进一步操作以完成请求 |
| 4xx | 400-499 | 客户端错误:请求包含语法错误或无法完成 |
| 5xx | 500-599 | 服务器错误:服务器无法完成有效请求 |
常用状态码详解
成功响应(2xx)
200 OK:请求成功。响应体包含请求的数据。
201 Created:资源创建成功。通常用于 POST 请求,响应中应包含 Location 头部指向新资源。
HTTP/1.1 201 Created
Location: /api/users/123
Content-Type: application/json
{"id": 123, "name": "张三"}
204 No Content:请求成功,但无返回内容。常用于 DELETE 请求或 PUT 请求。
206 Partial Content:服务器返回部分内容。用于断点续传和分块下载。
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/10240
Content-Length: 1024
[数据内容...]
重定向(3xx)
301 Moved Permanently:永久重定向。请求的资源已永久移动到新 URL,SEO 权重会转移。
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/new-path
302 Found:临时重定向。资源暂时移动到其他 URL,客户端应继续使用原 URL。
303 See Other:重定向到其他资源。常用于 POST 请求后重定向到结果页面。
304 Not Modified:资源未修改。客户端发送条件请求,服务器确认缓存仍有效。
# 客户端请求
GET /styles.css HTTP/1.1
If-None-Match: "33a64df551425fcc"
# 服务器响应
HTTP/1.1 304 Not Modified
ETag: "33a64df551425fcc"
307 Temporary Redirect:临时重定向,保持请求方法不变。302 可能会将 POST 改为 GET,307 保证方法不变。
308 Permanent Redirect:永久重定向,保持请求方法不变。
客户端错误(4xx)
400 Bad Request:请求语法错误,服务器无法理解。通常是由于请求参数格式错误。
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Invalid request",
"message": "email field is required"
}
401 Unauthorized:需要身份认证。客户端需要提供有效的认证信息。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
403 Forbidden:服务器拒绝请求。客户端已认证但没有访问权限。
404 Not Found:请求的资源不存在。
405 Method Not Allowed:请求方法不被允许。响应中应包含 Allow 头部。
HTTP/1.1 405 Method Not Allowed
Allow: GET, POST
408 Request Timeout:服务器等待请求超时。
409 Conflict:请求与服务器当前状态冲突。常用于资源版本冲突。
410 Gone:资源已永久删除。与 404 不同,410 表示资源曾经存在但已删除。
413 Payload Too Large:请求体太大,超过服务器限制。
414 URI Too Long:请求的 URL 太长。
415 Unsupported Media Type:不支持的媒体类型。
422 Unprocessable Entity:请求格式正确但语义错误。常用于验证失败。
429 Too Many Requests:请求过于频繁,被限流。
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
服务器错误(5xx)
500 Internal Server Error:服务器内部错误。服务器遇到意外情况,无法完成请求。
501 Not Implemented:服务器不支持请求的功能。
502 Bad Gateway:网关或代理从上游服务器收到无效响应。
503 Service Unavailable:服务暂时不可用。服务器过载或正在维护。
HTTP/1.1 503 Service Unavailable
Retry-After: 120
504 Gateway Timeout:网关或代理等待上游服务器响应超时。
HTTP 缓存机制
HTTP 缓存是提升 Web 性能的关键技术。合理使用缓存可以减少网络传输、降低服务器负载、加快页面加载速度。
缓存类型
私有缓存:只能被单个用户使用,如浏览器缓存。
共享缓存:可以被多个用户使用,如代理服务器缓存、CDN 缓存。
强缓存
强缓存指不需要向服务器验证,直接使用本地缓存。通过响应头部控制:
Expires
Expires 指定缓存的过期时间(绝对时间):
Expires: Wed, 21 Oct 2026 07:28:00 GMT
缺点:使用服务器时间,客户端时间不准确会导致问题。
Cache-Control: max-age
max-age 指定缓存的有效期(相对时间,秒):
Cache-Control: max-age=31536000 # 有效期一年
优点:使用相对时间,避免时间同步问题。
优先级:Cache-Control 优先于 Expires。
协商缓存
协商缓存需要向服务器验证缓存是否有效。
ETag / If-None-Match
ETag 是资源的唯一标识符(通常是内容哈希):
# 第一次请求
GET /api/users/123 HTTP/1.1
Host: example.com
# 服务器响应
HTTP/1.1 200 OK
ETag: "33a64df551425fcc"
Content-Type: application/json
{"name": "张三"}
# 第二次请求(条件请求)
GET /api/users/123 HTTP/1.1
Host: example.com
If-None-Match: "33a64df551425fcc"
# 资源未修改,返回 304
HTTP/1.1 304 Not Modified
ETag: "33a64df551425fcc"
Last-Modified / If-Modified-Since
Last-Modified 指定资源的最后修改时间:
# 第一次请求
GET /styles.css HTTP/1.1
Host: example.com
# 服务器响应
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
# 第二次请求(条件请求)
GET /styles.css HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
# 资源未修改,返回 304
HTTP/1.1 304 Not Modified
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
ETag vs Last-Modified:
| 特性 | ETag | Last-Modified |
|---|---|---|
| 精度 | 高(内容级别) | 低(时间级别,秒) |
| 准确性 | 内容变化才变 | 时间变化内容可能不变 |
| 性能 | 需要计算哈希 | 文件系统时间戳 |
| 优先级 | 高 | 低 |
缓存决策流程
缓存最佳实践
静态资源:使用强缓存,设置长过期时间。
Cache-Control: public, max-age=31536000, immutable
配合文件名哈希(如 app.abc123.js),内容变化时文件名变化,强制刷新。
动态内容:使用协商缓存,每次验证。
Cache-Control: no-cache
ETag: "version-123"
敏感数据:禁止缓存。
Cache-Control: no-store, private
Cookie 与 Session
HTTP 是无状态协议,Cookie 和 Session 是实现状态管理的两种机制。
Cookie
Cookie 是服务器发送到浏览器的小数据片段,浏览器会在后续请求中自动带上。
Set-Cookie 响应头
Set-Cookie: sessionid=abc123; Path=/; Domain=example.com; Max-Age=3600; HttpOnly; Secure; SameSite=Lax
属性说明:
| 属性 | 说明 |
|---|---|
| Name=Value | Cookie 名称和值 |
| Domain | 生效域名,默认当前域名 |
| Path | 生效路径,默认当前路径 |
| Expires | 过期时间(绝对时间) |
| Max-Age | 有效期(秒),优先于 Expires |
| Secure | 仅通过 HTTPS 传输 |
| HttpOnly | JavaScript 无法访问,防止 XSS |
| SameSite | 跨站请求策略:Strict、Lax、None |
Cookie 请求头
浏览器自动在请求中带上匹配的 Cookie:
Cookie: sessionid=abc123; userid=456; preferences=dark
Cookie 的限制
- 单个 Cookie 大小通常限制为 4KB
- 每个域名 Cookie 数量有限制(通常 20-50 个)
- 总 Cookie 数量有限制(通常 300-500 个)
Session
Session 是服务器端存储的用户状态,通常通过 Cookie 中的 Session ID 关联。
工作流程:
- 用户首次访问,服务器创建 Session,生成唯一 Session ID
- 服务器通过 Set-Cookie 将 Session ID 发送给浏览器
- 浏览器后续请求自动带上 Session ID
- 服务器根据 Session ID 查找对应的 Session 数据
# Flask 示例
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'your-secret-key'
@app.route('/login')
def login():
session['user_id'] = 123
session['username'] = '张三'
return '登录成功'
@app.route('/profile')
def profile():
user_id = session.get('user_id')
if user_id:
return f'用户 ID: {user_id}'
return '未登录'
Cookie vs Session
| 特性 | Cookie | Session |
|---|---|---|
| 存储位置 | 浏览器 | 服务器 |
| 安全性 | 较低(可被篡改) | 较高 |
| 存储容量 | 有限(约 4KB) | 无限制 |
| 服务器负担 | 低 | 高 |
| 跨域支持 | 受限 | 不受影响 |
JWT(JSON Web Token)
JWT 是一种无状态的认证方案,将用户信息编码在 Token 中,服务器无需存储 Session。
结构:Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuW8oOa4ryIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
优点:
- 无状态,易于水平扩展
- 跨域友好
- 包含用户信息,减少数据库查询
缺点:
- 无法主动失效(需要黑名单机制)
- Token 较大
- Payload 不加密,只签名
跨域资源共享(CORS)
同源策略是浏览器的安全机制,限制一个源的文档或脚本与另一个源的资源交互。CORS 是突破同源限制的标准方案。
同源策略
同源指协议、域名、端口完全相同。
https://example.com:443/path
协议: https
域名: example.com
端口: 443(默认端口)
简单请求
满足以下条件的请求是简单请求,浏览器直接发送,只检查响应:
- 方法:GET、HEAD、POST
- 头部:Accept、Accept-Language、Content-Language、Content-Type(仅限 application/x-www-form-urlencoded、multipart/form-data、text/plain)
POST /api/data HTTP/1.1
Host: api.example.com
Origin: https://client.example.com
Content-Type: application/x-www-form-urlencoded
key=value
服务器响应:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://client.example.com
Access-Control-Allow-Credentials: true
预检请求
不满足简单请求条件的请求,浏览器会先发送 OPTIONS 预检请求:
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://client.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
服务器响应:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://client.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
CORS 头部
| 头部 | 说明 |
|---|---|
| Access-Control-Allow-Origin | 允许的源,* 表示所有源 |
| Access-Control-Allow-Methods | 允许的方法 |
| Access-Control-Allow-Headers | 允许的请求头 |
| Access-Control-Allow-Credentials | 是否允许携带 Cookie |
| Access-Control-Max-Age | 预检请求的缓存时间 |
| Access-Control-Expose-Headers | 允许 JavaScript 访问的响应头 |
服务端配置示例
Node.js (Express):
const express = require('express');
const cors = require('cors');
const app = express();
// 允许所有源
app.use(cors());
// 或精细配置
app.use(cors({
origin: 'https://client.example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
}));
Nginx:
server {
location /api/ {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
add_header Access-Control-Allow-Headers 'Content-Type, Authorization';
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Max-Age 86400;
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend;
}
}
HTTP 安全最佳实践
使用 HTTPS
HTTPS = HTTP + TLS,加密传输数据,防止中间人攻击。
重定向到 HTTPS:
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
HSTS(HTTP Strict Transport Security):
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
强制浏览器使用 HTTPS,即使用户输入 http://。
安全头部
# 防止 XSS
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
# 防止点击劫持
X-Frame-Options: DENY
# 内容安全策略
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
# 隐藏服务器信息
Server:
X-Powered-By:
Cookie 安全
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Strict
- HttpOnly:防止 XSS 攻击读取 Cookie
- Secure:仅通过 HTTPS 传输
- SameSite:防止 CSRF 攻击
认证与授权
不使用 Basic Auth:Base64 编码不是加密,容易被解码。
使用 Bearer Token:JWT 或 OAuth 2.0 Token。
Token 存储位置:
- Authorization 头部(推荐)
- HttpOnly Cookie(防 XSS)
- 不应存储在 localStorage(XSS 风险)
实战:构建 HTTP 服务
Node.js 示例
const express = require('express');
const app = express();
// 中间件
app.use(express.json());
// 路由
app.get('/api/users', (req, res) => {
const { page = 1, size = 20 } = req.query;
res.json({
data: [],
page: parseInt(page),
size: parseInt(size)
});
});
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'name and email are required' });
}
const user = { id: Date.now(), name, email };
res.status(201)
.location(`/api/users/${user.id}`)
.json(user);
});
app.put('/api/users/:id', (req, res) => {
const { id } = req.params;
const { name, email } = req.body;
// 更新逻辑...
const user = { id, name, email };
res.json(user);
});
app.delete('/api/users/:id', (req, res) => {
const { id } = req.params;
// 删除逻辑...
res.status(204).end();
});
// 错误处理
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Internal Server Error' });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Python (Flask) 示例
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
# 认证装饰器
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
return jsonify({'error': 'Unauthorized'}), 401
# 验证 token...
return f(*args, **kwargs)
return decorated
@app.route('/api/users', methods=['GET'])
def get_users():
page = request.args.get('page', 1, type=int)
size = request.args.get('size', 20, type=int)
return jsonify({
'data': [],
'page': page,
'size': size
})
@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()
if not data.get('name') or not data.get('email'):
return jsonify({'error': 'name and email are required'}), 400
user = {
'id': 1,
'name': data['name'],
'email': data['email']
}
return jsonify(user), 201, {'Location': f'/api/users/{user["id"]}'}
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
data = request.get_json()
user = {
'id': user_id,
'name': data.get('name'),
'email': data.get('email')
}
return jsonify(user)
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
return '', 204
if __name__ == '__main__':
app.run(debug=True, port=3000)
HTTP 客户端示例
import requests
# GET 请求
response = requests.get(
'https://api.example.com/users',
params={'page': 1, 'size': 20},
headers={'Accept': 'application/json'}
)
print(response.status_code)
print(response.json())
# POST 请求
response = requests.post(
'https://api.example.com/users',
json={'name': '张三', 'email': '[email protected]'},
headers={'Authorization': 'Bearer token123'}
)
print(response.status_code)
print(response.json())
# 处理错误
try:
response = requests.get('https://api.example.com/users/999')
response.raise_for_status() # 非 2xx 状态码抛出异常
except requests.HTTPError as e:
print(f'HTTP Error: {e}')
except requests.ConnectionError:
print('Connection Error')
except requests.Timeout:
print('Timeout')
总结
HTTP 是现代互联网的基础协议,本章系统介绍了:
- 协议概述:HTTP 的设计目标和版本演进,从 HTTP/1.0 到 HTTP/3 的改进历程
- 消息格式:请求和响应的结构,请求行、头部、消息体的详细说明
- 请求方法:GET、POST、PUT、DELETE、PATCH 等方法的语义和使用场景
- 状态码:五类状态码的含义和常用状态码的详细说明
- 头部字段:通用头部、请求头部、响应头部的分类和重要头部详解
- 缓存机制:强缓存和协商缓存的原理,ETag、Last-Modified、Cache-Control 的使用
- 状态管理:Cookie、Session、JWT 三种方案的对比和选择
- 跨域:同源策略和 CORS 的工作原理,预检请求和响应头配置
- 安全实践:HTTPS、安全头部、Cookie 安全、认证授权的最佳实践
深入理解 HTTP 协议对于 Web 开发、API 设计、性能优化和安全防护至关重要。随着 HTTP/2 和 HTTP/3 的普及,HTTP 正变得更高效、更安全。
[!TIP] 想了解 HTTPS 如何保证安全?请看 HTTPS 协议详解。想了解 HTTP/2 的多路复用?请看 HTTP/3 与 QUIC 协议详解。