初始提交 - FutureOSS v1.0 插件化运行时框架

一切皆为插件的开发者工具运行时框架

🧩 核心特性:
  - 插件热插拔 (importlib 动态加载)
  - 依赖自动解析 (拓扑排序 + 循环检测)
  - 企业级稳定 (熔断/降级/重试/隔离)
  - 事件驱动 (发布/订阅事件总线)
  - 完整配置 (YAML 配置 + 热重载)
This commit is contained in:
Falck
2026-04-06 09:57:10 +08:00
commit 76147bae94
174 changed files with 15626 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
# http-tcp HTTP TCP 服务
提供基于 TCP 的 HTTP 协议实现。
## 功能
- 原始 TCP HTTP 服务器
- 路由匹配
- 中间件链(日志/CORS
- 连接管理
- 事件发布(通过 plugin-bridge
## 使用
```python
tcp = plugin_mgr.get("http-tcp")
# 注册路由
tcp.router.get("/api/status", lambda req: {
"status": 200,
"headers": {"Content-Type": "application/json"},
"body": '{"status": "ok"}'
})
# 获取客户端
clients = tcp.server.get_clients()
```
## 事件
```python
bridge = plugin_mgr.get("plugin-bridge")
bus = bridge.event_bus
bus.on("tcp.connect", lambda e: print(f"连接: {e.client.id}"))
bus.on("tcp.http.request", lambda e: print(f"请求: {e.context['request']['path']}"))
bus.on("tcp.disconnect", lambda e: print(f"断开: {e.client.id}"))
```
## 配置
```json
{
"config": {
"args": {
"host": "0.0.0.0",
"port": 8082
}
}
}
```

View File

@@ -0,0 +1,21 @@
"""HTTP TCP 事件定义"""
from dataclasses import dataclass, field
from typing import Any
@dataclass
class TcpEvent:
"""TCP 事件"""
type: str
client: Any = None
data: bytes = b""
context: dict[str, Any] = field(default_factory=dict)
# 事件类型常量
EVENT_CONNECT = "tcp.connect"
EVENT_DISCONNECT = "tcp.disconnect"
EVENT_DATA = "tcp.data"
EVENT_REQUEST = "tcp.http.request"
EVENT_RESPONSE = "tcp.http.response"
EVENT_ERROR = "tcp.error"

View File

@@ -0,0 +1,34 @@
"""HTTP TCP 插件入口"""
from oss.plugin.types import Plugin, register_plugin_type
from .server import TcpHttpServer
from .router import TcpRouter
from .middleware import TcpMiddlewareChain
class HttpTcpPlugin(Plugin):
"""HTTP TCP 插件"""
def __init__(self):
self.server = None
self.router = TcpRouter()
self.middleware = TcpMiddlewareChain()
def init(self, deps: dict = None):
"""初始化"""
self.server = TcpHttpServer(self.router, self.middleware)
def start(self):
"""启动"""
self.server.start()
def stop(self):
"""停止"""
if self.server:
self.server.stop()
register_plugin_type("HttpTcpPlugin", HttpTcpPlugin)
def New():
return HttpTcpPlugin()

View File

@@ -0,0 +1,18 @@
{
"metadata": {
"name": "http-tcp",
"version": "1.0.0",
"author": "FutureOSS",
"description": "HTTP TCP 服务 - 基于 TCP 的 HTTP 协议实现",
"type": "protocol"
},
"config": {
"enabled": true,
"args": {
"host": "0.0.0.0",
"port": 8082
}
},
"dependencies": [],
"permissions": []
}

View File

@@ -0,0 +1,53 @@
"""TCP HTTP 中间件链"""
from typing import Callable, Optional, Any
class TcpMiddleware:
"""TCP 中间件基类"""
def process(self, request: dict, next_fn: Callable) -> Optional[dict]:
"""处理请求"""
return next_fn()
class TcpLogMiddleware(TcpMiddleware):
"""日志中间件"""
def process(self, request, next_fn):
print(f"[http-tcp] {request.get('method')} {request.get('path')}")
return next_fn()
class TcpCorsMiddleware(TcpMiddleware):
"""CORS 中间件"""
def process(self, request, next_fn):
response = next_fn()
if response:
response.setdefault("headers", {})
response["headers"]["Access-Control-Allow-Origin"] = "*"
return response
class TcpMiddlewareChain:
"""TCP 中间件链"""
def __init__(self):
self.middlewares: list[TcpMiddleware] = []
self.add(TcpLogMiddleware())
self.add(TcpCorsMiddleware())
def add(self, middleware: TcpMiddleware):
"""添加中间件"""
self.middlewares.append(middleware)
def run(self, request: dict) -> Optional[dict]:
"""执行中间件链"""
idx = 0
def next_fn():
nonlocal idx
if idx < len(self.middlewares):
mw = self.middlewares[idx]
idx += 1
return mw.process(request, next_fn)
return None
return next_fn()

View File

@@ -0,0 +1,63 @@
"""TCP HTTP 路由器"""
from typing import Callable, Optional, Any
class TcpRoute:
"""TCP HTTP 路由"""
def __init__(self, method: str, path: str, handler: Callable):
self.method = method
self.path = path
self.handler = handler
class TcpRouter:
"""TCP HTTP 路由器"""
def __init__(self):
self.routes: list[TcpRoute] = []
def add(self, method: str, path: str, handler: Callable):
"""添加路由"""
self.routes.append(TcpRoute(method, path, handler))
def get(self, path: str, handler: Callable):
"""GET 路由"""
self.add("GET", path, handler)
def post(self, path: str, handler: Callable):
"""POST 路由"""
self.add("POST", path, handler)
def put(self, path: str, handler: Callable):
"""PUT 路由"""
self.add("PUT", path, handler)
def delete(self, path: str, handler: Callable):
"""DELETE 路由"""
self.add("DELETE", path, handler)
def handle(self, request: dict) -> dict:
"""处理请求"""
method = request.get("method", "GET")
path = request.get("path", "/")
for route in self.routes:
if route.method == method and self._match(route.path, path):
return route.handler(request)
return {"status": 404, "headers": {}, "body": "Not Found"}
def _match(self, pattern: str, path: str) -> bool:
"""路径匹配"""
if pattern == path:
return True
if ":" in pattern:
pattern_parts = pattern.strip("/").split("/")
path_parts = path.strip("/").split("/")
if len(pattern_parts) != len(path_parts):
return False
for p, a in zip(pattern_parts, path_parts):
if not p.startswith(":") and p != a:
return False
return True
return False

View File

@@ -0,0 +1,193 @@
"""TCP HTTP 服务器核心"""
import socket
import threading
import re
from typing import Any, Callable, Optional
from .events import TcpEvent, EVENT_CONNECT, EVENT_DISCONNECT, EVENT_DATA, EVENT_REQUEST, EVENT_RESPONSE
class TcpClient:
"""TCP 客户端连接"""
def __init__(self, conn: socket.socket, address: tuple):
self.conn = conn
self.address = address
self.id = f"{address[0]}:{address[1]}"
def send(self, data: bytes):
"""发送数据"""
self.conn.sendall(data)
def close(self):
"""关闭连接"""
self.conn.close()
class TcpHttpServer:
"""TCP HTTP 服务器"""
def __init__(self, router, middleware, event_bus=None, host="0.0.0.0", port=8082):
self.host = host
self.port = port
self.router = router
self.middleware = middleware
self.event_bus = event_bus
self._server = None
self._thread = None
self._running = False
self._clients: dict[str, TcpClient] = {}
def start(self):
"""启动服务器"""
self._server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._server.bind((self.host, self.port))
self._server.listen(128)
self._running = True
self._thread = threading.Thread(target=self._accept_loop, daemon=True)
self._thread.start()
print(f"[http-tcp] 服务器启动: {self.host}:{self.port}")
def _accept_loop(self):
"""接受连接循环"""
while self._running:
try:
conn, address = self._server.accept()
client = TcpClient(conn, address)
self._clients[client.id] = client
# 触发连接事件
if self.event_bus:
self.event_bus.emit(TcpEvent(type=EVENT_CONNECT, client=client))
# 启动处理线程
t = threading.Thread(target=self._handle_client, args=(client,), daemon=True)
t.start()
except Exception as e:
if self._running:
print(f"[http-tcp] 接受连接失败: {e}")
def _handle_client(self, client: TcpClient):
"""处理客户端请求"""
buffer = b""
try:
while self._running:
data = client.conn.recv(4096)
if not data:
break
buffer += data
# 检查 HTTP 请求是否完整
if b"\r\n\r\n" in buffer:
request = self._parse_request(buffer)
if request:
# 触发请求事件
if self.event_bus:
self.event_bus.emit(TcpEvent(
type=EVENT_REQUEST,
client=client,
context={"request": request}
))
# 路由处理
response = self.router.handle(request)
# 发送响应
response_bytes = self._format_response(response)
client.send(response_bytes)
# 触发响应事件
if self.event_bus:
self.event_bus.emit(TcpEvent(
type=EVENT_RESPONSE,
client=client,
data=response_bytes
))
buffer = b""
except Exception as e:
if self.event_bus:
self.event_bus.emit(TcpEvent(type=EVENT_ERROR, client=client, context={"error": str(e)}))
finally:
del self._clients[client.id]
client.close()
if self.event_bus:
self.event_bus.emit(TcpEvent(type=EVENT_DISCONNECT, client=client))
def _parse_request(self, data: bytes) -> Optional[dict]:
"""解析 HTTP 请求"""
try:
text = data.decode("utf-8", errors="replace")
lines = text.split("\r\n")
if not lines:
return None
# 解析请求行
match = re.match(r'(\w+)\s+(\S+)\s+HTTP/(\d\.\d)', lines[0])
if not match:
return None
method, path, version = match.groups()
# 解析头
headers = {}
body_start = 0
for i, line in enumerate(lines[1:], 1):
if line == "":
body_start = i + 1
break
if ":" in line:
key, value = line.split(":", 1)
headers[key.strip()] = value.strip()
# 解析体
content_length = int(headers.get("Content-Length", 0))
body = "\r\n".join(lines[body_start:]) if body_start else ""
return {
"method": method,
"path": path,
"version": version,
"headers": headers,
"body": body,
}
except Exception:
return None
def _format_response(self, response: dict) -> bytes:
"""格式化 HTTP 响应"""
status = response.get("status", 200)
headers = response.get("headers", {})
body = response.get("body", "")
status_text = {200: "OK", 404: "Not Found", 500: "Internal Server Error"}.get(status, "OK")
response_lines = [
f"HTTP/1.1 {status} {status_text}",
]
if "Content-Type" not in headers:
headers["Content-Type"] = "text/plain; charset=utf-8"
headers["Content-Length"] = str(len(body))
for key, value in headers.items():
response_lines.append(f"{key}: {value}")
response_lines.append("")
response_lines.append(body)
return "\r\n".join(response_lines).encode("utf-8")
def stop(self):
"""停止服务器"""
self._running = False
for client in self._clients.values():
client.close()
if self._server:
self._server.close()
print("[http-tcp] 服务器已停止")
def get_clients(self) -> list[TcpClient]:
"""获取所有客户端"""
return list(self._clients.values())