Create main.py
Browse files
main.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import httpx
|
| 2 |
+
from fastapi import FastAPI, Request
|
| 3 |
+
from starlette.responses import HTMLResponse, StreamingResponse
|
| 4 |
+
|
| 5 |
+
app = FastAPI()
|
| 6 |
+
TARGET = "https://ewt360.com" # 目标站
|
| 7 |
+
|
| 8 |
+
# -------- 工具函数 ---------------------------------------------------------
|
| 9 |
+
INJECT_JS = """
|
| 10 |
+
<script>
|
| 11 |
+
function clickZa() {
|
| 12 |
+
document.querySelectorAll('button').forEach(btn => {
|
| 13 |
+
if (btn.textContent.trim() === '在') btn.click();
|
| 14 |
+
});
|
| 15 |
+
}
|
| 16 |
+
document.addEventListener('DOMContentLoaded', clickZa);
|
| 17 |
+
|
| 18 |
+
// 处理 SPA / 懒加载:DOM 变化时再检测一次
|
| 19 |
+
new MutationObserver(clickZa).observe(document.documentElement, {
|
| 20 |
+
childList: true,
|
| 21 |
+
subtree: true
|
| 22 |
+
});
|
| 23 |
+
</script>
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
def _inject(html: str) -> str:
|
| 27 |
+
"""把注入脚本塞到 </body> 前;若无 </body> 则直接追加"""
|
| 28 |
+
return html.replace("</body>", INJECT_JS + "</body>") if "</body>" in html else html + INJECT_JS
|
| 29 |
+
|
| 30 |
+
async def _fetch_upstream(req: Request, path: str) -> httpx.Response:
|
| 31 |
+
"""把客户端请求转发到目标站"""
|
| 32 |
+
async with httpx.AsyncClient(follow_redirects=True, timeout=30) as client:
|
| 33 |
+
upstream_url = f"{TARGET}/{path}"
|
| 34 |
+
headers = {k: v for k, v in req.headers.items()
|
| 35 |
+
if k.lower() not in {"host", "content-length", "accept-encoding", "connection"}}
|
| 36 |
+
return await client.request(
|
| 37 |
+
req.method,
|
| 38 |
+
upstream_url,
|
| 39 |
+
params=req.query_params,
|
| 40 |
+
content=await req.body(),
|
| 41 |
+
headers=headers
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
# -------- 统一代理入口 ------------------------------------------------------
|
| 45 |
+
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
|
| 46 |
+
async def proxy(path: str, request: Request):
|
| 47 |
+
upstream = await _fetch_upstream(request, path)
|
| 48 |
+
ct = upstream.headers.get("content-type", "")
|
| 49 |
+
|
| 50 |
+
# 对 HTML 页面做脚本注入
|
| 51 |
+
if "text/html" in ct:
|
| 52 |
+
html = _inject(upstream.text)
|
| 53 |
+
# 过滤掉与内容长度 / 压缩相关的头,避免浏览器警告
|
| 54 |
+
safe_headers = {k: v for k, v in upstream.headers.items()
|
| 55 |
+
if k.lower() not in {"content-length", "content-encoding"}}
|
| 56 |
+
return HTMLResponse(html, status_code=upstream.status_code, headers=safe_headers)
|
| 57 |
+
|
| 58 |
+
# 其他资源直接流式返回(JS / CSS / 图片等)
|
| 59 |
+
return StreamingResponse(
|
| 60 |
+
upstream.aiter_bytes(),
|
| 61 |
+
status_code=upstream.status_code,
|
| 62 |
+
media_type=ct or None,
|
| 63 |
+
headers={k: v for k, v in upstream.headers.items()
|
| 64 |
+
if k.lower() not in {"content-length", "content-encoding"}}
|
| 65 |
+
)
|