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