From d8a53da8eea3080e43b1bf7a381b101645216fd4 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 27 Jun 2025 08:30:57 +0800 Subject: [PATCH] auto close RequestUtils --- app/modules/filemanager/storages/alist.py | 21 ++- app/utils/http.py | 160 +++++++++++++++++----- 2 files changed, 132 insertions(+), 49 deletions(-) diff --git a/app/modules/filemanager/storages/alist.py b/app/modules/filemanager/storages/alist.py index aaacdc84..e23e6f24 100644 --- a/app/modules/filemanager/storages/alist.py +++ b/app/modules/filemanager/storages/alist.py @@ -4,7 +4,6 @@ from pathlib import Path from typing import Optional, List, Dict import requests -from requests import Response from app import schemas from app.core.cache import cached @@ -77,7 +76,7 @@ class Alist(StorageBase, metaclass=Singleton): token = conf.get("token") if token: return str(token) - resp: Response = RequestUtils(headers={ + resp = RequestUtils(headers={ 'Content-Type': 'application/json' }).post_res( self.__get_api_url("/api/auth/login"), @@ -151,7 +150,7 @@ class Alist(StorageBase, metaclass=Singleton): if item: return [item] return [] - resp: Response = RequestUtils( + resp = RequestUtils( headers=self.__get_header_with_token() ).post_res( self.__get_api_url("/api/fs/list"), @@ -240,7 +239,7 @@ class Alist(StorageBase, metaclass=Singleton): :param name: 目录名 """ path = Path(fileitem.path) / name - resp: Response = RequestUtils( + resp = RequestUtils( headers=self.__get_header_with_token() ).post_res( self.__get_api_url("/api/fs/mkdir"), @@ -304,7 +303,7 @@ class Alist(StorageBase, metaclass=Singleton): :param per_page: 每页数量 :param refresh: 是否刷新 """ - resp: Response = RequestUtils( + resp = RequestUtils( headers=self.__get_header_with_token() ).post_res( self.__get_api_url("/api/fs/get"), @@ -381,7 +380,7 @@ class Alist(StorageBase, metaclass=Singleton): """ 删除文件 """ - resp: Response = RequestUtils( + resp = RequestUtils( headers=self.__get_header_with_token() ).post_res( self.__get_api_url("/api/fs/remove"), @@ -425,7 +424,7 @@ class Alist(StorageBase, metaclass=Singleton): """ 重命名文件 """ - resp: Response = RequestUtils( + resp = RequestUtils( headers=self.__get_header_with_token() ).post_res( self.__get_api_url("/api/fs/rename"), @@ -476,7 +475,7 @@ class Alist(StorageBase, metaclass=Singleton): :param path: 文件保存路径 :param password: 文件密码 """ - resp: Response = RequestUtils( + resp = RequestUtils( headers=self.__get_header_with_token() ).post_res( self.__get_api_url("/api/fs/get"), @@ -561,7 +560,7 @@ class Alist(StorageBase, metaclass=Singleton): headers.setdefault("As-Task", str(task).lower()) headers.setdefault("File-Path", encoded_path) with open(path, "rb") as f: - resp: Response = RequestUtils(headers=headers).put_res( + resp = RequestUtils(headers=headers).put_res( self.__get_api_url("/api/fs/put"), data=f, ) @@ -590,7 +589,7 @@ class Alist(StorageBase, metaclass=Singleton): :param path: 目标目录 :param new_name: 新文件名 """ - resp: Response = RequestUtils( + resp = RequestUtils( headers=self.__get_header_with_token() ).post_res( self.__get_api_url("/api/fs/copy"), @@ -649,7 +648,7 @@ class Alist(StorageBase, metaclass=Singleton): # 先重命名 if fileitem.name != new_name: self.rename(fileitem, new_name) - resp: Response = RequestUtils( + resp = RequestUtils( headers=self.__get_header_with_token() ).post_res( self.__get_api_url("/api/fs/move"), diff --git a/app/utils/http.py b/app/utils/http.py index af79e210..b089a209 100644 --- a/app/utils/http.py +++ b/app/utils/http.py @@ -13,6 +13,70 @@ from app.log import logger urllib3.disable_warnings(InsecureRequestWarning) +class AutoCloseResponse: + """ + 自动关闭连接的Response包装器 + 在访问常用属性后自动关闭连接 + """ + + def __init__(self, response: Response): + self._response = response + self._closed = False + + def __getattr__(self, name): + """ + 对于其他属性,直接委托给原始response + """ + return getattr(self._response, name) + + def _auto_close(self): + """ + 自动关闭连接 + """ + if not self._closed and self._response: + try: + self._response.close() + self._closed = True + except Exception as e: + logger.debug(f"自动关闭响应失败: {e}") + + def json(self, **kwargs): + """ + 获取JSON数据并自动关闭连接 + """ + try: + data = self._response.json(**kwargs) + return data + finally: + self._auto_close() + + @property + def text(self): + """ + 获取文本内容并自动关闭连接 + """ + try: + return self._response.text + finally: + self._auto_close() + + @property + def content(self): + """ + 获取二进制内容并自动关闭连接 + """ + try: + return self._response.content + finally: + self._auto_close() + + def close(self): + """ + 手动关闭连接 + """ + self._auto_close() + + class RequestUtils: def __init__(self, @@ -125,7 +189,8 @@ class RequestUtils: json: dict = None, allow_redirects: bool = True, raise_exception: bool = False, - **kwargs) -> Optional[Response]: + auto_close: bool = True, + **kwargs) -> Optional[AutoCloseResponse]: """ 发送GET请求并返回响应对象 :param url: 请求的URL @@ -134,18 +199,22 @@ class RequestUtils: :param json: 请求的JSON数据 :param allow_redirects: 是否允许重定向 :param raise_exception: 是否在发生异常时抛出异常,否则默认拦截异常返回None + :param auto_close: 是否自动关闭响应连接,None时使用全局配置 :param kwargs: 其他请求参数,如headers, cookies, proxies等 :return: HTTP响应对象,若发生RequestException则返回None :raises: requests.exceptions.RequestException 仅raise_exception为True时会抛出 """ - return self.request(method="get", - url=url, - params=params, - data=data, - json=json, - allow_redirects=allow_redirects, - raise_exception=raise_exception, - **kwargs) + response = self.request(method="get", + url=url, + params=params, + data=data, + json=json, + allow_redirects=allow_redirects, + raise_exception=raise_exception, + **kwargs) + if response is not None and auto_close: + return AutoCloseResponse(response) + return response @contextmanager def get_stream(self, url: str, params: dict = None, **kwargs): @@ -171,7 +240,8 @@ class RequestUtils: files: Any = None, json: dict = None, raise_exception: bool = False, - **kwargs) -> Optional[Response]: + auto_close: bool = True, + **kwargs) -> Optional[AutoCloseResponse]: """ 发送POST请求并返回响应对象 :param url: 请求的URL @@ -180,20 +250,24 @@ class RequestUtils: :param allow_redirects: 是否允许重定向 :param files: 请求的文件 :param json: 请求的JSON数据 - :param kwargs: 其他请求参数,如headers, cookies, proxies等 :param raise_exception: 是否在发生异常时抛出异常,否则默认拦截异常返回None + :param auto_close: 是否自动关闭响应连接,None时使用全局配置 + :param kwargs: 其他请求参数,如headers, cookies, proxies等 :return: HTTP响应对象,若发生RequestException则返回None :raises: requests.exceptions.RequestException 仅raise_exception为True时会抛出 """ - return self.request(method="post", - url=url, - data=data, - params=params, - allow_redirects=allow_redirects, - files=files, - json=json, - raise_exception=raise_exception, - **kwargs) + response = self.request(method="post", + url=url, + data=data, + params=params, + allow_redirects=allow_redirects, + files=files, + json=json, + raise_exception=raise_exception, + **kwargs) + if response is not None and auto_close: + return AutoCloseResponse(response) + return response def put_res(self, url: str, @@ -203,7 +277,8 @@ class RequestUtils: files: Any = None, json: dict = None, raise_exception: bool = False, - **kwargs) -> Optional[Response]: + auto_close: bool = True, + **kwargs) -> Optional[AutoCloseResponse]: """ 发送PUT请求并返回响应对象 :param url: 请求的URL @@ -213,19 +288,23 @@ class RequestUtils: :param files: 请求的文件 :param json: 请求的JSON数据 :param raise_exception: 是否在发生异常时抛出异常,否则默认拦截异常返回None + :param auto_close: 是否自动关闭响应连接,None时使用全局配置 :param kwargs: 其他请求参数,如headers, cookies, proxies等 :return: HTTP响应对象,若发生RequestException则返回None :raises: requests.exceptions.RequestException 仅raise_exception为True时会抛出 """ - return self.request(method="put", - url=url, - data=data, - params=params, - allow_redirects=allow_redirects, - files=files, - json=json, - raise_exception=raise_exception, - **kwargs) + response = self.request(method="put", + url=url, + data=data, + params=params, + allow_redirects=allow_redirects, + files=files, + json=json, + raise_exception=raise_exception, + **kwargs) + if response is not None and auto_close: + return AutoCloseResponse(response) + return response def delete_res(self, url: str, @@ -233,7 +312,8 @@ class RequestUtils: params: dict = None, allow_redirects: bool = True, raise_exception: bool = False, - **kwargs) -> Optional[Response]: + auto_close: bool = True, + **kwargs) -> Optional[AutoCloseResponse]: """ 发送DELETE请求并返回响应对象 :param url: 请求的URL @@ -241,17 +321,21 @@ class RequestUtils: :param params: 请求的参数 :param allow_redirects: 是否允许重定向 :param raise_exception: 是否在发生异常时抛出异常,否则默认拦截异常返回None + :param auto_close: 是否自动关闭响应连接,None时使用全局配置 :param kwargs: 其他请求参数,如headers, cookies, proxies等 :return: HTTP响应对象,若发生RequestException则返回None :raises: requests.exceptions.RequestException 仅raise_exception为True时会抛出 """ - return self.request(method="delete", - url=url, - data=data, - params=params, - allow_redirects=allow_redirects, - raise_exception=raise_exception, - **kwargs) + response = self.request(method="delete", + url=url, + data=data, + params=params, + allow_redirects=allow_redirects, + raise_exception=raise_exception, + **kwargs) + if response is not None and auto_close: + return AutoCloseResponse(response) + return response @staticmethod def cookie_parse(cookies_str: str, array: bool = False) -> Union[list, dict]: