HTTP、HTTPS、Socket、WebSocket 全解析第一部

4 阅读1小时+

第一部分:HTTP 协议答案

第一章:网络分层模型(5 题)

1.1 OSI七层模型是什么?各层的作用是什么?

答案:

OSI(Open Systems Interconnection)七层模型是国际标准化组织(ISO)提出的网络通信参考模型,将网络通信分为七层:

  1. 物理层(Physical Layer):传输原始比特流,定义物理接口标准
  2. 数据链路层(Data Link Layer):在物理层基础上提供可靠的数据传输
  3. 网络层(Network Layer):路由选择和数据包转发
  4. 传输层(Transport Layer):提供端到端的数据传输服务
  5. 会话层(Session Layer):建立、管理和终止会话
  6. 表示层(Presentation Layer):数据格式转换、加密解密、压缩解压
  7. 应用层(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四层模型是实际使用的网络模型,将网络通信分为四层:

  1. 网络接口层(Network Interface Layer)

    • 对应OSI的物理层和数据链路层
    • 作用:在物理网络上传输数据帧
    • 功能:MAC地址、帧格式、介质访问控制
    • 协议:Ethernet、WiFi、PPP
  2. 网际层(Internet Layer)

    • 对应OSI的网络层
    • 作用:路由选择和数据包转发
    • 功能:IP地址、路由、数据包转发
    • 协议:IP、ICMP、ARP、IGMP
  3. 传输层(Transport Layer)

    • 对应OSI的传输层
    • 作用:提供端到端的数据传输
    • 功能:端口号、可靠传输、流量控制
    • 协议:TCP、UDP
  4. 应用层(Application Layer)

    • 对应OSI的会话层、表示层、应用层
    • 作用:为用户提供网络服务
    • 功能:应用协议、数据格式、用户接口
    • 协议:HTTP、HTTPS、FTP、SMTP、DNS

TCP/IP四层模型的特点:

  • 更简洁实用
  • 与实际网络实现更接近
  • 应用层包含了OSI的会话层、表示层、应用层

1.3 TCP/IP五层模型是什么?各层的作用是什么?

答案:

TCP/IP五层模型是在四层模型基础上,将网络接口层拆分为物理层和数据链路层:

  1. 物理层(Physical Layer)

    • 作用:传输原始比特流
    • 功能:电信号、光信号、无线电波传输
    • 设备:网线、光纤、无线设备
  2. 数据链路层(Data Link Layer)

    • 作用:在物理层基础上提供可靠的数据传输
    • 功能:帧同步、差错控制、MAC地址
    • 协议:Ethernet、WiFi、PPP
  3. 网络层(Network Layer)

    • 对应OSI的网络层
    • 作用:路由选择和数据包转发
    • 功能:IP地址、路由、数据包转发
    • 协议:IP、ICMP、ARP
  4. 传输层(Transport Layer)

    • 对应OSI的传输层
    • 作用:提供端到端的数据传输
    • 功能:端口号、可靠传输、流量控制
    • 协议:TCP、UDP
    • 注:与四层模型的传输层相同
  5. 应用层(Application Layer)

    • 对应OSI的会话层、表示层、应用层
    • 作用:为用户提供网络服务
    • 功能:应用协议、数据格式
    • 协议:HTTP、HTTPS、FTP、SMTP、DNS
    • 注:与四层模型的应用层相同

TCP/IP五层模型的特点:

  • 更详细地划分了底层
  • 便于理解物理传输和数据链路控制
  • 教学和学习更清晰

1.4 TCP/IP四层模型和五层模型的区别是什么?

答案:

TCP/IP四层模型和五层模型的主要区别:

  1. 网络接口层的划分

    • 四层模型:网络接口层(包含物理层和数据链路层)
    • 五层模型:物理层 + 数据链路层(分开)
  2. 层次数量

    • 四层模型:4层
    • 五层模型:5层
  3. 使用场景

    • 四层模型:实际网络实现、RFC标准
    • 五层模型:教学、学习、理解
  4. 对应关系

    • 四层模型:网络接口层 = OSI物理层 + 数据链路层
    • 五层模型:物理层 = OSI物理层,数据链路层 = OSI数据链路层

对比表:

模型层次网络接口层处理
四层模型4层合并物理层和数据链路层
五层模型5层分开物理层和数据链路层

实际应用:

  • 实际网络实现通常使用四层模型
  • 教学和学习通常使用五层模型
  • 两者在功能上等价,只是划分方式不同

1.5 OSI七层模型和TCP/IP模型的对应关系是什么?

答案:

OSI七层模型和TCP/IP模型的对应关系:

对应关系表:

OSI七层模型TCP/IP四层模型TCP/IP五层模型
应用层应用层应用层
表示层应用层应用层
会话层应用层应用层
传输层传输层传输层
网络层网际层网络层
数据链路层网络接口层数据链路层
物理层网络接口层物理层

关键对应关系:

  1. 应用层对应

    • OSI的应用层、表示层、会话层 → TCP/IP的应用层
    • TCP/IP应用层包含了OSI上三层的功能
  2. 传输层对应

    • OSI传输层 ↔ TCP/IP传输层
    • 完全对应,协议相同(TCP、UDP)
  3. 网络层对应

    • OSI网络层 ↔ TCP/IP网际层/网络层
    • 功能相同,名称不同
  4. 底层对应

    • OSI数据链路层 + 物理层 → TCP/IP网络接口层(四层)或 数据链路层 + 物理层(五层)

实际应用:

  • OSI模型:理论参考模型,用于理解网络通信原理
  • TCP/IP模型:实际使用的模型,互联网的基础架构

第二章:HTTP 概述(15 题)

2.1 HTTP是什么?它的全称是什么?

答案:

HTTP(HyperText Transfer Protocol)是超文本传输协议,是互联网上应用最广泛的网络协议之一。

定义:

  • HTTP是一种应用层协议
  • 用于在Web浏览器和Web服务器之间传输数据
  • 基于请求-响应模型
  • 使用TCP作为传输层协议

特点:

  1. 无状态协议:每次请求都是独立的,服务器不保存客户端状态
  2. 请求-响应模型:客户端发送请求,服务器返回响应
  3. 明文传输:数据以明文形式传输(HTTPS是加密的)
  4. 灵活:支持多种数据格式(HTML、JSON、XML等)

应用场景:

  • 网页浏览
  • API调用
  • 文件下载
  • 图片加载

2.2 HTTP的特点是什么?

答案:

HTTP的主要特点:

  1. 无状态(Stateless)

    • 每次请求都是独立的
    • 服务器不保存客户端的状态信息
    • 优点:简化服务器设计,提高性能
    • 缺点:需要状态时使用Cookie、Session
  2. 请求-响应模型(Request-Response)

    • 客户端发起请求
    • 服务器处理请求并返回响应
    • 一次请求对应一次响应
  3. 基于TCP

    • 使用TCP作为传输层协议
    • 默认端口80
    • 保证数据传输的可靠性
  4. 明文传输

    • 数据以明文形式传输
    • 容易被窃听和篡改
    • HTTPS提供加密传输
  5. 灵活

    • 支持多种数据格式
    • 支持多种请求方法
    • 支持自定义请求头和响应头
  6. 可扩展

    • 支持HTTP/1.0、HTTP/1.1、HTTP/2.0、HTTP/3.0
    • 支持新的请求方法和状态码

2.3 HTTP属于OSI模型的哪一层?

答案:

HTTP属于OSI模型的应用层(Application Layer)

详细说明:

  1. HTTP的位置

    • HTTP是应用层协议(第7层)
    • 建立在传输层(TCP)之上
    • 使用网络层(IP)进行路由
  2. 协议栈

    应用层:    HTTP
    传输层:    TCP
    网络层:    IP
    数据链路层: Ethernet
    物理层:    网线/光纤
    
  3. 与其他协议的关系

    • HTTP使用TCP提供可靠传输
    • TCP使用IP进行路由
    • IP使用数据链路层进行帧传输

2.4 HTTP属于TCP/IP模型的哪一层?

答案:

HTTP属于TCP/IP模型的应用层(Application Layer)

详细说明:

  1. TCP/IP模型分层

    • 应用层
    • 传输层(TCP/UDP)
    • 网际层(IP)
    • 网络接口层
  2. HTTP的位置

    • HTTP是应用层协议
    • 建立在传输层(TCP)之上
    • 使用网际层(IP)进行路由
  3. 协议栈

    TCP/IP模型:
    应用层:    HTTP
    传输层:    TCP
    网际层:    IP
    网络接口层: Ethernet
    
  4. 与其他协议的关系

    • HTTP依赖TCP提供可靠传输
    • TCP依赖IP进行路由
    • IP依赖网络接口层进行物理传输

注意: 在TCP/IP模型中,应用层包含了OSI模型的会话层、表示层、应用层的功能。


2.5 HTTP和TCP/IP的关系是什么?

答案:

HTTP和TCP/IP的关系:

  1. 协议层次关系

    • HTTP是应用层协议
    • TCP是传输层协议
    • IP是网络层协议
    • HTTP建立在TCP/IP之上
  2. 依赖关系

    HTTP(应用层)
      ↓ 依赖
    TCP(传输层)
      ↓ 依赖
    IP(网络层)
      ↓ 依赖
    数据链路层
    
  3. HTTP使用TCP的原因

    • TCP提供可靠传输:保证数据不丢失、不重复、有序
    • TCP提供连接管理:三次握手建立连接,四次挥手关闭连接
    • TCP提供流量控制:防止发送方发送过快
    • TCP提供拥塞控制:防止网络拥塞
  4. HTTP不使用UDP的原因

    • UDP不保证可靠性:数据可能丢失
    • UDP无连接:不适合HTTP的请求-响应模型
    • HTTP需要可靠传输保证数据完整性
  5. 实际传输过程

    客户端发送HTTP请求
      ↓
    TCP将HTTP数据分段,添加TCP头部
      ↓
    IP将TCP段封装成IP数据包,添加IP头部
      ↓
    数据链路层封装成帧,添加MAC地址
      ↓
    物理层传输比特流
    

总结: HTTP是应用层协议,依赖TCP/IP提供传输服务,TCP/IP是HTTP的基础。


2.6 HTTP是无状态协议吗?为什么?

答案:

是的,HTTP是无状态协议(Stateless Protocol)

无状态的含义:

  • 服务器不保存客户端的状态信息
  • 每次请求都是独立的
  • 服务器不知道客户端之前的请求

为什么HTTP是无状态协议?

  1. 设计目标

    • 简化服务器设计
    • 提高服务器性能
    • 减少服务器资源消耗
  2. 优点

    • 简化设计:服务器不需要维护客户端状态
    • 提高性能:不需要查找和更新状态信息
    • 易于扩展:可以轻松添加服务器
    • 容错性好:服务器崩溃不影响其他请求
  3. 缺点

    • 无法记住用户登录状态
    • 无法实现购物车功能
    • 无法跟踪用户行为

如何解决无状态问题?

  1. Cookie

    Set-Cookie: sessionid=abc123; Path=/; HttpOnly
    Cookie: sessionid=abc123
    
  2. Session

    • 服务器端存储会话信息
    • 通过Session ID关联
  3. 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)

模型特点:

  1. 单向通信

    • 客户端发起请求
    • 服务器处理请求并返回响应
    • 一次请求对应一次响应
  2. 请求结构

    请求行:    方法 路径 协议版本
    请求头:    键值对
    空行:      分隔请求头和请求体
    请求体:    可选的数据
    
  3. 响应结构

    状态行:    协议版本 状态码 状态描述
    响应头:    键值对
    空行:      分隔响应头和响应体
    响应体:    返回的数据
    

请求示例:

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>

工作流程:

  1. 客户端建立TCP连接
  2. 客户端发送HTTP请求
  3. 服务器处理请求
  4. 服务器返回HTTP响应
  5. 关闭TCP连接(HTTP/1.0)或保持连接(HTTP/1.1)

2.8 HTTP的默认端口号是什么?

答案:

HTTP的默认端口号是80

详细说明:

  1. 端口号定义

    • 端口号用于区分不同的应用程序
    • HTTP默认使用80端口
    • HTTPS默认使用443端口
  2. URL中的端口

    http://www.example.com        # 默认80端口
    http://www.example.com:80     # 显式指定80端口
    http://www.example.com:8080   # 使用8080端口
    
  3. 常见端口号

    • HTTP: 80
    • HTTPS: 443
    • FTP: 21
    • SSH: 22
    • Telnet: 23
    • SMTP: 25
    • DNS: 53
    • POP3: 110
    • IMAP: 143
  4. 端口号范围

    • 0-1023:知名端口(Well-known ports)
    • 1024-49151:注册端口(Registered ports)
    • 49152-65535:动态端口(Dynamic ports)
  5. Android开发中的使用

    // 默认端口
    URL url = new URL("http://www.example.com");
    
    // 指定端口
    URL url = new URL("http://www.example.com:8080");
    

2.9 HTTP的版本有哪些?

答案:

HTTP的版本包括:

  1. HTTP/0.9(1991年)

    • 最早的版本
    • 只支持GET方法
    • 没有请求头和响应头
    • 已废弃
  2. HTTP/1.0(1996年)

    • 支持多种请求方法(GET、POST、HEAD)
    • 引入请求头和响应头
    • 支持状态码
    • 默认短连接
  3. HTTP/1.1(1997年)

    • 默认长连接(Keep-Alive)
    • 支持管道化(Pipelining)
    • 支持分块传输(Chunked Transfer)
    • 支持Host头
    • 支持缓存控制
    • 目前最广泛使用的版本
  4. HTTP/2.0(2015年)

    • 多路复用(Multiplexing)
    • 服务器推送(Server Push)
    • 头部压缩(Header Compression)
    • 二进制分帧(Binary Framing)
    • 流控制(Flow Control)
  5. HTTP/3.0(2020年)

    • 基于UDP的QUIC协议
    • 更快的连接建立
    • 改进的拥塞控制
    • 连接迁移

版本对比:

版本年份主要特性使用情况
HTTP/0.91991基础GET请求已废弃
HTTP/1.01996请求头、状态码很少使用
HTTP/1.11997长连接、管道化广泛使用
HTTP/2.02015多路复用、服务器推送逐渐普及
HTTP/3.02020QUIC、更快连接新兴技术

2.10 HTTP/1.0的特点是什么?

答案:

HTTP/1.0的主要特点:

  1. 请求方法

    • GET:获取资源
    • POST:提交数据
    • HEAD:获取响应头
  2. 请求头和响应头

    • 引入请求头:User-Agent、Accept等
    • 引入响应头:Content-Type、Content-Length等
    • 支持自定义头部
  3. 状态码

    • 200:成功
    • 404:未找到
    • 500:服务器错误
  4. 短连接(默认)

    • 每次请求建立新连接
    • 请求完成后关闭连接
    • 性能较差
  5. 支持多种内容类型

    • 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的主要特点:

  1. 默认长连接(Keep-Alive)

    • 默认保持连接打开
    • 可以复用连接发送多个请求
    • 减少连接建立的开销
  2. 管道化(Pipelining)

    • 可以发送多个请求而不等待响应
    • 提高性能
    • 但实现复杂,使用较少
  3. 分块传输(Chunked Transfer)

    • 支持流式传输
    • 不需要提前知道内容长度
    • Transfer-Encoding: chunked
  4. Host头(必需)

    • 支持虚拟主机
    • 一个IP可以对应多个域名
    • HTTP/1.1必须包含Host头
  5. 缓存控制

    • Cache-Control头
    • ETag支持
    • 更灵活的缓存策略
  6. 范围请求(Range Request)

    • 支持断点续传
    • Range头指定请求范围
    • 206 Partial Content响应
  7. 更多请求方法

    • 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的主要区别:

  1. 连接管理

    • HTTP/1.0:默认短连接,每次请求建立新连接
    • HTTP/1.1:默认长连接,可以复用连接
  2. Host头

    • HTTP/1.0:不需要Host头
    • HTTP/1.1:必须包含Host头(支持虚拟主机)
  3. 管道化

    • HTTP/1.0:不支持
    • HTTP/1.1:支持管道化(但使用较少)
  4. 分块传输

    • HTTP/1.0:不支持
    • HTTP/1.1:支持分块传输(Chunked Transfer)
  5. 缓存控制

    • HTTP/1.0:使用Expires头
    • HTTP/1.1:使用Cache-Control头,更灵活
  6. 范围请求

    • HTTP/1.0:不支持
    • HTTP/1.1:支持Range请求,断点续传
  7. 错误处理

    • HTTP/1.0:连接错误时关闭连接
    • HTTP/1.1:更好的错误处理和恢复

对比表:

特性HTTP/1.0HTTP/1.1
连接短连接长连接
Host头不需要必需
管道化不支持支持
分块传输不支持支持
缓存ExpiresCache-Control
范围请求不支持支持
请求方法GET、POST、HEAD更多方法

实际应用:

  • HTTP/1.0已很少使用
  • HTTP/1.1是目前最广泛使用的版本
  • 大多数Web服务器和浏览器都支持HTTP/1.1

2.13 HTTP/2.0的新特性有哪些?

答案:

HTTP/2.0的主要新特性:

  1. 多路复用(Multiplexing)

    • 单个TCP连接可以同时处理多个请求
    • 解决HTTP/1.1的队头阻塞问题
    • 提高性能
  2. 服务器推送(Server Push)

    • 服务器可以主动推送资源给客户端
    • 减少请求次数
    • 提高页面加载速度
  3. 头部压缩(Header Compression)

    • 使用HPACK算法压缩头部
    • 减少头部大小
    • 提高传输效率
  4. 二进制分帧(Binary Framing)

    • 使用二进制格式替代文本格式
    • 更高效的解析
    • 支持多路复用
  5. 流控制(Flow Control)

    • 类似TCP的流控制
    • 防止接收方被数据淹没
    • 更精细的控制
  6. 请求优先级(Priority)

    • 可以为请求设置优先级
    • 重要资源优先传输
    • 提高用户体验

HTTP/2.0的优势:

  1. 性能提升

    • 减少延迟
    • 提高吞吐量
    • 更好的资源利用
  2. 向后兼容

    • 保持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请求和响应

工作原理:

  1. 二进制分帧

    • 将HTTP消息分解为独立的帧
    • 每个帧有流ID标识
    • 帧可以在连接上交错传输
  2. 流(Stream)

    • 每个请求/响应对应一个流
    • 流有唯一的ID
    • 多个流可以并行传输
  3. 帧(Frame)

    • HEADERS帧:请求/响应头
    • DATA帧:请求/响应体
    • 其他控制帧

对比HTTP/1.1:

HTTP/1.1(串行):

请求1 → 响应1 → 请求2 → 响应2 → 请求3 → 响应3

HTTP/2.0(并行):

请求1 ──┐
请求2 ──┼──→ 并行传输 ──┼──→ 响应1
请求3 ──┘                └──→ 响应2
                              └──→ 响应3

优势:

  1. 解决队头阻塞

    • HTTP/1.1中,如果第一个请求慢,后续请求被阻塞
    • HTTP/2.0中,多个请求可以并行处理
  2. 减少连接数

    • HTTP/1.1可能需要多个TCP连接
    • HTTP/2.0只需一个TCP连接
  3. 提高性能

    • 减少延迟
    • 提高带宽利用率
    • 更好的资源利用

示例:

// 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)是指服务器可以主动推送资源给客户端,而不需要客户端请求

工作原理:

  1. 推送流程

    客户端请求:GET /index.html
    服务器响应:返回index.html + 推送style.css和script.js
    
  2. 推送条件

    • 服务器知道客户端需要哪些资源
    • 在响应主资源时推送相关资源
    • 客户端可以拒绝推送
  3. 推送的资源

    • CSS文件
    • JavaScript文件
    • 图片资源
    • 其他静态资源

