Files
PythonLearn/05_web基础/5_6_HTTP接口调试与综合实践/README.md
T

308 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第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`综合练习。