Update api.txt
Browse files
api.txt
CHANGED
@@ -13,9 +13,65 @@ from typing import Dict
|
|
13 |
from loguru import logger
|
14 |
from starlette.responses import StreamingResponse
|
15 |
import socket
|
|
|
|
|
|
|
|
|
16 |
|
17 |
app = FastAPI()
|
18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
# 浏览器实例管理
|
20 |
BROWSERS: Dict[str, dict] = {} # {browser_id: {"process": Popen, "ws": str, "port": int, "status": str, "info": dict}}
|
21 |
# 浏览器实例状态: open, closed
|
@@ -198,10 +254,12 @@ async def system_usage():
|
|
198 |
"""
|
199 |
try:
|
200 |
usage_info = get_system_usage()
|
201 |
-
|
|
|
202 |
except Exception as e:
|
203 |
logger.error(f"获取系统资源使用情况失败: {e}")
|
204 |
-
|
|
|
205 |
|
206 |
|
207 |
# BitBrowser风格API
|
@@ -214,12 +272,13 @@ async def open_browser(request: Request):
|
|
214 |
if browser_id in BROWSERS:
|
215 |
logger.info(f"Browser ID {browser_id}: {BROWSERS[browser_id]}")
|
216 |
if BROWSERS[browser_id]["status"] == "open":
|
217 |
-
|
218 |
"success": True,
|
219 |
"msg": "already opened",
|
220 |
"data": {"id": browser_id, "ws": BROWSERS[browser_id]["ws"]},
|
221 |
"info": BROWSERS[browser_id]["info"]
|
222 |
}
|
|
|
223 |
else:
|
224 |
port = get_free_port()
|
225 |
BROWSERS[browser_id]["port"] = port
|
@@ -253,7 +312,8 @@ async def open_browser(request: Request):
|
|
253 |
proc = subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
254 |
if not wait_port("127.0.0.1", port, timeout=60):
|
255 |
proc.terminate()
|
256 |
-
|
|
|
257 |
async with httpx.AsyncClient() as client:
|
258 |
resp = await client.get(f"http://127.0.0.1:{port}/json/version")
|
259 |
if resp.status_code == 200:
|
@@ -263,7 +323,8 @@ async def open_browser(request: Request):
|
|
263 |
ws_url = re.sub(r":\d+", "", ws_url)
|
264 |
logger.info(f"Browser opened with ID: {browser_id}, Info: {browser_info}")
|
265 |
BROWSERS[browser_id] = {"process": proc, "ws": ws_url, "port": port, "status": "open", "info": browser_info}
|
266 |
-
|
|
|
267 |
|
268 |
|
269 |
@app.post("/browser/close")
|
@@ -272,19 +333,23 @@ async def close_browser(request: Request):
|
|
272 |
browser_id = data.get("id")
|
273 |
b = BROWSERS.get(browser_id)
|
274 |
if not b:
|
275 |
-
|
|
|
276 |
b["process"].terminate()
|
277 |
b["status"] = "closed"
|
278 |
-
|
|
|
279 |
|
280 |
|
|
|
281 |
@app.post("/browser/delete")
|
282 |
async def delete_browser(request: Request):
|
283 |
data = await request.json()
|
284 |
browser_id = data.get("id")
|
285 |
b = BROWSERS.pop(browser_id, None)
|
286 |
if not b:
|
287 |
-
|
|
|
288 |
try:
|
289 |
b["process"].terminate()
|
290 |
except Exception:
|
@@ -294,7 +359,8 @@ async def delete_browser(request: Request):
|
|
294 |
if os.path.exists(profile_dir):
|
295 |
import shutil
|
296 |
shutil.rmtree(profile_dir, ignore_errors=True)
|
297 |
-
|
|
|
298 |
|
299 |
|
300 |
# @app.post("/browser/update")
|
@@ -316,12 +382,48 @@ async def delete_browser(request: Request):
|
|
316 |
async def browser_ports():
|
317 |
# 返回 {id: port} 格式
|
318 |
data = {k: str(v["ws"]) for k, v in BROWSERS.items() if v["status"] == "open"}
|
319 |
-
|
|
|
320 |
|
321 |
|
322 |
@app.post("/health")
|
323 |
async def health():
|
324 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
|
326 |
|
327 |
CDP_PATHS = [
|
|
|
13 |
from loguru import logger
|
14 |
from starlette.responses import StreamingResponse
|
15 |
import socket
|
16 |
+
import base64
|
17 |
+
from cryptography.fernet import Fernet
|
18 |
+
from cryptography.hazmat.primitives import hashes
|
19 |
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
20 |
|
21 |
app = FastAPI()
|
22 |
|
23 |
+
# 加密配置
|
24 |
+
ENCRYPTION_KEY = os.getenv('ENCRYPTION_KEY', 'default-encryption-key-change-in-production') # 生产环境中应该使用环境变量
|
25 |
+
SALT = b'stable_salt_123456' # 固定salt,生产环境中应该更安全
|
26 |
+
|
27 |
+
def get_encryption_key():
|
28 |
+
"""生成加密密钥"""
|
29 |
+
kdf = PBKDF2HMAC(
|
30 |
+
algorithm=hashes.SHA256(),
|
31 |
+
length=32,
|
32 |
+
salt=SALT,
|
33 |
+
iterations=100000,
|
34 |
+
)
|
35 |
+
key = base64.urlsafe_b64encode(kdf.derive(ENCRYPTION_KEY.encode()))
|
36 |
+
return key
|
37 |
+
|
38 |
+
def encrypt_data(data: str) -> str:
|
39 |
+
"""加密数据"""
|
40 |
+
try:
|
41 |
+
key = get_encryption_key()
|
42 |
+
f = Fernet(key)
|
43 |
+
encrypted_data = f.encrypt(data.encode())
|
44 |
+
return base64.urlsafe_b64encode(encrypted_data).decode()
|
45 |
+
except Exception as e:
|
46 |
+
logger.error(f"加密失败: {e}")
|
47 |
+
return data
|
48 |
+
|
49 |
+
def decrypt_data(encrypted_data: str) -> str:
|
50 |
+
"""解密数据"""
|
51 |
+
try:
|
52 |
+
key = get_encryption_key()
|
53 |
+
f = Fernet(key)
|
54 |
+
encrypted_bytes = base64.urlsafe_b64decode(encrypted_data.encode())
|
55 |
+
decrypted_data = f.decrypt(encrypted_bytes)
|
56 |
+
return decrypted_data.decode()
|
57 |
+
except Exception as e:
|
58 |
+
logger.error(f"解密失败: {e}")
|
59 |
+
return encrypted_data
|
60 |
+
|
61 |
+
def encrypt_json_response(data: dict) -> dict:
|
62 |
+
"""加密JSON响应数据"""
|
63 |
+
try:
|
64 |
+
json_str = json.dumps(data, ensure_ascii=False)
|
65 |
+
encrypted_str = encrypt_data(json_str)
|
66 |
+
return {
|
67 |
+
"encrypted": True,
|
68 |
+
"data": encrypted_str,
|
69 |
+
"timestamp": int(time.time())
|
70 |
+
}
|
71 |
+
except Exception as e:
|
72 |
+
logger.error(f"加密JSON响应失败: {e}")
|
73 |
+
return data
|
74 |
+
|
75 |
# 浏览器实例管理
|
76 |
BROWSERS: Dict[str, dict] = {} # {browser_id: {"process": Popen, "ws": str, "port": int, "status": str, "info": dict}}
|
77 |
# 浏览器实例状态: open, closed
|
|
|
254 |
"""
|
255 |
try:
|
256 |
usage_info = get_system_usage()
|
257 |
+
response_data = {"success": True, "msg": "success", "data": usage_info}
|
258 |
+
return encrypt_json_response(response_data)
|
259 |
except Exception as e:
|
260 |
logger.error(f"获取系统资源使用情况失败: {e}")
|
261 |
+
error_data = {"success": False, "msg": "failed", "error": str(e)}
|
262 |
+
return encrypt_json_response(error_data)
|
263 |
|
264 |
|
265 |
# BitBrowser风格API
|
|
|
272 |
if browser_id in BROWSERS:
|
273 |
logger.info(f"Browser ID {browser_id}: {BROWSERS[browser_id]}")
|
274 |
if BROWSERS[browser_id]["status"] == "open":
|
275 |
+
response_data = {
|
276 |
"success": True,
|
277 |
"msg": "already opened",
|
278 |
"data": {"id": browser_id, "ws": BROWSERS[browser_id]["ws"]},
|
279 |
"info": BROWSERS[browser_id]["info"]
|
280 |
}
|
281 |
+
return encrypt_json_response(response_data)
|
282 |
else:
|
283 |
port = get_free_port()
|
284 |
BROWSERS[browser_id]["port"] = port
|
|
|
312 |
proc = subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
313 |
if not wait_port("127.0.0.1", port, timeout=60):
|
314 |
proc.terminate()
|
315 |
+
error_data = {"success": False, "msg": f"chrome端口{port}未就绪", "data": {}}
|
316 |
+
return encrypt_json_response(error_data)
|
317 |
async with httpx.AsyncClient() as client:
|
318 |
resp = await client.get(f"http://127.0.0.1:{port}/json/version")
|
319 |
if resp.status_code == 200:
|
|
|
323 |
ws_url = re.sub(r":\d+", "", ws_url)
|
324 |
logger.info(f"Browser opened with ID: {browser_id}, Info: {browser_info}")
|
325 |
BROWSERS[browser_id] = {"process": proc, "ws": ws_url, "port": port, "status": "open", "info": browser_info}
|
326 |
+
response_data = {"success": True, "msg": "success", "data": {"id": browser_id, "ws": ws_url}, "info": browser_info}
|
327 |
+
return encrypt_json_response(response_data)
|
328 |
|
329 |
|
330 |
@app.post("/browser/close")
|
|
|
333 |
browser_id = data.get("id")
|
334 |
b = BROWSERS.get(browser_id)
|
335 |
if not b:
|
336 |
+
error_data = {"success": False, "msg": "not found"}
|
337 |
+
return encrypt_json_response(error_data)
|
338 |
b["process"].terminate()
|
339 |
b["status"] = "closed"
|
340 |
+
response_data = {"success": True, "msg": "closed", "data": {"id": browser_id}}
|
341 |
+
return encrypt_json_response(response_data)
|
342 |
|
343 |
|
344 |
+
@app.post("/browser/delete")
|
345 |
@app.post("/browser/delete")
|
346 |
async def delete_browser(request: Request):
|
347 |
data = await request.json()
|
348 |
browser_id = data.get("id")
|
349 |
b = BROWSERS.pop(browser_id, None)
|
350 |
if not b:
|
351 |
+
error_data = {"success": False, "msg": "not found"}
|
352 |
+
return encrypt_json_response(error_data)
|
353 |
try:
|
354 |
b["process"].terminate()
|
355 |
except Exception:
|
|
|
359 |
if os.path.exists(profile_dir):
|
360 |
import shutil
|
361 |
shutil.rmtree(profile_dir, ignore_errors=True)
|
362 |
+
response_data = {"success": True, "msg": "deleted", "data": {"id": browser_id}}
|
363 |
+
return encrypt_json_response(response_data)
|
364 |
|
365 |
|
366 |
# @app.post("/browser/update")
|
|
|
382 |
async def browser_ports():
|
383 |
# 返回 {id: port} 格式
|
384 |
data = {k: str(v["ws"]) for k, v in BROWSERS.items() if v["status"] == "open"}
|
385 |
+
response_data = {"success": True, "data": data}
|
386 |
+
return encrypt_json_response(response_data)
|
387 |
|
388 |
|
389 |
@app.post("/health")
|
390 |
async def health():
|
391 |
+
response_data = {"success": True, "msg": "ok"}
|
392 |
+
return encrypt_json_response(response_data)
|
393 |
+
|
394 |
+
|
395 |
+
@app.post("/decrypt")
|
396 |
+
async def decrypt_response(request: Request):
|
397 |
+
"""
|
398 |
+
解密接口,用于客户端解密响应数据
|
399 |
+
"""
|
400 |
+
try:
|
401 |
+
data = await request.json()
|
402 |
+
encrypted_data = data.get("data")
|
403 |
+
if not encrypted_data:
|
404 |
+
return {"success": False, "msg": "missing encrypted data"}
|
405 |
+
|
406 |
+
decrypted_str = decrypt_data(encrypted_data)
|
407 |
+
decrypted_json = json.loads(decrypted_str)
|
408 |
+
return {"success": True, "msg": "decrypted", "data": decrypted_json}
|
409 |
+
except Exception as e:
|
410 |
+
logger.error(f"解密失败: {e}")
|
411 |
+
return {"success": False, "msg": "decrypt failed", "error": str(e)}
|
412 |
+
|
413 |
+
|
414 |
+
# 添加获取加密配置的接口(仅用于开发调试)
|
415 |
+
@app.get("/encryption/info")
|
416 |
+
async def encryption_info():
|
417 |
+
"""
|
418 |
+
返回加密相关信息(用于开发调试)
|
419 |
+
"""
|
420 |
+
return {
|
421 |
+
"encrypted": True,
|
422 |
+
"algorithm": "Fernet (AES 128)",
|
423 |
+
"key_derivation": "PBKDF2HMAC with SHA256",
|
424 |
+
"iterations": 100000,
|
425 |
+
"info": "All JSON responses are encrypted. Use /decrypt endpoint to decrypt."
|
426 |
+
}
|
427 |
|
428 |
|
429 |
CDP_PATHS = [
|