优势:

  1. 减少请求次数

    • 客户端不需要发送额外请求
    • 减少往返时间(RTT)
  2. 提高性能

    • 资源提前到达客户端
    • 减少页面加载时间
    • 提高用户体验
  3. 智能推送

    • 服务器可以根据主资源推送相关资源
    • 减少延迟

示例:

# 客户端请求
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定义了多种请求方法,常用的包括:

  1. GET

    • 获取资源
    • 最常用的方法
    • 幂等、安全
  2. POST

    • 提交数据
    • 创建资源
    • 非幂等
  3. PUT

    • 更新资源
    • 完整替换
    • 幂等
  4. DELETE

    • 删除资源
    • 幂等
  5. HEAD

    • 获取响应头
    • 不返回响应体
    • 用于检查资源是否存在
  6. OPTIONS

    • 获取服务器支持的请求方法
    • 用于CORS预检
  7. PATCH

    • 部分更新资源
    • 非幂等
  8. TRACE

    • 回显请求
    • 用于诊断
    • 安全风险,很少使用
  9. CONNECT

    • 建立隧道
    • 用于HTTPS代理

方法分类:

方法用途幂等性安全性
GET获取资源
POST提交数据
PUT更新资源
DELETE删除资源
HEAD获取头信息
OPTIONS获取方法
PATCH部分更新
TRACE回显请求
CONNECT建立隧道--

3.2 GET方法的特点是什么?

答案:

GET方法的特点:

  1. 获取资源

    • 从服务器获取资源
    • 不应该修改服务器状态
    • 只读操作
  2. 参数传递

    • 参数通过URL传递
    • 格式:?key1=value1&key2=value2
    • 参数可见,不安全
  3. 幂等性

    • 多次请求结果相同
    • 不会改变服务器状态
    • 可以安全地重复请求
  4. 安全性

    • 是安全方法
    • 不应该有副作用
    • 可以被缓存
  5. 可缓存

    • 响应可以被缓存
    • 浏览器可以缓存GET请求
    • 提高性能
  6. 长度限制

    • 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方法的特点:

  1. 提交数据

    • 向服务器提交数据
    • 创建新资源
    • 可以修改服务器状态
  2. 参数传递

    • 参数通过请求体传递
    • 支持多种格式:表单、JSON、XML
    • 参数不可见(在请求体中)
  3. 非幂等性

    • 多次请求可能产生不同结果
    • 每次请求可能创建新资源
    • 不能安全地重复请求
  4. 非安全性

    • 不是安全方法
    • 可能有副作用
    • 不应该被缓存
  5. 数据长度

    • 没有严格的长度限制
    • 可以传递大量数据
    • 适合文件上传
  6. 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的主要区别:

  1. 用途

    • GET:获取资源
    • POST:提交数据
  2. 参数位置

    • GET:参数在URL中(查询字符串)
    • POST:参数在请求体中
  3. 参数可见性

    • GET:参数可见(在URL中)
    • POST:参数不可见(在请求体中)
  4. 安全性

    • GET:相对不安全(参数在URL中)
    • POST:相对安全(参数在请求体中)
  5. 幂等性

    • GET:幂等(多次请求结果相同)
    • POST:非幂等(可能产生不同结果)
  6. 可缓存性

    • GET:可以被缓存
    • POST:不应该被缓存
  7. 数据长度

    • GET:URL长度有限制(通常2048字符)
    • POST:没有严格限制
  8. 浏览器行为

    • GET:可以收藏、分享URL
    • POST:刷新会提示重新提交

对比表:

特性GETPOST
用途获取资源提交数据
参数位置URL请求体
参数可见
安全性较低较高
幂等性
可缓存
数据长度有限制无限制
浏览器后退正常提示重新提交

选择建议:

  • 获取数据 → 使用GET
  • 提交数据 → 使用POST
  • 查询操作 → 使用GET
  • 创建/更新资源 → 使用POST

3.5 GET方法可以传递参数吗?如何传递?

答案:

可以,GET方法通过**URL查询字符串(Query String)**传递参数。

传递方式:

  1. URL查询字符串

    http://example.com/api/users?id=123&name=John&age=25
    
  2. 参数格式

    • 格式:?key1=value1&key2=value2&key3=value3
    • ? 开始查询字符串
    • & 分隔多个参数
    • = 连接键值对
  3. 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)**传递参数。

