feat: fpw plugins, validation/crawl perf, WS stats, test DB isolation
- Add Free_Proxy_Website-style fpw_* plugins and register them - Per-plugin crawl timeout (crawl_timeout_seconds=120); remove global crawl_timeout setting - Validator: fix connect vs total timeout on save; SOCKS session LRU cache; drop redundant semaphore - Validation handler uses single DB connection; batch upsert after crawl; WorkerPool put_nowait - Remove unused max_retries from settings API/UI; settings maintenance SQL + init_db cleanup of deprecated keys - WebSocket dashboard stats; ProxyList pool_filter and API alignment - POST /api/proxies/delete-one for IPv6-safe deletes; task poll stops on 404 - pytest uses PROXYPOOL_DB_PATH=db/proxies.test.sqlite so tests do not wipe production DB - .gitignore: explicit proxies.test.sqlite patterns; fix plugin_service ValidationException import Made-with: Cursor
This commit is contained in:
52
app/api/ws_manager.py
Normal file
52
app/api/ws_manager.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""WebSocket 连接管理与广播"""
|
||||
import asyncio
|
||||
from typing import List
|
||||
|
||||
from starlette.websockets import WebSocket, WebSocketState
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self) -> None:
|
||||
self._connections: List[WebSocket] = []
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
@property
|
||||
def connection_count(self) -> int:
|
||||
return len(self._connections)
|
||||
|
||||
async def connect(self, websocket: WebSocket) -> None:
|
||||
async with self._lock:
|
||||
self._connections.append(websocket)
|
||||
|
||||
async def disconnect(self, websocket: WebSocket) -> None:
|
||||
async with self._lock:
|
||||
if websocket in self._connections:
|
||||
self._connections.remove(websocket)
|
||||
|
||||
async def broadcast_json(self, payload: dict) -> None:
|
||||
async with self._lock:
|
||||
targets = list(self._connections)
|
||||
stale: List[WebSocket] = []
|
||||
for ws in targets:
|
||||
try:
|
||||
if ws.client_state != WebSocketState.CONNECTED:
|
||||
stale.append(ws)
|
||||
continue
|
||||
await ws.send_json(payload)
|
||||
except Exception:
|
||||
stale.append(ws)
|
||||
if stale:
|
||||
async with self._lock:
|
||||
for ws in stale:
|
||||
if ws in self._connections:
|
||||
self._connections.remove(ws)
|
||||
|
||||
async def disconnect_all(self) -> None:
|
||||
async with self._lock:
|
||||
targets = list(self._connections)
|
||||
self._connections.clear()
|
||||
for ws in targets:
|
||||
try:
|
||||
await ws.close()
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user