🌐 Web 开发基础:HTTP 与 REST
目录
- [1. 引言](#1. 引言)
- [2. 知识要点](#2. 知识要点)
- [3. HTTP 协议基础](#3. HTTP 协议基础)
- [4. RESTful API 设计原则](#4. RESTful API 设计原则)
- [5. 用 Flask 写 REST 接口](#5. 用 Flask 写 REST 接口)
- [6. 完整代码示例](#6. 完整代码示例)
- [7. 逐行讲解](#7. 逐行讲解)
- [8. 运行与验证](#8. 运行与验证)
- [9. AtomCode 实战](#9. AtomCode 实战)
- [10. 常见错误与排查](#10. 常见错误与排查)
- [11. 小结与下一篇预告](#11. 小结与下一篇预告)
1. 引言
为什么学这个?
Web 开发的核心就是客户端(浏览器)与服务器之间的通信。这种通信遵循一个协议------HTTP(HyperText Transfer Protocol,超文本传输协议)。
无论你用什么框架(Flask、Django、Spring Boot、Express),最终都是:
- 客户端发一个 HTTP 请求(Request)
- 服务器回一个 HTTP 响应(Response)
不理解 HTTP 和 REST,你就无法真正理解 Web 开发。
解决了什么问题?
- ❌ 不知道 GET 和 POST 的区别 → ✅ 彻底搞懂
- ❌ 看到 404、500 报错就慌 → ✅ 知道每个状态码的含义
- ❌ 写的 API 接口不规范 → ✅ 掌握 RESTful 设计规范
- ❌ 不会用 Flask 写 API → ✅ 手写一个 REST API
2. 知识要点
| 编号 | 知识点 | 说明 |
|---|---|---|
| 1 | HTTP 请求方法 | GET / POST / PUT / DELETE |
| 2 | HTTP 状态码 | 200 / 201 / 400 / 404 / 500 |
| 3 | HTTP 请求/响应结构 | 请求行、头部、体 |
| 4 | RESTful 设计原则 | 资源、URL、动词 |
| 5 | Flask 路由 | @app.route() 装饰器 |
| 6 | Flask JSON 响应 | jsonify() |
| 7 | curl 调试 | 命令行测试 API |
3. HTTP 协议基础
3.1 一次 HTTP 请求的完整过程
当你在浏览器输入 http://example.com/api/users 并回车时,发生了什么?
┌─────────┐ HTTP 请求 (Request) ┌──────────┐
│ 浏览器 │ ──────────────────────────→ │ 服务器 │
│ (客户端) │ │ (服务端) │
│ │ ←────────────────────────── │ │
└─────────┘ HTTP 响应 (Response) └──────────┘
3.2 HTTP 请求结构
一个 HTTP 请求由三部分组成:
┌─────────────────────────────────────┐
│ 请求行 (Request Line) │
│ GET /api/users HTTP/1.1 │
├─────────────────────────────────────┤
│ 请求头 (Headers) │
│ Host: example.com │
│ Content-Type: application/json │
│ Authorization: Bearer xxx │
├─────────────────────────────────────┤
│ 请求体 (Body) --- 仅 POST/PUT 有 │
│ {"username": "张三", "age": 25} │
└─────────────────────────────────────┘
请求行包含三个部分:
- HTTP 方法:GET / POST / PUT / DELETE
- 请求路径 :
/api/users - HTTP 版本 :
HTTP/1.1或HTTP/2
3.3 HTTP 请求方法(动词)
| 方法 | 语义 | 有无 Body | 幂等 | 安全 |
|---|---|---|---|---|
| GET | 获取资源 | 无 | ✅ | ✅ |
| POST | 创建资源 | 有 | ❌ | ❌ |
| PUT | 更新/替换资源 | 有 | ✅ | ❌ |
| DELETE | 删除资源 | 无 | ✅ | ❌ |
| PATCH | 部分更新资源 | 有 | ❌ | ❌ |
关键概念解释:
- 幂等:同一个请求执行多次,结果一样。比如 DELETE 一次和 DELETE 十次,资源最终都是"已删除"状态。
- 安全:不会改变服务器状态。GET 只是"看",不会"改"。
3.4 HTTP 状态码
状态码是服务器对请求的"一句话总结",分为 5 类:
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 1xx | 信息 | 很少见 |
| 200 | OK 成功 | GET 请求成功 |
| 201 | Created 已创建 | POST 创建成功 |
| 204 | No Content 无内容 | DELETE 成功,无返回体 |
| 301 | Moved Permanently 永久重定向 | 网站换了域名 |
| 400 | Bad Request 请求错误 | 参数校验失败 |
| 401 | Unauthorized 未认证 | 需要登录 |
| 403 | Forbidden 禁止访问 | 权限不足 |
| 404 | Not Found 不存在 | 路径写错了 |
| 405 | Method Not Allowed 方法不允许 | GET 写了 POST 请求 |
| 500 | Internal Server Error 服务器错误 | 代码抛异常 |
| 502 | Bad Gateway 网关错误 | Nginx 代理问题 |
记住口诀: 1 消息、2 成功、3 重定向、4 客户端错、5 服务器错
4. RESTful API 设计原则
4.1 什么是 REST?
REST(Representational State Transfer,表现层状态转换)是一种 API 设计风格,不是标准,而是一套设计规范。
4.2 核心原则
原则一:面向资源
把一切都看作"资源"(Resource),用 URL 表示资源:
bash
# ❌ 不好的设计------用动词命名
/getUser
/createUser
/deleteUser
# ✅ 好的设计------用名词命名资源
/users # 用户集合
/users/1 # 单个用户
/users/1/posts # 用户 1 的帖子
原则二:用 HTTP 方法表示操作
| 操作 | 传统方式 | RESTful 方式 |
|---|---|---|
| 查询用户 | GET /getUser?id=1 | GET /users/1 |
| 创建用户 | POST /createUser | POST /users |
| 更新用户 | POST /updateUser | PUT /users/1 |
| 删除用户 | POST /deleteUser | DELETE /users/1 |
原则三:无状态
每个请求都包含服务器处理所需的所有信息,服务器不保存客户端状态。
4.3 留言板的 RESTful API
| 方法 | 路径 | 说明 | 状态码 |
|---|---|---|---|
| GET | /api/messages |
获取全部留言 | 200 |
| POST | /api/messages |
创建留言 | 201 |
| GET | /api/messages/1 |
获取 ID=1 的留言 | 200 |
| DELETE | /api/messages/1 |
删除 ID=1 的留言 | 204 |
5. 用 Flask 写 REST 接口
5.1 Flask 路由基础
Flask 使用 @app.route() 装饰器来定义路由:
python
from flask import Flask, jsonify
app = Flask(__name__)
# 默认 GET 方法
@app.route('/api/hello')
def hello():
return jsonify({'message': 'Hello, World!'})
# 指定 HTTP 方法
@app.route('/api/data', methods=['GET', 'POST'])
def data():
if request.method == 'GET':
return jsonify({'data': '这是 GET 响应'})
elif request.method == 'POST':
return jsonify({'data': '这是 POST 响应'})
5.2 接收请求参数
python
from flask import request
# 查询参数(Query String): GET /api/users?name=张三
@app.route('/api/users')
def get_users():
name = request.args.get('name', '默认值')
return jsonify({'name': name})
# JSON 请求体: POST /api/users body: {"name":"张三"}
@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()
name = data.get('name')
return jsonify({'name': name}), 201
5.3 返回 JSON 响应
python
from flask import jsonify
# 返回 JSON + 状态码
@app.route('/api/success')
def success():
return jsonify({'status': 'ok'}), 200
@app.route('/api/error')
def error():
return jsonify({'error': True, 'message': '出错了'}), 400
6. 完整代码示例
下面是一个完整的 Flask REST API 示例,包含留言板的原型:
python
# demo_rest.py --- 完整示例
from flask import Flask, jsonify, request
app = Flask(__name__)
# 模拟数据库(用列表代替)
messages = [
{'id': 1, 'username': '张三', 'content': '第一条留言', 'created_at': '2026-07-10 10:00:00'},
{'id': 2, 'username': '李四', 'content': 'Hello World', 'created_at': '2026-07-10 11:00:00'},
]
next_id = 3
# ---- GET /api/messages ----
@app.route('/api/messages', methods=['GET'])
def get_messages():
"""返回所有留言"""
return jsonify(messages)
# ---- POST /api/messages ----
@app.route('/api/messages', methods=['POST'])
def create_message():
"""创建新留言"""
data = request.get_json()
# 简单校验
if not data or 'username' not in data or 'content' not in data:
return jsonify({'error': True, 'message': '缺少必填字段'}), 400
global next_id
new_message = {
'id': next_id,
'username': data['username'],
'content': data['content'],
'created_at': '2026-07-11 00:00:00',
}
next_id += 1
messages.append(new_message)
return jsonify(new_message), 201
# ---- GET /api/messages/<id> ----
@app.route('/api/messages/<int:message_id>', methods=['GET'])
def get_message(message_id):
"""获取单条留言"""
for msg in messages:
if msg['id'] == message_id:
return jsonify(msg)
return jsonify({'error': True, 'message': '留言不存在'}), 404
# ---- DELETE /api/messages/<id> ----
@app.route('/api/messages/<int:message_id>', methods=['DELETE'])
def delete_message(message_id):
"""删除留言"""
global messages
for msg in messages:
if msg['id'] == message_id:
messages.remove(msg)
return jsonify({'message': '删除成功'}), 200
return jsonify({'error': True, 'message': '留言不存在'}), 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
7. 逐行讲解
第 1-4 行:导入与初始化
python
from flask import Flask, jsonify, request
app = Flask(__name__)
| 导入 | 用途 |
|---|---|
Flask |
Flask 核心类 |
jsonify |
将 Python 字典转为 JSON 响应 |
request |
解析 HTTP 请求(参数、Body 等) |
app = Flask(__name__) 创建 Flask 应用实例,__name__ 用于定位模板等资源。
第 6-10 行:模拟数据库
python
messages = [
{'id': 1, 'username': '张三', 'content': '第一条留言', 'created_at': '2026-07-10 10:00:00'},
{'id': 2, 'username': '李四', 'content': 'Hello World', 'created_at': '2026-07-10 11:00:00'},
]
next_id = 3
这段代码做了什么: 用 Python 列表模拟数据库,存储留言数据。next_id 跟踪下一个可用 ID。这是为了方便演示,实战中会改用 SQLite 数据库。
第 14-17 行:GET 接口
python
@app.route('/api/messages', methods=['GET'])
def get_messages():
return jsonify(messages)
@app.route('/api/messages', methods=['GET'])定义路由,只接受 GET 方法jsonify(messages)将列表转为 JSON 数组返回
curl 测试:
bash
curl http://localhost:5000/api/messages
预期输出:
json
[
{"id": 1, "username": "张三", "content": "第一条留言", "created_at": "2026-07-10 10:00:00"},
{"id": 2, "username": "李四", "content": "Hello World", "created_at": "2026-07-10 11:00:00"}
]
第 20-36 行:POST 接口
python
@app.route('/api/messages', methods=['POST'])
def create_message():
data = request.get_json()
if not data or 'username' not in data or 'content' not in data:
return jsonify({'error': True, 'message': '缺少必填字段'}), 400
global next_id
new_message = {
'id': next_id,
'username': data['username'],
'content': data['content'],
'created_at': '2026-07-11 00:00:00',
}
next_id += 1
messages.append(new_message)
return jsonify(new_message), 201
request.get_json()解析请求体中的 JSON 数据- 校验
username和content字段是否存在 return ..., 400返回 400 状态码表示客户端错误global next_id声明全局变量return jsonify(new_message), 201返回 201 表示资源创建成功
curl 测试:
bash
curl -X POST http://localhost:5000/api/messages \
-H "Content-Type: application/json" \
-d '{"username": "王五", "content": "新留言"}'
预期输出:
json
{"id": 3, "username": "王五", "content": "新留言", "created_at": "2026-07-11 00:00:00"}
第 39-44 行:GET 单条留言
python
@app.route('/api/messages/<int:message_id>', methods=['GET'])
def get_message(message_id):
for msg in messages:
if msg['id'] == message_id:
return jsonify(msg)
return jsonify({'error': True, 'message': '留言不存在'}), 404
<int:message_id>是 Flask 的 URL 变量,int:表示转换为整数- 如果找不到对应 ID,返回 404 状态码
第 47-54 行:DELETE 接口
python
@app.route('/api/messages/<int:message_id>', methods=['DELETE'])
def delete_message(message_id):
global messages
for msg in messages:
if msg['id'] == message_id:
messages.remove(msg)
return jsonify({'message': '删除成功'}), 200
return jsonify({'error': True, 'message': '留言不存在'}), 404
messages.remove(msg)从列表中删除元素- 删除成功返回 200,不存在返回 404
8. 运行与验证
8.1 启动服务
bash
# 保存代码到文件
cat > demo_rest.py << 'EOF'
# ... 上面完整的代码 ...
EOF
# 运行
python demo_rest.py
预期输出:
* Serving Flask app 'demo_rest'
* Debug mode: on
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.1.100:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
8.2 用 curl 测试所有接口
bash
# 1. GET 全部留言
echo "=== 1. GET 全部留言 ==="
curl -s http://localhost:5000/api/messages | python3 -m json.tool
echo ""
echo "=== 2. POST 创建留言 ==="
# 2. POST 创建留言
curl -s -X POST http://localhost:5000/api/messages \
-H "Content-Type: application/json" \
-d '{"username": "测试", "content": "Hello from curl!"}' | python3 -m json.tool
echo ""
echo "=== 3. GET 单条留言 ==="
# 3. GET 单条留言
curl -s http://localhost:5000/api/messages/1 | python3 -m json.tool
echo ""
echo "=== 4. GET 不存在的留言 ==="
# 4. 测试 404
curl -s http://localhost:5000/api/messages/999 | python3 -m json.tool
echo ""
echo "=== 5. DELETE 留言 ==="
# 5. DELETE
curl -s -X DELETE http://localhost:5000/api/messages/1 | python3 -m json.tool
预期输出:
json
=== 1. GET 全部留言 ===
[
{
"content": "第一条留言",
"created_at": "2026-07-10 10:00:00",
"id": 1,
"username": "张三"
},
{
"content": "Hello World",
"created_at": "2026-07-10 11:00:00",
"id": 2,
"username": "李四"
}
]
=== 2. POST 创建留言 ===
{
"content": "Hello from curl!",
"created_at": "2026-07-11 00:00:00",
"id": 3,
"username": "测试"
}
=== 3. GET 单条留言 ===
{
"content": "第一条留言",
"created_at": "2026-07-10 10:00:00",
"id": 1,
"username": "张三"
}
=== 4. GET 不存在的留言 ===
{
"error": true,
"message": "留言不存在"
}
=== 5. DELETE 留言 ===
{
"message": "删除成功"
}
8.3 验证 HTTP 状态码
bash
# 查看响应头,确认状态码
curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/api/messages
# 输出: 200
curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:5000/api/messages \
-H "Content-Type: application/json" \
-d '{"username": "test", "content": "hello"}'
# 输出: 201
curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/api/messages/999
# 输出: 404
9. AtomCode 实战
场景:用 AtomCode 生成 REST API
提示词:
"我是 Flask 新手,请帮我写一个完整的 REST API 留言板程序。
要求:
- 使用 Flask
- 用列表模拟数据库
- 实现 GET /api/messages(全部)、POST /api/messages(创建)、
GET /api/messages/<id>(单条)、DELETE /api/messages/<id>(删除)
- 每个接口都返回 JSON 和正确的 HTTP 状态码
- 包含输入校验
- 请输出完整代码并逐行注释"
AtomCode 生成的代码:
AtomCode 会生成类似上面 demo_rest.py 的完整代码,并附带注释说明每一行的作用。
场景:AtomCode 解释 HTTP 状态码
提示词:
"为什么 POST 创建成功后要返回 201,而不是 200?它们有什么区别?"
AtomCode 回复:
- 200 OK:请求成功,通常用于 GET 获取资源
- 201 Created:资源创建成功,通常用于 POST 创建资源
- 语义更精确,客户端(如 axios)可以根据状态码做不同处理
10. 常见错误与排查
错误 1:忘记安装 Flask
报错信息:
ModuleNotFoundError: No module named 'flask'
原因: 没有安装 Flask,或在虚拟环境外运行。
解决:
bash
# 确保激活了虚拟环境
source venv/bin/activate
# 安装 Flask
pip install flask
错误 2:POST 请求取不到数据
报错信息:
python
# 代码
data = request.get_json()
print(data) # 输出: None
原因: 请求头缺少 Content-Type: application/json。
解决: 确保 curl 请求包含:
bash
curl -X POST http://localhost:5000/api/messages \
-H "Content-Type: application/json" \ # ← 必须有这一行
-d '{"username": "test", "content": "hello"}'
错误 3:405 Method Not Allowed
报错信息:
<!DOCTYPE HTML PUBLIC ...>
<title>405 Method Not Allowed</title>
原因: 路由没有指定 methods=['POST'],Flask 默认只接受 GET。
解决: 在 @app.route() 中明确指定方法:
python
@app.route('/api/messages', methods=['GET', 'POST']) # 指定允许的方法
错误 4:端口被占用
报错信息:
OSError: [Errno 98] Address already in use
原因: 上次运行的 Flask 进程没有退出。
解决:
bash
# 方法一:杀死进程
pkill -f "python demo_rest.py"
# 方法二:换端口
app.run(host='0.0.0.0', port=5001)
11. 小结与下一篇预告
本篇小结
- ✅ 理解了 HTTP 请求的结构(请求行 + 请求头 + 请求体)
- ✅ 掌握了 HTTP 方法(GET / POST / PUT / DELETE)
- ✅ 学会了 HTTP 状态码(200 / 201 / 400 / 404 / 500)
- ✅ 理解了 RESTful API 设计原则(面向资源、用动词表示操作)
- ✅ 用 Flask 写了 4 个 REST 接口
- ✅ 用 curl 测试了所有接口
下一篇预告
虽然我们的留言板项目直接用 CDN 引入 Vue,不需要 Webpack,但了解 Webpack 对后续学习很重要。下一篇我们将学习:
- Webpack 的安装与配置
- 开发环境 vs 生产环境
- Loaders 和 Babel
- 文件 hash 和 clean-webpack-plugin
准备好了吗?继续前进!🚀