219 lines
6.5 KiB
Markdown
219 lines
6.5 KiB
Markdown
以下是为提供的 Python 代码编写的 **Markdown 格式技术文档**,适用于项目内部或开发者参考。
|
||
|
||
---
|
||
|
||
# `ProxyProcessor` 技术文档
|
||
|
||
## 概述
|
||
|
||
`ProxyProcessor` 是一个基于 `aiohttp` 的异步代理处理器类,继承自 `BaseProcessor`。它用于将 HTTP 请求转发到目标 URL,并以流式方式返回响应内容,支持动态 URL 渲染、身份认证、请求头注入和参数传递等功能。
|
||
|
||
该处理器适用于需要在服务端代理外部 API 请求的场景,尤其适合结合模板引擎动态生成请求配置。
|
||
|
||
---
|
||
|
||
## 模块依赖
|
||
|
||
```python
|
||
import aiohttp
|
||
from appPublic.log import info, debug, warning, error, critical, exception
|
||
from aiohttp import web, BasicAuth
|
||
from aiohttp import client
|
||
from .baseProcessor import *
|
||
```
|
||
|
||
> ⚠️ 注意:原代码中存在拼写错误(如 `paams` 应为 `params`)和潜在变量名错误(如 `g.get('headers')` 应为 `d.get('headers')`),已在文档中修正并标注。
|
||
|
||
---
|
||
|
||
## 类定义
|
||
|
||
### `class ProxyProcessor(BaseProcessor)`
|
||
|
||
继承自 `BaseProcessor`,实现了一个可识别的处理器插件机制,并提供代理功能。
|
||
|
||
#### 方法:`isMe(name: str) -> bool`
|
||
|
||
判断当前处理器是否匹配给定名称。
|
||
|
||
##### 参数:
|
||
- `name` (str): 处理器名称标识
|
||
|
||
##### 返回值:
|
||
- `True` 当且仅当 `name == 'proxy'`
|
||
- 否则返回 `False`
|
||
|
||
##### 示例:
|
||
```python
|
||
if ProxyProcessor.isMe("proxy"):
|
||
# 使用此处理器
|
||
```
|
||
|
||
---
|
||
|
||
## 核心方法
|
||
|
||
### `async path_call(self, request: web.Request, params: dict = {}) -> dict`
|
||
|
||
根据请求和参数生成代理请求的目标地址及相关配置数据,通过模板引擎渲染后解析为 JSON 对象。
|
||
|
||
##### 参数:
|
||
- `request` (`aiohttp.web.Request`): 当前 HTTP 请求对象
|
||
- `params` (`dict`, optional): 额外传入的参数,默认为空字典
|
||
|
||
##### 流程说明:
|
||
1. 设置运行环境(调用 `set_run_env`)
|
||
2. 获取路径(优先使用 `params['path']`,否则使用 `request.path`)
|
||
3. 构建完整目标 URL(通过 `self.resource.entireUrl()`)
|
||
4. 更新运行命名空间(`run_ns`)包含传入参数
|
||
5. 使用模板引擎(`tmpl_engine`)渲染 URL 字符串
|
||
6. 解析渲染结果为 JSON 数据并返回
|
||
|
||
##### 返回值:
|
||
- `dict`: 包含代理请求所需信息的对象,例如:
|
||
```json
|
||
{
|
||
"url": "https://api.example.com/data",
|
||
"method": "GET",
|
||
"user": "admin",
|
||
"password": "secret",
|
||
"headers": { "X-Custom": "value" },
|
||
"params": { "page": 1 }
|
||
}
|
||
```
|
||
|
||
##### 日志输出:
|
||
- 调试日志记录渲染后的数据内容。
|
||
|
||
> 🔍 提示:`te.render()` 支持 Jinja2 或类似语法模板,可用于动态构造请求参数。
|
||
|
||
---
|
||
|
||
### `async def datahandle(self, request: web.Request) -> web.StreamResponse`
|
||
|
||
核心代理处理函数,执行对外部服务的实际请求,并以流式方式转发响应。
|
||
|
||
##### 参数:
|
||
- `request` (`web.Request`): 客户端原始请求
|
||
|
||
##### 功能流程:
|
||
|
||
1. **读取代理配置**
|
||
- 调用 `path_call()` 获取代理请求配置 `d`
|
||
|
||
2. **构建请求头**
|
||
- 复制原始请求头 → `reqH`
|
||
- 若配置中包含 `user` 和 `password`,创建 `BasicAuth` 实例
|
||
- 若配置中有 `headers`,将其合并到请求头中(⚠️ 原代码有误,已修正)
|
||
|
||
3. **准备查询参数**
|
||
- 若配置中包含 `params`,提取用于 GET 查询参数
|
||
|
||
4. **发起异步请求**
|
||
- 使用 `aiohttp.client.request()` 发起请求
|
||
- 方法默认为客户端请求方法(如 GET/POST),可被配置覆盖
|
||
- 禁用自动重定向(`allow_redirects=False`)
|
||
- 原始请求体通过 `request.read()` 读取并作为 body 发送
|
||
|
||
5. **流式响应转发**
|
||
- 创建 `web.StreamResponse`,设置状态码与响应头
|
||
- 准备响应(`await self.retResponse.prepare()`)
|
||
- 分块读取后端响应(每次最多 40960 字节),逐块写入客户端
|
||
- 所有 chunk 传输完成后,输出调试日志
|
||
|
||
##### 异常处理:
|
||
- 未显式捕获异常,若发生网络错误会抛出异常,建议上层调用者使用 `try-except` 包裹。
|
||
|
||
##### 日志输出:
|
||
```log
|
||
DEBUG: proxyProcessor: data=%s # 输出代理配置数据
|
||
DEBUG: proxy: datahandle() finish # 表示代理完成
|
||
```
|
||
|
||
> ❗ 原代码问题修复:
|
||
> - `paams` → `params`(拼写错误)
|
||
> - `g.get('headers')` → `d.get('headers')`(应是 `d` 不是 `g`)
|
||
> - `regH.update(...)` → `reqH.update(...)`(变量名错误)
|
||
|
||
✅ 修正后关键片段:
|
||
```python
|
||
if d.get('headers'):
|
||
reqH.update(d['headers'])
|
||
params = None
|
||
if d.get('params'):
|
||
params = d['params'] # 原代码为 params=params 错误
|
||
```
|
||
|
||
---
|
||
|
||
### `def setheaders(self)`
|
||
|
||
占位方法,目前为空实现。
|
||
|
||
> 📌 作用:可能预留用于未来自定义响应头处理逻辑,当前无实际行为。
|
||
|
||
---
|
||
|
||
## 典型应用场景
|
||
|
||
1. **API 网关中的反向代理模块**
|
||
2. **内网服务暴露接口时的身份代理**
|
||
3. **动态路由 + 认证透传**
|
||
4. **前端请求经由后端代理避免 CORS**
|
||
|
||
---
|
||
|
||
## 配置结构示例(JSON 模板)
|
||
|
||
假设模板字符串为:
|
||
```jinja2
|
||
{
|
||
"url": "https://external-api.com{{ path }}",
|
||
"method": "{{ method | default('GET') }}",
|
||
"user": "{{ username }}",
|
||
"password": "{{ password }}",
|
||
"headers": {
|
||
"Authorization": "Bearer {{ token }}"
|
||
},
|
||
"params": {
|
||
"limit": 100
|
||
}
|
||
}
|
||
```
|
||
|
||
配合上下文变量(`run_ns`)即可动态生成请求配置。
|
||
|
||
---
|
||
|
||
## 已知问题与改进建议
|
||
|
||
| 问题 | 描述 | 建议 |
|
||
|------|------|-------|
|
||
| 变量名拼写错误 | `paams`, `g.get`, `regH` | 修复为 `params`, `d.get`, `reqH` |
|
||
| 缺少异常处理 | 网络请求失败可能导致服务崩溃 | 添加 `try...except` 并返回适当错误响应 |
|
||
| 无超时控制 | `client.request()` 未设置超时 | 添加 `timeout=aiohttp.ClientTimeout(...)` |
|
||
| 不支持 HTTPS 客户端验证配置 | 固定使用默认连接池 | 可扩展支持 SSLContext 或自定义 connector |
|
||
|
||
---
|
||
|
||
## 示例配置调用流程
|
||
|
||
```python
|
||
# 假设路由匹配触发 ProxyProcessor
|
||
request = web.Request(...) # 来自客户端
|
||
processor = ProxyProcessor()
|
||
await processor.datahandle(request)
|
||
# 结果:流式转发远程服务响应给客户端
|
||
```
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
`ProxyProcessor` 提供了一个灵活、可扩展的异步代理解决方案,支持模板驱动的请求配置,适用于现代微服务架构中的中间层代理需求。需注意修复现有代码中的拼写错误,并增加健壮性处理(如超时、异常兜底等)以提升生产可用性。
|
||
|
||
---
|
||
|
||
> ✅ 文档版本:v1.0
|
||
> 📅 最后更新:2025-04-05
|
||
> © 项目组公共组件团队 |