传递方式:

  1. 表单数据(Form Data)

    POST /api/users HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    name=John&email=john@example.com&age=25
    
  2. JSON数据

    POST /api/users HTTP/1.1
    Content-Type: application/json
    
    {"name":"John","email":"john@example.com","age":25}
    
  3. 文件上传(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/jsonJSON数据{"key":"value"}
multipart/form-data文件上传多部分数据
text/plain纯文本普通文本
application/xmlXML数据...

注意事项:

  • 必须设置Content-Type
  • 必须设置Content-Length
  • 数据在请求体中,相对安全
  • 没有严格的长度限制

3.7 GET和POST的数据长度限制是什么?

答案:

GET方法的数据长度限制:

  1. URL长度限制

    • 不同浏览器和服务器限制不同
    • IE:2083字符
    • Chrome:8192字符
    • Firefox:65536字符
    • 服务器:通常8KB-32KB
  2. 实际限制

    • 建议不超过2048字符
    • 包括协议、域名、路径、参数
    • 参数过多会导致URL过长
  3. 限制原因

    • 浏览器限制
    • 服务器限制
    • 代理服务器限制

POST方法的数据长度限制:

  1. 理论上无限制

    • HTTP协议本身没有限制
    • 可以传递大量数据
  2. 实际限制

    • 服务器配置限制
    • PHP:post_max_size(默认8MB)
    • Nginx:client_max_body_size(默认1MB)
    • Apache:LimitRequestBody
  3. 内存限制

    • 服务器内存限制
    • 客户端内存限制
    • 网络带宽限制

对比表:

方法理论限制实际限制建议
GET2048字符不超过2048字符
POST服务器配置根据服务器配置

最佳实践:

  • GET:参数少、数据小
  • POST:参数多、数据大、文件上传

3.8 GET和POST的安全性有什么区别?

答案:

GET和POST在安全性方面的区别:

  1. 参数可见性

    • GET:参数在URL中,完全可见
    • POST:参数在请求体中,不可见
  2. 浏览器历史

    • GET:URL保存在浏览器历史中
    • POST:URL不包含参数,历史记录更安全
  3. 服务器日志

    • GET:完整URL(包括参数)记录在日志中
    • POST:URL记录,但参数在请求体中,可能不记录
  4. 缓存

    • GET:可能被缓存,敏感数据可能泄露
    • POST:不应该被缓存,相对安全
  5. CSRF攻击

    • GET:更容易受到CSRF攻击
    • POST:相对安全,但仍需防护

安全建议:

  1. 敏感数据

    # ❌ 错误:密码在URL中
    GET /login?username=john&password=123456
    
    # ✅ 正确:密码在请求体中
    POST /login
    Content-Type: application/json
    
    {"username":"john","password":"123456"}
    
  2. HTTPS

    • GET和POST都应该使用HTTPS
    • HTTPS加密传输,提高安全性
  3. 参数验证

    • 服务器端必须验证所有参数
    • 防止SQL注入、XSS攻击

对比表:

安全方面GETPOST
参数可见是(URL中)否(请求体中)
浏览器历史保存完整URL不保存参数
服务器日志记录完整URL可能不记录参数
缓存风险
CSRF风险相对低

总结:

  • POST相对更安全,但都不是绝对安全
  • 敏感数据应该使用POST
  • 必须使用HTTPS加密传输
  • 服务器端必须验证和过滤所有输入

3.9 PUT方法的作用是什么?

答案:

PUT方法用于更新或创建资源

主要特点:

  1. 更新资源

    • 完整替换现有资源
    • 如果资源不存在,则创建
    • 幂等操作
  2. 幂等性

    • 多次PUT请求结果相同
    • 可以安全地重复请求
    • 不会产生副作用
  3. 完整替换

    • 替换整个资源
    • 不是部分更新
    • 需要提供完整的资源数据
  4. 资源定位

    • 通过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:

特性PUTPOST
用途更新/创建资源创建资源
幂等性
资源定位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的主要区别:

  1. 用途

    • PUT:更新或创建资源(完整替换)
    • POST:创建资源(通常由服务器生成ID)
  2. 幂等性

    • PUT:幂等(多次请求结果相同)
    • POST:非幂等(可能创建多个资源)
  3. 资源定位

    • PUT:URL指向具体资源(/api/users/123
    • POST:URL指向资源集合(/api/users
  4. 资源ID

    • PUT:客户端指定资源ID
    • POST:服务器生成资源ID
  5. 完整替换

    • 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方法用于删除指定的资源

主要特点:

  1. 删除资源

    • 删除URL指定的资源
    • 如果资源不存在,返回404
    • 幂等操作
  2. 幂等性

    • 多次DELETE请求结果相同
    • 删除不存在的资源也是幂等的
    • 可以安全地重复请求
  3. 资源定位

    • 通过URL定位要删除的资源
    • URL应该指向具体资源
    • 例如:DELETE /api/users/123
  4. 响应状态码

    • 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方法用于获取资源的响应头,不返回响应体

主要特点:

  1. 只返回响应头

    • 不返回响应体
    • 节省带宽
    • 提高性能
  2. 检查资源

    • 检查资源是否存在
    • 获取资源的元信息
    • 检查资源是否更新
  3. 幂等性

    • 幂等操作
    • 可以安全地重复请求
  4. 响应头信息

    • 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"

使用场景:

  1. 检查资源是否存在

    // 检查文件是否存在
    HEAD /files/document.pdf
    // 200 OK → 存在
    // 404 Not Found → 不存在
    
  2. 获取资源大小

    // 获取文件大小
    HEAD /files/video.mp4
    // Content-Length: 10485760 (10MB)
    
  3. 检查资源是否更新

    // 检查缓存是否有效
    HEAD /api/data
    // 比较ETag或Last-Modified
    
  4. 预检查

    // 下载前检查
    HEAD /download/file.zip
    // 获取文件大小和类型
    

HEAD vs GET:

特性HEADGET
响应体
带宽节省消耗
用途检查资源获取资源
幂等性

3.13 OPTIONS方法的作用是什么?

答案:

OPTIONS方法用于获取服务器支持的HTTP方法,主要用于CORS预检请求。

主要特点:

  1. 获取支持的方法

    • 返回服务器支持的HTTP方法
    • Allow响应头列出支持的方法
    • 用于API发现
  2. CORS预检请求

    • 浏览器在跨域请求前发送OPTIONS请求
    • 检查是否允许跨域请求
    • 检查允许的请求方法和头部
  3. 幂等性

    • 幂等操作
    • 可以安全地重复请求
  4. 响应头

    • 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预检流程:

  1. 浏览器发送OPTIONS请求

    OPTIONS /api/users
    Origin: https://example.com
    Access-Control-Request-Method: POST
    
  2. 服务器响应

    Access-Control-Allow-Origin: https://example.com
    Access-Control-Allow-Methods: POST
    
  3. 浏览器发送实际请求

    POST /api/users
    Origin: https://example.com
    

使用场景:

  • CORS跨域请求
  • API方法发现
  • 检查服务器能力

3.14 PATCH方法的作用是什么?

答案:

PATCH方法用于部分更新资源,只更新提供的字段。

主要特点:

  1. 部分更新

    • 只更新提供的字段
    • 不更新未提供的字段
    • 与PUT的完整替换不同
  2. 非幂等性

    • 非幂等操作
    • 多次请求可能产生不同结果
    • 取决于实现方式
  3. 资源定位

    • 通过URL定位资源
    • URL指向具体资源
    • 例如:PATCH /api/users/123
  4. 数据格式

    • 通常使用JSON
    • 只包含要更新的字段
    • 支持多种更新策略

PATCH请求示例:

PATCH /api/users/123 HTTP/1.1
Content-Type: application/json

{
  "email": "newemail@example.com"
}
# 只更新email字段,其他字段不变

PATCH vs PUT:

特性PATCHPUT
更新方式部分更新完整替换
幂等性通常非幂等幂等
数据量只包含更新字段包含完整资源
未提供字段保持不变可能被清空

更新策略:

  1. JSON Merge Patch(RFC 7396)

    {
      "email": "newemail@example.com",
      "age": null  // 删除age字段
    }
    
  2. JSON Patch(RFC 6902)

    [
      {"op": "replace", "path": "/email", "value": "newemail@example.com"},
      {"op": "remove", "path": "/age"}
    ]
    

使用场景:

  • 更新用户信息(只更新部分字段)
  • 更新文章(只更新标题或内容)
  • 部分资源更新

3.15 TRACE方法的作用是什么?

答案:

TRACE方法用于回显请求,用于诊断和调试

主要特点:

  1. 回显请求

    • 服务器返回收到的请求
    • 用于查看请求在传输过程中的变化
    • 诊断代理服务器问题
  2. 诊断工具

    • 检查请求是否被修改
    • 查看代理服务器添加的头部
    • 调试网络问题
  3. 安全风险

    • 可能泄露敏感信息
    • 可能被用于XST(Cross-Site Tracing)攻击
    • 大多数服务器禁用TRACE方法
  4. 幂等性

    • 幂等操作
    • 可以安全地重复请求

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

安全风险:

  1. 信息泄露

    • 可能泄露认证信息
    • 可能泄露内部网络结构
  2. XST攻击

    • Cross-Site Tracing攻击
    • 利用TRACE方法绕过HttpOnly Cookie

最佳实践:

  • 生产环境应该禁用TRACE方法
  • 只用于开发和调试
  • 使用其他诊断工具替代

替代方案:

  • 使用日志查看请求
  • 使用网络抓包工具(Wireshark、Charles)
  • 使用代理服务器日志

3.16 CONNECT方法的作用是什么?

答案:

CONNECT方法用于建立隧道,主要用于HTTPS代理

主要特点:

  1. 建立隧道

    • 在客户端和目标服务器之间建立隧道
    • 代理服务器转发数据
    • 用于HTTPS代理
  2. HTTPS代理

    • 客户端通过代理访问HTTPS网站
    • 代理服务器建立TCP连接
    • 数据加密传输,代理无法解密
  3. 连接建立

    • 客户端发送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连接,传输加密数据

工作流程:

  1. 客户端发送CONNECT请求

    CONNECT www.example.com:443 HTTP/1.1
    
  2. 代理服务器建立TCP连接

    • 代理连接到目标服务器
    • 返回200 Connection Established
  3. 建立TLS连接

    • 客户端和目标服务器建立TLS连接
    • 代理服务器只转发数据,不解密
  4. 数据传输

    • 所有数据加密传输
    • 代理服务器无法查看内容

使用场景:

  • HTTPS代理
  • 企业网络代理
  • 翻墙工具(VPN、代理)

注意事项:

  • 主要用于HTTPS代理
  • 代理服务器需要支持CONNECT方法
  • 需要代理认证时使用Proxy-Authorization头

3.17 什么是HTTP方法的幂等性?

答案:

HTTP方法的幂等性(Idempotency)是指多次执行相同的请求,结果应该相同

幂等性的定义:

  1. 数学定义

    • f(f(x)) = f(x)
    • 多次应用函数结果相同
  2. HTTP定义

    • 多次发送相同的请求
    • 服务器状态应该相同
    • 响应应该相同(除了时间相关字段)
  3. 重要性

    • 网络可能重传请求
    • 客户端可能重复提交
    • 保证数据一致性

幂等方法:

方法幂等性说明
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"}  # 可能创建两个用户

实际应用:

  1. 网络重传

    • 网络可能重传请求
    • 幂等方法可以安全重传
    • 非幂等方法需要去重
  2. 客户端重试

    • 请求失败时可以重试
    • 幂等方法可以安全重试
    • 非幂等方法需要特殊处理
  3. API设计

    • 设计幂等的API
    • 使用幂等方法更新资源
    • 使用唯一标识避免重复

3.18 哪些HTTP方法是幂等的?

答案:

幂等的HTTP方法:

  1. GET

    • ✅ 幂等
    • 获取资源,不改变服务器状态
    • 可以安全地重复请求
  2. HEAD

    • ✅ 幂等
    • 获取响应头,不改变服务器状态
    • 可以安全地重复请求
  3. PUT

    • ✅ 幂等
    • 完整替换资源
    • 多次请求结果相同
  4. DELETE

    • ✅ 幂等
    • 删除资源
    • 删除不存在的资源也是幂等的
  5. OPTIONS

    • ✅ 幂等
    • 获取支持的方法
    • 不改变服务器状态
  6. TRACE

    • ✅ 幂等
    • 回显请求
    • 不改变服务器状态

非幂等的HTTP方法:

  1. POST

    • ❌ 非幂等
    • 可能创建多个资源
    • 每次请求可能产生不同结果
  2. PATCH

    • ❌ 通常非幂等
    • 取决于实现方式
    • 某些实现可能是幂等的

幂等性总结表:

方法幂等性原因
GET只读操作
HEAD只读操作
PUT完整替换
DELETE删除操作
OPTIONS只读操作
TRACE只读操作
POST可能创建多个资源
PATCH部分更新,取决于实现

实际应用:

  • 幂等方法可以安全重试
  • 非幂等方法需要去重机制
  • RESTful API设计应该考虑幂等性

3.19 哪些HTTP方法是安全的?

答案:

安全的HTTP方法(Safe Methods):

  1. GET

    • ✅ 安全
    • 只读操作
    • 不应该有副作用
  2. HEAD

    • ✅ 安全
    • 只读操作
    • 不应该有副作用
  3. OPTIONS

    • ✅ 安全
    • 只读操作
    • 不应该有副作用
  4. TRACE

    • ✅ 安全(理论上)
    • 只读操作
    • 但实际使用有安全风险

不安全的HTTP方法:

  1. POST

    • ❌ 不安全
    • 可能修改服务器状态
    • 可能有副作用
  2. PUT

    • ❌ 不安全
    • 修改服务器状态
    • 创建或更新资源
  3. DELETE

    • ❌ 不安全
    • 修改服务器状态
    • 删除资源
  4. PATCH

    • ❌ 不安全
    • 修改服务器状态
    • 更新资源

安全性定义:

  • 安全方法:不应该修改服务器状态
  • 只读操作:不改变资源
  • 无副作用:不产生额外影响

安全方法总结表:

方法安全性说明
GET只读,无副作用
HEAD只读,无副作用
OPTIONS只读,无副作用
TRACE只读,无副作用(但有安全风险)
POST可能修改状态
PUT修改状态
DELETE修改状态
PATCH修改状态

实际应用:

  • 安全方法可以被缓存
  • 安全方法可以被预取
  • 安全方法不会触发CSRF保护
  • 浏览器对安全方法有特殊处理

3.20 如何设计幂等的API?

答案:

设计幂等API的方法:

  1. 使用幂等的HTTP方法

    # ✅ 使用PUT更新(幂等)
    PUT /api/users/123
    
    # ❌ 使用POST更新(非幂等)
    POST /api/users/123/update
    
  2. 使用唯一标识

    # 客户端提供唯一ID
    PUT /api/users/123
    {
      "id": 123,
      "name": "John"
    }
    
    # 使用唯一业务标识
    PUT /api/orders/ORDER-2024-001
    
  3. 使用条件更新

    # 使用ETag
    PUT /api/users/123
    If-Match: "abc123"
    {
      "name": "John"
    }
    
    # 使用版本号
    PUT /api/users/123
    {
      "id": 123,
      "version": 5,
      "name": "John"
    }
    
  4. 去重机制

    # 使用Idempotency-Key
    POST /api/orders
    Idempotency-Key: uuid-12345
    {
      "product": "Book",
      "quantity": 1
    }
    
  5. 返回相同结果

    // 第一次请求
    PUT /api/users/123
    Response: {"id": 123, "name": "John"}
    
    // 第二次请求(相同数据)
    PUT /api/users/123
    Response: {"id": 123, "name": "John"}  // 相同结果
    

最佳实践:

  1. RESTful设计

    # 创建:POST(非幂等,但可以加去重)
    POST /api/users
    Idempotency-Key: uuid
    
    # 更新:PUT(幂等)
    PUT /api/users/123
    
    # 部分更新:PATCH(可以设计为幂等)
    PATCH /api/users/123
    
  2. 服务器端实现

    // 检查是否已存在
    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);
    }
    
  3. 客户端实现

    // 生成唯一ID
    String idempotencyKey = UUID.randomUUID().toString();
    
    // 请求头中添加
    request.addHeader("Idempotency-Key", idempotencyKey);
    
    // 重试时使用相同的Key
    

设计原则:

  • 使用幂等的HTTP方法
  • 提供唯一标识
  • 实现去重机制
  • 返回相同结果
  • 文档说明幂等性

第四章:HTTP 状态码(20 题)

4.1 HTTP状态码的分类有哪些?

答案:

HTTP状态码分为5类,用第一位数字表示:

  1. 1xx(信息性状态码)

    • 100-199
    • 表示请求已接收,继续处理
    • 临时响应
  2. 2xx(成功状态码)

    • 200-299
    • 表示请求成功处理
    • 最常见的成功响应
  3. 3xx(重定向状态码)

    • 300-399
    • 表示需要进一步操作
    • 资源位置改变
  4. 4xx(客户端错误状态码)

    • 400-499
    • 表示客户端请求错误
    • 请求格式或权限问题
  5. 5xx(服务器错误状态码)

    • 500-599
    • 表示服务器处理错误
    • 服务器内部错误

状态码分类表:

类别范围含义示例
1xx100-199信息性100 Continue
2xx200-299成功200 OK, 201 Created
3xx300-399重定向301 Moved, 304 Not Modified
4xx400-499客户端错误400 Bad Request, 404 Not Found
5xx500-599服务器错误500 Internal Server Error

记忆口诀: 1信2成3重4客5服(信息、成功、重定向、客户端错误、服务器错误)


4.2 1xx状态码表示什么?有哪些?

答案:

1xx状态码表示信息性响应,请求已接收,继续处理。

1xx状态码列表:

  1. 100 Continue(继续)

    • 客户端应该继续请求
    • 用于POST/PUT大文件时的预检查
    • 客户端发送Expect: 100-continue
  2. 101 Switching Protocols(切换协议)

    • 服务器同意切换协议
    • 用于HTTP升级到WebSocket
    • Upgrade响应头
  3. 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状态码:

  1. 200 OK(成功)

    • 请求成功,最常见的状态码
    • GET、POST、PUT等请求的成功响应
  2. 201 Created(已创建)

    • 请求成功,已创建新资源
    • POST创建资源的成功响应
    • Location响应头指向新资源
  3. 202 Accepted(已接受)

    • 请求已接受,但尚未处理
    • 异步处理的操作
    • 最终结果可能成功或失败
  4. 204 No Content(无内容)

    • 请求成功,但无响应体
    • DELETE成功删除
    • PUT成功更新(不需要返回数据)
  5. 205 Reset Content(重置内容)

    • 请求成功,客户端应该重置视图
    • 表单提交后重置表单
  6. 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状态码:

  1. 301 Moved Permanently(永久移动)

    • 资源已永久移动到新位置
    • Location响应头指向新URL
    • 搜索引擎会更新索引
  2. 302 Found(临时移动)

    • 资源临时移动到新位置
    • Location响应头指向新URL
    • 搜索引擎不更新索引
  3. 303 See Other(查看其他位置)

    • 响应位于其他位置
    • 使用GET方法获取资源
    • POST后的重定向
  4. 304 Not Modified(未修改)

    • 资源未修改,使用缓存
    • 协商缓存的有效响应
    • 节省带宽
  5. 307 Temporary Redirect(临时重定向)

    • 临时重定向,保持原请求方法
    • 类似302,但更明确
  6. 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对比:

特性301302
类型永久重定向临时重定向
搜索引擎更新索引不更新索引
浏览器可能缓存重定向不缓存重定向
使用场景域名迁移、URL永久改变临时维护、A/B测试

4.5 4xx状态码表示什么?有哪些?

答案:

4xx状态码表示客户端错误,请求有问题。

常见4xx状态码:

  1. 400 Bad Request(错误请求)

    • 请求语法错误
    • 参数格式错误
    • 请求无法理解
  2. 401 Unauthorized(未授权)

    • 需要认证
    • 未提供认证信息
    • 认证失败
  3. 403 Forbidden(禁止)

    • 服务器理解请求,但拒绝执行
    • 权限不足
    • 已认证但无权限
  4. 404 Not Found(未找到)

    • 请求的资源不存在
    • 最常见的错误状态码
    • URL错误或资源已删除
  5. 405 Method Not Allowed(方法不允许)

    • 请求方法不被允许
    • Allow响应头列出允许的方法
  6. 408 Request Timeout(请求超时)

    • 请求超时
    • 客户端发送请求时间过长
  7. 409 Conflict(冲突)

    • 请求与当前状态冲突
    • 资源冲突
    • 版本冲突
  8. 413 Payload Too Large(负载过大)

    • 请求体过大
    • 超过服务器限制
  9. 414 URI Too Long(URI过长)

    • URI过长
    • 超过服务器限制
  10. 415 Unsupported Media Type(不支持的媒体类型)

    • Content-Type不支持
    • 服务器不支持请求的格式
  11. 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对比:

特性401403
含义未认证已认证但无权限
原因缺少认证信息权限不足
解决方案提供认证信息提升权限
响应头WWW-Authenticate

4.6 5xx状态码表示什么?有哪些?

答案:

5xx状态码表示服务器错误,服务器处理请求时出错。

常见5xx状态码:

  1. 500 Internal Server Error(内部服务器错误)

    • 服务器内部错误
    • 最常见的服务器错误
    • 代码异常、配置错误
  2. 501 Not Implemented(未实现)

    • 服务器不支持请求的功能
    • 请求方法不支持
  3. 502 Bad Gateway(错误网关)

    • 网关或代理服务器错误
    • 上游服务器无效响应
  4. 503 Service Unavailable(服务不可用)

    • 服务器暂时不可用
    • 过载或维护
    • Retry-After响应头
  5. 504 Gateway Timeout(网关超时)

    • 网关或代理服务器超时
    • 上游服务器响应超时
  6. 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重定向的主要区别:

  1. 永久性

    • 301:永久重定向
    • 302:临时重定向
  2. 搜索引擎处理

    • 301:搜索引擎会更新索引,将权重传递给新URL
    • 302:搜索引擎不更新索引,不传递权重
  3. 浏览器缓存

    • 301:浏览器可能缓存重定向
    • 302:浏览器不缓存重定向
  4. 书签

    • 301:浏览器可能更新书签
    • 302:浏览器不更新书签
  5. 使用场景

    • 301:域名迁移、URL永久改变
    • 302:临时维护、A/B测试

对比表:

特性301302
重定向类型永久临时
搜索引擎索引更新不更新
权重传递
浏览器缓存可能缓存不缓存
书签更新可能更新不更新
使用场景永久迁移临时跳转

示例对比:

# 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的主要区别:

  1. 认证状态

    • 401:未认证(需要登录)
    • 403:已认证但无权限(权限不足)
  2. 原因

    • 401:缺少认证信息或认证失败
    • 403:权限不足,资源被禁止访问
  3. 响应头

    • 401:WWW-Authenticate响应头指示认证方式
    • 403:无特殊响应头
  4. 解决方案

    • 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的区别:

  1. 502 Bad Gateway(错误网关)

    • 网关或代理服务器错误
    • 上游服务器返回无效响应
    • 网关无法获取有效响应
  2. 503 Service Unavailable(服务不可用)

    • 服务器暂时不可用
    • 过载或维护
    • 服务器主动返回
  3. 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)用于向服务器传递请求的元信息和客户端信息

