Python 现代开发学习笔记

(十六)状态、请求失败与数据失败

1. 把状态对应到下一步动作

状态类别 处理过程中的含义 本例动作
2xx 服务器完成请求 继续检查格式、解析与校验记录
3xx 资源位置或处理方向发生变化 查看 Location 与客户端重定向设置
4xx 这份请求无法按要求完成 检查路径、参数、身份与权限
5xx 服务器处理失败 保留状态与错误,按业务决定稍后重试

200 只说明这次 HTTP 请求成功,正文仍可能是 HTML、损坏的 JSON,或字段不符合书籍格式。401 表示需要认证,403 表示请求被拒绝,404 是目标没有找到,429 常用于请求过多;500、503 则提示服务器处理或当前服务状态出现问题。HTTP 状态

# 项目文件:demos/http_errors.py
# HTTPError 是带状态响应的错误;解析失败则发生在请求已经成功之后。
import json
from urllib.error import HTTPError, URLError
from urllib.request import urlopen

from labserver import running_server


if __name__ == "__main__":
    with running_server() as server:
        try:
            with urlopen(server.base_url + "/api/books/999", timeout=1) as response:
                print(json.load(response))
        except HTTPError as error:
            with error:
                print("HTTP 状态", error.code, json.load(error)["error"])
        except URLError as error:
            print("连接请求失败", error.reason)
        with urlopen(server.base_url + "/api/malformed", timeout=1) as response:
            print("HTTP 成功", response.status)
            try:
                json.load(response)
            except json.JSONDecodeError:
                print("正文不是完整 JSON")

依次看到 404、HTTP 成功 200 和正文解析失败。HTTPError 也是 URLError 的子类,先接收它,才能保留状态与错误正文;建立连接等环节失败则没有这份成功响应可解析。urllib 错误类别

连接、HTTP状态、正文解析和记录校验各在不同动作上失败

2. 一次调用超时不等于服务器撤销操作

urlopen(..., timeout=1) 给阻塞网络操作设置超时,不是为整套“重定向、读取、解析与保存”统一计算一秒总预算。异步客户端稍后还会分别设置连接、读取等等待,以及整批请求的总时间范围。

客户端超时或取消等待时,服务器可能已经处理了请求。读取书目失败后可以按需要再次读取;新增阅读日志的 POST 若已成功处理,只是响应没被客户端收到,直接重复发送会新增另一条。

# 项目文件:demos/http_post_twice.py
# 两次 POST 是两次新增操作;相同请求体不会自动让服务器当作同一条记录。
import json
from urllib.request import Request, urlopen

from labserver import running_server


if __name__ == "__main__":
    with running_server() as server:
        data = json.dumps({"book_id": 1, "minutes": 35}).encode("utf-8")
        for _ in range(2):
            request = Request(
                server.base_url + "/api/logs",
                data=data,
                method="POST",
                headers={"Content-Type": "application/json"},
            )
            with urlopen(request, timeout=1) as response:
                print(response.status, json.load(response)["log_id"])

得到 201 1、201 2。之后实现写操作重试时,可以设计操作编号与服务器去重记录,先确定同一次操作的身份,而不是只增加一个重试循环。

笔记正文与全部题解 · Ctrl / ⌘ K