feat(web基础): 新增零基础Web开发课程

This commit is contained in:
zhiye.sun
2026-08-20 16:14:29 +08:00
parent bf1f7042a9
commit 0c3d6402ea
19 changed files with 2005 additions and 8 deletions
@@ -0,0 +1,307 @@
# 第5-6课:HTTP接口调试与综合实践
## 一、本课定位
本课把客户端、服务器、URL、HTTP、状态码、JSON和RESTful API组合成一个可运行的本地闭环。示例使用Python标准库搭建临时图书接口,只服务于协议观察,不替代下一阶段的FastAPI。
## 二、本课目标
完成本课后,你能够:
1. 启动一个只监听本机的临时HTTP服务;
2. 使用客户端代码发送GET和POST请求;
3. 同时检查状态码、响应头和JSON响应体;
4. 识别JSON格式错误、字段错误和资源不存在;
5. 设计图书管理系统的RESTful API;
6. 说明HTTP知识如何迁移到FastAPI。
## 三、前置知识
- 客户端、服务器和请求响应流程;
- URL、HTTP方法、请求头和请求体;
- 常见状态码;
- JSON转换与RESTful API设计;
- Python类、异常、线程和上下文管理器的基本阅读能力。
线程(Thread)表示同一程序中的一条执行路线。本课只用后台线程让服务器等待请求,主线程同时充当客户端;不展开并发编程细节。
本课示例使用了前面阶段学过的类、异常和上下文管理器,但只要求按调用流程阅读,不重新讲解这些Python语法。
## 四、为什么现在才启动真实HTTP服务
前五课分别拆开观察了通信角色、URL、请求、响应、JSON和接口设计。如果一开始就使用框架,路由、自动校验和自动序列化会让新手看到结果,却不容易分清是谁完成了哪一步。
本课使用标准库建立一个最小闭环:
1. 程序在本机启动HTTP服务器;
2. 客户端向它发送真实HTTP请求;
3. 服务器读取路径、请求头和请求体;
4. 服务器修改内存数据;
5. 服务器返回状态码、响应头和JSON;
6. 客户端读取并输出响应;
7. 全部测试结束后关闭服务器。
它是教学服务器,不是下一阶段要长期使用的项目框架。
## 五、先认识本课的新名词
| 名词 | 本课中的含义 |
|---|---|
| `localhost` | 表示自己的计算机,常对应`127.0.0.1` |
| 监听(Listen) | 服务器等待某个地址和端口上的请求 |
| 随机端口 | 传入端口0,由操作系统选择当前可用端口 |
| 处理器(Handler) | 收到请求后负责读取并生成响应的代码 |
| 后台线程 | 在同一程序中持续运行服务器,让主流程可以发送请求 |
| 内存数据 | 只存在于当前程序运行期间,结束后不会保存 |
| 超时(Timeout) | 等待超过限定时间后停止继续等待 |
`127.0.0.1`只指向本机。绑定该地址可以避免本课没有认证的服务被局域网其他设备访问。
## 六、完整调用链
```text
send_request()
↓ HTTP请求
BookApiHandler
↓ 路径匹配、JSON解析、字段校验
内存图书列表
↓ JSON响应
send_request()读取状态码、响应头和响应体
```
示例绑定`127.0.0.1`,只接受本机连接;端口传入`0`,由操作系统选择当前可用端口,避免与现有服务冲突。数据只保存在内存中,程序结束即消失。
同一个Python程序同时扮演两种角色:
- `ThreadingHTTPServer`和`BookApiHandler`是服务器;
- `send_request()`是客户端;
- 它们通过本机网络连接交换HTTP消息,不是普通函数直接传参。
## 七、服务器如何启动
```python
server = ThreadingHTTPServer(("127.0.0.1", 0), BookApiHandler)
```
这行代码只创建服务器对象,还没有开始持续等待请求。地址元组中:
- `127.0.0.1`限制为本机;
- `0`请求操作系统分配端口;
- `BookApiHandler`指定收到请求后由哪个类处理。
随后创建后台线程并执行`serve_forever()`。名称表示服务器会持续等待,直到程序调用`shutdown()`。
## 八、服务端重点
`BaseHTTPRequestHandler`按请求方法调用`do_GET()`或`do_POST()`。`send_json()`负责:
1. 将Python对象序列化为JSON;
2. 编码成UTF-8字节;
3. 写入状态码和响应头;
4. 按字节数计算`Content-Length`;
5. 写出响应体。
中文字符的字符数量与UTF-8字节数量不同,因此必须对编码后的`response_body`调用`len()`。
### 8.1 GET请求
客户端请求`GET /books`时,标准库调用`do_GET()`:
1. 检查`self.path`是否等于`/books`;
2. 路径匹配时返回200和图书列表;
3. 路径不匹配时返回404和错误JSON。
### 8.2 POST请求
客户端请求`POST /books`时,标准库调用`do_POST()`:
1. 检查路径;
2. 从`Content-Length`得知需要读取多少字节;
3. 读取请求体并按UTF-8解码;
4. 使用`json.loads()`解析JSON;
5. 校验`title`是否为非空字符串;
6. 创建新图书并加入内存列表;
7. 返回201、新图书JSON和`Location`响应头。
JSON语法错误返回400,字段不符合要求返回422,未知路径返回404。三个结果代表不同问题。
## 九、客户端重点
`urllib.request.Request`创建请求,`urlopen()`发送请求。遇到4xx或5xx时,标准库会抛出`HTTPError`,但错误对象仍包含状态码和响应体,需要读取后再判断原因。
接口调试不能只看响应体。至少检查:
- 请求方法与完整URL;
- 请求头和请求体;
- HTTP状态码;
- 响应`Content-Type`;
- JSON响应结构;
- 服务端数据是否按预期变化。
客户端发送JSON前,需要完成与服务器相反的转换:
```text
Python字典 → JSON字符串 → UTF-8字节 → HTTP请求体
```
客户端收到响应后再反向处理:
```text
HTTP响应体字节 → UTF-8字符串 → 显示或解析JSON
```
示例为保持输出直观,只显示JSON字符串。实际业务客户端通常还会使用`json.loads()`转换成对象。
## 十、程序完整执行顺序
1. 创建服务器对象;
2. 创建并启动后台线程;
3. 从服务器对象取得操作系统分配的端口;
4. 第一次GET查询初始列表;
5. POST创建“HTTP接口实践”;
6. 第二次GET验证列表已经变化;
7. GET未知路径验证404;
8. 无论中间是否出错,`finally`都会关闭服务器;
9. 主线程等待后台线程结束;
10. 程序输出“本地服务已关闭”。
这里先新增再查询,是为了验证POST不只是返回201,还真实改变了当前程序的内存状态。
## 十一、运行方法
```powershell
cd D:\Code\Python\05_web基础\5_6_HTTP接口调试与综合实践
python local_book_api_example.py
```
## 十二、预期结果
```text
本地服务已启动:http://127.0.0.1:随机端口号
GET http://127.0.0.1:相同端口/books -> 200
响应体:{"items": [{"id": 1, "title": "Python入门"}]}
POST http://127.0.0.1:相同端口/books -> 201
响应体:{"id": 2, "title": "HTTP接口实践"}
GET http://127.0.0.1:相同端口/books -> 200
响应体中包含两本图书
GET http://127.0.0.1:相同端口/missing -> 404
响应体:{"message": "资源不存在"}
本地服务已关闭。
```
端口号每次不同是正常现象。第二次查询应包含新创建的“HTTP接口实践”。
## 十三、怎样判断测试真正通过
不要只检查程序是否结束,应逐项确认:
1. 四次请求使用同一个随机端口;
2. 第一次GET只有初始图书;
3. POST状态码是201而不是200;
4. POST响应中生成了编号2;
5. 第二次GET包含编号1和编号2;
6. 未知路径返回404;
7. 最后一行确认服务器已关闭;
8. 再运行一次时仍从一条初始数据开始。
最后一项说明数据只在内存中,没有持久保存,也说明示例可重复运行。
## 十四、使用接口调试工具
示例服务会自动结束,不适合手工操作。完成`practice.py`时,可以让服务等待键盘输入后再关闭,然后使用Postman、Apifox或IDE内置HTTP客户端发送请求:
```http
GET http://127.0.0.1:端口/books
```
```http
POST http://127.0.0.1:端口/books
Content-Type: application/json
{"title": "Web实践", "author": "张三"}
```
不要把服务绑定到`0.0.0.0`,本课没有实现认证和生产安全配置。
调试工具中的请求配置通常分为:
1. 选择HTTP方法;
2. 输入完整URL;
3. 配置请求头;
4. 选择JSON请求体并输入数据;
5. 发送请求;
6. 查看状态码、耗时、响应头和响应体。
不要只复制响应内容,还要记录发送了什么请求,否则别人无法复现问题。
## 十五、图书管理API设计草案
| 方法 | 路径 | 成功状态 | 作用 |
|---|---|---:|---|
| GET | `/books` | 200 | 分页查询图书 |
| GET | `/books/{book_id}` | 200 | 查询图书详情 |
| POST | `/books` | 201 | 新增图书 |
| PATCH | `/books/{book_id}` | 200 | 修改部分字段 |
| DELETE | `/books/{book_id}` | 204 | 删除图书 |
常见失败包括400格式错误、404图书不存在、409编号冲突和422字段校验失败。实际项目还要处理认证、权限、日志、数据库事务和并发。
## 十六、与FastAPI的衔接
下一阶段会由FastAPI代替底层标准库代码:
- 路由装饰器代替手工判断路径和方法;
- Pydantic模型代替手工字段校验;
- 框架自动序列化JSON并生成接口文档;
- 异常处理器统一生成错误响应。
不会改变的是HTTP层的设计:方法、路径、参数、状态码、请求结构和响应结构仍需开发者决定。
## 十七、常见错误
### 17.1 服务启动后程序不结束
服务器会持续等待请求。必须调用`shutdown()`和`server_close()`,并等待后台线程结束。
### 17.2 只测试成功情况
至少覆盖未知路径、无效JSON和缺少必填字段,否则无法确认错误边界。
### 17.3 把标准库示例当作生产服务器
本例只用于教学,没有完整认证、限流、日志、安全加固和部署能力。
### 17.4 把服务绑定到`0.0.0.0`
这会让服务监听所有可用网络接口。本课没有认证,不应扩大访问范围。
### 17.5 按字符数计算`Content-Length`
HTTP传输的是字节。包含中文时必须先编码,再计算字节长度。
### 17.6 忘记处理`HTTPError`
标准库遇到4xx和5xx会抛出`HTTPError`。错误对象中仍有响应体,应读取并用于定位问题。
### 17.7 端口不同就认为结果错误
本课故意使用随机可用端口。只要同一次运行中的请求端口一致即可。
## 十八、课堂练习
打开`practice.py`,完成图书管理接口设计、服务端实现和客户端调试。练习不提供答案或代码骨架。
## 十九、本课小结
一个接口是否正确,要从客户端请求、服务器处理和HTTP响应三个方向共同验证。框架会减少底层代码,但不会代替接口语义和业务规则设计。
## 二十、验收标准
- 本地服务能自动启动和关闭;
- GET、POST和404请求输出符合预期;
- JSON中文编码与`Content-Length`正确;
- 能设计完整的图书管理接口;
- 能说明本阶段知识与FastAPI的对应关系;
- 能完整描述GET和POST在服务器中的处理顺序;
- 能从请求方法、URL、状态码、响应头、响应体和数据变化六方面调试接口;
- 完成`practice.py`综合练习。