# `HttpClient` 与 `JsonHttpAPI` 技术文档 --- ## 概述 本文档介绍了一个基于 `aiohttp` 的异步 HTTP 客户端库,包含两个核心类: - `HttpClient`:提供灵活、可配置的异步 HTTP 请求功能,支持代理(SOCKS5)、自动重试、Cookie 管理、域名黑名单缓存等。 - `JsonHttpAPI`:构建在 `HttpClient` 上的高级接口,用于处理 JSON 格式的 API 调用,支持模板渲染、流式响应处理和动态参数注入。 该模块适用于需要高并发访问 Web 接口、支持代理切换、具备容错机制的应用场景。 --- ## 依赖项 ```txt aiohttp aiohttp_socks certifi ssl asyncio urllib.parse json re traceback os appPublic.myTE (自定义模板引擎) appPublic.log (日志模块) ``` > 注意:`appPublic.*` 是项目内部工具模块,请确保已安装或替换为对应实现。 --- ## 全局常量 | 常量 | 值 | 含义 | |------|----|------| | `RESPONSE_BIN` | `0` | 返回二进制数据(`bytes`) | | `RESPONSE_TEXT` | `1` | 返回文本字符串(使用编码解码) | | `RESPONSE_JSON` | `2` | 返回解析后的 JSON 对象 | | `RESPONSE_FILE` | `3` | 预留,表示文件下载(当前未使用) | | `RESPONSE_STREAM`| `4` | 流式传输模式(通过 `__call__` 实现) | --- ## 工具函数 ### `get_domain(url: str) -> str` 从 URL 中提取域名(主机名),若 URL 不带协议则默认补全为 `http://`。 #### 参数: - `url` (str): 输入的 URL 字符串。 #### 返回值: - `str`: 提取出的域名部分(不包含端口和路径)。 #### 示例: ```python get_domain("https://example.com:8080/path") → "example.com" get_domain("example.org") → "example.org" (自动补全 http) ``` --- ## 异常类 ### `HttpError(code, msg)` 继承自 `Exception`,表示 HTTP 请求失败时的错误。 #### 属性: - `code` (int): HTTP 状态码。 - `msg` (str): 错误描述信息。 #### 方法: - `__str__()`: 返回格式化错误信息 `"Error Code:{code}, {msg}"`。 - `__expr__()`: 同 `__str__()`,兼容调试输出。 --- ## 核心类:`HttpClient` 一个异步 HTTP 客户端,支持 SOCKS5 代理、自动故障转移、Cookie 管理及域名黑名单持久化。 ### 初始化:`__init__(coding='utf-8', socks5_proxy_url=None)` #### 参数: - `coding` (str): 文本响应的字符编码,默认 `'utf-8'`。 - `socks5_proxy_url` (str or None): 可选的 SOCKS5 代理地址,如 `'socks5://localhost:1086'`。 #### 内部状态: - `session`: `aiohttp.ClientSession` 实例(延迟初始化)。 - `cookies`: 存储各域名 Cookie 的字典。 - `proxy_connector`: 当前使用的代理连接器。 - `blocked_domains`: 被标记为无法直连需走代理的域名集合(从 `.proxytarget` 文件加载)。 - `load_cache()`: 自动加载本地缓存的被屏蔽域名列表。 --- ### 方法说明 #### `save_cache()` 将当前 `blocked_domains` 集合保存到用户主目录下的 `~/.proxytarget` 文件中,每行一个域名。 #### `load_cache()` 从 `~/.proxytarget` 加载被屏蔽域名。如果文件不存在,则创建空文件。 #### `close()` 关闭当前会话(释放资源),协程方法。 #### `setCookie(url, cookies)` 根据 URL 设置对应域名的 Cookie。 #### `getCookies(url)` 获取指定 URL 所属域名的 Cookies。 #### `getsession(url)` 懒加载并返回 `ClientSession` 实例,启用 `unsafe=True` 的 CookieJar 以接受任意域的 Cookie。 #### `response_generator(url, resp)` 生成器函数,逐块返回响应内容(每次最多 1024 字节),同时更新 Cookie。 #### `response_handle(url, resp, resp_type, stream_func)` 根据 `resp_type` 类型处理响应体,并可选地调用 `stream_func` 处理流式数据。 | `resp_type` | 行为 | |-------------------|------| | `RESPONSE_BIN` | `await resp.read()` | | `RESPONSE_TEXT` | `await resp.text(encoding)` | | `RESPONSE_JSON` | `await resp.json()` | | 其他 / `None` | 忽略 | 若提供了 `stream_func`,则以 chunk 方式流式传递数据。 #### `grapCookie(url)` 从当前 Session 的 CookieJar 中提取特定域名的所有 Cookie。 #### `make_request(...)` 底层请求构造函数,支持 GET/POST 等方法,允许传入 `params`, `data`, `jd`(JSON 数据)、`headers`。 ##### 参数: - `url`: 请求地址。 - `method`: HTTP 方法(GET、POST 等)。 - `params`: 查询参数(dict)。 - `data`: 表单数据(bytes 或 dict)。 - `jd`: JSON 数据(会被设置为 `json=` 参数)。 - `headers`: 请求头。 - `use_proxy`: 是否使用 SOCKS5 代理。 > 若是 HTTPS 请求,自动添加由 `certifi` 提供的 CA 证书上下文。 #### `get_request_response(...)` 智能路由请求:先尝试直连;若失败且存在代理配置,则记录失败域名并改用代理重试。 ##### 特性: - 自动检测是否应绕过代理(不在 `blocked_domains` 中)。 - 第一次请求失败后,将域名加入 `blocked_domains` 并持久化。 - 支持异常捕获与日志输出。 #### `request(...)` 高层封装,发送请求并按 `response_type` 解析结果。 ##### 参数: - `response_type`: 控制返回类型(见全局常量)。 - `stream_func`: 可选的异步回调函数,用于处理流式数据块。 - 其余同 `make_request`。 ##### 返回: - 成功时返回相应类型的响应数据。 - 失败时抛出 `HttpError`。 #### `__call__(...)` 支持 `async for` 的流式调用方式,适合大文件下载或 Server-Sent Events 场景。 ##### 示例: ```python async for chunk in hc('https://example.com/stream'): print(chunk) ``` #### `get(url, **kw)` 和 `post(url, **kw)` 便捷方法,分别发起 GET 和 POST 请求。 --- ## 高级类:`JsonHttpAPI` 专为调用 RESTful JSON API 设计的模板驱动客户端。 ### 初始化:`__init__(env={}, socks5_proxy_url=None)` #### 参数: - `env` (dict): 全局变量环境,供模板渲染使用。 - `socks5_proxy_url` (str): 传递给底层 `HttpClient` 的代理设置。 #### 内部组件: - `te`: 使用 `MyTemplateEngine` 进行模板渲染。 - `hc`: 实例化的 `HttpClient`。 --- ### 方法说明 #### `stream_func(chunk)` 内部流处理器,用于处理换行分隔的 JSON 流(如 SSE)。**注意:代码中有拼写错误 `chuck` 应为 `chunk`**。 > ⚠️ Bug 提示:原代码中 `d = self.chunk_buffer + chuck` 应改为 `chunk`。 功能包括: - 缓冲数据并按 `\n` 分割。 - 尝试解析每条 JSON 消息。 - 使用 `resptmpl` 渲染响应。 - 调用用户提供的 `user_stream_func` 回调。 #### `chunk_handle(chunk, lead, end)` 钩子函数,可用于预处理每个数据块(例如去除前缀、添加结束标记)。默认直接返回原块。 #### `__call__(...)` 异步生成器接口,支持流式 API 调用。 ##### 参数: - `url`: API 地址。 - `method`: 请求方法。 - `ns`: 当前命名空间变量(优先级高于 `env`)。 - `headerstmpl`: 请求头的 JSON 模板(字符串形式的 JSON + 模板语法)。 - `paramstmpl`: 查询参数模板。 - `datatmpl`: 请求体模板(JSON 字符串含变量)。 - `chunk_leading`, `chunk_end`: 分块控制标记。 - `resptmpl`: 响应数据的输出模板。 ##### 流程: 1. 合并 `env` 与 `ns` 得到上下文。 2. 渲染各个模板(headers/params/data)。 3. 发起流式请求。 4. 分块处理响应,可结合 `resptmpl` 动态转换输出。 ##### 输出: - `yield` 经过处理和模板渲染后的每一“段”数据。 #### `call(...)` 同步风格的调用入口(实际仍是 `await` 协程),支持非流式和流式两种模式。 ##### 参数: - `stream_func`: 用户自定义流处理函数(接收 JSON 对象)。 - 其他同 `__call__`。 ##### 返回: - 若无 `resptmpl`:原始 JSON 响应。 - 若有 `resptmpl`:经模板渲染后再反序列化的 JSON 结果。 --- ## 使用示例 ### 1. 基础请求(主程序测试) ```python async def main(): hc = HttpClient(socks5_proxy_url='socks5://localhost:1086') # 流式读取百度首页 async for d in hc('https://www.baidu.com'): print(d) # 获取 Google 主页文本 r = await hc.request('https://www.google.com') print(r) await hc.close() if __name__ == '__main__': loop = asyncio.new_event_loop() loop.run_until_complete(main()) ``` ### 2. 使用 `JsonHttpAPI` 调用模板化 API ```python api = JsonHttpAPI(env={'token': 'abc123'}, socks5_proxy_url='socks5://127.0.0.1:1086') # 定义模板 headers_tmpl = '{"Authorization": "Bearer {{token}}"}' params_tmpl = '{"page": "{{page}}"}' async def on_chunk(data): print("Received:", data) result = await api.call( url="https://api.example.com/data", method="GET", ns={"page": 1}, headerstmpl=headers_tmpl, paramstmpl=params_tmpl, stream_func=on_chunk ) ``` --- ## 注意事项与建议 ### ✅ 优点 - 支持异步高并发。 - 内建代理自动切换机制。 - Cookie 自动管理。 - 模板化请求构建,适合复杂 API 集成。 - 黑名单域名持久化,避免重复探测。 ### ❗ 已知问题 / 改进建议 1. **Bug:`stream_func` 中 `chuck` 拼写错误** ```python d = self.chunk_buffer + chuck # 应为 chunk ``` 2. **`jd` 参数未正确使用** - 在 `make_request` 中设置了 `hp['jd'] = jd`,但 `aiohttp.request()` 不识别 `jd`。 - 应改为 `hp['json'] = jd`。 3. **`datatmpl` 中 multipart 注释未启用** - 当前行被注释,导致无法上传文件。 - 如需支持 form-data,应取消注释并修复逻辑。 4. **`chunk_handle` 接口设计模糊** - 当前仅作占位,建议明确其用途(如过滤、转换、拼接等)。 5. **安全性考虑** - `unsafe=True` 的 CookieJar 可能带来安全风险,建议限制作用域。 - 模板渲染可能引入注入风险,建议对输入做校验。 6. **日志级别使用建议** - `info(f'{headers=}...')` 输出敏感信息(如 token),建议降级为 `debug`。 --- ## 总结 本模块是一个功能完整的异步 HTTP 客户端解决方案,特别适用于以下场景: - 需要通过 SOCKS5 代理访问受限资源。 - 面向多个 JSON API 的自动化集成。 - 支持流式响应(如聊天机器人、事件流)。 - 具备一定的容错和自适应能力。 配合模板引擎,可以实现高度可配置的 API 调用系统,适合作为微服务网关、爬虫框架或自动化测试工具的基础组件。 --- ## 版本信息 - 作者:未知 - 最后修改时间:根据代码推断为近期开发 - 兼容性:Python 3.7+ > 建议增加版本号字段和单元测试覆盖。 --- ✅ **文档完成**