第一部分:HTTP 协议答案
第一章:网络分层模型(5 题)
1.1 OSI七层模型是什么?各层的作用是什么?
答案:
OSI(Open Systems Interconnection)七层模型是国际标准化组织(ISO)提出的网络通信参考模型,将网络通信分为七层:
- 物理层(Physical Layer):传输原始比特流,定义物理接口标准
- 数据链路层(Data Link Layer):在物理层基础上提供可靠的数据传输
- 网络层(Network Layer):路由选择和数据包转发
- 传输层(Transport Layer):提供端到端的数据传输服务
- 会话层(Session Layer):建立、管理和终止会话
- 表示层(Presentation Layer):数据格式转换、加密解密、压缩解压
- 应用层(Application Layer):为用户提供网络服务接口
记忆口诀: 物数网传会表应(物理、数据链路、网络、传输、会话、表示、应用)
各层的主要功能:
| 层级 | 名称 | 主要功能 | 典型协议 |
|---|---|---|---|
| 7 | 应用层 | 为用户提供网络服务 | HTTP、HTTPS、FTP、SMTP、DNS |
| 6 | 表示层 | 数据格式转换、加密解密 | SSL/TLS、JPEG、MPEG |
| 5 | 会话层 | 建立、管理和终止会话 | RPC、SQL、NetBIOS |
| 4 | 传输层 | 端到端的数据传输 | TCP、UDP |
| 3 | 网络层 | 路由选择和数据包转发 | IP、ICMP、ARP |
| 2 | 数据链路层 | 可靠的数据传输 | Ethernet、WiFi、PPP |
| 1 | 物理层 | 传输原始比特流 | 网线、光纤、无线电波 |
1.2 TCP/IP四层模型是什么?各层的作用是什么?
答案:
TCP/IP四层模型是实际使用的网络模型,将网络通信分为四层:
-
网络接口层(Network Interface Layer)
- 对应OSI的物理层和数据链路层
- 作用:在物理网络上传输数据帧
- 功能:MAC地址、帧格式、介质访问控制
- 协议:Ethernet、WiFi、PPP
-
网际层(Internet Layer)
- 对应OSI的网络层
- 作用:路由选择和数据包转发
- 功能:IP地址、路由、数据包转发
- 协议:IP、ICMP、ARP、IGMP
-
传输层(Transport Layer)
- 对应OSI的传输层
- 作用:提供端到端的数据传输
- 功能:端口号、可靠传输、流量控制
- 协议:TCP、UDP
-
应用层(Application Layer)
- 对应OSI的会话层、表示层、应用层
- 作用:为用户提供网络服务
- 功能:应用协议、数据格式、用户接口
- 协议:HTTP、HTTPS、FTP、SMTP、DNS
TCP/IP四层模型的特点:
- 更简洁实用
- 与实际网络实现更接近
- 应用层包含了OSI的会话层、表示层、应用层
1.3 TCP/IP五层模型是什么?各层的作用是什么?
答案:
TCP/IP五层模型是在四层模型基础上,将网络接口层拆分为物理层和数据链路层:
-
物理层(Physical Layer)
- 作用:传输原始比特流
- 功能:电信号、光信号、无线电波传输
- 设备:网线、光纤、无线设备
-
数据链路层(Data Link Layer)
- 作用:在物理层基础上提供可靠的数据传输
- 功能:帧同步、差错控制、MAC地址
- 协议:Ethernet、WiFi、PPP
-
网络层(Network Layer)
- 对应OSI的网络层
- 作用:路由选择和数据包转发
- 功能:IP地址、路由、数据包转发
- 协议:IP、ICMP、ARP
-
传输层(Transport Layer)
- 对应OSI的传输层
- 作用:提供端到端的数据传输
- 功能:端口号、可靠传输、流量控制
- 协议:TCP、UDP
- 注:与四层模型的传输层相同
-
应用层(Application Layer)
- 对应OSI的会话层、表示层、应用层
- 作用:为用户提供网络服务
- 功能:应用协议、数据格式
- 协议:HTTP、HTTPS、FTP、SMTP、DNS
- 注:与四层模型的应用层相同
TCP/IP五层模型的特点:
- 更详细地划分了底层
- 便于理解物理传输和数据链路控制
- 教学和学习更清晰
1.4 TCP/IP四层模型和五层模型的区别是什么?
答案:
TCP/IP四层模型和五层模型的主要区别:
-
网络接口层的划分
- 四层模型:网络接口层(包含物理层和数据链路层)
- 五层模型:物理层 + 数据链路层(分开)
-
层次数量
- 四层模型:4层
- 五层模型:5层
-
使用场景
- 四层模型:实际网络实现、RFC标准
- 五层模型:教学、学习、理解
-
对应关系
- 四层模型:网络接口层 = OSI物理层 + 数据链路层
- 五层模型:物理层 = OSI物理层,数据链路层 = OSI数据链路层
对比表:
| 模型 | 层次 | 网络接口层处理 |
|---|---|---|
| 四层模型 | 4层 | 合并物理层和数据链路层 |
| 五层模型 | 5层 | 分开物理层和数据链路层 |
实际应用:
- 实际网络实现通常使用四层模型
- 教学和学习通常使用五层模型
- 两者在功能上等价,只是划分方式不同
1.5 OSI七层模型和TCP/IP模型的对应关系是什么?
答案:
OSI七层模型和TCP/IP模型的对应关系:
对应关系表:
| OSI七层模型 | TCP/IP四层模型 | TCP/IP五层模型 |
|---|---|---|
| 应用层 | 应用层 | 应用层 |
| 表示层 | 应用层 | 应用层 |
| 会话层 | 应用层 | 应用层 |
| 传输层 | 传输层 | 传输层 |
| 网络层 | 网际层 | 网络层 |
| 数据链路层 | 网络接口层 | 数据链路层 |
| 物理层 | 网络接口层 | 物理层 |
关键对应关系:
-
应用层对应
- OSI的应用层、表示层、会话层 → TCP/IP的应用层
- TCP/IP应用层包含了OSI上三层的功能
-
传输层对应
- OSI传输层 ↔ TCP/IP传输层
- 完全对应,协议相同(TCP、UDP)
-
网络层对应
- OSI网络层 ↔ TCP/IP网际层/网络层
- 功能相同,名称不同
-
底层对应
- OSI数据链路层 + 物理层 → TCP/IP网络接口层(四层)或 数据链路层 + 物理层(五层)
实际应用:
- OSI模型:理论参考模型,用于理解网络通信原理
- TCP/IP模型:实际使用的模型,互联网的基础架构
第二章:HTTP 概述(15 题)
2.1 HTTP是什么?它的全称是什么?
答案:
HTTP(HyperText Transfer Protocol)是超文本传输协议,是互联网上应用最广泛的网络协议之一。
定义:
- HTTP是一种应用层协议
- 用于在Web浏览器和Web服务器之间传输数据
- 基于请求-响应模型
- 使用TCP作为传输层协议
特点:
- 无状态协议:每次请求都是独立的,服务器不保存客户端状态
- 请求-响应模型:客户端发送请求,服务器返回响应
- 明文传输:数据以明文形式传输(HTTPS是加密的)
- 灵活:支持多种数据格式(HTML、JSON、XML等)
应用场景:
- 网页浏览
- API调用
- 文件下载
- 图片加载
2.2 HTTP的特点是什么?
答案:
HTTP的主要特点:
-
无状态(Stateless)
- 每次请求都是独立的
- 服务器不保存客户端的状态信息
- 优点:简化服务器设计,提高性能
- 缺点:需要状态时使用Cookie、Session
-
请求-响应模型(Request-Response)
- 客户端发起请求
- 服务器处理请求并返回响应
- 一次请求对应一次响应
-
基于TCP
- 使用TCP作为传输层协议
- 默认端口80
- 保证数据传输的可靠性
-
明文传输
- 数据以明文形式传输
- 容易被窃听和篡改
- HTTPS提供加密传输
-
灵活
- 支持多种数据格式
- 支持多种请求方法
- 支持自定义请求头和响应头
-
可扩展
- 支持HTTP/1.0、HTTP/1.1、HTTP/2.0、HTTP/3.0
- 支持新的请求方法和状态码
2.3 HTTP属于OSI模型的哪一层?
答案:
HTTP属于OSI模型的应用层(Application Layer)。
详细说明:
-
HTTP的位置
- HTTP是应用层协议(第7层)
- 建立在传输层(TCP)之上
- 使用网络层(IP)进行路由
-
协议栈
应用层: HTTP 传输层: TCP 网络层: IP 数据链路层: Ethernet 物理层: 网线/光纤 -
与其他协议的关系
- HTTP使用TCP提供可靠传输
- TCP使用IP进行路由
- IP使用数据链路层进行帧传输
2.4 HTTP属于TCP/IP模型的哪一层?
答案:
HTTP属于TCP/IP模型的应用层(Application Layer)。
详细说明:
-
TCP/IP模型分层
- 应用层
- 传输层(TCP/UDP)
- 网际层(IP)
- 网络接口层
-
HTTP的位置
- HTTP是应用层协议
- 建立在传输层(TCP)之上
- 使用网际层(IP)进行路由
-
协议栈
TCP/IP模型: 应用层: HTTP 传输层: TCP 网际层: IP 网络接口层: Ethernet -
与其他协议的关系
- HTTP依赖TCP提供可靠传输
- TCP依赖IP进行路由
- IP依赖网络接口层进行物理传输
注意: 在TCP/IP模型中,应用层包含了OSI模型的会话层、表示层、应用层的功能。
2.5 HTTP和TCP/IP的关系是什么?
答案:
HTTP和TCP/IP的关系:
-
协议层次关系
- HTTP是应用层协议
- TCP是传输层协议
- IP是网络层协议
- HTTP建立在TCP/IP之上
-
依赖关系
HTTP(应用层) ↓ 依赖 TCP(传输层) ↓ 依赖 IP(网络层) ↓ 依赖 数据链路层 -
HTTP使用TCP的原因
- TCP提供可靠传输:保证数据不丢失、不重复、有序
- TCP提供连接管理:三次握手建立连接,四次挥手关闭连接
- TCP提供流量控制:防止发送方发送过快
- TCP提供拥塞控制:防止网络拥塞
-
HTTP不使用UDP的原因
- UDP不保证可靠性:数据可能丢失
- UDP无连接:不适合HTTP的请求-响应模型
- HTTP需要可靠传输保证数据完整性
-
实际传输过程
客户端发送HTTP请求 ↓ TCP将HTTP数据分段,添加TCP头部 ↓ IP将TCP段封装成IP数据包,添加IP头部 ↓ 数据链路层封装成帧,添加MAC地址 ↓ 物理层传输比特流
总结: HTTP是应用层协议,依赖TCP/IP提供传输服务,TCP/IP是HTTP的基础。
2.6 HTTP是无状态协议吗?为什么?
答案:
是的,HTTP是无状态协议(Stateless Protocol)。
无状态的含义:
- 服务器不保存客户端的状态信息
- 每次请求都是独立的
- 服务器不知道客户端之前的请求
为什么HTTP是无状态协议?
-
设计目标
- 简化服务器设计
- 提高服务器性能
- 减少服务器资源消耗
-
优点
- 简化设计:服务器不需要维护客户端状态
- 提高性能:不需要查找和更新状态信息
- 易于扩展:可以轻松添加服务器
- 容错性好:服务器崩溃不影响其他请求
-
缺点
- 无法记住用户登录状态
- 无法实现购物车功能
- 无法跟踪用户行为
如何解决无状态问题?
-
Cookie
Set-Cookie: sessionid=abc123; Path=/; HttpOnly Cookie: sessionid=abc123 -
Session
- 服务器端存储会话信息
- 通过Session ID关联
-
Token
- JWT Token
- 在请求头中携带
示例:
# 第一次请求
GET /login HTTP/1.1
Host: example.com
# 服务器返回Cookie
HTTP/1.1 200 OK
Set-Cookie: sessionid=abc123
# 后续请求携带Cookie
GET /profile HTTP/1.1
Host: example.com
Cookie: sessionid=abc123
2.7 HTTP的请求响应模型是什么?
答案:
HTTP使用请求-响应模型(Request-Response Model)。
模型特点:
-
单向通信
- 客户端发起请求
- 服务器处理请求并返回响应
- 一次请求对应一次响应
-
请求结构
请求行: 方法 路径 协议版本 请求头: 键值对 空行: 分隔请求头和请求体 请求体: 可选的数据 -
响应结构
状态行: 协议版本 状态码 状态描述 响应头: 键值对 空行: 分隔响应头和响应体 响应体: 返回的数据
请求示例:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Connection: keep-alive
响应示例:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
Server: Apache/2.4
<html>
<body>Hello World</body>
</html>
工作流程:
- 客户端建立TCP连接
- 客户端发送HTTP请求
- 服务器处理请求
- 服务器返回HTTP响应
- 关闭TCP连接(HTTP/1.0)或保持连接(HTTP/1.1)
2.8 HTTP的默认端口号是什么?
答案:
HTTP的默认端口号是80。
详细说明:
-
端口号定义
- 端口号用于区分不同的应用程序
- HTTP默认使用80端口
- HTTPS默认使用443端口
-
URL中的端口
http://www.example.com # 默认80端口 http://www.example.com:80 # 显式指定80端口 http://www.example.com:8080 # 使用8080端口 -
常见端口号
- HTTP: 80
- HTTPS: 443
- FTP: 21
- SSH: 22
- Telnet: 23
- SMTP: 25
- DNS: 53
- POP3: 110
- IMAP: 143
-
端口号范围
- 0-1023:知名端口(Well-known ports)
- 1024-49151:注册端口(Registered ports)
- 49152-65535:动态端口(Dynamic ports)
-
Android开发中的使用
// 默认端口 URL url = new URL("http://www.example.com"); // 指定端口 URL url = new URL("http://www.example.com:8080");
2.9 HTTP的版本有哪些?
答案:
HTTP的版本包括:
-
HTTP/0.9(1991年)
- 最早的版本
- 只支持GET方法
- 没有请求头和响应头
- 已废弃
-
HTTP/1.0(1996年)
- 支持多种请求方法(GET、POST、HEAD)
- 引入请求头和响应头
- 支持状态码
- 默认短连接
-
HTTP/1.1(1997年)
- 默认长连接(Keep-Alive)
- 支持管道化(Pipelining)
- 支持分块传输(Chunked Transfer)
- 支持Host头
- 支持缓存控制
- 目前最广泛使用的版本
-
HTTP/2.0(2015年)
- 多路复用(Multiplexing)
- 服务器推送(Server Push)
- 头部压缩(Header Compression)
- 二进制分帧(Binary Framing)
- 流控制(Flow Control)
-
HTTP/3.0(2020年)
- 基于UDP的QUIC协议
- 更快的连接建立
- 改进的拥塞控制
- 连接迁移
版本对比:
| 版本 | 年份 | 主要特性 | 使用情况 |
|---|---|---|---|
| HTTP/0.9 | 1991 | 基础GET请求 | 已废弃 |
| HTTP/1.0 | 1996 | 请求头、状态码 | 很少使用 |
| HTTP/1.1 | 1997 | 长连接、管道化 | 广泛使用 |
| HTTP/2.0 | 2015 | 多路复用、服务器推送 | 逐渐普及 |
| HTTP/3.0 | 2020 | QUIC、更快连接 | 新兴技术 |
2.10 HTTP/1.0的特点是什么?
答案:
HTTP/1.0的主要特点:
-
请求方法
- GET:获取资源
- POST:提交数据
- HEAD:获取响应头
-
请求头和响应头
- 引入请求头:User-Agent、Accept等
- 引入响应头:Content-Type、Content-Length等
- 支持自定义头部
-
状态码
- 200:成功
- 404:未找到
- 500:服务器错误
-
短连接(默认)
- 每次请求建立新连接
- 请求完成后关闭连接
- 性能较差
-
支持多种内容类型
- Content-Type头指定内容类型
- 支持HTML、图片、文本等
HTTP/1.0请求示例:
GET /index.html HTTP/1.0
User-Agent: Mozilla/5.0
Accept: text/html
HTTP/1.0响应示例:
HTTP/1.0 200 OK
Content-Type: text/html
Content-Length: 1234
<html>...</html>
缺点:
- 每次请求都要建立新连接,开销大
- 不支持长连接
- 不支持管道化
- 不支持Host头(一个IP只能对应一个域名)
2.11 HTTP/1.1的特点是什么?
答案:
HTTP/1.1的主要特点:
-
默认长连接(Keep-Alive)
- 默认保持连接打开
- 可以复用连接发送多个请求
- 减少连接建立的开销
-
管道化(Pipelining)
- 可以发送多个请求而不等待响应
- 提高性能
- 但实现复杂,使用较少
-
分块传输(Chunked Transfer)
- 支持流式传输
- 不需要提前知道内容长度
- Transfer-Encoding: chunked
-
Host头(必需)
- 支持虚拟主机
- 一个IP可以对应多个域名
- HTTP/1.1必须包含Host头
-
缓存控制
- Cache-Control头
- ETag支持
- 更灵活的缓存策略
-
范围请求(Range Request)
- 支持断点续传
- Range头指定请求范围
- 206 Partial Content响应
-
更多请求方法
- PUT、DELETE、OPTIONS、TRACE、CONNECT
HTTP/1.1请求示例:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Connection: keep-alive
优点:
- 长连接减少开销
- 支持虚拟主机
- 更好的缓存控制
- 支持断点续传
缺点:
- 队头阻塞(Head-of-line blocking)
- 头部冗余
- 不支持服务器推送
2.12 HTTP/1.0和HTTP/1.1的区别是什么?
答案:
HTTP/1.0和HTTP/1.1的主要区别:
-
连接管理
- HTTP/1.0:默认短连接,每次请求建立新连接
- HTTP/1.1:默认长连接,可以复用连接
-
Host头
- HTTP/1.0:不需要Host头
- HTTP/1.1:必须包含Host头(支持虚拟主机)
-
管道化
- HTTP/1.0:不支持
- HTTP/1.1:支持管道化(但使用较少)
-
分块传输
- HTTP/1.0:不支持
- HTTP/1.1:支持分块传输(Chunked Transfer)
-
缓存控制
- HTTP/1.0:使用Expires头
- HTTP/1.1:使用Cache-Control头,更灵活
-
范围请求
- HTTP/1.0:不支持
- HTTP/1.1:支持Range请求,断点续传
-
错误处理
- HTTP/1.0:连接错误时关闭连接
- HTTP/1.1:更好的错误处理和恢复
对比表:
| 特性 | HTTP/1.0 | HTTP/1.1 |
|---|---|---|
| 连接 | 短连接 | 长连接 |
| Host头 | 不需要 | 必需 |
| 管道化 | 不支持 | 支持 |
| 分块传输 | 不支持 | 支持 |
| 缓存 | Expires | Cache-Control |
| 范围请求 | 不支持 | 支持 |
| 请求方法 | GET、POST、HEAD | 更多方法 |
实际应用:
- HTTP/1.0已很少使用
- HTTP/1.1是目前最广泛使用的版本
- 大多数Web服务器和浏览器都支持HTTP/1.1
2.13 HTTP/2.0的新特性有哪些?
答案:
HTTP/2.0的主要新特性:
-
多路复用(Multiplexing)
- 单个TCP连接可以同时处理多个请求
- 解决HTTP/1.1的队头阻塞问题
- 提高性能
-
服务器推送(Server Push)
- 服务器可以主动推送资源给客户端
- 减少请求次数
- 提高页面加载速度
-
头部压缩(Header Compression)
- 使用HPACK算法压缩头部
- 减少头部大小
- 提高传输效率
-
二进制分帧(Binary Framing)
- 使用二进制格式替代文本格式
- 更高效的解析
- 支持多路复用
-
流控制(Flow Control)
- 类似TCP的流控制
- 防止接收方被数据淹没
- 更精细的控制
-
请求优先级(Priority)
- 可以为请求设置优先级
- 重要资源优先传输
- 提高用户体验
HTTP/2.0的优势:
-
性能提升
- 减少延迟
- 提高吞吐量
- 更好的资源利用
-
向后兼容
- 保持HTTP语义不变
- 应用层无需修改
- 只需升级服务器和客户端
HTTP/2.0示例:
# HTTP/2.0使用二进制帧,不再是文本格式
# 客户端可以同时发送多个请求
GET /page1.html
GET /page2.html
GET /image.jpg
# 服务器可以并行处理并返回
使用情况:
- 需要HTTPS(大多数浏览器要求)
- 服务器和客户端都需要支持HTTP/2.0
- 逐渐普及,但HTTP/1.1仍是主流
2.14 HTTP/2.0的多路复用是什么?
答案:
HTTP/2.0的多路复用(Multiplexing)是指在单个TCP连接上同时传输多个HTTP请求和响应。
工作原理:
-
二进制分帧
- 将HTTP消息分解为独立的帧
- 每个帧有流ID标识
- 帧可以在连接上交错传输
-
流(Stream)
- 每个请求/响应对应一个流
- 流有唯一的ID
- 多个流可以并行传输
-
帧(Frame)
- HEADERS帧:请求/响应头
- DATA帧:请求/响应体
- 其他控制帧
对比HTTP/1.1:
HTTP/1.1(串行):
请求1 → 响应1 → 请求2 → 响应2 → 请求3 → 响应3
HTTP/2.0(并行):
请求1 ──┐
请求2 ──┼──→ 并行传输 ──┼──→ 响应1
请求3 ──┘ └──→ 响应2
└──→ 响应3
优势:
-
解决队头阻塞
- HTTP/1.1中,如果第一个请求慢,后续请求被阻塞
- HTTP/2.0中,多个请求可以并行处理
-
减少连接数
- HTTP/1.1可能需要多个TCP连接
- HTTP/2.0只需一个TCP连接
-
提高性能
- 减少延迟
- 提高带宽利用率
- 更好的资源利用
示例:
// HTTP/1.1:需要等待
fetch('/api/data1').then(...) // 等待响应
.then(() => fetch('/api/data2')) // 然后发送下一个请求
// HTTP/2.0:可以并行
Promise.all([
fetch('/api/data1'),
fetch('/api/data2'),
fetch('/api/data3')
]) // 同时发送,并行处理
2.15 HTTP/2.0的服务器推送是什么?
答案:
HTTP/2.0的服务器推送(Server Push)是指服务器可以主动推送资源给客户端,而不需要客户端请求。
工作原理:
-
推送流程
客户端请求:GET /index.html 服务器响应:返回index.html + 推送style.css和script.js -
推送条件
- 服务器知道客户端需要哪些资源
- 在响应主资源时推送相关资源
- 客户端可以拒绝推送
-
推送的资源
- CSS文件
- JavaScript文件
- 图片资源
- 其他静态资源
优势:
-
减少请求次数
- 客户端不需要发送额外请求
- 减少往返时间(RTT)
-
提高性能
- 资源提前到达客户端
- 减少页面加载时间
- 提高用户体验
-
智能推送
- 服务器可以根据主资源推送相关资源
- 减少延迟
示例:
# 客户端请求
GET /index.html HTTP/2.0
# 服务器响应并推送
HTTP/2.0 200 OK
# 推送style.css
PUSH_PROMISE: GET /style.css
# 推送script.js
PUSH_PROMISE: GET /script.js
# 返回index.html
DATA: <html>...</html>
使用场景:
- 页面加载时推送CSS和JS
- 推送关键资源
- 减少页面加载时间
注意事项:
- 客户端可以拒绝推送
- 推送的资源需要缓存验证
- 过度推送可能浪费带宽
第三章:HTTP 请求方法(20 题)
3.1 HTTP的请求方法有哪些?
答案:
HTTP定义了多种请求方法,常用的包括:
-
GET
- 获取资源
- 最常用的方法
- 幂等、安全
-
POST
- 提交数据
- 创建资源
- 非幂等
-
PUT
- 更新资源
- 完整替换
- 幂等
-
DELETE
- 删除资源
- 幂等
-
HEAD
- 获取响应头
- 不返回响应体
- 用于检查资源是否存在
-
OPTIONS
- 获取服务器支持的请求方法
- 用于CORS预检
-
PATCH
- 部分更新资源
- 非幂等
-
TRACE
- 回显请求
- 用于诊断
- 安全风险,很少使用
-
CONNECT
- 建立隧道
- 用于HTTPS代理
方法分类:
| 方法 | 用途 | 幂等性 | 安全性 |
|---|---|---|---|
| GET | 获取资源 | 是 | 是 |
| POST | 提交数据 | 否 | 否 |
| PUT | 更新资源 | 是 | 否 |
| DELETE | 删除资源 | 是 | 否 |
| HEAD | 获取头信息 | 是 | 是 |
| OPTIONS | 获取方法 | 是 | 是 |
| PATCH | 部分更新 | 否 | 否 |
| TRACE | 回显请求 | 是 | 否 |
| CONNECT | 建立隧道 | - | - |
3.2 GET方法的特点是什么?
答案:
GET方法的特点:
-
获取资源
- 从服务器获取资源
- 不应该修改服务器状态
- 只读操作
-
参数传递
- 参数通过URL传递
- 格式:
?key1=value1&key2=value2 - 参数可见,不安全
-
幂等性
- 多次请求结果相同
- 不会改变服务器状态
- 可以安全地重复请求
-
安全性
- 是安全方法
- 不应该有副作用
- 可以被缓存
-
可缓存
- 响应可以被缓存
- 浏览器可以缓存GET请求
- 提高性能
-
长度限制
- URL长度有限制
- 不同浏览器限制不同(通常2048字符)
- 不适合传递大量数据
GET请求示例:
GET /api/users?id=123&name=John HTTP/1.1
Host: api.example.com
使用场景:
- 获取网页内容
- 查询数据
- 下载文件
- 获取API数据
注意事项:
- 不要用GET修改数据
- 敏感数据不要放在URL中
- URL长度有限制
3.3 POST方法的特点是什么?
答案:
POST方法的特点:
-
提交数据
- 向服务器提交数据
- 创建新资源
- 可以修改服务器状态
-
参数传递
- 参数通过请求体传递
- 支持多种格式:表单、JSON、XML
- 参数不可见(在请求体中)
-
非幂等性
- 多次请求可能产生不同结果
- 每次请求可能创建新资源
- 不能安全地重复请求
-
非安全性
- 不是安全方法
- 可能有副作用
- 不应该被缓存
-
数据长度
- 没有严格的长度限制
- 可以传递大量数据
- 适合文件上传
-
Content-Type
- 需要指定Content-Type
- application/x-www-form-urlencoded
- application/json
- multipart/form-data
POST请求示例:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 45
{"name":"John","email":"john@example.com"}
使用场景:
- 提交表单
- 创建资源
- 文件上传
- 登录认证
注意事项:
- 不要用POST做幂等操作
- 需要指定Content-Type
- 数据在请求体中,相对安全
3.4 GET和POST的区别是什么?
答案:
GET和POST的主要区别:
-
用途
- GET:获取资源
- POST:提交数据
-
参数位置
- GET:参数在URL中(查询字符串)
- POST:参数在请求体中
-
参数可见性
- GET:参数可见(在URL中)
- POST:参数不可见(在请求体中)
-
安全性
- GET:相对不安全(参数在URL中)
- POST:相对安全(参数在请求体中)
-
幂等性
- GET:幂等(多次请求结果相同)
- POST:非幂等(可能产生不同结果)
-
可缓存性
- GET:可以被缓存
- POST:不应该被缓存
-
数据长度
- GET:URL长度有限制(通常2048字符)
- POST:没有严格限制
-
浏览器行为
- GET:可以收藏、分享URL
- POST:刷新会提示重新提交
对比表:
| 特性 | GET | POST |
|---|---|---|
| 用途 | 获取资源 | 提交数据 |
| 参数位置 | URL | 请求体 |
| 参数可见 | 是 | 否 |
| 安全性 | 较低 | 较高 |
| 幂等性 | 是 | 否 |
| 可缓存 | 是 | 否 |
| 数据长度 | 有限制 | 无限制 |
| 浏览器后退 | 正常 | 提示重新提交 |
选择建议:
- 获取数据 → 使用GET
- 提交数据 → 使用POST
- 查询操作 → 使用GET
- 创建/更新资源 → 使用POST
3.5 GET方法可以传递参数吗?如何传递?
答案:
可以,GET方法通过**URL查询字符串(Query String)**传递参数。
传递方式:
-
URL查询字符串
http://example.com/api/users?id=123&name=John&age=25 -
参数格式
- 格式:
?key1=value1&key2=value2&key3=value3 ?开始查询字符串&分隔多个参数=连接键值对
- 格式:
-
URL编码
- 特殊字符需要编码
- 空格 →
%20或+ - 中文需要UTF-8编码
示例:
# 基本参数
GET /api/users?id=123 HTTP/1.1
# 多个参数
GET /api/users?id=123&name=John&age=25 HTTP/1.1
# 数组参数
GET /api/users?ids[]=1&ids[]=2&ids[]=3 HTTP/1.1
# 编码参数
GET /api/search?q=hello%20world HTTP/1.1
注意事项:
- URL长度有限制
- 参数可见,不安全
- 特殊字符需要编码
- 不适合传递大量数据
3.6 POST方法可以传递参数吗?如何传递?
答案:
可以,POST方法通过**请求体(Request Body)**传递参数。
传递方式:
-
表单数据(Form Data)
POST /api/users HTTP/1.1 Content-Type: application/x-www-form-urlencoded name=John&email=john@example.com&age=25 -
JSON数据
POST /api/users HTTP/1.1 Content-Type: application/json {"name":"John","email":"john@example.com","age":25} -
文件上传(Multipart)
POST /api/upload HTTP/1.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary ------WebKitFormBoundary Content-Disposition: form-data; name="file"; filename="image.jpg" Content-Type: image/jpeg [文件二进制数据] ------WebKitFormBoundary--
Content-Type类型:
| Content-Type | 用途 | 格式 |
|---|---|---|
| application/x-www-form-urlencoded | 表单数据 | key1=value1&key2=value2 |
| application/json | JSON数据 | {"key":"value"} |
| multipart/form-data | 文件上传 | 多部分数据 |
| text/plain | 纯文本 | 普通文本 |
| application/xml | XML数据 | ... |
注意事项:
- 必须设置Content-Type
- 必须设置Content-Length
- 数据在请求体中,相对安全
- 没有严格的长度限制
3.7 GET和POST的数据长度限制是什么?
答案:
GET方法的数据长度限制:
-
URL长度限制
- 不同浏览器和服务器限制不同
- IE:2083字符
- Chrome:8192字符
- Firefox:65536字符
- 服务器:通常8KB-32KB
-
实际限制
- 建议不超过2048字符
- 包括协议、域名、路径、参数
- 参数过多会导致URL过长
-
限制原因
- 浏览器限制
- 服务器限制
- 代理服务器限制
POST方法的数据长度限制:
-
理论上无限制
- HTTP协议本身没有限制
- 可以传递大量数据
-
实际限制
- 服务器配置限制
- PHP:post_max_size(默认8MB)
- Nginx:client_max_body_size(默认1MB)
- Apache:LimitRequestBody
-
内存限制
- 服务器内存限制
- 客户端内存限制
- 网络带宽限制
对比表:
| 方法 | 理论限制 | 实际限制 | 建议 |
|---|---|---|---|
| GET | 无 | 2048字符 | 不超过2048字符 |
| POST | 无 | 服务器配置 | 根据服务器配置 |
最佳实践:
- GET:参数少、数据小
- POST:参数多、数据大、文件上传
3.8 GET和POST的安全性有什么区别?
答案:
GET和POST在安全性方面的区别:
-
参数可见性
- GET:参数在URL中,完全可见
- POST:参数在请求体中,不可见
-
浏览器历史
- GET:URL保存在浏览器历史中
- POST:URL不包含参数,历史记录更安全
-
服务器日志
- GET:完整URL(包括参数)记录在日志中
- POST:URL记录,但参数在请求体中,可能不记录
-
缓存
- GET:可能被缓存,敏感数据可能泄露
- POST:不应该被缓存,相对安全
-
CSRF攻击
- GET:更容易受到CSRF攻击
- POST:相对安全,但仍需防护
安全建议:
-
敏感数据
# ❌ 错误:密码在URL中 GET /login?username=john&password=123456 # ✅ 正确:密码在请求体中 POST /login Content-Type: application/json {"username":"john","password":"123456"} -
HTTPS
- GET和POST都应该使用HTTPS
- HTTPS加密传输,提高安全性
-
参数验证
- 服务器端必须验证所有参数
- 防止SQL注入、XSS攻击
对比表:
| 安全方面 | GET | POST |
|---|---|---|
| 参数可见 | 是(URL中) | 否(请求体中) |
| 浏览器历史 | 保存完整URL | 不保存参数 |
| 服务器日志 | 记录完整URL | 可能不记录参数 |
| 缓存风险 | 高 | 低 |
| CSRF风险 | 高 | 相对低 |
总结:
- POST相对更安全,但都不是绝对安全
- 敏感数据应该使用POST
- 必须使用HTTPS加密传输
- 服务器端必须验证和过滤所有输入
3.9 PUT方法的作用是什么?
答案:
PUT方法用于更新或创建资源。
主要特点:
-
更新资源
- 完整替换现有资源
- 如果资源不存在,则创建
- 幂等操作
-
幂等性
- 多次PUT请求结果相同
- 可以安全地重复请求
- 不会产生副作用
-
完整替换
- 替换整个资源
- 不是部分更新
- 需要提供完整的资源数据
-
资源定位
- 通过URL定位资源
- URL应该指向具体资源
- 例如:
PUT /api/users/123
PUT请求示例:
PUT /api/users/123 HTTP/1.1
Content-Type: application/json
{
"id": 123,
"name": "John",
"email": "john@example.com",
"age": 25
}
使用场景:
- 更新用户信息
- 更新文章内容
- 创建或更新资源
PUT vs POST:
| 特性 | PUT | POST |
|---|---|---|
| 用途 | 更新/创建资源 | 创建资源 |
| 幂等性 | 是 | 否 |
| 资源定位 | URL指定资源 | URL指定集合 |
| 完整替换 | 是 | 否 |
RESTful API示例:
# 创建或更新用户(ID已知)
PUT /api/users/123
{
"name": "John",
"email": "john@example.com"
}
# 创建新用户(ID由服务器生成)
POST /api/users
{
"name": "John",
"email": "john@example.com"
}
3.10 PUT和POST的区别是什么?
答案:
PUT和POST的主要区别:
-
用途
- PUT:更新或创建资源(完整替换)
- POST:创建资源(通常由服务器生成ID)
-
幂等性
- PUT:幂等(多次请求结果相同)
- POST:非幂等(可能创建多个资源)
-
资源定位
- PUT:URL指向具体资源(
/api/users/123) - POST:URL指向资源集合(
/api/users)
- PUT:URL指向具体资源(
-
资源ID
- PUT:客户端指定资源ID
- POST:服务器生成资源ID
-
完整替换
- PUT:完整替换资源
- POST:创建新资源
对比示例:
# PUT:更新用户123
PUT /api/users/123
{
"name": "John",
"email": "john@example.com"
}
# 结果:更新或创建ID为123的用户
# POST:创建新用户
POST /api/users
{
"name": "John",
"email": "john@example.com"
}
# 结果:创建新用户,服务器生成ID(如124)
RESTful API设计:
| 操作 | 方法 | URL | 说明 |
|---|---|---|---|
| 创建资源 | POST | /api/users | 服务器生成ID |
| 更新资源 | PUT | /api/users/123 | 完整替换 |
| 部分更新 | PATCH | /api/users/123 | 部分更新 |
| 删除资源 | DELETE | /api/users/123 | 删除资源 |
选择建议:
- 创建资源(ID未知)→ 使用POST
- 更新资源(ID已知)→ 使用PUT
- 部分更新 → 使用PATCH
3.11 DELETE方法的作用是什么?
答案:
DELETE方法用于删除指定的资源。
主要特点:
-
删除资源
- 删除URL指定的资源
- 如果资源不存在,返回404
- 幂等操作
-
幂等性
- 多次DELETE请求结果相同
- 删除不存在的资源也是幂等的
- 可以安全地重复请求
-
资源定位
- 通过URL定位要删除的资源
- URL应该指向具体资源
- 例如:
DELETE /api/users/123
-
响应状态码
- 200 OK:删除成功
- 202 Accepted:已接受删除请求
- 204 No Content:删除成功,无返回内容
- 404 Not Found:资源不存在
DELETE请求示例:
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
# 响应
HTTP/1.1 204 No Content
RESTful API示例:
# 删除用户
DELETE /api/users/123
# 删除文章
DELETE /api/articles/456
# 批量删除(非标准,建议使用POST)
POST /api/users/batch-delete
{
"ids": [123, 124, 125]
}
注意事项:
- 删除操作不可逆,需要谨慎
- 应该要求用户确认
- 可以实现软删除(标记删除)
- 批量删除建议使用POST方法
3.12 HEAD方法的作用是什么?
答案:
HEAD方法用于获取资源的响应头,不返回响应体。
主要特点:
-
只返回响应头
- 不返回响应体
- 节省带宽
- 提高性能
-
检查资源
- 检查资源是否存在
- 获取资源的元信息
- 检查资源是否更新
-
幂等性
- 幂等操作
- 可以安全地重复请求
-
响应头信息
- Content-Type:资源类型
- Content-Length:资源大小
- Last-Modified:最后修改时间
- ETag:资源标识
HEAD请求示例:
HEAD /api/users/123 HTTP/1.1
Host: api.example.com
# 响应(无响应体)
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1234
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
ETag: "abc123"
使用场景:
-
检查资源是否存在
// 检查文件是否存在 HEAD /files/document.pdf // 200 OK → 存在 // 404 Not Found → 不存在 -
获取资源大小
// 获取文件大小 HEAD /files/video.mp4 // Content-Length: 10485760 (10MB) -
检查资源是否更新
// 检查缓存是否有效 HEAD /api/data // 比较ETag或Last-Modified -
预检查
// 下载前检查 HEAD /download/file.zip // 获取文件大小和类型
HEAD vs GET:
| 特性 | HEAD | GET |
|---|---|---|
| 响应体 | 无 | 有 |
| 带宽 | 节省 | 消耗 |
| 用途 | 检查资源 | 获取资源 |
| 幂等性 | 是 | 是 |
3.13 OPTIONS方法的作用是什么?
答案:
OPTIONS方法用于获取服务器支持的HTTP方法,主要用于CORS预检请求。
主要特点:
-
获取支持的方法
- 返回服务器支持的HTTP方法
- Allow响应头列出支持的方法
- 用于API发现
-
CORS预检请求
- 浏览器在跨域请求前发送OPTIONS请求
- 检查是否允许跨域请求
- 检查允许的请求方法和头部
-
幂等性
- 幂等操作
- 可以安全地重复请求
-
响应头
- Allow:支持的方法
- Access-Control-Allow-Origin:允许的源
- Access-Control-Allow-Methods:允许的方法
- Access-Control-Allow-Headers:允许的头部
OPTIONS请求示例:
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
# 响应
HTTP/1.1 200 OK
Allow: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
CORS预检流程:
-
浏览器发送OPTIONS请求
OPTIONS /api/users Origin: https://example.com Access-Control-Request-Method: POST -
服务器响应
Access-Control-Allow-Origin: https://example.com Access-Control-Allow-Methods: POST -
浏览器发送实际请求
POST /api/users Origin: https://example.com
使用场景:
- CORS跨域请求
- API方法发现
- 检查服务器能力
3.14 PATCH方法的作用是什么?
答案:
PATCH方法用于部分更新资源,只更新提供的字段。
主要特点:
-
部分更新
- 只更新提供的字段
- 不更新未提供的字段
- 与PUT的完整替换不同
-
非幂等性
- 非幂等操作
- 多次请求可能产生不同结果
- 取决于实现方式
-
资源定位
- 通过URL定位资源
- URL指向具体资源
- 例如:
PATCH /api/users/123
-
数据格式
- 通常使用JSON
- 只包含要更新的字段
- 支持多种更新策略
PATCH请求示例:
PATCH /api/users/123 HTTP/1.1
Content-Type: application/json
{
"email": "newemail@example.com"
}
# 只更新email字段,其他字段不变
PATCH vs PUT:
| 特性 | PATCH | PUT |
|---|---|---|
| 更新方式 | 部分更新 | 完整替换 |
| 幂等性 | 通常非幂等 | 幂等 |
| 数据量 | 只包含更新字段 | 包含完整资源 |
| 未提供字段 | 保持不变 | 可能被清空 |
更新策略:
-
JSON Merge Patch(RFC 7396)
{ "email": "newemail@example.com", "age": null // 删除age字段 } -
JSON Patch(RFC 6902)
[ {"op": "replace", "path": "/email", "value": "newemail@example.com"}, {"op": "remove", "path": "/age"} ]
使用场景:
- 更新用户信息(只更新部分字段)
- 更新文章(只更新标题或内容)
- 部分资源更新
3.15 TRACE方法的作用是什么?
答案:
TRACE方法用于回显请求,用于诊断和调试。
主要特点:
-
回显请求
- 服务器返回收到的请求
- 用于查看请求在传输过程中的变化
- 诊断代理服务器问题
-
诊断工具
- 检查请求是否被修改
- 查看代理服务器添加的头部
- 调试网络问题
-
安全风险
- 可能泄露敏感信息
- 可能被用于XST(Cross-Site Tracing)攻击
- 大多数服务器禁用TRACE方法
-
幂等性
- 幂等操作
- 可以安全地重复请求
TRACE请求示例:
TRACE /api/test HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0
# 响应(回显请求)
HTTP/1.1 200 OK
Content-Type: message/http
TRACE /api/test HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0
Via: 1.1 proxy.example.com
安全风险:
-
信息泄露
- 可能泄露认证信息
- 可能泄露内部网络结构
-
XST攻击
- Cross-Site Tracing攻击
- 利用TRACE方法绕过HttpOnly Cookie
最佳实践:
- 生产环境应该禁用TRACE方法
- 只用于开发和调试
- 使用其他诊断工具替代
替代方案:
- 使用日志查看请求
- 使用网络抓包工具(Wireshark、Charles)
- 使用代理服务器日志
3.16 CONNECT方法的作用是什么?
答案:
CONNECT方法用于建立隧道,主要用于HTTPS代理。
主要特点:
-
建立隧道
- 在客户端和目标服务器之间建立隧道
- 代理服务器转发数据
- 用于HTTPS代理
-
HTTPS代理
- 客户端通过代理访问HTTPS网站
- 代理服务器建立TCP连接
- 数据加密传输,代理无法解密
-
连接建立
- 客户端发送CONNECT请求
- 代理服务器建立TCP连接
- 返回200 Connection Established
CONNECT请求示例:
CONNECT www.example.com:443 HTTP/1.1
Host: www.example.com:443
Proxy-Authorization: Basic xxxxxx
# 代理服务器响应
HTTP/1.1 200 Connection Established
# 之后建立TLS连接,传输加密数据
工作流程:
-
客户端发送CONNECT请求
CONNECT www.example.com:443 HTTP/1.1 -
代理服务器建立TCP连接
- 代理连接到目标服务器
- 返回200 Connection Established
-
建立TLS连接
- 客户端和目标服务器建立TLS连接
- 代理服务器只转发数据,不解密
-
数据传输
- 所有数据加密传输
- 代理服务器无法查看内容
使用场景:
- HTTPS代理
- 企业网络代理
- 翻墙工具(VPN、代理)
注意事项:
- 主要用于HTTPS代理
- 代理服务器需要支持CONNECT方法
- 需要代理认证时使用Proxy-Authorization头
3.17 什么是HTTP方法的幂等性?
答案:
HTTP方法的幂等性(Idempotency)是指多次执行相同的请求,结果应该相同。
幂等性的定义:
-
数学定义
- f(f(x)) = f(x)
- 多次应用函数结果相同
-
HTTP定义
- 多次发送相同的请求
- 服务器状态应该相同
- 响应应该相同(除了时间相关字段)
-
重要性
- 网络可能重传请求
- 客户端可能重复提交
- 保证数据一致性
幂等方法:
| 方法 | 幂等性 | 说明 |
|---|---|---|
| GET | ✅ 是 | 获取资源,不改变状态 |
| HEAD | ✅ 是 | 获取头信息,不改变状态 |
| PUT | ✅ 是 | 完整替换,结果相同 |
| DELETE | ✅ 是 | 删除资源,多次删除结果相同 |
| OPTIONS | ✅ 是 | 获取方法,不改变状态 |
| POST | ❌ 否 | 可能创建多个资源 |
| PATCH | ❌ 通常否 | 取决于实现 |
幂等性示例:
# GET:幂等
GET /api/users/123
GET /api/users/123 # 结果相同
# PUT:幂等
PUT /api/users/123 {"name":"John"}
PUT /api/users/123 {"name":"John"} # 结果相同
# POST:非幂等
POST /api/users {"name":"John"}
POST /api/users {"name":"John"} # 可能创建两个用户
实际应用:
-
网络重传
- 网络可能重传请求
- 幂等方法可以安全重传
- 非幂等方法需要去重
-
客户端重试
- 请求失败时可以重试
- 幂等方法可以安全重试
- 非幂等方法需要特殊处理
-
API设计
- 设计幂等的API
- 使用幂等方法更新资源
- 使用唯一标识避免重复
3.18 哪些HTTP方法是幂等的?
答案:
幂等的HTTP方法:
-
GET
- ✅ 幂等
- 获取资源,不改变服务器状态
- 可以安全地重复请求
-
HEAD
- ✅ 幂等
- 获取响应头,不改变服务器状态
- 可以安全地重复请求
-
PUT
- ✅ 幂等
- 完整替换资源
- 多次请求结果相同
-
DELETE
- ✅ 幂等
- 删除资源
- 删除不存在的资源也是幂等的
-
OPTIONS
- ✅ 幂等
- 获取支持的方法
- 不改变服务器状态
-
TRACE
- ✅ 幂等
- 回显请求
- 不改变服务器状态
非幂等的HTTP方法:
-
POST
- ❌ 非幂等
- 可能创建多个资源
- 每次请求可能产生不同结果
-
PATCH
- ❌ 通常非幂等
- 取决于实现方式
- 某些实现可能是幂等的
幂等性总结表:
| 方法 | 幂等性 | 原因 |
|---|---|---|
| GET | ✅ | 只读操作 |
| HEAD | ✅ | 只读操作 |
| PUT | ✅ | 完整替换 |
| DELETE | ✅ | 删除操作 |
| OPTIONS | ✅ | 只读操作 |
| TRACE | ✅ | 只读操作 |
| POST | ❌ | 可能创建多个资源 |
| PATCH | ❌ | 部分更新,取决于实现 |
实际应用:
- 幂等方法可以安全重试
- 非幂等方法需要去重机制
- RESTful API设计应该考虑幂等性
3.19 哪些HTTP方法是安全的?
答案:
安全的HTTP方法(Safe Methods):
-
GET
- ✅ 安全
- 只读操作
- 不应该有副作用
-
HEAD
- ✅ 安全
- 只读操作
- 不应该有副作用
-
OPTIONS
- ✅ 安全
- 只读操作
- 不应该有副作用
-
TRACE
- ✅ 安全(理论上)
- 只读操作
- 但实际使用有安全风险
不安全的HTTP方法:
-
POST
- ❌ 不安全
- 可能修改服务器状态
- 可能有副作用
-
PUT
- ❌ 不安全
- 修改服务器状态
- 创建或更新资源
-
DELETE
- ❌ 不安全
- 修改服务器状态
- 删除资源
-
PATCH
- ❌ 不安全
- 修改服务器状态
- 更新资源
安全性定义:
- 安全方法:不应该修改服务器状态
- 只读操作:不改变资源
- 无副作用:不产生额外影响
安全方法总结表:
| 方法 | 安全性 | 说明 |
|---|---|---|
| GET | ✅ | 只读,无副作用 |
| HEAD | ✅ | 只读,无副作用 |
| OPTIONS | ✅ | 只读,无副作用 |
| TRACE | ✅ | 只读,无副作用(但有安全风险) |
| POST | ❌ | 可能修改状态 |
| PUT | ❌ | 修改状态 |
| DELETE | ❌ | 修改状态 |
| PATCH | ❌ | 修改状态 |
实际应用:
- 安全方法可以被缓存
- 安全方法可以被预取
- 安全方法不会触发CSRF保护
- 浏览器对安全方法有特殊处理
3.20 如何设计幂等的API?
答案:
设计幂等API的方法:
-
使用幂等的HTTP方法
# ✅ 使用PUT更新(幂等) PUT /api/users/123 # ❌ 使用POST更新(非幂等) POST /api/users/123/update -
使用唯一标识
# 客户端提供唯一ID PUT /api/users/123 { "id": 123, "name": "John" } # 使用唯一业务标识 PUT /api/orders/ORDER-2024-001 -
使用条件更新
# 使用ETag PUT /api/users/123 If-Match: "abc123" { "name": "John" } # 使用版本号 PUT /api/users/123 { "id": 123, "version": 5, "name": "John" } -
去重机制
# 使用Idempotency-Key POST /api/orders Idempotency-Key: uuid-12345 { "product": "Book", "quantity": 1 } -
返回相同结果
// 第一次请求 PUT /api/users/123 Response: {"id": 123, "name": "John"} // 第二次请求(相同数据) PUT /api/users/123 Response: {"id": 123, "name": "John"} // 相同结果
最佳实践:
-
RESTful设计
# 创建:POST(非幂等,但可以加去重) POST /api/users Idempotency-Key: uuid # 更新:PUT(幂等) PUT /api/users/123 # 部分更新:PATCH(可以设计为幂等) PATCH /api/users/123 -
服务器端实现
// 检查是否已存在 if (request.hasIdempotencyKey()) { String key = request.getIdempotencyKey(); Response cached = cache.get(key); if (cached != null) { return cached; // 返回缓存结果 } } // 处理请求 Response response = processRequest(request); // 缓存结果 if (request.hasIdempotencyKey()) { cache.put(key, response); } -
客户端实现
// 生成唯一ID String idempotencyKey = UUID.randomUUID().toString(); // 请求头中添加 request.addHeader("Idempotency-Key", idempotencyKey); // 重试时使用相同的Key
设计原则:
- 使用幂等的HTTP方法
- 提供唯一标识
- 实现去重机制
- 返回相同结果
- 文档说明幂等性
第四章:HTTP 状态码(20 题)
4.1 HTTP状态码的分类有哪些?
答案:
HTTP状态码分为5类,用第一位数字表示:
-
1xx(信息性状态码)
- 100-199
- 表示请求已接收,继续处理
- 临时响应
-
2xx(成功状态码)
- 200-299
- 表示请求成功处理
- 最常见的成功响应
-
3xx(重定向状态码)
- 300-399
- 表示需要进一步操作
- 资源位置改变
-
4xx(客户端错误状态码)
- 400-499
- 表示客户端请求错误
- 请求格式或权限问题
-
5xx(服务器错误状态码)
- 500-599
- 表示服务器处理错误
- 服务器内部错误
状态码分类表:
| 类别 | 范围 | 含义 | 示例 |
|---|---|---|---|
| 1xx | 100-199 | 信息性 | 100 Continue |
| 2xx | 200-299 | 成功 | 200 OK, 201 Created |
| 3xx | 300-399 | 重定向 | 301 Moved, 304 Not Modified |
| 4xx | 400-499 | 客户端错误 | 400 Bad Request, 404 Not Found |
| 5xx | 500-599 | 服务器错误 | 500 Internal Server Error |
记忆口诀: 1信2成3重4客5服(信息、成功、重定向、客户端错误、服务器错误)
4.2 1xx状态码表示什么?有哪些?
答案:
1xx状态码表示信息性响应,请求已接收,继续处理。
1xx状态码列表:
-
100 Continue(继续)
- 客户端应该继续请求
- 用于POST/PUT大文件时的预检查
- 客户端发送Expect: 100-continue
-
101 Switching Protocols(切换协议)
- 服务器同意切换协议
- 用于HTTP升级到WebSocket
- Upgrade响应头
-
102 Processing(处理中)
- 服务器已接收请求,正在处理
- 用于长时间处理的操作
- WebDAV使用
100 Continue示例:
# 客户端请求
POST /upload HTTP/1.1
Host: example.com
Content-Length: 10485760
Expect: 100-continue
# 服务器响应
HTTP/1.1 100 Continue
# 客户端继续发送数据
[文件数据...]
101 Switching Protocols示例:
# 客户端请求
GET /websocket HTTP/1.1
Upgrade: websocket
Connection: Upgrade
# 服务器响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
使用场景:
- 100 Continue:大文件上传
- 101 Switching Protocols:协议升级(WebSocket)
- 102 Processing:长时间处理
注意: 1xx状态码在实际应用中很少使用,大多数情况下直接使用2xx、3xx、4xx、5xx。
4.3 2xx状态码表示什么?有哪些?
答案:
2xx状态码表示请求成功处理。
常见2xx状态码:
-
200 OK(成功)
- 请求成功,最常见的状态码
- GET、POST、PUT等请求的成功响应
-
201 Created(已创建)
- 请求成功,已创建新资源
- POST创建资源的成功响应
- Location响应头指向新资源
-
202 Accepted(已接受)
- 请求已接受,但尚未处理
- 异步处理的操作
- 最终结果可能成功或失败
-
204 No Content(无内容)
- 请求成功,但无响应体
- DELETE成功删除
- PUT成功更新(不需要返回数据)
-
205 Reset Content(重置内容)
- 请求成功,客户端应该重置视图
- 表单提交后重置表单
-
206 Partial Content(部分内容)
- 部分请求成功
- Range请求的响应
- 断点续传
200 OK示例:
GET /api/users/123 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "John"
}
201 Created示例:
POST /api/users HTTP/1.1
Content-Type: application/json
{"name": "John"}
HTTP/1.1 201 Created
Location: /api/users/124
Content-Type: application/json
{
"id": 124,
"name": "John"
}
204 No Content示例:
DELETE /api/users/123 HTTP/1.1
HTTP/1.1 204 No Content
206 Partial Content示例:
GET /video.mp4 HTTP/1.1
Range: bytes=0-1023
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/10485760
Content-Length: 1024
[视频数据...]
使用场景:
- 200 OK:大多数成功请求
- 201 Created:创建资源成功
- 204 No Content:删除或更新成功(无返回数据)
- 206 Partial Content:断点续传
4.4 3xx状态码表示什么?有哪些?
答案:
3xx状态码表示重定向,需要客户端进一步操作。
常见3xx状态码:
-
301 Moved Permanently(永久移动)
- 资源已永久移动到新位置
- Location响应头指向新URL
- 搜索引擎会更新索引
-
302 Found(临时移动)
- 资源临时移动到新位置
- Location响应头指向新URL
- 搜索引擎不更新索引
-
303 See Other(查看其他位置)
- 响应位于其他位置
- 使用GET方法获取资源
- POST后的重定向
-
304 Not Modified(未修改)
- 资源未修改,使用缓存
- 协商缓存的有效响应
- 节省带宽
-
307 Temporary Redirect(临时重定向)
- 临时重定向,保持原请求方法
- 类似302,但更明确
-
308 Permanent Redirect(永久重定向)
- 永久重定向,保持原请求方法
- 类似301,但更明确
301 Moved Permanently示例:
GET /old-page HTTP/1.1
HTTP/1.1 301 Moved Permanently
Location: /new-page
302 Found示例:
GET /old-page HTTP/1.1
HTTP/1.1 302 Found
Location: /new-page
304 Not Modified示例:
GET /api/data HTTP/1.1
If-None-Match: "abc123"
HTTP/1.1 304 Not Modified
ETag: "abc123"
# 无响应体,使用缓存
301 vs 302对比:
| 特性 | 301 | 302 |
|---|---|---|
| 类型 | 永久重定向 | 临时重定向 |
| 搜索引擎 | 更新索引 | 不更新索引 |
| 浏览器 | 可能缓存重定向 | 不缓存重定向 |
| 使用场景 | 域名迁移、URL永久改变 | 临时维护、A/B测试 |
4.5 4xx状态码表示什么?有哪些?
答案:
4xx状态码表示客户端错误,请求有问题。
常见4xx状态码:
-
400 Bad Request(错误请求)
- 请求语法错误
- 参数格式错误
- 请求无法理解
-
401 Unauthorized(未授权)
- 需要认证
- 未提供认证信息
- 认证失败
-
403 Forbidden(禁止)
- 服务器理解请求,但拒绝执行
- 权限不足
- 已认证但无权限
-
404 Not Found(未找到)
- 请求的资源不存在
- 最常见的错误状态码
- URL错误或资源已删除
-
405 Method Not Allowed(方法不允许)
- 请求方法不被允许
- Allow响应头列出允许的方法
-
408 Request Timeout(请求超时)
- 请求超时
- 客户端发送请求时间过长
-
409 Conflict(冲突)
- 请求与当前状态冲突
- 资源冲突
- 版本冲突
-
413 Payload Too Large(负载过大)
- 请求体过大
- 超过服务器限制
-
414 URI Too Long(URI过长)
- URI过长
- 超过服务器限制
-
415 Unsupported Media Type(不支持的媒体类型)
- Content-Type不支持
- 服务器不支持请求的格式
-
429 Too Many Requests(请求过多)
- 请求频率过高
- 限流响应
- Retry-After响应头
400 Bad Request示例:
POST /api/users HTTP/1.1
Content-Type: application/json
{invalid json}
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Invalid JSON format"
}
401 Unauthorized示例:
GET /api/profile HTTP/1.1
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
# 客户端需要提供认证
GET /api/profile HTTP/1.1
Authorization: Bearer token123
403 Forbidden示例:
GET /api/admin/users HTTP/1.1
Authorization: Bearer token123
HTTP/1.1 403 Forbidden
{
"error": "Insufficient permissions"
}
404 Not Found示例:
GET /api/users/999 HTTP/1.1
HTTP/1.1 404 Not Found
{
"error": "User not found"
}
401 vs 403对比:
| 特性 | 401 | 403 |
|---|---|---|
| 含义 | 未认证 | 已认证但无权限 |
| 原因 | 缺少认证信息 | 权限不足 |
| 解决方案 | 提供认证信息 | 提升权限 |
| 响应头 | WWW-Authenticate | 无 |
4.6 5xx状态码表示什么?有哪些?
答案:
5xx状态码表示服务器错误,服务器处理请求时出错。
常见5xx状态码:
-
500 Internal Server Error(内部服务器错误)
- 服务器内部错误
- 最常见的服务器错误
- 代码异常、配置错误
-
501 Not Implemented(未实现)
- 服务器不支持请求的功能
- 请求方法不支持
-
502 Bad Gateway(错误网关)
- 网关或代理服务器错误
- 上游服务器无效响应
-
503 Service Unavailable(服务不可用)
- 服务器暂时不可用
- 过载或维护
- Retry-After响应头
-
504 Gateway Timeout(网关超时)
- 网关或代理服务器超时
- 上游服务器响应超时
-
505 HTTP Version Not Supported(HTTP版本不支持)
- 服务器不支持HTTP版本
- 协议版本不匹配
500 Internal Server Error示例:
POST /api/users HTTP/1.1
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "Internal server error",
"message": "Database connection failed"
}
502 Bad Gateway示例:
GET /api/data HTTP/1.1
HTTP/1.1 502 Bad Gateway
Content-Type: text/html
<html>
<body>502 Bad Gateway</body>
</html>
503 Service Unavailable示例:
GET /api/data HTTP/1.1
HTTP/1.1 503 Service Unavailable
Retry-After: 60
Content-Type: application/json
{
"error": "Service temporarily unavailable",
"retry_after": 60
}
502 vs 503 vs 504对比:
| 状态码 | 含义 | 原因 | 解决方案 |
|---|---|---|---|
| 502 | 网关错误 | 上游服务器无效响应 | 检查上游服务器 |
| 503 | 服务不可用 | 过载或维护 | 等待后重试 |
| 504 | 网关超时 | 上游服务器超时 | 检查网络或服务器 |
4.7 200状态码表示什么?
答案:
200 OK表示请求成功处理,是最常见的成功状态码。
说明:
- 请求已成功处理,服务器返回请求的资源
- 适用于GET、POST、PUT、PATCH等请求的成功响应
- 响应体包含数据
注:2xx状态码的详细说明请参见4.3题答案。
4.8 201状态码表示什么?
答案:
201 Created表示请求成功,已创建新资源。
说明:
- 请求已成功处理,新资源已创建
- Location响应头指向新资源
- 适用于POST创建资源的成功响应
注:2xx状态码的详细说明请参见4.3题答案。
4.9 204状态码表示什么?
答案:
204 No Content表示请求成功处理,但无响应体。
说明:
- 请求成功处理,但不返回响应体
- 适用于DELETE成功删除、PUT成功更新(不需要返回数据)
- 节省带宽
注:2xx状态码的详细说明请参见4.3题答案。
4.10 301状态码表示什么?
答案:
301 Moved Permanently表示资源已永久移动到新位置。
说明:
- 资源永久移动到新URL,Location响应头指向新URL
- 搜索引擎会更新索引,将权重传递给新URL
- 浏览器可能缓存重定向
- 适用于域名迁移、URL永久改变
注:3xx状态码的详细说明请参见4.4题答案。
4.11 302状态码表示什么?
答案:
302 Found表示资源临时移动到新位置。
说明:
- 资源临时移动到新URL,Location响应头指向新URL
- 搜索引擎不更新索引,不传递权重
- 浏览器不缓存重定向
- 适用于临时维护、A/B测试
注意: HTTP/1.1中,302的实际语义有些模糊,建议使用307或303替代。
注:3xx状态码的详细说明请参见4.4题答案。
4.12 301和302重定向的区别是什么?
答案:
301和302重定向的主要区别:
-
永久性
- 301:永久重定向
- 302:临时重定向
-
搜索引擎处理
- 301:搜索引擎会更新索引,将权重传递给新URL
- 302:搜索引擎不更新索引,不传递权重
-
浏览器缓存
- 301:浏览器可能缓存重定向
- 302:浏览器不缓存重定向
-
书签
- 301:浏览器可能更新书签
- 302:浏览器不更新书签
-
使用场景
- 301:域名迁移、URL永久改变
- 302:临时维护、A/B测试
对比表:
| 特性 | 301 | 302 |
|---|---|---|
| 重定向类型 | 永久 | 临时 |
| 搜索引擎索引 | 更新 | 不更新 |
| 权重传递 | 是 | 否 |
| 浏览器缓存 | 可能缓存 | 不缓存 |
| 书签更新 | 可能更新 | 不更新 |
| 使用场景 | 永久迁移 | 临时跳转 |
示例对比:
# 301永久重定向
GET /old-page HTTP/1.1
HTTP/1.1 301 Moved Permanently
Location: https://www.newdomain.com/new-page
# 302临时重定向
GET /page HTTP/1.1
HTTP/1.1 302 Found
Location: https://www.example.com/temp-page
最佳实践:
- 域名迁移 → 使用301
- URL永久改变 → 使用301
- 临时维护 → 使用302或503
- A/B测试 → 使用302
- 登录后跳转 → 使用302
注意: HTTP/1.1中,建议使用307(临时重定向)和308(永久重定向)替代302和301,语义更明确。
4.13 304状态码的作用是什么?
答案:
304 Not Modified表示资源未修改,使用缓存。
说明:
- 客户端发送If-None-Match或If-Modified-Since,服务器检查资源未修改时返回304
- 不返回响应体,使用缓存的响应,节省带宽
- 适用于缓存验证,减少数据传输,提高性能
注:3xx状态码的详细说明请参见4.4题答案。
4.14 400状态码表示什么?
答案:
400 Bad Request表示请求语法错误。
说明:
- 请求语法错误、参数格式错误、请求无法理解
- 适用于JSON格式错误、参数类型错误、缺少必需参数
注:4xx状态码的详细说明请参见4.5题答案。
4.15 401状态码表示什么?
答案:
401 Unauthorized表示未授权,需要认证。 说明:
- 需要认证但未提供认证信息,或认证失败
- 适用于需要登录、Token过期、认证信息无效的场景
- WWW-Authenticate响应头指示认证方式
注:4xx状态码的详细说明请参见4.5题答案。
4.16 403状态码表示什么?
答案:
403 Forbidden表示服务器理解请求,但拒绝执行。
说明:
- 服务器理解请求,但拒绝执行,权限不足
- 适用于已认证但无权限、IP被禁止、资源访问受限的场景
- 与401的区别:401是未认证,403是已认证但无权限
注:4xx状态码的详细说明请参见4.5题答案。
4.17 401和403的区别是什么?
答案:
401和403的主要区别:
-
认证状态
- 401:未认证(需要登录)
- 403:已认证但无权限(权限不足)
-
原因
- 401:缺少认证信息或认证失败
- 403:权限不足,资源被禁止访问
-
响应头
- 401:WWW-Authenticate响应头指示认证方式
- 403:无特殊响应头
-
解决方案
- 401:提供认证信息(登录)
- 403:提升权限(联系管理员)
注:4xx状态码的详细说明请参见4.5题答案。
4.18 404状态码表示什么?
答案:
404 Not Found表示请求的资源不存在。 说明:
- 请求的资源不存在,最常见的错误状态码
- 适用于URL错误、资源已删除、路径不存在的场景
注:4xx状态码的详细说明请参见4.5题答案。
4.19 500状态码表示什么?
答案:
500 Internal Server Error表示服务器内部错误。 说明:
- 服务器内部错误,无法完成请求
- 适用于代码异常、数据库错误、配置错误的场景
注:5xx状态码的详细说明请参见4.6题答案。
4.20 502、503、504的区别是什么?
答案:
502、503、504的区别:
-
502 Bad Gateway(错误网关)
- 网关或代理服务器错误
- 上游服务器返回无效响应
- 网关无法获取有效响应
-
503 Service Unavailable(服务不可用)
- 服务器暂时不可用
- 过载或维护
- 服务器主动返回
-
504 Gateway Timeout(网关超时)
- 网关或代理服务器超时
- 上游服务器响应超时
- 网关等待超时
对比表:
| 状态码 | 含义 | 原因 | 解决方案 |
|---|---|---|---|
| 502 | 网关错误 | 上游服务器无效响应 | 检查上游服务器 |
| 503 | 服务不可用 | 过载或维护 | 等待后重试 |
| 504 | 网关超时 | 上游服务器超时 | 检查网络或服务器 |
502 Bad Gateway示例:
GET /api/data HTTP/1.1
HTTP/1.1 502 Bad Gateway
Content-Type: text/html
<html>
<body>502 Bad Gateway</body>
</html>
503 Service Unavailable示例:
GET /api/data HTTP/1.1
HTTP/1.1 503 Service Unavailable
Retry-After: 60
Content-Type: application/json
{
"error": "Service temporarily unavailable",
"retry_after": 60
}
504 Gateway Timeout示例:
GET /api/data HTTP/1.1
HTTP/1.1 504 Gateway Timeout
Content-Type: text/html
<html>
<body>504 Gateway Timeout</body>
</html>
架构示例:
客户端 → 网关/代理 → 上游服务器
↓
502/503/504
502 vs 503 vs 504:
| 状态码 | 网关状态 | 上游服务器状态 | 时间因素 |
|---|---|---|---|
| 502 | 错误 | 无效响应 | 不相关 |
| 503 | 正常 | 不可用 | 可能相关 |
| 504 | 正常 | 超时 | 相关 |
处理建议:
- 502:检查上游服务器,重试
- 503:等待后重试(Retry-After)
- 504:检查网络或服务器性能,重试
第五章:HTTP 请求头和响应头(21 题)
5.1 HTTP请求头的作用是什么?
答案:
HTTP请求头(Request Headers)用于向服务器传递请求的元信息和客户端信息。
请求头的作用:
-
传递请求信息
- 请求方法、路径、协议版本
- 客户端信息
- 请求参数
-
控制请求行为
- 控制缓存
- 控制压缩
- 控制连接
-
认证和授权
- 传递认证信息
- 传递授权令牌
- 传递API密钥
-
内容协商
- 指定接受的内容类型
- 指定接受的编码
- 指定接受的语言
请求头格式:
Header-Name: Header-Value
请求头示例:
GET /api/users HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0
Accept: application/json
Accept-Language: zh-CN,zh;q=0.9
Accept-Encoding: gzip, deflate
Authorization: Bearer token123
Connection: keep-alive
常见请求头分类:
-
通用请求头
- Connection:连接控制
- Cache-Control:缓存控制
- Pragma:兼容性
-
请求头
- Host:目标主机
- User-Agent:客户端信息
- Accept:接受的内容类型
- Accept-Language:接受的语言
- Accept-Encoding:接受的编码
- Authorization:认证信息
- Content-Type:请求体类型
- Content-Length:请求体长度
- Cookie:Cookie信息
- Referer:来源页面
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置请求头
conn.setRequestProperty("User-Agent", "MyApp/1.0");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setRequestProperty("Content-Type", "application/json");
请求头的特点:
- 不区分大小写(但推荐使用标准格式)
- 可以有多个值(用逗号分隔)
- 自定义请求头以X-开头(非标准,但常用)
5.2 User-Agent的作用是什么?
答案:
User-Agent用于标识客户端应用程序的信息。
User-Agent的作用:
-
标识客户端
- 浏览器类型和版本
- 操作系统信息
- 设备信息
-
服务器适配
- 根据客户端返回不同内容
- 移动端和桌面端适配
- 浏览器兼容性处理
-
统计分析
- 统计浏览器使用情况
- 统计设备类型
- 分析用户行为
-
安全防护
- 识别爬虫和机器人
- 防止恶意请求
- 访问控制
User-Agent格式:
User-Agent: 产品名/版本号 (系统信息; 其他信息)
常见User-Agent示例:
-
Chrome浏览器
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 -
Firefox浏览器
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0 -
Safari浏览器
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15 -
Android Chrome
Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36 -
iOS Safari
Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置User-Agent
String userAgent = "MyApp/1.0 (Android " + Build.VERSION.RELEASE + "; " + Build.MODEL + ")";
conn.setRequestProperty("User-Agent", userAgent);
// 或者使用系统默认
conn.setRequestProperty("User-Agent", System.getProperty("http.agent"));
使用场景:
- 浏览器访问网页
- 移动应用API调用
- 爬虫标识
- 统计分析
注意事项:
- User-Agent可以被伪造
- 不应依赖User-Agent进行安全验证
- 某些服务器可能要求User-Agent
5.3 Accept的作用是什么?
答案:
Accept用于指定客户端可以接受的内容类型(MIME类型)。
Accept的作用:
-
内容协商
- 告诉服务器客户端接受的内容类型
- 服务器根据Accept返回相应格式
- 支持多种格式和优先级
-
MIME类型
- 指定接受的媒体类型
- 可以指定多个类型
- 使用q值指定优先级
-
优先级
- q值范围:0.0-1.0
- 默认q值为1.0
- q值越大,优先级越高
Accept格式:
Accept: type/subtype;q=priority, type/subtype;q=priority
Accept示例:
-
接受JSON
Accept: application/json -
接受JSON和XML(JSON优先)
Accept: application/json, application/xml -
指定优先级
Accept: application/json;q=0.9, application/xml;q=0.8, text/html;q=0.5 -
接受所有类型
Accept: */* -
接受特定子类型
Accept: application/json, application/*, */*
常见MIME类型:
| MIME类型 | 说明 | 示例 |
|---|---|---|
| application/json | JSON数据 | {"key": "value"} |
| application/xml | XML数据 | ... |
| application/x-www-form-urlencoded | 表单数据 | key=value&key2=value2 |
| text/html | HTML文档 | ... |
| text/plain | 纯文本 | Plain text |
| text/css | CSS样式 | body { color: red; } |
| image/jpeg | JPEG图片 | 图片数据 |
| image/png | PNG图片 | 图片数据 |
| multipart/form-data | 多部分数据 | 文件上传 |
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 只接受JSON
conn.setRequestProperty("Accept", "application/json");
// 接受JSON和XML(JSON优先)
conn.setRequestProperty("Accept", "application/json, application/xml");
// 指定优先级
conn.setRequestProperty("Accept", "application/json;q=0.9, application/xml;q=0.8");
服务器处理:
- 服务器检查Accept头
- 返回匹配的内容类型
- 如果都不匹配,返回406 Not Acceptable
- 如果未指定Accept,返回默认类型
使用场景:
- API请求指定返回格式
- 浏览器请求指定内容类型
- 内容协商
5.4 Accept-Language的作用是什么?
答案:
Accept-Language用于指定客户端接受的语言。
Accept-Language的作用:
-
语言协商
- 告诉服务器客户端接受的语言
- 服务器根据Accept-Language返回相应语言
- 支持多种语言和优先级
-
国际化
- 多语言网站
- 本地化内容
- 语言切换
-
优先级
- 使用q值指定优先级
- q值范围:0.0-1.0
- q值越大,优先级越高
Accept-Language格式:
Accept-Language: language;q=priority, language;q=priority
Accept-Language示例:
-
接受中文
Accept-Language: zh-CN -
接受多种语言(中文优先)
Accept-Language: zh-CN, zh, en-US, en -
指定优先级
Accept-Language: zh-CN;q=0.9, zh;q=0.8, en-US;q=0.5, en;q=0.3 -
接受所有语言
Accept-Language: *
语言代码格式:
- 语言代码:zh, en, fr
- 语言-地区:zh-CN, zh-TW, en-US, en-GB
- 语言-脚本-地区:zh-Hans-CN, zh-Hant-TW
常见语言代码:
| 语言代码 | 语言 | 地区 |
|---|---|---|
| zh-CN | 简体中文 | 中国大陆 |
| zh-TW | 繁体中文 | 台湾 |
| zh-HK | 繁体中文 | 香港 |
| en-US | 英语 | 美国 |
| en-GB | 英语 | 英国 |
| ja | 日语 | 日本 |
| ko | 韩语 | 韩国 |
| fr | 法语 | 法国 |
| de | 德语 | 德国 |
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 获取系统语言
Locale locale = Locale.getDefault();
String language = locale.getLanguage();
String country = locale.getCountry();
// 设置Accept-Language
String acceptLanguage = language + "-" + country + ", " + language;
conn.setRequestProperty("Accept-Language", acceptLanguage);
// 例如:zh-CN, zh
服务器处理:
- 服务器检查Accept-Language
- 返回匹配的语言版本
- 如果没有匹配,返回默认语言
- Content-Language响应头表示返回的语言
使用场景:
- 多语言网站
- 国际化API
- 本地化内容
5.5 Accept-Encoding的作用是什么?
答案:
Accept-Encoding用于指定客户端可以接受的编码方式。
Accept-Encoding的作用:
-
内容压缩
- 告诉服务器客户端支持的编码方式
- 服务器可以对响应进行压缩
- 减少数据传输量
-
编码方式
- gzip:最常用
- deflate:较少使用
- br:Brotli,较新
- identity:不压缩
-
优先级
- 使用q值指定优先级
- 默认q值为1.0
- 可以指定不接受某种编码(q=0)
Accept-Encoding格式:
Accept-Encoding: encoding;q=priority, encoding;q=priority
Accept-Encoding示例:
-
接受gzip
Accept-Encoding: gzip -
接受多种编码(gzip优先)
Accept-Encoding: gzip, deflate, br -
指定优先级
Accept-Encoding: gzip;q=0.9, deflate;q=0.8, br;q=0.7 -
不接受某种编码
Accept-Encoding: gzip, deflate;q=0 -
不压缩
Accept-Encoding: identity
常见编码方式:
| 编码方式 | 说明 | 压缩率 | 性能 |
|---|---|---|---|
| gzip | 最常用 | 高 | 好 |
| deflate | 较少使用 | 中 | 中 |
| br | Brotli,较新 | 很高 | 好 |
| identity | 不压缩 | 无 | 最快 |
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 接受gzip压缩
conn.setRequestProperty("Accept-Encoding", "gzip");
// 读取压缩响应
InputStream inputStream = conn.getInputStream();
String encoding = conn.getContentEncoding();
if ("gzip".equals(encoding)) {
inputStream = new GZIPInputStream(inputStream);
}
服务器处理:
- 服务器检查Accept-Encoding
- 选择支持的编码方式
- 压缩响应体
- Content-Encoding响应头表示使用的编码
优势:
- 减少数据传输量
- 提高传输速度
- 节省带宽
- 提高用户体验
注意事项:
- 压缩需要CPU资源
- 小文件可能不需要压缩
- 二进制文件可能已经压缩
5.6 Content-Type的作用是什么?
答案:
Content-Type用于指定请求体或响应体的媒体类型(MIME类型)。
Content-Type的作用:
-
指定内容类型
- 请求体:指定发送的数据格式
- 响应体:指定返回的数据格式
- 帮助接收方正确解析数据
-
字符编码
- 可以指定字符编码
- 格式:
type/subtype; charset=encoding - 例如:
text/html; charset=utf-8
-
边界(Multipart)
- multipart/form-data需要指定边界
- 格式:
multipart/form-data; boundary=boundary
Content-Type格式:
Content-Type: type/subtype; charset=encoding; boundary=boundary
Content-Type示例:
-
JSON数据
Content-Type: application/json -
JSON数据(UTF-8编码)
Content-Type: application/json; charset=utf-8 -
表单数据
Content-Type: application/x-www-form-urlencoded -
文件上传
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary -
HTML文档
Content-Type: text/html; charset=utf-8 -
纯文本
Content-Type: text/plain; charset=utf-8
常见Content-Type:
| Content-Type | 用途 | 示例 |
|---|---|---|
| application/json | JSON数据 | {"key": "value"} |
| application/x-www-form-urlencoded | 表单数据 | key=value&key2=value2 |
| multipart/form-data | 文件上传 | 多部分数据 |
| text/html | HTML文档 | ... |
| text/plain | 纯文本 | Plain text |
| text/xml | XML数据 | ... |
| image/jpeg | JPEG图片 | 图片数据 |
| image/png | PNG图片 | 图片数据 |
Android代码示例:
-
POST JSON数据
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/json; charset=utf-8"); conn.setDoOutput(true); String json = "{\"name\":\"John\"}"; OutputStream os = conn.getOutputStream(); os.write(json.getBytes("UTF-8")); os.close(); -
POST表单数据
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoOutput(true); String formData = "name=John&email=john@example.com"; OutputStream os = conn.getOutputStream(); os.write(formData.getBytes("UTF-8")); os.close(); -
文件上传
String boundary = "----WebKitFormBoundary"; HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); conn.setDoOutput(true); OutputStream os = conn.getOutputStream(); // 写入多部分数据 os.close();
使用场景:
- POST请求指定请求体格式
- 响应指定返回数据格式
- 文件上传指定边界
- 字符编码指定
注意事项:
- POST请求必须设置Content-Type
- 字符编码要正确
- multipart需要正确设置boundary
5.7 Content-Length的作用是什么?
答案:
Content-Length用于指定请求体或响应体的字节长度。
Content-Length的作用:
-
指定内容长度
- 请求体:指定发送数据的字节数
- 响应体:指定返回数据的字节数
- 帮助接收方知道数据大小
-
数据传输控制
- 接收方知道何时接收完成
- 可以显示传输进度
- 可以验证数据完整性
-
必需性
- 有请求体时必须设置
- 响应体通常也应该设置
- 分块传输(Chunked)时不需要
Content-Length格式:
Content-Length: 1234
Content-Length示例:
-
请求体长度
POST /api/users HTTP/1.1 Content-Type: application/json Content-Length: 45 {"name":"John","email":"john@example.com"} -
响应体长度
HTTP/1.1 200 OK Content-Type: application/json Content-Length: 123 {"id":123,"name":"John"}
Android代码示例:
-
发送数据(需要设置Content-Length)
String json = "{\"name\":\"John\"}"; byte[] data = json.getBytes("UTF-8"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("Content-Length", String.valueOf(data.length)); conn.setDoOutput(true); OutputStream os = conn.getOutputStream(); os.write(data); os.close(); -
接收数据(读取Content-Length)
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); int contentLength = conn.getContentLength(); InputStream inputStream = conn.getInputStream(); byte[] buffer = new byte[1024]; int totalRead = 0; int bytesRead; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); while (totalRead < contentLength && (bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); totalRead += bytesRead; // 可以显示进度 int progress = (int) (totalRead * 100.0 / contentLength); } byte[] data = outputStream.toByteArray();
分块传输(Chunked):
当使用分块传输时,不需要Content-Length:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/plain
5
Hello
6
World
0
注意事项:
- Content-Length必须是实际数据的字节数
- 不包含请求行、请求头的长度
- 字符串长度不等于字节长度(UTF-8编码)
- 分块传输时使用Transfer-Encoding: chunked
使用场景:
- POST请求指定请求体长度
- 下载文件时显示进度
- 验证数据完整性
5.8 Cookie的作用是什么?
答案:
Cookie用于在客户端存储会话信息,实现有状态的HTTP通信。
Cookie的作用:
-
会话管理
- 存储用户登录状态
- 存储会话ID
- 实现有状态的HTTP通信
-
个性化设置
- 存储用户偏好
- 存储语言设置
- 存储主题设置
-
购物车
- 存储购物车内容
- 存储用户行为
- 跟踪用户
-
跟踪和分析
- 跟踪用户行为
- 统计分析
- 广告投放
Cookie工作原理:
-
服务器设置Cookie
HTTP/1.1 200 OK Set-Cookie: sessionid=abc123; Path=/; HttpOnly Set-Cookie: theme=dark; Path=/; Max-Age=3600 -
客户端保存Cookie
- 浏览器保存Cookie
- 根据域名和路径保存
- 下次请求自动携带
-
客户端发送Cookie
GET /api/profile HTTP/1.1 Cookie: sessionid=abc123; theme=dark
Cookie属性:
-
Name和Value
- Cookie的名称和值
- 格式:
name=value
-
Domain(域)
- Cookie的域名
- 格式:
Domain=.example.com - 子域名可以访问
-
Path(路径)
- Cookie的路径
- 格式:
Path=/api - 只有该路径及其子路径可以访问
-
Expires和Max-Age(过期时间)
- Expires:绝对时间
- Max-Age:相对时间(秒)
- 过期后Cookie被删除
-
Secure(安全)
- 只在HTTPS连接中发送
- 格式:
Secure
-
HttpOnly(HTTP Only)
- JavaScript无法访问
- 防止XSS攻击
- 格式:
HttpOnly
-
SameSite(同站)
- Strict:只在同站请求中发送
- Lax:同站和顶级导航中发送
- None:所有请求中发送
Cookie示例:
# 服务器设置Cookie
HTTP/1.1 200 OK
Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure; SameSite=Strict
Set-Cookie: theme=dark; Path=/; Max-Age=3600
# 客户端发送Cookie
GET /api/profile HTTP/1.1
Cookie: sessionid=abc123; theme=dark
Android代码示例:
// 读取Cookie
CookieManager cookieManager = CookieManager.getInstance();
String cookies = cookieManager.getCookie(url);
if (cookies != null) {
conn.setRequestProperty("Cookie", cookies);
}
// 读取Set-Cookie
String setCookie = conn.getHeaderField("Set-Cookie");
if (setCookie != null) {
cookieManager.setCookie(url, setCookie);
}
使用场景:
- 用户登录状态
- 会话管理
- 个性化设置
- 购物车
安全问题:
- XSS攻击(使用HttpOnly)
- CSRF攻击(使用SameSite)
- 敏感信息泄露(使用Secure)
- 不要存储敏感信息
5.9 Referer的作用是什么?
答案:
Referer用于标识请求的来源页面URL。
Referer的作用:
-
来源跟踪
- 标识请求来自哪个页面
- 用于统计分析
- 用于防盗链
-
统计分析
- 分析流量来源
- 分析用户行为
- 分析转化率
-
安全防护
- CSRF防护
- 检查请求来源
- 防止跨站请求
-
防盗链
- 检查图片请求来源
- 只允许特定域名访问
- 保护资源
Referer格式:
Referer: https://www.example.com/page
Referer示例:
-
基本Referer
GET /api/data HTTP/1.1 Referer: https://www.example.com/page -
空Referer
GET /api/data HTTP/1.1 # 无Referer头(直接访问、隐私模式等) -
不同协议的Referer
# HTTPS页面请求HTTP资源(无Referer) GET http://example.com/image.jpg HTTP/1.1 # Referer被移除(安全原因)
Referer策略(Referrer Policy):
-
no-referrer
- 不发送Referer
-
no-referrer-when-downgrade(默认)
- HTTPS→HTTP不发送
- 其他情况发送
-
origin
- 只发送源(协议+域名)
-
origin-when-cross-origin
- 同源发送完整URL
- 跨域只发送源
-
same-origin
- 同源发送
- 跨域不发送
-
strict-origin
- HTTPS→HTTPS发送源
- 其他情况不发送
-
strict-origin-when-cross-origin
- 同源发送完整URL
- 跨域HTTPS→HTTPS发送源
- 其他情况不发送
-
unsafe-url
- 始终发送完整URL
HTML设置:
<meta name="referrer" content="no-referrer">
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置Referer
String referer = "https://www.example.com/page";
conn.setRequestProperty("Referer", referer);
使用场景:
- 统计分析(流量来源)
- 防盗链(图片、视频)
- CSRF防护(检查来源)
- 用户行为分析
注意事项:
- Referer可能被伪造
- Referer可能被浏览器移除
- 不应该依赖Referer进行安全验证
- 隐私保护可能禁用Referer
5.10 Authorization的作用是什么?
答案:
Authorization用于传递认证信息,用于身份验证。
Authorization的作用:
-
身份认证
- 传递认证凭证
- 验证用户身份
- 访问受保护资源
-
认证方式
- Bearer Token(JWT)
- Basic认证
- Digest认证
- API Key
-
使用场景
- API访问
- 受保护资源
- 用户认证
Authorization格式:
Authorization: <认证方式> <凭证>
常见认证方式:
-
Bearer Token(最常用)
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... -
Basic认证
Authorization: Basic base64(username:password) # 例如:Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ= -
Digest认证
Authorization: Digest username="user", realm="api", nonce="...", response="..." -
API Key
Authorization: ApiKey your_api_key
Bearer Token示例:
GET /api/profile HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Basic认证示例:
GET /api/data HTTP/1.1
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
# username:password 的Base64编码
Android代码示例:
-
Bearer Token
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); String token = getAuthToken(); conn.setRequestProperty("Authorization", "Bearer " + token); -
Basic认证
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); String username = "user"; String password = "pass"; String credentials = username + ":" + password; String encoded = Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP); conn.setRequestProperty("Authorization", "Basic " + encoded); -
使用OkHttp
// Bearer Token Request request = new Request.Builder() .url(url) .header("Authorization", "Bearer " + token) .build(); // Basic认证 String credentials = Credentials.basic("username", "password"); Request request = new Request.Builder() .url(url) .header("Authorization", credentials) .build();
认证流程:
-
客户端请求(未认证)
GET /api/data HTTP/1.1 -
服务器响应401
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="api" -
客户端请求(已认证)
GET /api/data HTTP/1.1 Authorization: Bearer token123 -
服务器响应200
HTTP/1.1 200 OK Content-Type: application/json {"data": "..."}
使用场景:
- RESTful API认证
- 用户登录验证
- 受保护资源访问
- OAuth2认证
安全建议:
- 使用HTTPS传输
- Token定期刷新
- Token存储在安全位置
- 敏感信息不要放在Authorization头
5.11 Host的作用是什么?
答案:
Host用于指定目标服务器的主机名和端口号。
Host的作用:
-
虚拟主机
- 一个IP可以对应多个域名
- 服务器根据Host头区分虚拟主机
- HTTP/1.1必须包含Host头
-
主机标识
- 指定目标服务器
- 指定端口号
- 区分不同的服务
-
必需性
- HTTP/1.1必须包含
- HTTP/1.0不需要
- 缺少Host头返回400错误
Host格式:
Host: hostname:port
Host示例:
-
基本Host
GET /api/users HTTP/1.1 Host: api.example.com -
指定端口
GET /api/users HTTP/1.1 Host: api.example.com:8080 -
默认端口
# HTTP默认80端口 GET /api/users HTTP/1.1 Host: api.example.com # HTTPS默认443端口 GET /api/users HTTP/1.1 Host: api.example.com
虚拟主机示例:
-
一个IP多个域名
IP: 192.168.1.100 Domain1: www.example.com Domain2: api.example.com Domain3: blog.example.com -
服务器根据Host分发
# 请求1 GET / HTTP/1.1 Host: www.example.com # 返回www网站 # 请求2 GET / HTTP/1.1 Host: api.example.com # 返回API服务
Android代码示例:
URL url = new URL("http://api.example.com/api/users");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Host头自动设置
// Host: api.example.com
// 手动设置(不推荐)
// conn.setRequestProperty("Host", "api.example.com");
URL解析:
URL url = new URL("http://api.example.com:8080/api/users");
// Host: api.example.com:8080
URL url = new URL("http://api.example.com/api/users");
// Host: api.example.com (默认80端口)
URL url = new URL("https://api.example.com/api/users");
// Host: api.example.com (默认443端口)
使用场景:
- 虚拟主机
- 负载均衡
- 反向代理
- 多域名服务
注意事项:
- HTTP/1.1必须包含Host头
- Host头必须匹配URL中的主机名
- 代理服务器可能修改Host头
- 不要手动设置Host头(使用URL自动设置)
5.12 Connection的作用是什么?
答案:
Connection用于控制连接的行为,指定连接的管理方式。
Connection的作用:
-
连接管理
- 控制连接是否保持
- 指定连接关闭方式
- 控制连接复用
-
常用值
- keep-alive:保持连接
- close:关闭连接
- Upgrade:升级协议
-
HTTP版本差异
- HTTP/1.0:默认close,需要显式设置keep-alive
- HTTP/1.1:默认keep-alive,可以显式设置close
Connection格式:
Connection: keep-alive | close | Upgrade | 其他值
Connection示例:
-
保持连接(HTTP/1.1默认)
GET /api/users HTTP/1.1 Connection: keep-alive -
关闭连接
GET /api/users HTTP/1.1 Connection: close -
协议升级(WebSocket)
GET /websocket HTTP/1.1 Connection: Upgrade Upgrade: websocket
HTTP/1.0 vs HTTP/1.1:
| HTTP版本 | 默认行为 | Connection头 |
|---|---|---|
| HTTP/1.0 | 短连接(close) | 需要显式设置keep-alive |
| HTTP/1.1 | 长连接(keep-alive) | 默认keep-alive,可设置close |
Keep-Alive示例:
# HTTP/1.0需要显式设置
GET /api/users HTTP/1.0
Connection: keep-alive
HTTP/1.0 200 OK
Connection: keep-alive
Keep-Alive: timeout=5, max=100
# HTTP/1.1默认保持连接
GET /api/users HTTP/1.1
# Connection: keep-alive(默认)
Keep-Alive响应头:
HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5, max=100
- timeout:空闲连接保持时间(秒)
- max:最大请求数
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// HTTP/1.1默认keep-alive
// 可以显式设置
conn.setRequestProperty("Connection", "keep-alive");
// 或者关闭连接
conn.setRequestProperty("Connection", "close");
使用场景:
- 长连接(减少连接开销)
- 短连接(节省服务器资源)
- 协议升级(WebSocket)
最佳实践:
- HTTP/1.1默认使用keep-alive
- 减少连接建立开销
- 提高性能
- 合理设置Keep-Alive参数
5.13 HTTP响应头的作用是什么?
答案:
HTTP响应头(Response Headers)用于向客户端传递响应的元信息和服务器信息。
响应头的作用:
-
传递响应信息
- 状态码、状态描述
- 服务器信息
- 响应元信息
-
控制响应行为
- 控制缓存
- 控制压缩
- 控制连接
-
内容协商
- 指定内容类型
- 指定内容编码
- 指定内容长度
-
安全控制
- CORS控制
- 安全策略
- 认证信息
响应头格式:
Header-Name: Header-Value
响应头示例:
HTTP/1.1 200 OK
Server: Apache/2.4
Date: Wed, 21 Oct 2024 10:00:00 GMT
Content-Type: application/json
Content-Length: 123
Content-Encoding: gzip
Cache-Control: max-age=3600
ETag: "abc123"
Connection: keep-alive
常见响应头分类:
-
通用响应头
- Date:响应时间
- Connection:连接控制
- Cache-Control:缓存控制
-
响应头
- Server:服务器信息
- Content-Type:内容类型
- Content-Length:内容长度
- Content-Encoding:内容编码
- Location:重定向位置
- Set-Cookie:设置Cookie
- ETag:资源标识
- Last-Modified:最后修改时间
-
CORS响应头
- Access-Control-Allow-Origin:允许的源
- Access-Control-Allow-Methods:允许的方法
- Access-Control-Allow-Headers:允许的头部
- Access-Control-Max-Age:预检缓存时间
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取响应头
String contentType = conn.getHeaderField("Content-Type");
String contentLength = conn.getHeaderField("Content-Length");
String etag = conn.getHeaderField("ETag");
String lastModified = conn.getHeaderField("Last-Modified");
// 读取所有响应头
Map<String, List<String>> headers = conn.getHeaderFields();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String key = entry.getKey();
List<String> values = entry.getValue();
// 处理响应头
}
使用场景:
- 内容类型识别
- 缓存控制
- CORS控制
- 安全策略
5.14 Server的作用是什么?
答案:
Server用于标识服务器软件的名称和版本。
Server的作用:
-
服务器标识
- 标识服务器软件
- 显示服务器版本
- 用于诊断
-
安全考虑
- 可能泄露服务器信息
- 建议隐藏或简化
- 防止攻击者利用版本漏洞
-
调试和诊断
- 帮助调试问题
- 识别服务器类型
- 版本兼容性检查
Server格式:
Server: software/version (additional info)
Server示例:
-
Apache服务器
Server: Apache/2.4.41 (Unix) -
Nginx服务器
Server: nginx/1.18.0 -
Tomcat服务器
Server: Apache-Coyote/1.1 -
隐藏Server信息
Server: WebServer # 或 # 不返回Server头
安全建议:
-
隐藏版本信息
# ❌ 不推荐 Server: Apache/2.4.41 (Unix) OpenSSL/1.1.1 # ✅ 推荐 Server: WebServer # 或完全不返回Server头 -
配置示例(Nginx)
# 隐藏Server头 server_tokens off; # 或自定义 more_set_headers "Server: WebServer"; -
配置示例(Apache)
# 隐藏Server头 ServerTokens Prod ServerSignature Off
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取Server头
String server = conn.getHeaderField("Server");
if (server != null) {
// 解析服务器信息
// 例如:Apache/2.4.41
}
使用场景:
- 调试和诊断
- 服务器识别
- 版本检查
注意事项:
- 生产环境建议隐藏Server信息
- 防止泄露服务器版本
- 防止攻击者利用版本漏洞
5.15 Date的作用是什么?
答案:
Date用于指定响应生成的时间。
Date的作用:
-
时间戳
- 响应生成的时间
- HTTP时间格式
- GMT时间
-
缓存计算
- 用于计算缓存过期时间
- 与Expires配合使用
- 与Cache-Control配合使用
-
调试和日志
- 记录响应时间
- 分析性能
- 时间同步检查
Date格式:
Date: Wed, 21 Oct 2024 10:00:00 GMT
Date格式说明:
- 星期:Mon, Tue, Wed, Thu, Fri, Sat, Sun
- 日期:21 Oct 2024
- 时间:10:00:00 GMT
Date示例:
HTTP/1.1 200 OK
Date: Wed, 21 Oct 2024 10:00:00 GMT
Content-Type: application/json
{"data": "..."}
时间格式(Java):
// 格式化HTTP时间
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = sdf.format(new Date());
// 结果:Wed, 21 Oct 2024 10:00:00 GMT
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取Date头
String date = conn.getHeaderField("Date");
if (date != null) {
// 解析日期
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
try {
Date responseDate = sdf.parse(date);
// 使用响应时间
} catch (ParseException e) {
// 解析失败
}
}
使用场景:
- 缓存时间计算
- 响应时间记录
- 时间同步检查
- 日志记录
注意事项:
- Date必须是GMT时间
- 格式必须正确
- 服务器时间应该准确
5.16 Location的作用是什么?
答案:
Location用于指定重定向的目标URL。
Location的作用:
-
重定向
- 3xx重定向响应
- 指向新的URL
- 客户端应该访问新URL
-
资源创建
- 201 Created响应
- 指向新创建的资源
- 客户端可以访问新资源
-
URL格式
- 绝对URL:
https://www.example.com/new-page - 相对URL:
/new-page(相对于当前请求)
- 绝对URL:
Location格式:
Location: absolute-URL | relative-URL
Location示例:
-
301永久重定向
GET /old-page HTTP/1.1 HTTP/1.1 301 Moved Permanently Location: https://www.example.com/new-page -
302临时重定向
GET /page HTTP/1.1 HTTP/1.1 302 Found Location: /temp-page -
201创建资源
POST /api/users HTTP/1.1 HTTP/1.1 201 Created Location: /api/users/124 Content-Type: application/json { "id": 124, "name": "John" }
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setInstanceFollowRedirects(false); // 不自动跟随
int responseCode = conn.getResponseCode();
if (responseCode >= 300 && responseCode < 400) {
// 重定向
String location = conn.getHeaderField("Location");
if (location != null) {
// 处理相对URL
URL newUrl = new URL(url, location);
// 访问新URL
}
}
相对URL处理:
// 绝对URL
String location = "https://www.example.com/new-page";
URL newUrl = new URL(location);
// 相对URL
String location = "/new-page";
URL baseUrl = new URL("https://www.example.com/old-page");
URL newUrl = new URL(baseUrl, location);
// 结果:https://www.example.com/new-page
使用场景:
- 301/302重定向
- 201资源创建
- 资源位置变更
注意事项:
- Location必须是有效的URL
- 相对URL相对于请求URL
- 客户端应该遵循重定向
- 防止重定向循环
5.17 Set-Cookie的作用是什么?
答案:
Set-Cookie用于服务器设置Cookie,在客户端存储数据。
Set-Cookie的作用:
-
设置Cookie
- 服务器设置Cookie
- 客户端保存Cookie
- 下次请求自动携带
-
Cookie属性
- Name和Value:Cookie名称和值
- Domain:域名
- Path:路径
- Expires/Max-Age:过期时间
- Secure:安全传输
- HttpOnly:JavaScript不可访问
- SameSite:同站策略
Set-Cookie格式:
Set-Cookie: name=value; Domain=domain; Path=path; Expires=date; Secure; HttpOnly; SameSite=value
Set-Cookie示例:
-
基本Cookie
HTTP/1.1 200 OK Set-Cookie: sessionid=abc123 -
带属性的Cookie
HTTP/1.1 200 OK Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure; SameSite=Strict -
多个Cookie
HTTP/1.1 200 OK Set-Cookie: sessionid=abc123; Path=/; HttpOnly Set-Cookie: theme=dark; Path=/; Max-Age=3600
Cookie属性说明:
-
Domain(域)
Set-Cookie: sessionid=abc123; Domain=.example.com- 指定Cookie的域名
- .example.com表示所有子域名
-
Path(路径)
Set-Cookie: sessionid=abc123; Path=/api- 指定Cookie的路径
- 只有该路径及其子路径可以访问
-
Expires(过期时间)
Set-Cookie: sessionid=abc123; Expires=Wed, 21 Oct 2024 10:00:00 GMT- 绝对过期时间
- GMT时间格式
-
Max-Age(最大存活时间)
Set-Cookie: sessionid=abc123; Max-Age=3600- 相对过期时间(秒)
- 3600秒 = 1小时
-
Secure(安全)
Set-Cookie: sessionid=abc123; Secure- 只在HTTPS连接中发送
- 保护Cookie传输安全
-
HttpOnly(HTTP Only)
Set-Cookie: sessionid=abc123; HttpOnly- JavaScript无法访问
- 防止XSS攻击
-
SameSite(同站策略)
Set-Cookie: sessionid=abc123; SameSite=Strict- Strict:只在同站请求中发送
- Lax:同站和顶级导航中发送
- None:所有请求中发送(需要Secure)
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取Set-Cookie
Map<String, List<String>> headers = conn.getHeaderFields();
List<String> setCookies = headers.get("Set-Cookie");
if (setCookies != null) {
for (String cookie : setCookies) {
// 解析Cookie
// 例如:sessionid=abc123; Path=/; HttpOnly
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setCookie(url.toString(), cookie);
}
}
使用场景:
- 用户登录状态
- 会话管理
- 个性化设置
- 购物车
安全建议:
- 使用HttpOnly防止XSS
- 使用Secure保护传输
- 使用SameSite防止CSRF
- 敏感信息使用Secure + HttpOnly
5.18 Cache-Control的作用是什么?
答案:
Cache-Control用于控制缓存的行为,是HTTP/1.1的缓存控制机制。
Cache-Control的作用:
-
缓存控制
- 控制是否缓存
- 控制缓存时间
- 控制缓存行为
-
请求和响应
- 请求头:客户端控制缓存
- 响应头:服务器控制缓存
- 指令可以组合使用
-
优先级
- 比Expires优先级高
- HTTP/1.1的缓存机制
- 更灵活和精确
Cache-Control格式:
Cache-Control: directive, directive, ...
Cache-Control指令:
-
可缓存性
public:可以被任何缓存缓存private:只能被私有缓存缓存no-cache:使用前必须验证no-store:不缓存
-
过期时间
max-age=seconds:最大存活时间(秒)s-maxage=seconds:共享缓存的最大存活时间max-stale=seconds:允许使用过期缓存的时间
-
重新验证
must-revalidate:过期后必须重新验证proxy-revalidate:共享缓存必须重新验证
-
转换
no-transform:不转换内容
Cache-Control示例:
-
不缓存
Cache-Control: no-store, no-cache, must-revalidate -
缓存1小时
Cache-Control: public, max-age=3600 -
私有缓存1小时
Cache-Control: private, max-age=3600 -
使用前验证
Cache-Control: no-cache, must-revalidate -
组合使用
Cache-Control: public, max-age=3600, must-revalidate
Cache-Control vs Expires:
| 特性 | Cache-Control | Expires |
|---|---|---|
| HTTP版本 | HTTP/1.1 | HTTP/1.0 |
| 时间格式 | 相对时间(秒) | 绝对时间 |
| 优先级 | 高 | 低 |
| 灵活性 | 高 | 低 |
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取Cache-Control
String cacheControl = conn.getHeaderField("Cache-Control");
if (cacheControl != null) {
// 解析Cache-Control指令
// 例如:public, max-age=3600
if (cacheControl.contains("no-cache")) {
// 不使用缓存
} else if (cacheControl.contains("max-age")) {
// 解析max-age值
String maxAge = extractMaxAge(cacheControl);
// 计算过期时间
}
}
使用场景:
- 静态资源缓存
- API数据缓存
- 页面缓存控制
- 缓存策略配置
最佳实践:
- 静态资源使用public, max-age
- 私有数据使用private
- 敏感数据使用no-store
- 动态数据使用no-cache
5.19 Expires的作用是什么?
答案:
Expires用于指定缓存的过期时间,是HTTP/1.0的缓存机制。
Expires的特点:
-
过期时间
- 绝对时间(GMT时间)
- 指定缓存何时过期
- HTTP/1.0的缓存机制
-
优先级
- 比Cache-Control优先级低
- 如果同时存在,Cache-Control优先
- 主要用于兼容HTTP/1.0
-
时间格式
- HTTP时间格式
- GMT时间
- 例如:
Wed, 21 Oct 2024 10:00:00 GMT
Expires格式:
Expires: Wed, 21 Oct 2024 10:00:00 GMT
Expires示例:
-
基本Expires
HTTP/1.1 200 OK Expires: Wed, 21 Oct 2024 11:00:00 GMT -
与Cache-Control配合
HTTP/1.1 200 OK Cache-Control: max-age=3600 Expires: Wed, 21 Oct 2024 11:00:00 GMT -
不缓存
HTTP/1.1 200 OK Expires: Wed, 21 Oct 1997 00:00:00 GMT # 或 Expires: -1
Expires vs Cache-Control:
| 特性 | Expires | Cache-Control |
|---|---|---|
| HTTP版本 | HTTP/1.0 | HTTP/1.1 |
| 时间格式 | 绝对时间 | 相对时间 |
| 优先级 | 低 | 高 |
| 灵活性 | 低 | 高 |
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取Expires
String expires = conn.getHeaderField("Expires");
if (expires != null) {
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
try {
Date expireDate = sdf.parse(expires);
Date now = new Date();
if (now.before(expireDate)) {
// 缓存有效
} else {
// 缓存过期
}
} catch (ParseException e) {
// 解析失败
}
}
使用场景:
- 兼容HTTP/1.0
- 静态资源缓存
- 与Cache-Control配合使用
最佳实践:
- 优先使用Cache-Control
- Expires用于兼容性
- 两者同时设置时,Cache-Control优先
- 避免使用Expires(使用Cache-Control)
5.20 Last-Modified的作用是什么?
答案:
Last-Modified用于指定资源的最后修改时间,用于协商缓存。
Last-Modified的作用:
-
最后修改时间
- 资源的最后修改时间
- HTTP时间格式
- GMT时间
-
协商缓存
- 与If-Modified-Since配合
- 客户端发送If-Modified-Since
- 服务器比较返回304或200
-
缓存验证
- 检查资源是否修改
- 未修改返回304(使用缓存)
- 已修改返回200(返回新数据)
Last-Modified格式:
Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT
Last-Modified示例:
-
基本Last-Modified
HTTP/1.1 200 OK Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT Content-Type: application/json {"data": "..."} -
协商缓存(未修改)
# 客户端请求 GET /api/data HTTP/1.1 If-Modified-Since: Wed, 21 Oct 2024 10:00:00 GMT # 服务器响应(未修改) HTTP/1.1 304 Not Modified Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT # 无响应体,使用缓存 -
协商缓存(已修改)
# 客户端请求 GET /api/data HTTP/1.1 If-Modified-Since: Wed, 21 Oct 2024 10:00:00 GMT # 服务器响应(已修改) HTTP/1.1 200 OK Last-Modified: Wed, 21 Oct 2024 11:00:00 GMT Content-Type: application/json {"data": "updated"}
Last-Modified vs ETag:
| 特性 | Last-Modified | ETag |
|---|---|---|
| 精度 | 秒级 | 任意 |
| 格式 | 时间 | 字符串 |
| 问题 | 1秒内多次修改 | 无 |
| 计算开销 | 低 | 可能高 |
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取Last-Modified
String lastModified = conn.getHeaderField("Last-Modified");
if (lastModified != null) {
// 保存Last-Modified
saveLastModified(url, lastModified);
}
// 下次请求时使用
String cachedLastModified = getLastModified(url);
if (cachedLastModified != null) {
conn.setRequestProperty("If-Modified-Since", cachedLastModified);
}
int responseCode = conn.getResponseCode();
if (responseCode == 304) {
// 使用缓存
} else if (responseCode == 200) {
// 资源已修改,使用新数据
String newLastModified = conn.getHeaderField("Last-Modified");
saveLastModified(url, newLastModified);
}
使用场景:
- 静态资源缓存
- API数据缓存
- 文件缓存验证
注意事项:
- 时间必须准确
- 格式必须正确
- 与ETag相比精度较低
5.21 ETag的作用是什么?
答案:
ETag用于标识资源的版本,用于协商缓存。
ETag的作用:
-
资源标识
- 资源的唯一标识
- 资源改变时ETag改变
- 用于缓存验证
-
协商缓存
- 与If-None-Match配合
- 客户端发送If-None-Match
- 服务器比较返回304或200
-
缓存验证
- 检查资源是否修改
- 未修改返回304(使用缓存)
- 已修改返回200(返回新数据)
ETag格式:
ETag: "abc123"
ETag: W/"abc123" # 弱ETag
ETag类型:
-
强ETag
ETag: "abc123"- 资源完全匹配
- 任何改变都会改变ETag
-
弱ETag
ETag: W/"abc123"- 资源语义相同
- 某些改变不改变ETag
ETag示例:
-
基本ETag
HTTP/1.1 200 OK ETag: "abc123" Content-Type: application/json {"data": "..."} -
协商缓存(未修改)
# 客户端请求 GET /api/data HTTP/1.1 If-None-Match: "abc123" # 服务器响应(未修改) HTTP/1.1 304 Not Modified ETag: "abc123" # 无响应体,使用缓存 -
协商缓存(已修改)
# 客户端请求 GET /api/data HTTP/1.1 If-None-Match: "abc123" # 服务器响应(已修改) HTTP/1.1 200 OK ETag: "def456" Content-Type: application/json {"data": "updated"}
ETag生成方式:
-
内容哈希
// 使用MD5 MessageDigest md = MessageDigest.getInstance("MD5"); byte[] hash = md.digest(content.getBytes()); String etag = "\"" + toHexString(hash) + "\""; -
版本号
// 使用版本号 String etag = "\"" + version + "\""; -
修改时间+大小
// 使用修改时间和大小 String etag = "\"" + lastModified + "-" + size + "\"";
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取ETag
String etag = conn.getHeaderField("ETag");
if (etag != null) {
// 保存ETag
saveETag(url, etag);
}
// 下次请求时使用
String cachedETag = getETag(url);
if (cachedETag != null) {
conn.setRequestProperty("If-None-Match", cachedETag);
}
int responseCode = conn.getResponseCode();
if (responseCode == 304) {
// 使用缓存
} else if (responseCode == 200) {
// 资源已修改,使用新数据
String newETag = conn.getHeaderField("ETag");
saveETag(url, newETag);
}
Last-Modified vs ETag:
| 特性 | Last-Modified | ETag |
|---|---|---|
| 精度 | 秒级 | 任意 |
| 格式 | 时间 | 字符串 |
| 问题 | 1秒内多次修改 | 无 |
| 计算开销 | 低 | 可能高 |
| 灵活性 | 低 | 高 |
使用场景:
- 静态资源缓存
- API数据缓存
- 文件缓存验证
- 精确的缓存控制
最佳实践:
- 优先使用ETag(更精确)
- Last-Modified用于兼容性
- 两者可以同时使用
- ETag计算开销要考虑
第六章:HTTP 缓存机制(11 题)
6.1 HTTP缓存的作用是什么?
答案:
HTTP缓存用于减少网络请求,提高性能,节省带宽。
HTTP缓存的作用:
-
提高性能
- 减少网络请求次数
- 减少服务器负载
- 提高响应速度
-
节省带宽
- 减少数据传输量
- 节省网络流量
- 降低服务器成本
-
改善用户体验
- 页面加载更快
- 减少等待时间
- 离线可用(部分资源)
-
减轻服务器压力
- 减少服务器请求
- 降低服务器负载
- 提高服务器性能
缓存位置:
-
浏览器缓存
- 客户端缓存
- 本地存储
- 内存缓存和磁盘缓存
-
代理缓存
- CDN缓存
- 反向代理缓存
- 共享缓存
-
服务器缓存
- 服务器端缓存
- 应用缓存
- 数据库缓存
缓存流程:
客户端请求
↓
检查浏览器缓存
↓
缓存存在?
├─ 是 → 检查缓存是否有效
│ ├─ 有效 → 使用缓存(304)
│ └─ 无效 → 请求服务器验证
└─ 否 → 请求服务器
↓
服务器响应
↓
保存到缓存
Android代码示例:
// 检查缓存
String cachedData = getCachedData(url);
if (cachedData != null && isCacheValid(url)) {
// 使用缓存
return cachedData;
}
// 请求服务器
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置缓存验证头
String etag = getCachedETag(url);
if (etag != null) {
conn.setRequestProperty("If-None-Match", etag);
}
int responseCode = conn.getResponseCode();
if (responseCode == 304) {
// 使用缓存
return cachedData;
} else if (responseCode == 200) {
// 获取新数据并缓存
String newData = readResponse(conn);
String newETag = conn.getHeaderField("ETag");
saveCache(url, newData, newETag);
return newData;
}
使用场景:
- 静态资源(CSS、JS、图片)
- API数据缓存
- 页面内容缓存
- 减少重复请求
缓存策略:
- 静态资源:长期缓存
- 动态内容:短期缓存
- 实时数据:不缓存
- 敏感数据:不缓存
6.2 HTTP缓存分为哪些类型?
答案:
HTTP缓存分为强缓存和协商缓存两种类型。
缓存分类:
-
强缓存(Strong Cache)
- 也称为本地缓存
- 不发送请求到服务器
- 直接使用缓存
- 响应头:Cache-Control、Expires
-
协商缓存(Negotiation Cache)
- 也称为对比缓存
- 需要发送请求到服务器验证
- 未修改返回304,修改返回200
- 响应头:Last-Modified、ETag
- 请求头:If-Modified-Since、If-None-Match
缓存类型对比:
| 类型 | 特点 | 响应头 | 请求头 | 是否请求服务器 |
|---|---|---|---|---|
| 强缓存 | 直接使用缓存 | Cache-Control、Expires | 无 | 否 |
| 协商缓存 | 验证后使用缓存 | Last-Modified、ETag | If-Modified-Since、If-None-Match | 是 |
缓存流程对比:
强缓存流程:
客户端请求
↓
检查缓存
↓
缓存存在且未过期?
├─ 是 → 直接使用缓存(不请求服务器)
└─ 否 → 请求服务器
协商缓存流程:
客户端请求
↓
检查缓存
↓
缓存存在?
├─ 是 → 发送验证请求(If-Modified-Since/If-None-Match)
│ ↓
│ 服务器验证
│ ├─ 未修改 → 304 Not Modified(使用缓存)
│ └─ 已修改 → 200 OK(返回新数据)
└─ 否 → 请求服务器
Android代码示例:
// 强缓存
String cachedData = getCachedData(url);
Date expireDate = getCacheExpireDate(url);
if (cachedData != null && new Date().before(expireDate)) {
// 使用强缓存
return cachedData;
}
// 协商缓存
String cachedETag = getCachedETag(url);
if (cachedETag != null) {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("If-None-Match", cachedETag);
int responseCode = conn.getResponseCode();
if (responseCode == 304) {
// 使用协商缓存
return getCachedData(url);
}
}
使用场景:
- 强缓存:静态资源、长期不变的内容
- 协商缓存:动态内容、需要验证的内容
最佳实践:
- 静态资源使用强缓存
- 动态内容使用协商缓存
- 两者可以结合使用
6.3 强缓存和协商缓存的区别是什么?
答案:
强缓存和协商缓存的主要区别:
-
是否需要请求服务器
- 强缓存:不需要请求服务器,直接使用缓存
- 协商缓存:需要请求服务器验证,返回304或200
-
响应头
- 强缓存:Cache-Control、Expires
- 协商缓存:Last-Modified、ETag
-
请求头
- 强缓存:不需要特殊请求头
- 协商缓存:If-Modified-Since、If-None-Match
-
响应状态码
- 强缓存:不发送请求,无状态码
- 协商缓存:304 Not Modified或200 OK
-
性能
- 强缓存:性能最好(不请求服务器)
- 协商缓存:需要请求但无响应体(节省带宽)
对比表:
| 特性 | 强缓存 | 协商缓存 |
|---|---|---|
| 是否需要请求 | 否 | 是 |
| 响应头 | Cache-Control、Expires | Last-Modified、ETag |
| 请求头 | 无 | If-Modified-Since、If-None-Match |
| 响应状态码 | 无(不请求) | 304或200 |
| 性能 | 最好 | 好(304时无响应体) |
| 准确性 | 可能过期 | 准确 |
示例对比:
强缓存:
# 第一次请求
GET /static/style.css HTTP/1.1
HTTP/1.1 200 OK
Cache-Control: max-age=3600
# 第二次请求(1小时内)
GET /static/style.css HTTP/1.1
# 浏览器直接使用缓存,不发送请求
协商缓存:
# 第一次请求
GET /api/data HTTP/1.1
HTTP/1.1 200 OK
ETag: "abc123"
# 第二次请求
GET /api/data HTTP/1.1
If-None-Match: "abc123"
# 服务器验证
HTTP/1.1 304 Not Modified
ETag: "abc123"
# 无响应体,使用缓存
Android代码示例:
// 强缓存实现
private String getCachedDataWithStrongCache(URL url) {
String cachedData = cache.get(url.toString());
long expireTime = cacheExpireTime.get(url.toString());
if (cachedData != null && System.currentTimeMillis() < expireTime) {
// 强缓存有效
return cachedData;
}
// 缓存过期,请求服务器
return fetchFromServer(url);
}
// 协商缓存实现
private String getCachedDataWithNegotiationCache(URL url) {
String cachedETag = cacheETag.get(url.toString());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (cachedETag != null) {
conn.setRequestProperty("If-None-Match", cachedETag);
}
int responseCode = conn.getResponseCode();
if (responseCode == 304) {
// 使用缓存
return cache.get(url.toString());
} else if (responseCode == 200) {
// 获取新数据
String newData = readResponse(conn);
String newETag = conn.getHeaderField("ETag");
cache.put(url.toString(), newData);
cacheETag.put(url.toString(), newETag);
return newData;
}
return null;
}
选择建议:
- 静态资源(CSS、JS、图片)→ 强缓存
- 动态内容(API数据)→ 协商缓存
- 长期不变的内容 → 强缓存
- 需要实时性 → 协商缓存
6.4 强缓存的工作原理是什么?
答案:
强缓存的工作原理是浏览器检查缓存,如果缓存有效,直接使用缓存,不请求服务器。
强缓存工作流程:
-
第一次请求
客户端 → 服务器请求资源 服务器 → 返回资源 + Cache-Control/Expires 客户端 → 保存资源到缓存 -
后续请求
客户端 → 检查缓存 缓存有效? ├─ 是 → 直接使用缓存(不请求服务器) └─ 否 → 请求服务器
强缓存判断依据:
-
Cache-Control(HTTP/1.1)
max-age=seconds:最大存活时间(秒)- 优先级高于Expires
- 例如:
Cache-Control: max-age=3600(1小时)
-
Expires(HTTP/1.0)
- 绝对过期时间(GMT时间)
- 优先级低于Cache-Control
- 例如:
Expires: Wed, 21 Oct 2024 11:00:00 GMT
强缓存示例:
# 第一次请求
GET /static/style.css HTTP/1.1
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
Content-Type: text/css
body { color: red; }
# 浏览器保存到缓存,设置过期时间(当前时间 + 3600秒)
# 第二次请求(1小时内)
GET /static/style.css HTTP/1.1
# 浏览器检查缓存
# 缓存存在且未过期
# 直接使用缓存,不发送请求
Android代码示例:
public class StrongCacheManager {
private Map<String, CacheEntry> cache = new HashMap<>();
private static class CacheEntry {
String data;
long expireTime;
}
public String get(URL url) {
String key = url.toString();
CacheEntry entry = cache.get(key);
if (entry != null && System.currentTimeMillis() < entry.expireTime) {
// 强缓存有效
return entry.data;
}
// 缓存无效,请求服务器
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
String data = readResponse(conn);
String cacheControl = conn.getHeaderField("Cache-Control");
// 解析max-age
long maxAge = parseMaxAge(cacheControl);
long expireTime = System.currentTimeMillis() + maxAge * 1000;
// 保存缓存
cache.put(key, new CacheEntry(data, expireTime));
return data;
}
return null;
}
private long parseMaxAge(String cacheControl) {
if (cacheControl == null) return 0;
String[] parts = cacheControl.split(",");
for (String part : parts) {
part = part.trim();
if (part.startsWith("max-age=")) {
String maxAge = part.substring(8);
return Long.parseLong(maxAge);
}
}
return 0;
}
}
Cache-Control指令:
-
可缓存性
public:可以被任何缓存缓存private:只能被私有缓存缓存no-cache:使用前必须验证(协商缓存)no-store:不缓存
-
过期时间
max-age=seconds:最大存活时间(秒)
使用场景:
- 静态资源(CSS、JS、图片)
- 长期不变的内容
- 公共资源
注意事项:
- 强缓存可能使用过期内容
- 需要更新时更改URL或文件名
- 结合版本号或哈希值
6.5 协商缓存的工作原理是什么?
答案:
协商缓存的工作原理是客户端发送验证请求,服务器检查资源是否修改,未修改返回304使用缓存,已修改返回200返回新数据。
协商缓存工作流程:
-
第一次请求
客户端 → 服务器请求资源 服务器 → 返回资源 + Last-Modified/ETag 客户端 → 保存资源和标识 -
后续请求
客户端 → 发送验证请求(If-Modified-Since/If-None-Match) 服务器 → 检查资源是否修改 ├─ 未修改 → 304 Not Modified(无响应体) └─ 已修改 → 200 OK(返回新数据)
协商缓存判断依据:
-
Last-Modified / If-Modified-Since
- 服务器返回Last-Modified
- 客户端发送If-Modified-Since
- 服务器比较时间
-
ETag / If-None-Match
- 服务器返回ETag
- 客户端发送If-None-Match
- 服务器比较ETag
协商缓存示例:
# 第一次请求
GET /api/data HTTP/1.1
HTTP/1.1 200 OK
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT
Content-Type: application/json
{"data": "..."}
# 浏览器保存数据和ETag
# 第二次请求
GET /api/data HTTP/1.1
If-None-Match: "abc123"
If-Modified-Since: Wed, 21 Oct 2024 10:00:00 GMT
# 服务器检查
# ETag匹配 → 资源未修改
HTTP/1.1 304 Not Modified
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT
# 无响应体
# 浏览器使用缓存
Android代码示例:
public class NegotiationCacheManager {
private Map<String, CacheEntry> cache = new HashMap<>();
private static class CacheEntry {
String data;
String etag;
String lastModified;
}
public String get(URL url) {
String key = url.toString();
CacheEntry entry = cache.get(key);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 发送验证请求
if (entry != null) {
if (entry.etag != null) {
conn.setRequestProperty("If-None-Match", entry.etag);
}
if (entry.lastModified != null) {
conn.setRequestProperty("If-Modified-Since", entry.lastModified);
}
}
int responseCode = conn.getResponseCode();
if (responseCode == 304) {
// 使用缓存
return entry.data;
} else if (responseCode == 200) {
// 获取新数据
String data = readResponse(conn);
String etag = conn.getHeaderField("ETag");
String lastModified = conn.getHeaderField("Last-Modified");
// 保存缓存
cache.put(key, new CacheEntry(data, etag, lastModified));
return data;
}
return null;
}
}
Last-Modified vs ETag:
| 特性 | Last-Modified | ETag |
|---|---|---|
| 精度 | 秒级 | 任意 |
| 格式 | 时间 | 字符串 |
| 问题 | 1秒内多次修改 | 无 |
| 计算开销 | 低 | 可能高 |
使用场景:
- 动态内容(API数据)
- 需要实时性的内容
- 频繁更新的内容
优势:
- 准确(服务器验证)
- 节省带宽(304时无响应体)
- 实时性好(总是验证)
6.6 Cache-Control的常用值有哪些?
答案:
Cache-Control的常用值:
-
可缓存性
public:可以被任何缓存缓存private:只能被私有缓存缓存no-cache:使用前必须验证no-store:不缓存
-
过期时间
max-age=seconds:最大存活时间(秒)s-maxage=seconds:共享缓存的最大存活时间max-stale=seconds:允许使用过期缓存的时间
-
重新验证
must-revalidate:过期后必须重新验证proxy-revalidate:共享缓存必须重新验证
-
转换
no-transform:不转换内容
常用组合:
-
不缓存
Cache-Control: no-store, no-cache, must-revalidate -
缓存1小时
Cache-Control: public, max-age=3600 -
私有缓存1小时
Cache-Control: private, max-age=3600 -
使用前验证
Cache-Control: no-cache, must-revalidate -
静态资源(长期缓存)
Cache-Control: public, max-age=31536000 # 1年
Android代码示例:
// 解析Cache-Control
private Map<String, String> parseCacheControl(String cacheControl) {
Map<String, String> directives = new HashMap<>();
if (cacheControl == null) return directives;
String[] parts = cacheControl.split(",");
for (String part : parts) {
part = part.trim();
if (part.contains("=")) {
String[] kv = part.split("=", 2);
directives.put(kv[0].trim(), kv[1].trim());
} else {
directives.put(part, "true");
}
}
return directives;
}
6.7 Cache-Control和Expires的区别是什么?
答案:
Cache-Control和Expires的区别:
-
HTTP版本
- Cache-Control:HTTP/1.1
- Expires:HTTP/1.0
-
时间格式
- Cache-Control:相对时间(秒)
- Expires:绝对时间(GMT时间)
-
优先级
- Cache-Control:优先级高
- Expires:优先级低
-
灵活性
- Cache-Control:更灵活(多种指令)
- Expires:较简单(只指定过期时间)
对比示例:
# Cache-Control(相对时间)
HTTP/1.1 200 OK
Cache-Control: max-age=3600
# 1小时后过期
# Expires(绝对时间)
HTTP/1.1 200 OK
Expires: Wed, 21 Oct 2024 11:00:00 GMT
# 指定时间过期
# 两者同时存在(Cache-Control优先)
HTTP/1.1 200 OK
Cache-Control: max-age=3600
Expires: Wed, 21 Oct 2024 11:00:00 GMT
# Cache-Control优先
最佳实践:
- 优先使用Cache-Control
- Expires用于兼容HTTP/1.0
- 两者同时设置时,Cache-Control生效
6.10 如何设置强缓存?
答案:
设置强缓存的方法:
-
使用Cache-Control(推荐)
HTTP/1.1 200 OK Cache-Control: public, max-age=3600 -
使用Expires(兼容)
HTTP/1.1 200 OK Expires: Wed, 21 Oct 2024 11:00:00 GMT -
两者同时使用
HTTP/1.1 200 OK Cache-Control: public, max-age=3600 Expires: Wed, 21 Oct 2024 11:00:00 GMT
服务器配置示例(Nginx):
location ~* \.(css|js|jpg|png|gif|ico)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000";
}
服务器配置示例(Apache):
<FilesMatch "\.(css|js|jpg|png|gif|ico)$">
ExpiresActive On
ExpiresDefault "access plus 1 year"
Header set Cache-Control "public, max-age=31536000"
</FilesMatch>
Android代码示例:
// 设置响应头(如果自己实现服务器)
response.setHeader("Cache-Control", "public, max-age=3600");
response.setHeader("Expires", getExpireDate(3600));
缓存时间建议:
- 静态资源(CSS、JS、图片):1年
- API数据:根据更新频率
- 动态内容:0(不缓存)或协商缓存
6.8 If-Modified-Since的作用是什么?
答案:
If-Modified-Since用于客户端发送资源的最后修改时间,用于协商缓存验证。
If-Modified-Since的作用:
-
协商缓存验证
- 客户端在请求头中发送
- 值是上次响应中的Last-Modified
- 服务器比较时间判断是否修改
-
工作流程
第一次请求 → 服务器返回Last-Modified 客户端保存Last-Modified 第二次请求 → 客户端发送If-Modified-Since 服务器比较时间 → 304或200
If-Modified-Since格式:
If-Modified-Since: Wed, 21 Oct 2024 10:00:00 GMT
If-Modified-Since示例:
# 第一次请求
GET /api/data HTTP/1.1
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT
Content-Type: application/json
{"data": "..."}
# 客户端保存Last-Modified
# 第二次请求
GET /api/data HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2024 10:00:00 GMT
# 服务器比较
# 资源未修改
HTTP/1.1 304 Not Modified
Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT
# 无响应体
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取缓存的Last-Modified
String lastModified = getCachedLastModified(url);
if (lastModified != null) {
conn.setRequestProperty("If-Modified-Since", lastModified);
}
int responseCode = conn.getResponseCode();
if (responseCode == 304) {
// 使用缓存
return getCachedData(url);
} else if (responseCode == 200) {
// 保存新的Last-Modified
String newLastModified = conn.getHeaderField("Last-Modified");
saveCache(url, readResponse(conn), newLastModified);
}
注意事项:
- 时间格式必须正确(HTTP时间格式)
- 必须是GMT时间
- 服务器时间必须准确
- 精度为秒级(1秒内多次修改无法检测)
6.9 If-None-Match的作用是什么?
答案:
If-None-Match用于客户端发送资源的ETag,用于协商缓存验证。
If-None-Match的作用:
-
协商缓存验证
- 客户端在请求头中发送
- 值是上次响应中的ETag
- 服务器比较ETag判断是否修改
-
工作流程
第一次请求 → 服务器返回ETag 客户端保存ETag 第二次请求 → 客户端发送If-None-Match 服务器比较ETag → 304或200
If-None-Match格式:
If-None-Match: "abc123"
If-None-Match: "abc123", "def456" # 多个ETag
If-None-Match: * # 匹配任何ETag
If-None-Match示例:
# 第一次请求
GET /api/data HTTP/1.1
HTTP/1.1 200 OK
ETag: "abc123"
Content-Type: application/json
{"data": "..."}
# 客户端保存ETag
# 第二次请求
GET /api/data HTTP/1.1
If-None-Match: "abc123"
# 服务器比较
# ETag匹配 → 资源未修改
HTTP/1.1 304 Not Modified
ETag: "abc123"
# 无响应体
Android代码示例:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 读取缓存的ETag
String etag = getCachedETag(url);
if (etag != null) {
conn.setRequestProperty("If-None-Match", etag);
}
int responseCode = conn.getResponseCode();
if (responseCode == 304) {
// 使用缓存
return getCachedData(url);
} else if (responseCode == 200) {
// 保存新的ETag
String newETag = conn.getHeaderField("ETag");
saveCache(url, readResponse(conn), newETag);
}
If-None-Match vs If-Modified-Since:
| 特性 | If-None-Match | If-Modified-Since |
|---|---|---|
| 基于 | ETag | Last-Modified |
| 精度 | 任意 | 秒级 |
| 格式 | 字符串 | 时间 |
| 优先级 | 高(如果同时存在) | 低 |
最佳实践:
- 优先使用ETag(更精确)
- Last-Modified用于兼容性
- 两者可以同时使用
- 服务器优先检查If-None-Match
6.11 Last-Modified和ETag的区别是什么?
答案:
Last-Modified和ETag的主要区别:
-
精度
- Last-Modified:秒级
- ETag:任意精度
-
格式
- Last-Modified:时间(HTTP时间格式)
- ETag:字符串
-
问题
- Last-Modified:1秒内多次修改无法检测
- ETag:无此问题
-
计算开销
- Last-Modified:低(文件修改时间)
- ETag:可能高(需要计算哈希)
-
灵活性
- Last-Modified:低(只能是时间)
- ETag:高(可以是任意字符串)
对比示例:
# Last-Modified(秒级精度)
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT
# 问题:10:00:00.500和10:00:00.600的修改无法区分
# ETag(任意精度)
HTTP/1.1 200 OK
ETag: "abc123"
# 任何修改都会改变ETag
使用建议:
- 优先使用ETag(更精确)
- Last-Modified用于兼容性
- 两者可以同时使用
- 服务器优先检查ETag
Android代码示例:
// 优先使用ETag
String etag = getCachedETag(url);
String lastModified = getCachedLastModified(url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (etag != null) {
conn.setRequestProperty("If-None-Match", etag);
}
if (lastModified != null) {
conn.setRequestProperty("If-Modified-Since", lastModified);
}
// 服务器优先检查If-None-Match
int responseCode = conn.getResponseCode();
对比表:
| 特性 | Last-Modified | ETag |
|---|---|---|
| 精度 | 秒级 | 任意 |
| 格式 | 时间 | 字符串 |
| 问题 | 1秒内多次修改 | 无 |
| 计算开销 | 低 | 可能高 |
| 灵活性 | 低 | 高 |
| 优先级 | 低 | 高 |