Update fun.py
Browse files
fun.py
CHANGED
@@ -13,6 +13,7 @@ from typing import Dict
|
|
13 |
from loguru import logger
|
14 |
from starlette.responses import StreamingResponse
|
15 |
import socket
|
|
|
16 |
app = FastAPI()
|
17 |
|
18 |
# 浏览器实例管理
|
@@ -20,6 +21,8 @@ BROWSERS: Dict[str, dict] = {} # {browser_id: {"process": Popen, "port": int, "
|
|
20 |
PORT_RANGE = (9300, 9700)
|
21 |
CHROME_PATH = "google-chrome" # Linux下可用命令,或自定义绝对路径
|
22 |
PROFILE_BASE = "/tmp/profiles"
|
|
|
|
|
23 |
def get_system_usage():
|
24 |
cpu = subprocess.check_output("top -bn1 | grep 'Cpu(s)'", shell=True).decode().strip()
|
25 |
mem = subprocess.check_output("free -m | grep Mem", shell=True).decode().strip()
|
@@ -31,6 +34,8 @@ def get_system_usage():
|
|
31 |
logger.info(f"CPU: {cpu}, Memory: {mem}")
|
32 |
info = {"CPU": {cpu}, "Memory": {mem}}
|
33 |
return info
|
|
|
|
|
34 |
def get_free_port():
|
35 |
used_ports = {b["port"] for b in BROWSERS.values()}
|
36 |
for _ in range(100):
|
@@ -39,9 +44,11 @@ def get_free_port():
|
|
39 |
return port
|
40 |
raise RuntimeError("No free port available")
|
41 |
|
|
|
42 |
def gen_browser_id():
|
43 |
return str(int(time.time() * 1000)) + str(random.randint(1000, 9999))
|
44 |
|
|
|
45 |
def get_default_launch_args():
|
46 |
return [
|
47 |
"--disable-gpu",
|
@@ -58,6 +65,8 @@ def get_default_launch_args():
|
|
58 |
"--remote-allow-origins=*",
|
59 |
"--headless"
|
60 |
]
|
|
|
|
|
61 |
def wait_port(host, port, timeout=5.0):
|
62 |
"""等待端口可用"""
|
63 |
start = time.time()
|
@@ -69,6 +78,7 @@ def wait_port(host, port, timeout=5.0):
|
|
69 |
time.sleep(0.1)
|
70 |
return False
|
71 |
|
|
|
72 |
@app.get("/system/usage")
|
73 |
async def system_usage():
|
74 |
"""
|
@@ -81,22 +91,25 @@ async def system_usage():
|
|
81 |
logger.error(f"获取系统资源使用情况失败: {e}")
|
82 |
return {"code": 1, "msg": "failed", "error": str(e)}
|
83 |
|
|
|
84 |
# BitBrowser风格API
|
85 |
@app.post("/browser/open")
|
86 |
async def open_browser(request: Request):
|
87 |
data = await request.json()
|
88 |
-
browser_id = data.get("id",
|
89 |
if browser_id in BROWSERS:
|
|
|
90 |
if BROWSERS[browser_id]["status"] == "open":
|
91 |
-
return {"code": 0, "msg": "already opened",
|
92 |
-
|
93 |
-
port = BROWSERS[browser_id]["port"]
|
94 |
-
BROWSERS[browser_id]["status"] = "reopening"
|
95 |
else:
|
96 |
port = get_free_port()
|
|
|
97 |
else:
|
|
|
|
|
98 |
port = get_free_port()
|
99 |
-
profile_dir = f"{PROFILE_BASE}/{
|
100 |
os.makedirs(profile_dir, exist_ok=True)
|
101 |
# 支持自定义启动参数并去重
|
102 |
launch_args = data.get("launchArgs", "")
|
@@ -125,11 +138,12 @@ async def open_browser(request: Request):
|
|
125 |
resp = await client.get(f"http://127.0.0.1:{port}/json/version")
|
126 |
if resp.status_code == 200:
|
127 |
browser_info = resp.json()
|
128 |
-
|
129 |
|
130 |
logger.info(f"Browser opened with ID: {browser_id}, Info: {browser_info}")
|
131 |
-
BROWSERS[browser_id] = {"process": proc, "port": port, "status": "open", "info": browser_info}
|
132 |
-
return {"code": 0, "msg": "success", "data": {"id": browser_id, "
|
|
|
133 |
|
134 |
@app.post("/browser/close")
|
135 |
async def close_browser(request: Request):
|
@@ -142,6 +156,7 @@ async def close_browser(request: Request):
|
|
142 |
b["status"] = "closed"
|
143 |
return {"code": 0, "msg": "closed", "data": {"id": browser_id}}
|
144 |
|
|
|
145 |
@app.post("/browser/delete")
|
146 |
async def delete_browser(request: Request):
|
147 |
data = await request.json()
|
@@ -154,12 +169,13 @@ async def delete_browser(request: Request):
|
|
154 |
except Exception:
|
155 |
pass
|
156 |
# 删除用户数据目录
|
157 |
-
profile_dir = f"{PROFILE_BASE}/{
|
158 |
if os.path.exists(profile_dir):
|
159 |
import shutil
|
160 |
shutil.rmtree(profile_dir, ignore_errors=True)
|
161 |
return {"code": 0, "msg": "deleted", "data": {"id": browser_id}}
|
162 |
|
|
|
163 |
@app.post("/browser/update")
|
164 |
async def update_browser(request: Request):
|
165 |
data = await request.json()
|
@@ -174,12 +190,14 @@ async def update_browser(request: Request):
|
|
174 |
data["launchArgs"] = launch_args
|
175 |
return await open_browser(Request({**request.scope, "body": json.dumps(data).encode()}, receive=request._receive))
|
176 |
|
|
|
177 |
@app.post("/browser/ports")
|
178 |
async def browser_ports():
|
179 |
-
|
180 |
-
data = {k: str(v["
|
181 |
return {"success": True, "data": data}
|
182 |
|
|
|
183 |
@app.post("/health")
|
184 |
async def health():
|
185 |
return {"code": 0, "msg": "ok"}
|
@@ -192,6 +210,7 @@ CDP_PATHS = [
|
|
192 |
"/json/protocol",
|
193 |
]
|
194 |
|
|
|
195 |
def get_browser_by_id(browser_id: str):
|
196 |
b = BROWSERS.get(browser_id)
|
197 |
if not b or b["status"] != "open":
|
@@ -235,6 +254,8 @@ async def find_browser_by_target_id(target_id: str):
|
|
235 |
return browser
|
236 |
|
237 |
return None
|
|
|
|
|
238 |
@app.api_route("/json", methods=["GET"])
|
239 |
@app.api_route("/json/list", methods=["GET"])
|
240 |
@app.api_route("/json/version", methods=["GET"])
|
@@ -270,6 +291,7 @@ async def cdp_native_proxy(request: Request):
|
|
270 |
headers=dict(resp.headers)
|
271 |
)
|
272 |
|
|
|
273 |
@app.websocket("/devtools/{tab_type}/{target_id}")
|
274 |
async def cdp_native_ws_proxy(websocket: WebSocket, tab_type: str, target_id: str):
|
275 |
if not target_id:
|
@@ -280,13 +302,13 @@ async def cdp_native_ws_proxy(websocket: WebSocket, tab_type: str, target_id: st
|
|
280 |
if not browser:
|
281 |
await websocket.close(code=1008, reason="无法找到有效的浏览器实例")
|
282 |
return
|
283 |
-
|
284 |
await websocket.accept()
|
285 |
tab_type = "page" if tab_type == "page" else "browser"
|
286 |
port = browser["port"]
|
287 |
target_url = f"ws://127.0.0.1:{port}/devtools/{tab_type}/{target_id}"
|
288 |
logger.info(f"Forwarding to: {target_url}")
|
289 |
-
|
290 |
try:
|
291 |
async with websockets.connect(target_url) as target_ws:
|
292 |
async def forward_client_to_server():
|
@@ -296,7 +318,7 @@ async def cdp_native_ws_proxy(websocket: WebSocket, tab_type: str, target_id: st
|
|
296 |
await target_ws.send(data)
|
297 |
except WebSocketDisconnect:
|
298 |
pass
|
299 |
-
|
300 |
async def forward_server_to_client():
|
301 |
try:
|
302 |
while True:
|
@@ -304,7 +326,7 @@ async def cdp_native_ws_proxy(websocket: WebSocket, tab_type: str, target_id: st
|
|
304 |
await websocket.send_text(response)
|
305 |
except websockets.exceptions.ConnectionClosed:
|
306 |
pass
|
307 |
-
|
308 |
await asyncio.gather(
|
309 |
forward_client_to_server(),
|
310 |
forward_server_to_client()
|
@@ -315,6 +337,5 @@ async def cdp_native_ws_proxy(websocket: WebSocket, tab_type: str, target_id: st
|
|
315 |
await websocket.close()
|
316 |
|
317 |
|
318 |
-
|
319 |
if __name__ == "__main__":
|
320 |
-
uvicorn.run("fun:app", host="0.0.0.0", port=8000, reload=True)
|
|
|
13 |
from loguru import logger
|
14 |
from starlette.responses import StreamingResponse
|
15 |
import socket
|
16 |
+
|
17 |
app = FastAPI()
|
18 |
|
19 |
# 浏览器实例管理
|
|
|
21 |
PORT_RANGE = (9300, 9700)
|
22 |
CHROME_PATH = "google-chrome" # Linux下可用命令,或自定义绝对路径
|
23 |
PROFILE_BASE = "/tmp/profiles"
|
24 |
+
|
25 |
+
|
26 |
def get_system_usage():
|
27 |
cpu = subprocess.check_output("top -bn1 | grep 'Cpu(s)'", shell=True).decode().strip()
|
28 |
mem = subprocess.check_output("free -m | grep Mem", shell=True).decode().strip()
|
|
|
34 |
logger.info(f"CPU: {cpu}, Memory: {mem}")
|
35 |
info = {"CPU": {cpu}, "Memory": {mem}}
|
36 |
return info
|
37 |
+
|
38 |
+
|
39 |
def get_free_port():
|
40 |
used_ports = {b["port"] for b in BROWSERS.values()}
|
41 |
for _ in range(100):
|
|
|
44 |
return port
|
45 |
raise RuntimeError("No free port available")
|
46 |
|
47 |
+
|
48 |
def gen_browser_id():
|
49 |
return str(int(time.time() * 1000)) + str(random.randint(1000, 9999))
|
50 |
|
51 |
+
|
52 |
def get_default_launch_args():
|
53 |
return [
|
54 |
"--disable-gpu",
|
|
|
65 |
"--remote-allow-origins=*",
|
66 |
"--headless"
|
67 |
]
|
68 |
+
|
69 |
+
|
70 |
def wait_port(host, port, timeout=5.0):
|
71 |
"""等待端口可用"""
|
72 |
start = time.time()
|
|
|
78 |
time.sleep(0.1)
|
79 |
return False
|
80 |
|
81 |
+
|
82 |
@app.get("/system/usage")
|
83 |
async def system_usage():
|
84 |
"""
|
|
|
91 |
logger.error(f"获取系统资源使用情况失败: {e}")
|
92 |
return {"code": 1, "msg": "failed", "error": str(e)}
|
93 |
|
94 |
+
|
95 |
# BitBrowser风格API
|
96 |
@app.post("/browser/open")
|
97 |
async def open_browser(request: Request):
|
98 |
data = await request.json()
|
99 |
+
browser_id = data.get("id", '')
|
100 |
if browser_id in BROWSERS:
|
101 |
+
logger.info(f"Browser ID {browser_id}: {BROWSERS[browser_id]}")
|
102 |
if BROWSERS[browser_id]["status"] == "open":
|
103 |
+
return {"code": 0, "msg": "already opened",
|
104 |
+
"data": {"id": browser_id, "port": BROWSERS[browser_id]["port"]}}
|
|
|
|
|
105 |
else:
|
106 |
port = get_free_port()
|
107 |
+
BROWSERS[browser_id]["port"] = port
|
108 |
else:
|
109 |
+
# 新建浏览器实例
|
110 |
+
browser_id = gen_browser_id()
|
111 |
port = get_free_port()
|
112 |
+
profile_dir = f"{PROFILE_BASE}/{browser_id}"
|
113 |
os.makedirs(profile_dir, exist_ok=True)
|
114 |
# 支持自定义启动参数并去重
|
115 |
launch_args = data.get("launchArgs", "")
|
|
|
138 |
resp = await client.get(f"http://127.0.0.1:{port}/json/version")
|
139 |
if resp.status_code == 200:
|
140 |
browser_info = resp.json()
|
141 |
+
ws_url = browser_info.get("webSocketDebuggerUrl")
|
142 |
|
143 |
logger.info(f"Browser opened with ID: {browser_id}, Info: {browser_info}")
|
144 |
+
BROWSERS[browser_id] = {"process": proc, "ws": ws_url, "port": port, "status": "open", "info": browser_info}
|
145 |
+
return {"code": 0, "msg": "success", "data": {"id": browser_id, "ws": ws_url}, "info": browser_info}
|
146 |
+
|
147 |
|
148 |
@app.post("/browser/close")
|
149 |
async def close_browser(request: Request):
|
|
|
156 |
b["status"] = "closed"
|
157 |
return {"code": 0, "msg": "closed", "data": {"id": browser_id}}
|
158 |
|
159 |
+
|
160 |
@app.post("/browser/delete")
|
161 |
async def delete_browser(request: Request):
|
162 |
data = await request.json()
|
|
|
169 |
except Exception:
|
170 |
pass
|
171 |
# 删除用户数据目录
|
172 |
+
profile_dir = f"{PROFILE_BASE}/{browser_id}"
|
173 |
if os.path.exists(profile_dir):
|
174 |
import shutil
|
175 |
shutil.rmtree(profile_dir, ignore_errors=True)
|
176 |
return {"code": 0, "msg": "deleted", "data": {"id": browser_id}}
|
177 |
|
178 |
+
|
179 |
@app.post("/browser/update")
|
180 |
async def update_browser(request: Request):
|
181 |
data = await request.json()
|
|
|
190 |
data["launchArgs"] = launch_args
|
191 |
return await open_browser(Request({**request.scope, "body": json.dumps(data).encode()}, receive=request._receive))
|
192 |
|
193 |
+
|
194 |
@app.post("/browser/ports")
|
195 |
async def browser_ports():
|
196 |
+
# 返回 {id: port} 格式
|
197 |
+
data = {k: str(v["ws"]) for k, v in BROWSERS.items() if v["status"] == "open"}
|
198 |
return {"success": True, "data": data}
|
199 |
|
200 |
+
|
201 |
@app.post("/health")
|
202 |
async def health():
|
203 |
return {"code": 0, "msg": "ok"}
|
|
|
210 |
"/json/protocol",
|
211 |
]
|
212 |
|
213 |
+
|
214 |
def get_browser_by_id(browser_id: str):
|
215 |
b = BROWSERS.get(browser_id)
|
216 |
if not b or b["status"] != "open":
|
|
|
254 |
return browser
|
255 |
|
256 |
return None
|
257 |
+
|
258 |
+
|
259 |
@app.api_route("/json", methods=["GET"])
|
260 |
@app.api_route("/json/list", methods=["GET"])
|
261 |
@app.api_route("/json/version", methods=["GET"])
|
|
|
291 |
headers=dict(resp.headers)
|
292 |
)
|
293 |
|
294 |
+
|
295 |
@app.websocket("/devtools/{tab_type}/{target_id}")
|
296 |
async def cdp_native_ws_proxy(websocket: WebSocket, tab_type: str, target_id: str):
|
297 |
if not target_id:
|
|
|
302 |
if not browser:
|
303 |
await websocket.close(code=1008, reason="无法找到有效的浏览器实例")
|
304 |
return
|
305 |
+
|
306 |
await websocket.accept()
|
307 |
tab_type = "page" if tab_type == "page" else "browser"
|
308 |
port = browser["port"]
|
309 |
target_url = f"ws://127.0.0.1:{port}/devtools/{tab_type}/{target_id}"
|
310 |
logger.info(f"Forwarding to: {target_url}")
|
311 |
+
|
312 |
try:
|
313 |
async with websockets.connect(target_url) as target_ws:
|
314 |
async def forward_client_to_server():
|
|
|
318 |
await target_ws.send(data)
|
319 |
except WebSocketDisconnect:
|
320 |
pass
|
321 |
+
|
322 |
async def forward_server_to_client():
|
323 |
try:
|
324 |
while True:
|
|
|
326 |
await websocket.send_text(response)
|
327 |
except websockets.exceptions.ConnectionClosed:
|
328 |
pass
|
329 |
+
|
330 |
await asyncio.gather(
|
331 |
forward_client_to_server(),
|
332 |
forward_server_to_client()
|
|
|
337 |
await websocket.close()
|
338 |
|
339 |
|
|
|
340 |
if __name__ == "__main__":
|
341 |
+
uvicorn.run("fun:app", host="0.0.0.0", port=8000, reload=True)
|