http_client.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. import requests
  2. import logging
  3. import os
  4. import json
  5. import io
  6. from typing import Dict, Any, Optional
  7. from urllib3.util.retry import Retry
  8. from requests.adapters import HTTPAdapter
  9. # 配置日志
  10. logging.basicConfig(level=logging.INFO)
  11. logger = logging.getLogger(__name__)
  12. class HTTPClient:
  13. """HTTP请求工具类,用于发送各种HTTP请求"""
  14. def __init__(self, base_url: str, api_key: str = None, auth_type: str = 'bearer'):
  15. """
  16. 初始化HTTP客户端
  17. Args:
  18. base_url: API基础URL
  19. api_key: API密钥
  20. auth_type: 认证类型,支持'bearer'和'basic'
  21. """
  22. self.base_url = base_url.rstrip('/')
  23. self.api_key = api_key
  24. self.session = requests.Session()
  25. # 设置请求超时(秒)
  26. self.timeout = 30
  27. # 配置重试机制
  28. retry_strategy = Retry(
  29. total=3, # 最大重试次数
  30. backoff_factor=1, # 重试间隔:1秒、2秒、4秒
  31. status_forcelist=[502, 503, 504], # 重试的状态码(移除500,避免过多无效重试)
  32. allowed_methods=["GET", "POST", "PUT", "DELETE"] # 允许重试的方法
  33. )
  34. # 创建HTTP适配器并设置重试策略
  35. adapter = HTTPAdapter(max_retries=retry_strategy)
  36. # 将适配器应用到所有请求
  37. self.session.mount("http://", adapter)
  38. self.session.mount("https://", adapter)
  39. # 设置默认请求头
  40. if self.api_key:
  41. if auth_type == 'bearer':
  42. self.session.headers.update({
  43. 'Authorization': f'Bearer {self.api_key}'
  44. })
  45. elif auth_type == 'basic':
  46. # 处理Basic Auth,格式为"username:password"
  47. self.session.headers.update({
  48. 'Authorization': f'Basic {self.api_key}'
  49. })
  50. self.session.headers.update({
  51. 'Content-Type': 'application/json'
  52. })
  53. def post(self, endpoint: str, data: Optional[Dict] = None,
  54. json_data: Optional[Dict] = None, files: Optional[Dict] = None,
  55. headers: Optional[Dict] = None) -> Dict[str, Any]:
  56. """
  57. 发送POST请求
  58. Args:
  59. endpoint: API端点路径(以/开头)
  60. data: 表单数据
  61. json_data: JSON数据
  62. files: 文件数据
  63. headers: 自定义请求头
  64. Returns:
  65. Dict: 响应JSON数据
  66. Raises:
  67. requests.exceptions.RequestException: 请求失败时抛出
  68. """
  69. url = f"{self.base_url}{endpoint}"
  70. # 记录请求日志
  71. request_info = {
  72. "method": "POST",
  73. "url": url,
  74. "data": data,
  75. "json": json_data,
  76. "headers": headers,
  77. "files": files is not None # 不记录文件内容,只记录是否有文件
  78. }
  79. logger.info(f"Sending request: {request_info}")
  80. try:
  81. # 当上传文件时,不使用默认的Content-Type: application/json头
  82. # 让requests库自动生成正确的multipart/form-data头
  83. if files:
  84. # 创建一个临时会话,不包含默认的Content-Type头
  85. temp_session = requests.Session()
  86. # 复制认证头
  87. if self.api_key:
  88. auth_header = self.session.headers.get('Authorization')
  89. if auth_header:
  90. temp_session.headers.update({'Authorization': auth_header})
  91. # 使用临时会话发送请求
  92. response = temp_session.post(
  93. url=url,
  94. data=data,
  95. json=json_data,
  96. files=files,
  97. headers=headers,
  98. timeout=self.timeout # 添加超时参数
  99. )
  100. else:
  101. # 正常请求,使用默认会话
  102. response = self.session.post(
  103. url=url,
  104. data=data,
  105. json=json_data,
  106. headers=headers,
  107. timeout=self.timeout # 添加超时参数
  108. )
  109. # 记录响应日志
  110. response_info = {
  111. "status_code": response.status_code,
  112. "url": url,
  113. "headers": dict(response.headers),
  114. "content_length": len(response.content)
  115. }
  116. logger.info(f"Received response: {response_info}")
  117. response.raise_for_status() # 抛出HTTP错误
  118. return response.json()
  119. except Exception as e:
  120. # 记录错误日志
  121. logger.error(f"Request failed: {str(e)}")
  122. raise
  123. def get(self, endpoint: str, params: Optional[Dict] = None,
  124. headers: Optional[Dict] = None) -> Dict[str, Any]:
  125. """
  126. 发送GET请求
  127. Args:
  128. endpoint: API端点路径(以/开头)
  129. params: 查询参数
  130. headers: 自定义请求头
  131. Returns:
  132. Dict: 响应JSON数据
  133. Raises:
  134. requests.exceptions.RequestException: 请求失败时抛出
  135. """
  136. url = f"{self.base_url}{endpoint}"
  137. # 记录请求日志
  138. request_info = {
  139. "method": "GET",
  140. "url": url,
  141. "params": params,
  142. "headers": headers
  143. }
  144. logger.info(f"Sending request: {request_info}")
  145. try:
  146. response = self.session.get(
  147. url=url,
  148. params=params,
  149. headers=headers,
  150. timeout=self.timeout # 添加超时参数
  151. )
  152. # 记录响应日志
  153. response_info = {
  154. "status_code": response.status_code,
  155. "url": url,
  156. "headers": dict(response.headers),
  157. "content_length": len(response.content)
  158. }
  159. logger.info(f"Received response: {response_info}")
  160. response.raise_for_status() # 抛出HTTP错误
  161. return response.json()
  162. except Exception as e:
  163. # 记录错误日志
  164. logger.error(f"Request failed: {str(e)}")
  165. raise
  166. def get_json(self, endpoint: str, json_data: Optional[Dict] = None,
  167. headers: Optional[Dict] = None) -> Dict[str, Any]:
  168. """
  169. 发送带有JSON数据的GET请求
  170. Args:
  171. endpoint: API端点路径(以/开头)
  172. json_data: JSON数据
  173. headers: 自定义请求头
  174. Returns:
  175. Dict: 响应JSON数据
  176. Raises:
  177. requests.exceptions.RequestException: 请求失败时抛出
  178. """
  179. url = f"{self.base_url}{endpoint}"
  180. # 记录请求日志
  181. request_info = {
  182. "method": "GET",
  183. "url": url,
  184. "json": json_data,
  185. "headers": headers
  186. }
  187. logger.info(f"Sending request: {request_info}")
  188. try:
  189. response = self.session.get(
  190. url=url,
  191. json=json_data,
  192. headers=headers,
  193. timeout=self.timeout # 添加超时参数
  194. )
  195. # 记录响应日志
  196. response_info = {
  197. "status_code": response.status_code,
  198. "url": url,
  199. "headers": dict(response.headers),
  200. "content_length": len(response.content)
  201. }
  202. logger.info(f"Received response: {response_info}")
  203. response.raise_for_status() # 抛出HTTP错误
  204. # 将response.content转换为JSON
  205. return json.loads(response.content.decode('utf-8'))
  206. except Exception as e:
  207. # 记录错误日志
  208. logger.error(f"Request failed: {str(e)}")
  209. raise
  210. def put(self, endpoint: str, data: Optional[Dict] = None,
  211. json_data: Optional[Dict] = None, headers: Optional[Dict] = None) -> Dict[str, Any]:
  212. """
  213. 发送PUT请求
  214. Args:
  215. endpoint: API端点路径(以/开头)
  216. data: 表单数据
  217. json_data: JSON数据
  218. headers: 自定义请求头
  219. Returns:
  220. Dict: 响应JSON数据
  221. Raises:
  222. requests.exceptions.RequestException: 请求失败时抛出
  223. """
  224. url = f"{self.base_url}{endpoint}"
  225. # 记录请求日志
  226. request_info = {
  227. "method": "PUT",
  228. "url": url,
  229. "data": data,
  230. "json": json_data,
  231. "headers": headers
  232. }
  233. logger.info(f"Sending request: {request_info}")
  234. try:
  235. response = self.session.put(
  236. url=url,
  237. data=data,
  238. json=json_data,
  239. headers=headers,
  240. timeout=self.timeout # 添加超时参数
  241. )
  242. # 记录响应日志
  243. response_info = {
  244. "status_code": response.status_code,
  245. "url": url,
  246. "headers": dict(response.headers),
  247. "content_length": len(response.content)
  248. }
  249. logger.info(f"Received response: {response_info}")
  250. response.raise_for_status() # 抛出HTTP错误
  251. return response.json()
  252. except Exception as e:
  253. # 记录错误日志
  254. logger.error(f"Request failed: {str(e)}")
  255. raise
  256. def delete(self, endpoint: str, data: Optional[Dict] = None,
  257. json_data: Optional[Dict] = None, headers: Optional[Dict] = None) -> Dict[str, Any]:
  258. """
  259. 发送DELETE请求
  260. Args:
  261. endpoint: API端点路径(以/开头)
  262. data: 表单数据
  263. json_data: JSON数据
  264. headers: 自定义请求头
  265. Returns:
  266. Dict: 响应JSON数据
  267. Raises:
  268. requests.exceptions.RequestException: 请求失败时抛出
  269. """
  270. url = f"{self.base_url}{endpoint}"
  271. # 记录请求日志
  272. request_info = {
  273. "method": "DELETE",
  274. "url": url,
  275. "data": data,
  276. "json": json_data,
  277. "headers": headers
  278. }
  279. logger.info(f"Sending request: {request_info}")
  280. try:
  281. response = self.session.delete(
  282. url=url,
  283. data=data,
  284. json=json_data,
  285. headers=headers,
  286. timeout=self.timeout # 添加超时参数
  287. )
  288. # 记录响应日志
  289. response_info = {
  290. "status_code": response.status_code,
  291. "url": url,
  292. "headers": dict(response.headers),
  293. "content_length": len(response.content)
  294. }
  295. logger.info(f"Received response: {response_info}")
  296. response.raise_for_status() # 抛出HTTP错误
  297. return response.json()
  298. except Exception as e:
  299. # 记录错误日志
  300. logger.error(f"Request failed: {str(e)}")
  301. raise
  302. def upload_file(self, endpoint: str, file_path: str = None, file_content: io.BytesIO = None,
  303. file_field_name: str = 'file', original_filename: str = None,
  304. data: Optional[Dict] = None, headers: Optional[Dict] = None) -> Dict[str, Any]:
  305. """
  306. 上传文件
  307. Args:
  308. endpoint: API端点路径(以/开头)
  309. file_path: 本地文件路径
  310. file_content: 文件内容(字节流),如果提供则优先使用
  311. file_field_name: 表单字段名称
  312. original_filename: 原始文件名,如果提供则优先使用
  313. data: 额外的表单数据
  314. headers: 自定义请求头
  315. Returns:
  316. Dict: 响应JSON数据
  317. Raises:
  318. requests.exceptions.RequestException: 请求失败时抛出
  319. """
  320. # 构建files字典
  321. if file_content:
  322. # 使用字节流上传
  323. filename = original_filename if original_filename else "uploaded_file.pdf"
  324. files = {
  325. file_field_name: (filename, file_content)
  326. }
  327. else:
  328. # 使用文件路径上传
  329. with open(file_path, 'rb') as f:
  330. filename = original_filename if original_filename else os.path.basename(file_path)
  331. files = {
  332. file_field_name: (filename, f)
  333. }
  334. # 发送POST请求
  335. return self.post(endpoint, data=data, files=files, headers=headers)
  336. # 发送POST请求(字节流方式)
  337. return self.post(endpoint, data=data, files=files, headers=headers)