# 第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`综合练习。