请求头的作用:

  1. 传递请求信息

    • 请求方法、路径、协议版本
    • 客户端信息
    • 请求参数
  2. 控制请求行为

    • 控制缓存
    • 控制压缩
    • 控制连接
  3. 认证和授权

    • 传递认证信息
    • 传递授权令牌
    • 传递API密钥
  4. 内容协商

    • 指定接受的内容类型
    • 指定接受的编码
    • 指定接受的语言

请求头格式:

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

常见请求头分类:

  1. 通用请求头

    • Connection:连接控制
    • Cache-Control:缓存控制
    • Pragma:兼容性
  2. 请求头

    • 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的作用:

  1. 标识客户端

    • 浏览器类型和版本
    • 操作系统信息
    • 设备信息
  2. 服务器适配

    • 根据客户端返回不同内容
    • 移动端和桌面端适配
    • 浏览器兼容性处理
  3. 统计分析

    • 统计浏览器使用情况
    • 统计设备类型
    • 分析用户行为
  4. 安全防护

    • 识别爬虫和机器人
    • 防止恶意请求
    • 访问控制

User-Agent格式:

User-Agent: 产品名/版本号 (系统信息; 其他信息)

常见User-Agent示例:

  1. Chrome浏览器

    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
    
  2. Firefox浏览器

    Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
    
  3. 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
    
  4. 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
    
  5. 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的作用:

  1. 内容协商

    • 告诉服务器客户端接受的内容类型
    • 服务器根据Accept返回相应格式
    • 支持多种格式和优先级
  2. MIME类型

    • 指定接受的媒体类型
    • 可以指定多个类型
    • 使用q值指定优先级
  3. 优先级

    • q值范围:0.0-1.0
    • 默认q值为1.0
    • q值越大,优先级越高

Accept格式:

Accept: type/subtype;q=priority, type/subtype;q=priority

Accept示例:

  1. 接受JSON

    Accept: application/json
    
  2. 接受JSON和XML(JSON优先)

    Accept: application/json, application/xml
    
  3. 指定优先级

    Accept: application/json;q=0.9, application/xml;q=0.8, text/html;q=0.5
    
  4. 接受所有类型

    Accept: */*
    
  5. 接受特定子类型

    Accept: application/json, application/*, */*
    

常见MIME类型:

