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()

浙公网安备 33010602011771号