Spaces:
BG5
/
Running

BG5 commited on
Commit
2009755
·
verified ·
1 Parent(s): a74414e

Update fun.py

Browse files
Files changed (1) hide show
  1. fun.py +39 -18
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", "data": {"id": browser_id, "port": BROWSERS[browser_id]["port"]}}
92
- elif BROWSERS[browser_id]["status"] == "closed":
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}/{port}"
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
- browser_id = browser_info.get("webSocketDebuggerUrl").split("/")[-1]
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, "port": port}, "info": browser_info}
 
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}/{b['port']}"
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
- # 返回 {id: port} 格式
180
- data = {k: str(v["port"]) for k, v in BROWSERS.items() if v["status"] == "open"}
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)