125 lines
4.0 KiB
Python
125 lines
4.0 KiB
Python
# 开发服务器,用于在不构建vite应用的情况下仍然像生产环境中那样使用相对路径调用api,兼容Vite HMR
|
||
# 如果出现Bug请注意先检查是不是proxy导致的问题,再检查是不是代码写错了!!!
|
||
|
||
import asyncio
|
||
import aiohttp
|
||
from aiohttp import web
|
||
import logging
|
||
|
||
# 全局变量配置
|
||
VITE_ADD = "http://localhost:5173"
|
||
PY_ADD = "http://127.0.0.1:5000"
|
||
PORT = 8080
|
||
|
||
logging.basicConfig(level=logging.INFO)
|
||
logger = logging.getLogger("dev_proxy")
|
||
|
||
async def proxy_handler(request):
|
||
path = request.path
|
||
query = request.query_string
|
||
method = request.method
|
||
headers = dict(request.headers)
|
||
|
||
# 判定转发目标
|
||
if path.startswith('/api/'):
|
||
target_url = f"{PY_ADD}{path}"
|
||
else:
|
||
target_url = f"{VITE_ADD}{path}"
|
||
|
||
if query:
|
||
target_url += f"?{query}"
|
||
|
||
# 处理 WebSocket 升级请求 (Vite HMR)
|
||
if request.headers.get('Upgrade', '').lower() == 'websocket':
|
||
return await ws_proxy_handler(request, target_url)
|
||
|
||
try:
|
||
async with aiohttp.ClientSession() as session:
|
||
# 读取请求体
|
||
data = await request.read()
|
||
|
||
async with session.request(
|
||
method=method,
|
||
url=target_url,
|
||
headers=headers,
|
||
data=data,
|
||
allow_redirects=False
|
||
) as resp:
|
||
# 构建响应头
|
||
resp_headers = dict(resp.headers)
|
||
|
||
# 读取响应体
|
||
body = await resp.read()
|
||
return web.Response(
|
||
body=body,
|
||
status=resp.status,
|
||
headers=resp_headers
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Error proxying {method} {path}: {e}")
|
||
return web.Response(text=str(e), status=502)
|
||
|
||
async def ws_proxy_handler(request, target_url):
|
||
"""处理 WebSocket 转发,主要用于 Vite HMR"""
|
||
# 将 http:// 转换为 ws://
|
||
ws_target_url = target_url.replace('http://', 'ws://').replace('https://', 'wss://')
|
||
|
||
# 获取并转发 WebSocket 子协议
|
||
protocol = request.headers.get('Sec-WebSocket-Protocol', '')
|
||
protocols = [p.strip() for p in protocol.split(',')] if protocol else []
|
||
|
||
ws_server = web.WebSocketResponse(protocols=protocols)
|
||
await ws_server.prepare(request)
|
||
|
||
logger.info(f"WebSocket connection upgraded: {request.path} (Protocols: {protocols})")
|
||
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.ws_connect(ws_target_url, protocols=protocols) as ws_client:
|
||
|
||
async def forward(src, dst):
|
||
async for msg in src:
|
||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||
await dst.send_str(msg.data)
|
||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||
await dst.send_bytes(msg.data)
|
||
elif msg.type == aiohttp.WSMsgType.CLOSE:
|
||
await dst.close()
|
||
break
|
||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||
break
|
||
|
||
# 双向转发
|
||
await asyncio.gather(
|
||
forward(ws_server, ws_client),
|
||
forward(ws_client, ws_server)
|
||
)
|
||
|
||
return ws_server
|
||
|
||
async def main():
|
||
app = web.Application()
|
||
# 捕获所有路径
|
||
app.router.add_route('*', '/{path:.*}', proxy_handler)
|
||
|
||
runner = web.AppRunner(app)
|
||
await runner.setup()
|
||
site = web.TCPSite(runner, '127.0.0.1', PORT)
|
||
|
||
logger.info(f"Dev Proxy started at http://127.0.0.1:{PORT}")
|
||
logger.info(f"Routing /api/* to {PY_ADD}")
|
||
logger.info(f"Routing others to {VITE_ADD}")
|
||
|
||
await site.start()
|
||
|
||
# 保持运行
|
||
try:
|
||
while True:
|
||
await asyncio.sleep(3600)
|
||
except KeyboardInterrupt:
|
||
pass
|
||
finally:
|
||
await runner.cleanup()
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(main())
|