ahserver/aidocs/baseProcessor.md
2025-10-05 12:07:12 +08:00

399 lines
11 KiB
Markdown
Raw Permalink 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.

# 技术文档处理器框架Processor Framework
本文档描述了一个基于 `aiohttp` 的异步 Web 处理器系统支持多种资源类型如模板、脚本、Markdown 等)的动态处理。该系统通过继承和多态机制实现不同类型的处理器,并结合运行时环境注入与缓存策略提升性能。
---
## 📦 模块概览
```python
import os
import re
import json
import codecs
import aiofiles
from aiohttp.web_request import Request
from aiohttp.web_response import Response, StreamResponse
from appPublic.jsonConfig import getConfig
from appPublic.dictObject import DictObject
from appPublic.folderUtils import listFile
from appPublic.argsConvert import ArgsConvert
from appPublic.log import info, debug, warning, error, critical, exception
from .utils import unicode_escape
from .serverenv import ServerEnv
from .filetest import current_fileno
```
### 依赖说明
| 包/模块 | 用途 |
|--------|------|
| `aiohttp` | 提供异步请求/响应对象 |
| `appPublic.*` | 自定义公共库:配置读取、字典封装、日志等 |
| `aiofiles` | 异步文件操作 |
| `json`, `re` | 数据序列化与正则替换 |
---
## 🔁 核心组件
### 1. `ObjectCache` 类
用于缓存已加载的对象,避免重复解析或编译开销。
#### 方法
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `__init__()` | - | `ObjectCache` 实例 | 初始化空缓存字典 |
| `store(path, obj)` | `path: str`, `obj: Any` | `None` | 将对象按路径存储记录其最后修改时间mtime |
| `get(path)` | `path: str` | `Any or None` | 若文件未被修改则返回缓存对象,否则返回 `None` |
> ⚠️ 缓存失效判断依据:`os.path.getmtime(path) > cached_mtime`
#### 示例用法
```python
cache = ObjectCache()
cache.store('/path/to/file.py', compiled_code)
obj = cache.get('/path/to/file.py') # 如果文件未变,返回缓存对象
```
---
### 2. `BaseProcessor` 基类
所有处理器的基类,提供通用处理流程和响应构造能力。
#### 属性
| 属性 | 类型 | 初始值 | 说明 |
|------|------|-------|------|
| `path` | `str` | 构造传入 | 请求对应的虚拟路径 |
| `resource` | `Resource` 对象 | 构造传入 | 资源管理器实例 |
| `retResponse` | `Response or None` | `None` | 可直接返回的自定义响应对象 |
| `headers` | `dict` | 默认头信息 | 响应头字段集合 |
| `content` | `str / dict / bytes / ...` | `''` | 处理后的输出内容 |
| `env_set` | `bool` | `False` | 是否已设置运行环境 |
| `real_path` | `str` | 动态设置 | 映射到本地文件系统的实际路径 |
#### 静态方法
##### `isMe(name: str) -> bool`
- **用途**:判断当前处理器是否匹配给定名称。
- **默认实现**:仅当 `name == 'base'` 时返回 `True`
- 子类需重写此方法以支持识别。
#### 异步方法
##### `be_call(request: Request, params: dict = {}) -> Response`
调用入口,最终返回 HTTP 响应。
```python
return await self.path_call(request, params)
```
##### `set_run_env(request: Request, params: dict = {}) -> None`
初始化运行命名空间(`run_ns`),注入上下文变量。
###### 注入变量包括:
| 变量名 | 来源 | 说明 |
|--------|------|------|
| `request` | 参数 | 当前请求对象 |
| `app` | `request.app` | 应用实例 |
| `params_kw` | 合并参数 | 来自 URL 参数和外部传参 |
| `ref_real_path` | 映射结果 | 文件系统真实路径 |
| `processor` | `self` | 当前处理器引用 |
| 其他全局环境 | `ServerEnv().to_dict()``resource.y_env` | 服务端环境配置 |
> ✅ 支持 `request2ns()` 扩展函数将请求参数转换为命名空间。
##### `execute(request: Request) -> str`
执行主逻辑链:
```python
await self.set_run_env()
await self.datahandle() # 子类实现具体处理
return self.content
```
##### `handle(request: Request) -> Response`
生成最终 HTTP 响应对象,自动处理不同类型的内容输出。
###### 内容类型自动检测与转换:
| 输入类型 | 输出行为 |
|---------|----------|
| `Response` / `StreamResponse` | 直接返回 |
| `dict`, `list`, `tuple`, `DictObject` | JSON 序列化,设置 `Content-Type: application/json` |
| `bytes` | 设置二进制响应体 |
| 字符串且合法 JSON | 视为 JSON 输出 |
| 其他字符串 | 普通文本响应 |
> ✅ 自动添加 CORS 相关头部(暴露 Set-Cookie
##### `datahandle(request: Request)`
**抽象方法**,子类必须实现具体的业务逻辑。
默认实现打印错误日志并清空内容。
##### `setheaders()`
可选覆写方法,用于在响应前设置额外头部(如 Content-Length。目前留空。
---
## 🧩 处理器子类
### 1. `TemplateProcessor(BaseProcessor)`
用于渲染 `.tmpl` 模板文件。
#### 特性
- 使用 `tmpl_engine` 渲染模板
- 支持扩展名决定内容类型(`.tmpl.css`, `.tmpl.js`
#### 关键方法
##### `isMe(name)`
返回 `True``name == 'tmpl'`
##### `path_call(request, params)`
使用模板引擎渲染指定路径模板。
```python
te = self.run_ns['tmpl_engine']
return await te.render(path, **ns)
```
##### `datahandle(request)`
调用 `path_call` 获取模板渲染结果。
##### `setheaders()`
根据文件后缀设置正确的 MIME 类型:
- `.tmpl.css``text/css`
- `.tmpl.js``application/javascript`
- 其他 → `text/html`
---
### 2. `BricksAppProcessor(TemplateProcessor)`
包装应用级模板,嵌入主布局框架。
#### 特性
- 继承模板功能
- 将原始模板内容插入到 `bricksapp.tmpl` 主容器中
#### `datahandle(request)`
1. 先调用父类渲染原始模板 → `txt`
2. 加载 `/bricks/bricksapp.tmpl` 作为外壳
3. 使用 `ArgsConvert("${", "}$")` 替换 `${appdic}` 占位符为 `txt`
```python
ac = ArgsConvert("${", "}$")
self.content = ac.convert(template_wrapper, {'appdic': txt})
```
#### `isMe(name)`
返回 `True``name == 'app'`
---
### 3. `BricksUIProcessor(TemplateProcessor)`
条件性包裹页面 UI 结构header + content + footer
#### 行为逻辑
检查参数 `_webbricks_`
- 若不存在或等于 `0` → 不包装,直接保留原内容
- 否则 → 包装 header 和 footer 模板
#### `datahandle(request)`
```python
if should_wrap:
header = await resource.path_call(... '/header.tmpl')
footer = await resource.path_call(... '/footer.tmpl')
self.content = f"{header}{original}{footer}"
```
#### `isMe(name)`
返回 `True``name == 'bui'`
---
### 4. `PythonScriptProcessor(BaseProcessor)`
执行 Python 异步脚本(`.dspy` 文件)
#### 特性
-`.dspy` 文件内容包装成 `async def myfunc(request, **ns): ...`
- 在安全命名空间内 `exec()` 执行
- 调用并返回结果
#### 关键方法
##### `loadScript(path)`
异步读取脚本内容,每行前加缩进 `\t`,拼接为函数体字符串。
> 💡 注意:`\r\n` 被统一处理为 `\n`
##### `path_call(request, params)`
1. 准备运行环境 `lenv`(移除 `request` 防止污染)
2. 加载并编译脚本代码
3. `exec()` 注入命名空间
4. 调用 `myfunc(request, **lenv)` 并返回结果
##### `datahandle(request)`
调用 `path_call` 获取脚本执行结果并赋值给 `content`
##### `isMe(name)`
返回 `True``name == 'dspy'`
---
### 5. `MarkdownProcessor(BaseProcessor)`
渲染 Markdown 文件,并自动重写链接为完整 URL。
#### 特性
- 支持内联 `[text](url)` 链接自动补全域名
- 使用 `resource.entireUrl()` 解析相对路径
#### 方法
##### `datahandle(request)`
异步读取 `.md` 文件内容,调用 `urlreplace()` 处理链接。
##### `urlreplace(mdtxt, request)`
使用正则替换所有 Markdown 链接:
```regex
\[(.*)\]\((.*)\)
```
替换为:
```text
[显示文本](完整URL)
```
示例:
```md
[首页](/home) → [首页](https://example.com/home)
```
##### `isMe(name)`
返回 `True``name == 'md'`
---
## 🔄 工厂函数:处理器查找
### `getProcessor(name: str) -> Type[BaseProcessor] or None`
根据名称递归查找匹配的处理器类。
#### 实现逻辑
```python
def getProcessor(name):
return _getProcessor(BaseProcessor, name)
def _getProcessor(kclass, name):
for subclass in kclass.__subclasses__():
if hasattr(subclass, 'isMe') and subclass.isMe(name):
return subclass
# 递归搜索子类
found = _getProcessor(subclass, name)
if found:
return found
return None
```
#### 示例
```python
cls = getProcessor('tmpl') # → TemplateProcessor
cls = getProcessor('dspy') # → PythonScriptProcessor
cls = getProcessor('unknown') # → None
```
> ✅ 支持深度继承结构下的处理器发现
---
## 🛠️ 设计亮点
| 特性 | 描述 |
|------|------|
| **插件式架构** | 通过 `isMe()` + 继承实现处理器注册与发现 |
| **运行环境隔离** | 每个请求独立 `run_ns`,防止状态污染 |
| **异步友好** | 全面使用 `async/await`,适配 `aiohttp` 生态 |
| **内容类型智能推导** | 自动判断输出类型并设置正确 `Content-Type` |
| **CORS 支持** | 默认暴露 `Set-Cookie` 头部 |
| **模板嵌套机制** | 支持布局包装BricksApp/UI |
| **动态脚本执行** | 安全沙箱模式执行用户脚本(谨慎使用) |
---
## ⚠️ 安全建议
1. **慎用 `PythonScriptProcessor`**
- `exec()` 存在严重安全风险
- 建议限制 `.dspy` 文件访问权限或禁用生产环境使用
2. **输入验证**
- 所有来自客户端的参数应进行严格校验
- 防止路径遍历攻击(如 `../../etc/passwd`
3. **CORS 策略**
- 当前只暴露 `Set-Cookie`,但未启用 `Allow-Credentials``Allow-Origin`
- 如需跨域,请在 `set_response_headers` 中补充策略
---
## 📎 总结
本模块构建了一个灵活、可扩展的异步 Web 资源处理框架,适用于:
- 模板渲染服务
- 动态 Markdown 页面
- 内嵌脚本执行(开发调试场景)
- 布局嵌套系统Bricks 架构)
通过简单的继承与注册机制,开发者可以轻松扩展新的处理器类型,满足多样化的前端集成需求。
---
> 📁 **建议目录结构参考**
```
/templates/
home.tmpl
bricks/
bricksapp.tmpl
header.tmpl
footer.tmpl
/scripts/
api.dspy
/pages/
about.md
/static/
...
```
> 📄 **路由映射示意**
| URL Path | Processor | Name Hint |
|---------|-----------|----------|
| `/app/home` | BricksAppProcessor | `app` |
| `/bui/dashboard` | BricksUIProcessor | `bui` |
| `/api/data.dspy` | PythonScriptProcessor | `dspy` |
| `/doc/intro.md` | MarkdownProcessor | `md` |
| `/view/page.tmpl` | TemplateProcessor | `tmpl` |
---
📝 *文档版本v1.0*
📅 *最后更新2025-04-05*