apppublic/aidocs/httpclient.md
2025-10-05 11:23:33 +08:00

363 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# `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+
> 建议增加版本号字段和单元测试覆盖。
---
✅ **文档完成**