MIME类型说明示例
application/jsonJSON数据{"key": "value"}
application/xmlXML数据...
application/x-www-form-urlencoded表单数据key=value&key2=value2
text/htmlHTML文档...
text/plain纯文本Plain text
text/cssCSS样式body { color: red; }
image/jpegJPEG图片图片数据
image/pngPNG图片图片数据
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的作用:

  1. 语言协商

    • 告诉服务器客户端接受的语言
    • 服务器根据Accept-Language返回相应语言
    • 支持多种语言和优先级
  2. 国际化

    • 多语言网站
    • 本地化内容
    • 语言切换
  3. 优先级

    • 使用q值指定优先级
    • q值范围:0.0-1.0
    • q值越大,优先级越高

Accept-Language格式:

Accept-Language: language;q=priority, language;q=priority

Accept-Language示例:

  1. 接受中文

    Accept-Language: zh-CN
    
  2. 接受多种语言(中文优先)

    Accept-Language: zh-CN, zh, en-US, en
    
  3. 指定优先级

    Accept-Language: zh-CN;q=0.9, zh;q=0.8, en-US;q=0.5, en;q=0.3
    
  4. 接受所有语言

    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的作用:

  1. 内容压缩

    • 告诉服务器客户端支持的编码方式
    • 服务器可以对响应进行压缩
    • 减少数据传输量
  2. 编码方式

    • gzip:最常用
    • deflate:较少使用
    • br:Brotli,较新
    • identity:不压缩
  3. 优先级

    • 使用q值指定优先级
    • 默认q值为1.0
    • 可以指定不接受某种编码(q=0)

Accept-Encoding格式:

Accept-Encoding: encoding;q=priority, encoding;q=priority

Accept-Encoding示例:

  1. 接受gzip

    Accept-Encoding: gzip
    
  2. 接受多种编码(gzip优先)

    Accept-Encoding: gzip, deflate, br
    
  3. 指定优先级

    Accept-Encoding: gzip;q=0.9, deflate;q=0.8, br;q=0.7
    
  4. 不接受某种编码

    Accept-Encoding: gzip, deflate;q=0
    
  5. 不压缩

    Accept-Encoding: identity
    

常见编码方式:

编码方式说明压缩率性能
gzip最常用
deflate较少使用
brBrotli,较新很高
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的作用:

  1. 指定内容类型

    • 请求体:指定发送的数据格式
    • 响应体:指定返回的数据格式
    • 帮助接收方正确解析数据
  2. 字符编码

    • 可以指定字符编码
    • 格式:type/subtype; charset=encoding
    • 例如:text/html; charset=utf-8
  3. 边界(Multipart)

    • multipart/form-data需要指定边界
    • 格式:multipart/form-data; boundary=boundary

Content-Type格式:

Content-Type: type/subtype; charset=encoding; boundary=boundary

Content-Type示例:

  1. JSON数据

    Content-Type: application/json
    
  2. JSON数据(UTF-8编码)

    Content-Type: application/json; charset=utf-8
    
  3. 表单数据

    Content-Type: application/x-www-form-urlencoded
    
  4. 文件上传

    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
    
  5. HTML文档

    Content-Type: text/html; charset=utf-8
    
  6. 纯文本

    Content-Type: text/plain; charset=utf-8
    

常见Content-Type:

Content-Type用途示例
application/jsonJSON数据{"key": "value"}
application/x-www-form-urlencoded表单数据key=value&key2=value2
multipart/form-data文件上传多部分数据
text/htmlHTML文档...
text/plain纯文本Plain text
text/xmlXML数据...
image/jpegJPEG图片图片数据
image/pngPNG图片图片数据

Android代码示例:

  1. 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();
    
  2. 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();
    
  3. 文件上传

    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的作用:

  1. 指定内容长度

    • 请求体:指定发送数据的字节数
    • 响应体:指定返回数据的字节数
    • 帮助接收方知道数据大小
  2. 数据传输控制

    • 接收方知道何时接收完成
    • 可以显示传输进度
    • 可以验证数据完整性
  3. 必需性

    • 有请求体时必须设置
    • 响应体通常也应该设置
    • 分块传输(Chunked)时不需要

Content-Length格式:

Content-Length: 1234

Content-Length示例:

  1. 请求体长度

    POST /api/users HTTP/1.1
    Content-Type: application/json
    Content-Length: 45
    
    {"name":"John","email":"john@example.com"}
    
  2. 响应体长度

    HTTP/1.1 200 OK
    Content-Type: application/json
    Content-Length: 123
    
    {"id":123,"name":"John"}
    

Android代码示例:

  1. 发送数据(需要设置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();
    
  2. 接收数据(读取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的作用:

  1. 会话管理

    • 存储用户登录状态
    • 存储会话ID
    • 实现有状态的HTTP通信
  2. 个性化设置

    • 存储用户偏好
    • 存储语言设置
    • 存储主题设置
  3. 购物车

    • 存储购物车内容
    • 存储用户行为
    • 跟踪用户
  4. 跟踪和分析

    • 跟踪用户行为
    • 统计分析
    • 广告投放

Cookie工作原理:

  1. 服务器设置Cookie

    HTTP/1.1 200 OK
    Set-Cookie: sessionid=abc123; Path=/; HttpOnly
    Set-Cookie: theme=dark; Path=/; Max-Age=3600
    
  2. 客户端保存Cookie

    • 浏览器保存Cookie
    • 根据域名和路径保存
    • 下次请求自动携带
  3. 客户端发送Cookie

    GET /api/profile HTTP/1.1
    Cookie: sessionid=abc123; theme=dark
    

Cookie属性:

  1. Name和Value

    • Cookie的名称和值
    • 格式:name=value
  2. Domain(域)

    • Cookie的域名
    • 格式:Domain=.example.com
    • 子域名可以访问
  3. Path(路径)

    • Cookie的路径
    • 格式:Path=/api
    • 只有该路径及其子路径可以访问
  4. Expires和Max-Age(过期时间)

    • Expires:绝对时间
    • Max-Age:相对时间(秒)
    • 过期后Cookie被删除
  5. Secure(安全)

    • 只在HTTPS连接中发送
    • 格式:Secure
  6. HttpOnly(HTTP Only)

    • JavaScript无法访问
    • 防止XSS攻击
    • 格式:HttpOnly
  7. 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的作用:

  1. 来源跟踪

    • 标识请求来自哪个页面
    • 用于统计分析
    • 用于防盗链
  2. 统计分析

    • 分析流量来源
    • 分析用户行为
    • 分析转化率
  3. 安全防护

    • CSRF防护
    • 检查请求来源
    • 防止跨站请求
  4. 防盗链

    • 检查图片请求来源
    • 只允许特定域名访问
    • 保护资源

Referer格式:

Referer: https://www.example.com/page

Referer示例:

  1. 基本Referer

    GET /api/data HTTP/1.1
    Referer: https://www.example.com/page
    
  2. 空Referer

    GET /api/data HTTP/1.1
    # 无Referer头(直接访问、隐私模式等)
    
  3. 不同协议的Referer

    # HTTPS页面请求HTTP资源(无Referer)
    GET http://example.com/image.jpg HTTP/1.1
    # Referer被移除(安全原因)
    

Referer策略(Referrer Policy):

  1. no-referrer

    • 不发送Referer
  2. no-referrer-when-downgrade(默认)

    • HTTPS→HTTP不发送
    • 其他情况发送
  3. origin

    • 只发送源(协议+域名)
  4. origin-when-cross-origin

    • 同源发送完整URL
    • 跨域只发送源
  5. same-origin

    • 同源发送
    • 跨域不发送
  6. strict-origin

    • HTTPS→HTTPS发送源
    • 其他情况不发送
  7. strict-origin-when-cross-origin

    • 同源发送完整URL
    • 跨域HTTPS→HTTPS发送源
    • 其他情况不发送
  8. 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的作用:

  1. 身份认证

    • 传递认证凭证
    • 验证用户身份
    • 访问受保护资源
  2. 认证方式

    • Bearer Token(JWT)
    • Basic认证
    • Digest认证
    • API Key
  3. 使用场景

    • API访问
    • 受保护资源
    • 用户认证

Authorization格式:

Authorization: <认证方式> <凭证>

常见认证方式:

  1. Bearer Token(最常用)

    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    
  2. Basic认证

    Authorization: Basic base64(username:password)
    # 例如:Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
    
  3. Digest认证

    Authorization: Digest username="user", realm="api", nonce="...", response="..."
    
  4. 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代码示例:

  1. Bearer Token

    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    
    String token = getAuthToken();
    conn.setRequestProperty("Authorization", "Bearer " + token);
    
  2. 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);
    
  3. 使用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();
    

认证流程:

  1. 客户端请求(未认证)

    GET /api/data HTTP/1.1
    
  2. 服务器响应401

    HTTP/1.1 401 Unauthorized
    WWW-Authenticate: Bearer realm="api"
    
  3. 客户端请求(已认证)

    GET /api/data HTTP/1.1
    Authorization: Bearer token123
    
  4. 服务器响应200

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

使用场景:

  • RESTful API认证
  • 用户登录验证
  • 受保护资源访问
  • OAuth2认证

安全建议:

  • 使用HTTPS传输
  • Token定期刷新
  • Token存储在安全位置
  • 敏感信息不要放在Authorization头

5.11 Host的作用是什么?

答案:

Host用于指定目标服务器的主机名和端口号

Host的作用:

  1. 虚拟主机

    • 一个IP可以对应多个域名
    • 服务器根据Host头区分虚拟主机
    • HTTP/1.1必须包含Host头
  2. 主机标识

    • 指定目标服务器
    • 指定端口号
    • 区分不同的服务
  3. 必需性

    • HTTP/1.1必须包含
    • HTTP/1.0不需要
    • 缺少Host头返回400错误

Host格式:

Host: hostname:port

Host示例:

  1. 基本Host

    GET /api/users HTTP/1.1
    Host: api.example.com
    
  2. 指定端口

    GET /api/users HTTP/1.1
    Host: api.example.com:8080
    
  3. 默认端口

    # HTTP默认80端口
    GET /api/users HTTP/1.1
    Host: api.example.com
    
    # HTTPS默认443端口
    GET /api/users HTTP/1.1
    Host: api.example.com
    

虚拟主机示例:

  1. 一个IP多个域名

    IP: 192.168.1.100
    Domain1: www.example.com
    Domain2: api.example.com
    Domain3: blog.example.com
    
  2. 服务器根据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的作用:

  1. 连接管理

    • 控制连接是否保持
    • 指定连接关闭方式
    • 控制连接复用
  2. 常用值

    • keep-alive:保持连接
    • close:关闭连接
    • Upgrade:升级协议
  3. HTTP版本差异

    • HTTP/1.0:默认close,需要显式设置keep-alive
    • HTTP/1.1:默认keep-alive,可以显式设置close

Connection格式:

Connection: keep-alive | close | Upgrade | 其他值

Connection示例:

  1. 保持连接(HTTP/1.1默认)

    GET /api/users HTTP/1.1
    Connection: keep-alive
    
  2. 关闭连接

    GET /api/users HTTP/1.1
    Connection: close
    
  3. 协议升级(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)用于向客户端传递响应的元信息和服务器信息

响应头的作用:

  1. 传递响应信息

    • 状态码、状态描述
    • 服务器信息
    • 响应元信息
  2. 控制响应行为

    • 控制缓存
    • 控制压缩
    • 控制连接
  3. 内容协商

    • 指定内容类型
    • 指定内容编码
    • 指定内容长度
  4. 安全控制

    • 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

常见响应头分类:

  1. 通用响应头

    • Date:响应时间
    • Connection:连接控制
    • Cache-Control:缓存控制
  2. 响应头

    • Server:服务器信息
    • Content-Type:内容类型
    • Content-Length:内容长度
    • Content-Encoding:内容编码
    • Location:重定向位置
    • Set-Cookie:设置Cookie
    • ETag:资源标识
    • Last-Modified:最后修改时间
  3. 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的作用:

  1. 服务器标识

    • 标识服务器软件
    • 显示服务器版本
    • 用于诊断
  2. 安全考虑

    • 可能泄露服务器信息
    • 建议隐藏或简化
    • 防止攻击者利用版本漏洞
  3. 调试和诊断

    • 帮助调试问题
    • 识别服务器类型
    • 版本兼容性检查

Server格式:

Server: software/version (additional info)

Server示例:

  1. Apache服务器

    Server: Apache/2.4.41 (Unix)
    
  2. Nginx服务器

    Server: nginx/1.18.0
    
  3. Tomcat服务器

    Server: Apache-Coyote/1.1
    
  4. 隐藏Server信息

    Server: WebServer
    # 或
    # 不返回Server头
    

安全建议:

  1. 隐藏版本信息

    # ❌ 不推荐
    Server: Apache/2.4.41 (Unix) OpenSSL/1.1.1
    
    # ✅ 推荐
    Server: WebServer
    # 或完全不返回Server头
    
  2. 配置示例(Nginx)

    # 隐藏Server头
    server_tokens off;
    # 或自定义
    more_set_headers "Server: WebServer";
    
  3. 配置示例(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的作用:

  1. 时间戳

    • 响应生成的时间
    • HTTP时间格式
    • GMT时间
  2. 缓存计算

    • 用于计算缓存过期时间
    • 与Expires配合使用
    • 与Cache-Control配合使用
  3. 调试和日志

    • 记录响应时间
    • 分析性能
    • 时间同步检查

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的作用:

  1. 重定向

    • 3xx重定向响应
    • 指向新的URL
    • 客户端应该访问新URL
  2. 资源创建

    • 201 Created响应
    • 指向新创建的资源
    • 客户端可以访问新资源
  3. URL格式

    • 绝对URL:https://www.example.com/new-page
    • 相对URL:/new-page(相对于当前请求)

Location格式:

Location: absolute-URL | relative-URL

Location示例:

  1. 301永久重定向

    GET /old-page HTTP/1.1
    
    HTTP/1.1 301 Moved Permanently
    Location: https://www.example.com/new-page
    
  2. 302临时重定向

    GET /page HTTP/1.1
    
    HTTP/1.1 302 Found
    Location: /temp-page
    
  3. 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的作用:

  1. 设置Cookie

    • 服务器设置Cookie
    • 客户端保存Cookie
    • 下次请求自动携带
  2. 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示例:

  1. 基本Cookie

    HTTP/1.1 200 OK
    Set-Cookie: sessionid=abc123
    
  2. 带属性的Cookie

    HTTP/1.1 200 OK
    Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure; SameSite=Strict
    
  3. 多个Cookie

    HTTP/1.1 200 OK
    Set-Cookie: sessionid=abc123; Path=/; HttpOnly
    Set-Cookie: theme=dark; Path=/; Max-Age=3600
    

Cookie属性说明:

  1. Domain(域)

    Set-Cookie: sessionid=abc123; Domain=.example.com
    
    • 指定Cookie的域名
    • .example.com表示所有子域名
  2. Path(路径)

    Set-Cookie: sessionid=abc123; Path=/api
    
    • 指定Cookie的路径
    • 只有该路径及其子路径可以访问
  3. Expires(过期时间)

    Set-Cookie: sessionid=abc123; Expires=Wed, 21 Oct 2024 10:00:00 GMT
    
    • 绝对过期时间
    • GMT时间格式
  4. Max-Age(最大存活时间)

    Set-Cookie: sessionid=abc123; Max-Age=3600
    
    • 相对过期时间(秒)
    • 3600秒 = 1小时
  5. Secure(安全)

    Set-Cookie: sessionid=abc123; Secure
    
    • 只在HTTPS连接中发送
    • 保护Cookie传输安全
  6. HttpOnly(HTTP Only)

    Set-Cookie: sessionid=abc123; HttpOnly
    
    • JavaScript无法访问
    • 防止XSS攻击
  7. 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的作用:

  1. 缓存控制

    • 控制是否缓存
    • 控制缓存时间
    • 控制缓存行为
  2. 请求和响应

    • 请求头:客户端控制缓存
    • 响应头:服务器控制缓存
    • 指令可以组合使用
  3. 优先级

    • 比Expires优先级高
    • HTTP/1.1的缓存机制
    • 更灵活和精确

Cache-Control格式:

Cache-Control: directive, directive, ...

Cache-Control指令:

  1. 可缓存性

    • public:可以被任何缓存缓存
    • private:只能被私有缓存缓存
    • no-cache:使用前必须验证
    • no-store:不缓存
  2. 过期时间

    • max-age=seconds:最大存活时间(秒)
    • s-maxage=seconds:共享缓存的最大存活时间
    • max-stale=seconds:允许使用过期缓存的时间
  3. 重新验证

    • must-revalidate:过期后必须重新验证
    • proxy-revalidate:共享缓存必须重新验证
  4. 转换

    • no-transform:不转换内容

Cache-Control示例:

  1. 不缓存

    Cache-Control: no-store, no-cache, must-revalidate
    
  2. 缓存1小时

    Cache-Control: public, max-age=3600
    
  3. 私有缓存1小时

    Cache-Control: private, max-age=3600
    
  4. 使用前验证

    Cache-Control: no-cache, must-revalidate
    
  5. 组合使用

    Cache-Control: public, max-age=3600, must-revalidate
    

Cache-Control vs Expires:

特性Cache-ControlExpires
HTTP版本HTTP/1.1HTTP/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的特点:

  1. 过期时间

    • 绝对时间(GMT时间)
    • 指定缓存何时过期
    • HTTP/1.0的缓存机制
  2. 优先级

    • 比Cache-Control优先级低
    • 如果同时存在,Cache-Control优先
    • 主要用于兼容HTTP/1.0
  3. 时间格式

    • HTTP时间格式
    • GMT时间
    • 例如:Wed, 21 Oct 2024 10:00:00 GMT

Expires格式:

Expires: Wed, 21 Oct 2024 10:00:00 GMT

Expires示例:

  1. 基本Expires

    HTTP/1.1 200 OK
    Expires: Wed, 21 Oct 2024 11:00:00 GMT
    
  2. 与Cache-Control配合

    HTTP/1.1 200 OK
    Cache-Control: max-age=3600
    Expires: Wed, 21 Oct 2024 11:00:00 GMT
    
  3. 不缓存

    HTTP/1.1 200 OK
    Expires: Wed, 21 Oct 1997 00:00:00 GMT
    # 或
    Expires: -1
    

Expires vs Cache-Control:

特性ExpiresCache-Control
HTTP版本HTTP/1.0HTTP/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的作用:

  1. 最后修改时间

    • 资源的最后修改时间
    • HTTP时间格式
    • GMT时间
  2. 协商缓存

    • 与If-Modified-Since配合
    • 客户端发送If-Modified-Since
    • 服务器比较返回304或200
  3. 缓存验证

    • 检查资源是否修改
    • 未修改返回304(使用缓存)
    • 已修改返回200(返回新数据)

Last-Modified格式:

Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT

Last-Modified示例:

  1. 基本Last-Modified

    HTTP/1.1 200 OK
    Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT
    Content-Type: application/json
    
    {"data": "..."}
    
  2. 协商缓存(未修改)

    # 客户端请求
    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
    # 无响应体,使用缓存
    
  3. 协商缓存(已修改)

    # 客户端请求
    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-ModifiedETag
精度秒级任意
格式时间字符串
问题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的作用:

  1. 资源标识

    • 资源的唯一标识
    • 资源改变时ETag改变
    • 用于缓存验证
  2. 协商缓存

    • 与If-None-Match配合
    • 客户端发送If-None-Match
    • 服务器比较返回304或200
  3. 缓存验证

    • 检查资源是否修改
    • 未修改返回304(使用缓存)
    • 已修改返回200(返回新数据)

ETag格式:

ETag: "abc123"
ETag: W/"abc123"  # 弱ETag

ETag类型:

  1. 强ETag

    ETag: "abc123"
    
    • 资源完全匹配
    • 任何改变都会改变ETag
  2. 弱ETag

    ETag: W/"abc123"
    
    • 资源语义相同
    • 某些改变不改变ETag

ETag示例:

  1. 基本ETag

    HTTP/1.1 200 OK
    ETag: "abc123"
    Content-Type: application/json
    
    {"data": "..."}
    
  2. 协商缓存(未修改)

    # 客户端请求
    GET /api/data HTTP/1.1
    If-None-Match: "abc123"
    
    # 服务器响应(未修改)
    HTTP/1.1 304 Not Modified
    ETag: "abc123"
    # 无响应体,使用缓存
    
  3. 协商缓存(已修改)

    # 客户端请求
    GET /api/data HTTP/1.1
    If-None-Match: "abc123"
    
    # 服务器响应(已修改)
    HTTP/1.1 200 OK
    ETag: "def456"
    Content-Type: application/json
    
    {"data": "updated"}
    

ETag生成方式:

  1. 内容哈希

    // 使用MD5
    MessageDigest md = MessageDigest.getInstance("MD5");
    byte[] hash = md.digest(content.getBytes());
    String etag = "\"" + toHexString(hash) + "\"";
    
  2. 版本号

    // 使用版本号
    String etag = "\"" + version + "\"";
    
  3. 修改时间+大小

    // 使用修改时间和大小
    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-ModifiedETag
精度秒级任意
格式时间字符串
问题1秒内多次修改
计算开销可能高
灵活性

使用场景:

  • 静态资源缓存
  • API数据缓存
  • 文件缓存验证
  • 精确的缓存控制

最佳实践:

  • 优先使用ETag(更精确)
  • Last-Modified用于兼容性
  • 两者可以同时使用
  • ETag计算开销要考虑

第六章:HTTP 缓存机制(11 题)

6.1 HTTP缓存的作用是什么?

答案:

HTTP缓存用于减少网络请求,提高性能,节省带宽

HTTP缓存的作用:

  1. 提高性能

    • 减少网络请求次数
    • 减少服务器负载
    • 提高响应速度
  2. 节省带宽

    • 减少数据传输量
    • 节省网络流量
    • 降低服务器成本
  3. 改善用户体验

    • 页面加载更快
    • 减少等待时间
    • 离线可用(部分资源)
  4. 减轻服务器压力

    • 减少服务器请求
    • 降低服务器负载
    • 提高服务器性能

缓存位置:

  1. 浏览器缓存

    • 客户端缓存
    • 本地存储
    • 内存缓存和磁盘缓存
  2. 代理缓存

    • CDN缓存
    • 反向代理缓存
    • 共享缓存
  3. 服务器缓存

    • 服务器端缓存
    • 应用缓存
    • 数据库缓存

缓存流程:

客户端请求
  ↓
检查浏览器缓存
  ↓
缓存存在?
  ├─ 是 → 检查缓存是否有效
  │     ├─ 有效 → 使用缓存(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缓存分为强缓存和协商缓存两种类型。

缓存分类:

  1. 强缓存(Strong Cache)

    • 也称为本地缓存
    • 不发送请求到服务器
    • 直接使用缓存
    • 响应头:Cache-Control、Expires
  2. 协商缓存(Negotiation Cache)

    • 也称为对比缓存
    • 需要发送请求到服务器验证
    • 未修改返回304,修改返回200
    • 响应头:Last-Modified、ETag
    • 请求头:If-Modified-Since、If-None-Match

缓存类型对比:

类型特点响应头请求头是否请求服务器
强缓存直接使用缓存Cache-Control、Expires
协商缓存验证后使用缓存Last-Modified、ETagIf-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 强缓存和协商缓存的区别是什么?

答案:

强缓存和协商缓存的主要区别:

  1. 是否需要请求服务器

    • 强缓存:不需要请求服务器,直接使用缓存
    • 协商缓存:需要请求服务器验证,返回304或200
  2. 响应头

    • 强缓存:Cache-Control、Expires
    • 协商缓存:Last-Modified、ETag
  3. 请求头

    • 强缓存:不需要特殊请求头
    • 协商缓存:If-Modified-Since、If-None-Match
  4. 响应状态码

    • 强缓存:不发送请求,无状态码
    • 协商缓存:304 Not Modified或200 OK
  5. 性能

    • 强缓存:性能最好(不请求服务器)
    • 协商缓存:需要请求但无响应体(节省带宽)

对比表:

特性强缓存协商缓存
是否需要请求
响应头Cache-Control、ExpiresLast-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 强缓存的工作原理是什么?

答案:

强缓存的工作原理是浏览器检查缓存,如果缓存有效,直接使用缓存,不请求服务器

强缓存工作流程:

  1. 第一次请求

    客户端 → 服务器请求资源
    服务器 → 返回资源 + Cache-Control/Expires
    客户端 → 保存资源到缓存
    
  2. 后续请求

    客户端 → 检查缓存
    缓存有效?
      ├─ 是 → 直接使用缓存(不请求服务器)
      └─ 否 → 请求服务器
    

强缓存判断依据:

  1. Cache-Control(HTTP/1.1)

    • max-age=seconds:最大存活时间(秒)
    • 优先级高于Expires
    • 例如:Cache-Control: max-age=3600(1小时)
  2. 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指令:

  1. 可缓存性

    • public:可以被任何缓存缓存
    • private:只能被私有缓存缓存
    • no-cache:使用前必须验证(协商缓存)
    • no-store:不缓存
  2. 过期时间

    • max-age=seconds:最大存活时间(秒)

使用场景:

  • 静态资源(CSS、JS、图片)
  • 长期不变的内容
  • 公共资源

注意事项:

  • 强缓存可能使用过期内容
  • 需要更新时更改URL或文件名
  • 结合版本号或哈希值

6.5 协商缓存的工作原理是什么?

答案:

协商缓存的工作原理是客户端发送验证请求,服务器检查资源是否修改,未修改返回304使用缓存,已修改返回200返回新数据

协商缓存工作流程:

  1. 第一次请求

    客户端 → 服务器请求资源
    服务器 → 返回资源 + Last-Modified/ETag
    客户端 → 保存资源和标识
    
  2. 后续请求

    客户端 → 发送验证请求(If-Modified-Since/If-None-Match)
    服务器 → 检查资源是否修改
      ├─ 未修改 → 304 Not Modified(无响应体)
      └─ 已修改 → 200 OK(返回新数据)
    

协商缓存判断依据:

  1. Last-Modified / If-Modified-Since

    • 服务器返回Last-Modified
    • 客户端发送If-Modified-Since
    • 服务器比较时间
  2. 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-ModifiedETag
精度秒级任意
格式时间字符串
问题1秒内多次修改
计算开销可能高

使用场景:

  • 动态内容(API数据)
  • 需要实时性的内容
  • 频繁更新的内容

优势:

  • 准确(服务器验证)
  • 节省带宽(304时无响应体)
  • 实时性好(总是验证)

6.6 Cache-Control的常用值有哪些?

答案:

Cache-Control的常用值:

  1. 可缓存性

    • public:可以被任何缓存缓存
    • private:只能被私有缓存缓存
    • no-cache:使用前必须验证
    • no-store:不缓存
  2. 过期时间

    • max-age=seconds:最大存活时间(秒)
    • s-maxage=seconds:共享缓存的最大存活时间
    • max-stale=seconds:允许使用过期缓存的时间
  3. 重新验证

    • must-revalidate:过期后必须重新验证
    • proxy-revalidate:共享缓存必须重新验证
  4. 转换

    • no-transform:不转换内容

常用组合:

  1. 不缓存

    Cache-Control: no-store, no-cache, must-revalidate
    
  2. 缓存1小时

    Cache-Control: public, max-age=3600
    
  3. 私有缓存1小时

    Cache-Control: private, max-age=3600
    
  4. 使用前验证

    Cache-Control: no-cache, must-revalidate
    
  5. 静态资源(长期缓存)

    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的区别:

  1. HTTP版本

    • Cache-Control:HTTP/1.1
    • Expires:HTTP/1.0
  2. 时间格式

    • Cache-Control:相对时间(秒)
    • Expires:绝对时间(GMT时间)
  3. 优先级

    • Cache-Control:优先级高
    • Expires:优先级低
  4. 灵活性

    • 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 如何设置强缓存?

答案:

设置强缓存的方法:

  1. 使用Cache-Control(推荐)

    HTTP/1.1 200 OK
    Cache-Control: public, max-age=3600
    
  2. 使用Expires(兼容)

    HTTP/1.1 200 OK
    Expires: Wed, 21 Oct 2024 11:00:00 GMT
    
  3. 两者同时使用

    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的作用:

  1. 协商缓存验证

    • 客户端在请求头中发送
    • 值是上次响应中的Last-Modified
    • 服务器比较时间判断是否修改
  2. 工作流程

    第一次请求 → 服务器返回Last-Modified
    客户端保存Last-Modified
    第二次请求 → 客户端发送If-Modified-Since
    服务器比较时间 → 304200
    

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的作用:

  1. 协商缓存验证

    • 客户端在请求头中发送
    • 值是上次响应中的ETag
    • 服务器比较ETag判断是否修改
  2. 工作流程

    第一次请求 → 服务器返回ETag
    客户端保存ETag
    第二次请求 → 客户端发送If-None-Match
    服务器比较ETag → 304200
    

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-MatchIf-Modified-Since
基于ETagLast-Modified
精度任意秒级
格式字符串时间
优先级高(如果同时存在)

最佳实践:

  • 优先使用ETag(更精确)
  • Last-Modified用于兼容性
  • 两者可以同时使用
  • 服务器优先检查If-None-Match

6.11 Last-Modified和ETag的区别是什么?

答案:

Last-Modified和ETag的主要区别:

  1. 精度

    • Last-Modified:秒级
    • ETag:任意精度
  2. 格式

    • Last-Modified:时间(HTTP时间格式)
    • ETag:字符串
  3. 问题

    • Last-Modified:1秒内多次修改无法检测
    • ETag:无此问题
  4. 计算开销

    • Last-Modified:低(文件修改时间)
    • ETag:可能高(需要计算哈希)
  5. 灵活性

    • 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-ModifiedETag
精度秒级任意
格式时间字符串
问题1秒内多次修改
计算开销可能高
灵活性
优先级