192 lines
6.2 KiB
Python
192 lines
6.2 KiB
Python
import httpx
|
||
import json
|
||
import logging
|
||
from typing import Any, Dict, List, Optional, Union
|
||
from urllib.parse import urljoin
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class HttpClient:
|
||
"""
|
||
HTTP客户端工具类
|
||
封装了请求和响应处理逻辑
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
base_url: str = "",
|
||
timeout: int = 30,
|
||
headers: Optional[Dict[str, str]] = None,
|
||
verify_ssl: bool = True
|
||
):
|
||
"""
|
||
初始化HTTP客户端
|
||
|
||
Args:
|
||
base_url: API基础URL
|
||
timeout: 请求超时时间(秒)
|
||
headers: 默认请求头
|
||
verify_ssl: 是否验证SSL证书
|
||
"""
|
||
self.base_url = base_url
|
||
self.timeout = timeout
|
||
self.headers = headers or {}
|
||
self.verify_ssl = verify_ssl
|
||
|
||
async def request(
|
||
self,
|
||
method: str,
|
||
url: str,
|
||
params: Optional[Dict[str, Any]] = None,
|
||
data: Optional[Any] = None,
|
||
json_data: Optional[Dict[str, Any]] = None,
|
||
headers: Optional[Dict[str, str]] = None,
|
||
timeout: Optional[int] = None,
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
发送HTTP请求
|
||
|
||
Args:
|
||
method: 请求方法 (GET, POST, PUT, DELETE等)
|
||
url: 请求URL路径
|
||
params: URL查询参数
|
||
data: 表单数据或二进制数据
|
||
json_data: JSON数据
|
||
headers: 请求头
|
||
timeout: 超时时间(秒)
|
||
|
||
Returns:
|
||
响应数据
|
||
|
||
Raises:
|
||
Exception: 请求失败时抛出异常
|
||
"""
|
||
if self.base_url:
|
||
full_url = urljoin(self.base_url, url)
|
||
else:
|
||
full_url = url
|
||
|
||
request_headers = {**self.headers}
|
||
if headers:
|
||
request_headers.update(headers)
|
||
|
||
timeout_value = timeout or self.timeout
|
||
|
||
try:
|
||
async with httpx.AsyncClient(verify=self.verify_ssl) as client:
|
||
response = await client.request(
|
||
method=method,
|
||
url=full_url,
|
||
params=params,
|
||
data=data,
|
||
json=json_data,
|
||
headers=request_headers,
|
||
timeout=timeout_value,
|
||
)
|
||
|
||
# 记录请求和响应
|
||
logger.debug(f"HTTP请求: {method} {full_url}")
|
||
logger.debug(f"状态码: {response.status_code}")
|
||
|
||
# 尝试解析JSON响应
|
||
response_data = None
|
||
try:
|
||
response_data = response.json()
|
||
except json.JSONDecodeError:
|
||
response_data = {"content": response.text}
|
||
|
||
# 检查状态码
|
||
response.raise_for_status()
|
||
|
||
# 验证是否成功
|
||
self._verify_success(response_data, response.status_code)
|
||
|
||
return response_data
|
||
|
||
except httpx.HTTPStatusError as e:
|
||
logger.error(f"HTTP状态错误: {e.response.status_code} - {e.response.text}")
|
||
try:
|
||
error_data = e.response.json()
|
||
msg = error_data.get("message", str(e))
|
||
except json.JSONDecodeError:
|
||
msg = e.response.text or str(e)
|
||
raise Exception(f"请求失败 ({e.response.status_code}): {msg}")
|
||
|
||
except httpx.RequestError as e:
|
||
logger.error(f"请求错误: {str(e)}")
|
||
raise Exception(f"请求错误: {str(e)}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"未知错误: {str(e)}")
|
||
raise
|
||
|
||
def _verify_success(self, data: Dict[str, Any], status_code: int) -> None:
|
||
"""
|
||
验证响应是否成功
|
||
|
||
Args:
|
||
data: 响应数据
|
||
status_code: HTTP状态码
|
||
|
||
Raises:
|
||
Exception: 验证失败时抛出异常
|
||
"""
|
||
# 检查HTTP状态码
|
||
if status_code >= 400:
|
||
raise Exception(f"请求失败,状态码: {status_code}")
|
||
|
||
# 检查响应体中的success字段(如果存在)
|
||
if isinstance(data, dict) and "success" in data and data["success"] is False:
|
||
msg = data.get("message", "未知错误")
|
||
code = data.get("code", status_code)
|
||
raise Exception(f"请求失败 (code: {code}): {msg}")
|
||
|
||
async def get(
|
||
self,
|
||
url: str,
|
||
params: Optional[Dict[str, Any]] = None,
|
||
headers: Optional[Dict[str, str]] = None,
|
||
timeout: Optional[int] = None,
|
||
) -> Dict[str, Any]:
|
||
"""发送GET请求"""
|
||
return await self.request("GET", url, params=params, headers=headers, timeout=timeout)
|
||
|
||
async def post(
|
||
self,
|
||
url: str,
|
||
data: Optional[Any] = None,
|
||
json_data: Optional[Dict[str, Any]] = None,
|
||
params: Optional[Dict[str, Any]] = None,
|
||
headers: Optional[Dict[str, str]] = None,
|
||
timeout: Optional[int] = None,
|
||
) -> Dict[str, Any]:
|
||
"""发送POST请求"""
|
||
return await self.request(
|
||
"POST", url, params=params, data=data, json_data=json_data,
|
||
headers=headers, timeout=timeout
|
||
)
|
||
|
||
async def put(
|
||
self,
|
||
url: str,
|
||
data: Optional[Any] = None,
|
||
json_data: Optional[Dict[str, Any]] = None,
|
||
params: Optional[Dict[str, Any]] = None,
|
||
headers: Optional[Dict[str, str]] = None,
|
||
timeout: Optional[int] = None,
|
||
) -> Dict[str, Any]:
|
||
"""发送PUT请求"""
|
||
return await self.request(
|
||
"PUT", url, params=params, data=data, json_data=json_data,
|
||
headers=headers, timeout=timeout
|
||
)
|
||
|
||
async def delete(
|
||
self,
|
||
url: str,
|
||
params: Optional[Dict[str, Any]] = None,
|
||
headers: Optional[Dict[str, str]] = None,
|
||
timeout: Optional[int] = None,
|
||
) -> Dict[str, Any]:
|
||
"""发送DELETE请求"""
|
||
return await self.request("DELETE", url, params=params, headers=headers, timeout=timeout) |