⚡ 初始提交 - FutureOSS v1.0 插件化运行时框架
一切皆为插件的开发者工具运行时框架
🧩 核心特性:
- 插件热插拔 (importlib 动态加载)
- 依赖自动解析 (拓扑排序 + 循环检测)
- 企业级稳定 (熔断/降级/重试/隔离)
- 事件驱动 (发布/订阅事件总线)
- 完整配置 (YAML 配置 + 热重载)
This commit is contained in:
53
store/@{FutureOSS}/http-api/README.md
Normal file
53
store/@{FutureOSS}/http-api/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# http-api HTTP API 服务
|
||||
|
||||
提供 HTTP RESTful API 服务,支持路由、中间件等功能。
|
||||
|
||||
## 功能
|
||||
|
||||
- HTTP 服务器(GET/POST/PUT/DELETE)
|
||||
- 路由匹配(支持参数路由 `:id`)
|
||||
- 中间件链(CORS/日志/限流)
|
||||
- 分散式布局(每个文件 < 200 行)
|
||||
|
||||
## 路由使用
|
||||
|
||||
```python
|
||||
# 在插件中获取 router
|
||||
http_plugin = plugin_mgr.get("http-api")
|
||||
router = http_plugin.router
|
||||
|
||||
# 添加路由
|
||||
router.get("/health", lambda req: Response(status=200, body='{"status": "ok"}'))
|
||||
router.get("/api/users", handle_users)
|
||||
router.post("/api/users", handle_create_user)
|
||||
router.get("/api/users/:id", handle_user_by_id)
|
||||
```
|
||||
|
||||
## 中间件
|
||||
|
||||
```python
|
||||
middleware = http_plugin.middleware
|
||||
|
||||
# 添加自定义中间件
|
||||
class MyMiddleware(Middleware):
|
||||
def process(self, ctx, next_fn):
|
||||
# 前置处理
|
||||
resp = next_fn() # 继续执行
|
||||
# 后置处理
|
||||
return resp
|
||||
|
||||
middleware.add(MyMiddleware())
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"args": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
BIN
store/@{FutureOSS}/http-api/__pycache__/events.cpython-313.pyc
Normal file
BIN
store/@{FutureOSS}/http-api/__pycache__/events.cpython-313.pyc
Normal file
Binary file not shown.
BIN
store/@{FutureOSS}/http-api/__pycache__/main.cpython-313.pyc
Normal file
BIN
store/@{FutureOSS}/http-api/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
store/@{FutureOSS}/http-api/__pycache__/router.cpython-313.pyc
Normal file
BIN
store/@{FutureOSS}/http-api/__pycache__/router.cpython-313.pyc
Normal file
Binary file not shown.
BIN
store/@{FutureOSS}/http-api/__pycache__/server.cpython-313.pyc
Normal file
BIN
store/@{FutureOSS}/http-api/__pycache__/server.cpython-313.pyc
Normal file
Binary file not shown.
58
store/@{FutureOSS}/http-api/events.py
Normal file
58
store/@{FutureOSS}/http-api/events.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""HTTP 事件系统 - 请求/响应生命周期事件"""
|
||||
from typing import Callable, Any, Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class HttpEvent:
|
||||
"""HTTP 事件"""
|
||||
type: str # request, response, error, etc
|
||||
request: Any = None
|
||||
response: Any = None
|
||||
error: Exception = None
|
||||
context: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
class HttpEventBus:
|
||||
"""HTTP 事件总线"""
|
||||
|
||||
def __init__(self):
|
||||
self._handlers: dict[str, list[Callable]] = {}
|
||||
|
||||
def on(self, event_type: str, handler: Callable):
|
||||
"""订阅事件"""
|
||||
if event_type not in self._handlers:
|
||||
self._handlers[event_type] = []
|
||||
self._handlers[event_type].append(handler)
|
||||
|
||||
def off(self, event_type: str, handler: Callable):
|
||||
"""取消订阅"""
|
||||
if event_type in self._handlers:
|
||||
try:
|
||||
self._handlers[event_type].remove(handler)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def emit(self, event: HttpEvent):
|
||||
"""发布事件"""
|
||||
handlers = self._handlers.get(event.type, [])
|
||||
for handler in handlers:
|
||||
try:
|
||||
handler(event)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def clear(self):
|
||||
"""清空所有订阅"""
|
||||
self._handlers.clear()
|
||||
|
||||
|
||||
# 事件类型常量
|
||||
EVENT_REQUEST = "http.request"
|
||||
EVENT_BEFORE_ROUTE = "http.before_route"
|
||||
EVENT_AFTER_ROUTE = "http.after_route"
|
||||
EVENT_BEFORE_HANDLER = "http.before_handler"
|
||||
EVENT_AFTER_HANDLER = "http.after_handler"
|
||||
EVENT_RESPONSE = "http.response"
|
||||
EVENT_ERROR = "http.error"
|
||||
EVENT_COMPLETE = "http.complete"
|
||||
68
store/@{FutureOSS}/http-api/main.py
Normal file
68
store/@{FutureOSS}/http-api/main.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""HTTP API 插件 - 分散式布局"""
|
||||
import json
|
||||
from oss.plugin.types import Plugin, register_plugin_type
|
||||
from .server import HttpServer, Response
|
||||
from .router import Router
|
||||
from .middleware import MiddlewareChain
|
||||
|
||||
|
||||
class HttpApiPlugin(Plugin):
|
||||
"""HTTP API 插件"""
|
||||
|
||||
def __init__(self):
|
||||
self.server = None
|
||||
self.router = Router()
|
||||
self.middleware = MiddlewareChain()
|
||||
|
||||
def init(self, deps: dict = None):
|
||||
"""初始化"""
|
||||
# 注册基础路由
|
||||
self.router.get("/health", self._health_handler)
|
||||
self.router.get("/api/server/info", self._server_info_handler)
|
||||
self.router.get("/api/status", self._status_handler)
|
||||
|
||||
self.server = HttpServer(self.router, self.middleware)
|
||||
|
||||
def start(self):
|
||||
"""启动"""
|
||||
self.server.start()
|
||||
|
||||
def stop(self):
|
||||
"""停止"""
|
||||
if self.server:
|
||||
self.server.stop()
|
||||
|
||||
def _health_handler(self, request):
|
||||
"""健康检查"""
|
||||
return Response(
|
||||
status=200,
|
||||
body=json.dumps({"status": "ok", "service": "http-api"}),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
def _server_info_handler(self, request):
|
||||
"""服务器信息"""
|
||||
return Response(
|
||||
status=200,
|
||||
body=json.dumps({
|
||||
"name": "FutureOSS HTTP API",
|
||||
"version": "1.0.0",
|
||||
"endpoints": ["/health", "/api/server/info", "/api/status"]
|
||||
}),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
def _status_handler(self, request):
|
||||
"""状态检查"""
|
||||
return Response(
|
||||
status=200,
|
||||
body=json.dumps({"status": "running", "plugins_loaded": True}),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
|
||||
register_plugin_type("HttpApiPlugin", HttpApiPlugin)
|
||||
|
||||
|
||||
def New():
|
||||
return HttpApiPlugin()
|
||||
18
store/@{FutureOSS}/http-api/manifest.json
Normal file
18
store/@{FutureOSS}/http-api/manifest.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "http-api",
|
||||
"version": "1.0.0",
|
||||
"author": "FutureOSS",
|
||||
"description": "HTTP API 服务 - 提供 RESTful API 和路由功能",
|
||||
"type": "protocol"
|
||||
},
|
||||
"config": {
|
||||
"enabled": true,
|
||||
"args": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080
|
||||
}
|
||||
},
|
||||
"dependencies": [],
|
||||
"permissions": ["lifecycle", "circuit-breaker"]
|
||||
}
|
||||
57
store/@{FutureOSS}/http-api/middleware.py
Normal file
57
store/@{FutureOSS}/http-api/middleware.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""中间件链 - CORS/日志/限流等"""
|
||||
from typing import Callable, Optional, Any
|
||||
from .server import Request, Response
|
||||
|
||||
|
||||
class Middleware:
|
||||
"""中间件基类"""
|
||||
def process(self, ctx: dict[str, Any], next_fn: Callable) -> Optional[Response]:
|
||||
"""处理请求"""
|
||||
return None
|
||||
|
||||
|
||||
class CorsMiddleware(Middleware):
|
||||
"""CORS 中间件"""
|
||||
def process(self, ctx: dict, next_fn: Callable) -> Optional[Response]:
|
||||
ctx["response_headers"] = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type",
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
class LoggerMiddleware(Middleware):
|
||||
"""日志中间件"""
|
||||
def process(self, ctx: dict, next_fn: Callable) -> Optional[Response]:
|
||||
req = ctx.get("request")
|
||||
if req:
|
||||
print(f"[http-api] {req.method} {req.path}")
|
||||
return None
|
||||
|
||||
|
||||
class MiddlewareChain:
|
||||
"""中间件链"""
|
||||
|
||||
def __init__(self):
|
||||
self.middlewares: list[Middleware] = []
|
||||
self.add(LoggerMiddleware())
|
||||
self.add(CorsMiddleware())
|
||||
|
||||
def add(self, middleware: Middleware):
|
||||
"""添加中间件"""
|
||||
self.middlewares.append(middleware)
|
||||
|
||||
def run(self, ctx: dict[str, Any]) -> Optional[Response]:
|
||||
"""执行中间件链"""
|
||||
idx = 0
|
||||
|
||||
def next_fn():
|
||||
nonlocal idx
|
||||
if idx < len(self.middlewares):
|
||||
mw = self.middlewares[idx]
|
||||
idx += 1
|
||||
return mw.process(ctx, next_fn)
|
||||
return None
|
||||
|
||||
return next_fn()
|
||||
72
store/@{FutureOSS}/http-api/router.py
Normal file
72
store/@{FutureOSS}/http-api/router.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""路由器 - 路径匹配和处理器分发"""
|
||||
from typing import Callable, Optional
|
||||
from .server import Request, Response
|
||||
|
||||
|
||||
class Route:
|
||||
"""路由定义"""
|
||||
def __init__(self, method: str, path: str, handler: Callable):
|
||||
self.method = method
|
||||
self.path = path
|
||||
self.handler = handler
|
||||
|
||||
|
||||
class Router:
|
||||
"""路由器"""
|
||||
|
||||
def __init__(self):
|
||||
self.routes: list[Route] = []
|
||||
|
||||
def add(self, method: str, path: str, handler: Callable):
|
||||
"""添加路由"""
|
||||
self.routes.append(Route(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: Request) -> Response:
|
||||
"""处理请求"""
|
||||
for route in self.routes:
|
||||
if route.method == request.method and self._match(route.path, request.path):
|
||||
return route.handler(request)
|
||||
return Response(status=404, body='{"error": "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("/")
|
||||
|
||||
# 检查前缀是否匹配
|
||||
for i, p in enumerate(pattern_parts):
|
||||
if i >= len(path_parts):
|
||||
return False
|
||||
if not p.startswith(":") and p != path_parts[i]:
|
||||
return False
|
||||
|
||||
# 如果最后一个 pattern 是 :path(通配符),允许更多路径段
|
||||
last_pattern = pattern_parts[-1]
|
||||
if last_pattern.startswith(":") and len(path_parts) >= len(pattern_parts):
|
||||
return True
|
||||
|
||||
# 否则必须精确匹配段数
|
||||
if len(pattern_parts) != len(path_parts):
|
||||
return False
|
||||
|
||||
return True
|
||||
return False
|
||||
110
store/@{FutureOSS}/http-api/server.py
Normal file
110
store/@{FutureOSS}/http-api/server.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""HTTP 服务器核心"""
|
||||
import threading
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Request:
|
||||
"""请求对象"""
|
||||
def __init__(self, method, path, headers, body):
|
||||
self.method = method
|
||||
self.path = path
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
|
||||
|
||||
class Response:
|
||||
"""响应对象"""
|
||||
def __init__(self, status=200, headers=None, body=""):
|
||||
self.status = status
|
||||
self.headers = headers or {}
|
||||
self.body = body
|
||||
|
||||
|
||||
class HttpServer:
|
||||
"""HTTP 服务器"""
|
||||
|
||||
def __init__(self, router, middleware, host="0.0.0.0", port=8080):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.router = router
|
||||
self.middleware = middleware
|
||||
self._server = None
|
||||
self._thread = None
|
||||
|
||||
def start(self):
|
||||
"""启动服务器"""
|
||||
handler = self._create_handler()
|
||||
self._server = HTTPServer((self.host, self.port), handler)
|
||||
self._thread = threading.Thread(target=self._server.serve_forever, daemon=True)
|
||||
self._thread.start()
|
||||
print(f"[http-api] 服务器启动: {self.host}:{self.port}")
|
||||
|
||||
def stop(self):
|
||||
"""停止服务器"""
|
||||
if self._server:
|
||||
self._server.shutdown()
|
||||
print("[http-api] 服务器已停止")
|
||||
|
||||
def _create_handler(self):
|
||||
"""创建请求处理器"""
|
||||
router = self.router
|
||||
middleware = self.middleware
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self._handle("GET")
|
||||
|
||||
def do_POST(self):
|
||||
self._handle("POST")
|
||||
|
||||
def do_PUT(self):
|
||||
self._handle("PUT")
|
||||
|
||||
def do_DELETE(self):
|
||||
self._handle("DELETE")
|
||||
|
||||
def do_OPTIONS(self):
|
||||
"""处理 CORS 预检请求"""
|
||||
self.send_response(200)
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
||||
self.end_headers()
|
||||
|
||||
def _handle(self, method):
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length) if content_length else b""
|
||||
|
||||
req = Request(
|
||||
method=method,
|
||||
path=self.path,
|
||||
headers=dict(self.headers),
|
||||
body=body.decode("utf-8")
|
||||
)
|
||||
|
||||
# 执行中间件
|
||||
ctx = {"request": req, "response": None}
|
||||
result = middleware.run(ctx)
|
||||
if result:
|
||||
self._send_response(result)
|
||||
return
|
||||
|
||||
# 路由匹配
|
||||
resp = router.handle(req)
|
||||
self._send_response(resp)
|
||||
|
||||
def _send_response(self, resp: Response):
|
||||
self.send_response(resp.status)
|
||||
for k, v in resp.headers.items():
|
||||
self.send_header(k, v)
|
||||
self.end_headers()
|
||||
if isinstance(resp.body, str):
|
||||
self.wfile.write(resp.body.encode("utf-8"))
|
||||
else:
|
||||
self.wfile.write(resp.body)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
return Handler
|
||||
Reference in New Issue
Block a user