Python 标准库 HTTP Client 封装 (带 Headers 的 JSON 请求)

问了DeepSeek,然后优化了一下:

from typing import Any, Self
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

try:
    import orjson

    def json_loads(data: bytes) -> Any:
        return orjson.loads(data)

    def json_dump_bytes(data: Any) -> bytes:
        return orjson.dumps(data)
except ImportError:
    import json as _json

    def json_loads(data: bytes) -> Any:
        return _json.loads(data)

    def json_dump_bytes(data: Any) -> bytes:
        return _json.dumps(data).encode()


class HttpClient:
    """带 Headers 的 JSON HTTP 客户端"""

    def __init__(
        self,
        base_url: str = "",
        default_headers: dict[str, str] | None = None,
        timeout: int = 10,
    ):
        """
        初始化 HTTP 客户端

        :param base_url: 基础 URL (可选)
        :param default_headers: 默认请求头 (可选)
        :param timeout: 请求超时时间 (秒)
        """
        self._content = b""
        self.base_url = base_url.rstrip("/")
        self.default_headers = default_headers or {}
        self.timeout = timeout

    def post(
        self,
        endpoint: str,
        json: dict[str, Any],
        headers: dict[str, str] | None = None,
        **kwargs: Any,
    ) -> Self:
        """
        发送 POST 请求 (JSON 格式)

        :param endpoint: API 端点 (如 "/api/user")
        :param data: 要发送的 JSON 数据
        :param headers: 附加请求头 (会与默认请求头合并)
        :return: 解析后的 JSON 响应
        :raises: HTTPError 如果请求失败
        """
        return self._request(
            method="POST", endpoint=endpoint, data=json, headers=headers, **kwargs
        )

    def json(self) -> Any:
        return json_loads(self._content)

    def _request(
        self,
        method: str,
        endpoint: str,
        data: dict[str, Any],
        headers: dict[str, str] | None = None,
        **kwargs: Any,
    ) -> Self:
        """
        内部请求方法

        :param method: HTTP 方法 (GET, POST 等)
        :param endpoint: API 端点
        :param data: 请求数据
        :param headers: 请求头
        :return: 解析后的 JSON 响应
        """
        url = f"{self.base_url}/{endpoint.lstrip('/')}"

        # 合并 headers
        final_headers = {
            "Content-Type": "application/json",
            **self.default_headers,
            **(headers or {}),
        }

        # 准备请求数据
        json_data = json_dump_bytes(data)

        # 创建请求对象
        req = Request(
            url, data=json_data, headers=final_headers, method=method.upper(), **kwargs
        )

        try:
            with urlopen(req, timeout=self.timeout) as response:
                self._content = response.read()
        except HTTPError as e:
            # 尝试读取错误响应体
            error_body = e.read().decode("utf-8") if e.readable() else ""
            raise HTTPError(
                e.url, e.code, f"{e.reason} | {error_body}", e.headers, e.fp
            ) from e
        except URLError as e:
            raise ConnectionError(f"Failed to connect to {url}: {str(e)}") from e
        return self


# 使用示例
def main() -> None:
    # 1. 创建客户端实例
    client = HttpClient(
        base_url="https://httpbin.org",
        default_headers={"User-Agent": "MyApp/1.0", "Accept": "application/json"},
    )

    # 2. 发送 POST 请求
    try:
        response = client.post(
            "/post",
            json={"name": "John", "age": 30},
            headers={"X-Custom-Header": "12345"},
        )
        print("请求成功:", response.json())
    except Exception as e:
        print("请求失败:", str(e))


if __name__ == "__main__":
    main()
posted @ 2025-08-10 20:12  waketzheng  阅读(17)  评论(0)    收藏  举报