Spaces:
Paused
Paused
Upload 2 files
Browse files- Dockerfile +32 -0
- main.py +122 -0
Dockerfile
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM nkpro/chrome-novnc
|
2 |
+
|
3 |
+
# 安装 Python3 和 pip
|
4 |
+
RUN apk update && \
|
5 |
+
apk add --no-cache python3 py3-pip && \
|
6 |
+
rm -rf /var/cache/apk/*
|
7 |
+
|
8 |
+
# 复制 main.py 到容器
|
9 |
+
COPY main.py /workspace/main.py
|
10 |
+
|
11 |
+
# 设置工作目录
|
12 |
+
WORKDIR /workspace
|
13 |
+
|
14 |
+
# 如有 requirements.txt 可取消注释以下两行
|
15 |
+
# COPY requirements.txt /workspace/requirements.txt
|
16 |
+
# RUN pip3 install -r requirements.txt
|
17 |
+
|
18 |
+
# 创建启动脚本,在原始启动命令后在后台执行main.py
|
19 |
+
RUN echo '#!/bin/sh \n\
|
20 |
+
# 启动原始的entrypoint脚本,在后台运行 \n\
|
21 |
+
/entrypoint.sh "$@" & \n\
|
22 |
+
# 等待VNC和noVNC完全启动 \n\
|
23 |
+
sleep 5 \n\
|
24 |
+
# 运行main.py \n\
|
25 |
+
cd /workspace && python3 main.py & \n\
|
26 |
+
# 保持容器运行 \n\
|
27 |
+
wait' > /start.sh && \
|
28 |
+
chmod +x /start.sh
|
29 |
+
|
30 |
+
# 设置新的启动命令
|
31 |
+
ENTRYPOINT ["/start.sh"]
|
32 |
+
CMD []
|
main.py
ADDED
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# main.py
|
2 |
+
import asyncio
|
3 |
+
from fastapi import FastAPI, WebSocket
|
4 |
+
import uvicorn
|
5 |
+
|
6 |
+
# 创建 FastAPI 应用实例
|
7 |
+
app = FastAPI()
|
8 |
+
|
9 |
+
@app.websocket("/")
|
10 |
+
async def tunnel(websocket: WebSocket):
|
11 |
+
"""
|
12 |
+
WebSocket 隧道入口:
|
13 |
+
1. 接受客户端 WebSocket 连接;
|
14 |
+
2. 等待客户端发送 CONNECT 请求,解析出目标主机及端口;
|
15 |
+
3. 尝试与目标主机建立 TCP 连接;
|
16 |
+
4. 返回 HTTP 200 成功建立隧道;
|
17 |
+
5. 启动双向数据转发(TCP <--> WebSocket)。
|
18 |
+
"""
|
19 |
+
await websocket.accept()
|
20 |
+
try:
|
21 |
+
# ---------------------------
|
22 |
+
# 1. 等待客户端发来的 CONNECT 请求
|
23 |
+
# ---------------------------
|
24 |
+
# CONNECT 请求格式示例:
|
25 |
+
# CONNECT destHost:destPort HTTP/1.1\r\nHost: destHost:destPort\r\n\r\n
|
26 |
+
request_text = await websocket.receive_text()
|
27 |
+
lines = request_text.splitlines()
|
28 |
+
if not lines:
|
29 |
+
await websocket.send_text("HTTP/1.1 400 Bad Request\r\n\r\n")
|
30 |
+
await websocket.close()
|
31 |
+
return
|
32 |
+
|
33 |
+
# 解析第一行
|
34 |
+
first_line = lines[0].strip()
|
35 |
+
parts = first_line.split()
|
36 |
+
if len(parts) < 3 or parts[0].upper() != "CONNECT":
|
37 |
+
await websocket.send_text("HTTP/1.1 400 Bad Request\r\n\r\n")
|
38 |
+
await websocket.close()
|
39 |
+
return
|
40 |
+
|
41 |
+
# 从第二个字段中获取目标主机及端口,如 destHost:destPort
|
42 |
+
dest = parts[1]
|
43 |
+
if ":" not in dest:
|
44 |
+
await websocket.send_text("HTTP/1.1 400 Bad Request\r\n\r\n")
|
45 |
+
await websocket.close()
|
46 |
+
return
|
47 |
+
|
48 |
+
dest_parts = dest.split(":", 1)
|
49 |
+
dest_host = dest_parts[0]
|
50 |
+
try:
|
51 |
+
dest_port = int(dest_parts[1])
|
52 |
+
except Exception:
|
53 |
+
await websocket.send_text("HTTP/1.1 400 Bad Request\r\n\r\n")
|
54 |
+
await websocket.close()
|
55 |
+
return
|
56 |
+
|
57 |
+
# ---------------------------
|
58 |
+
# 2. 建立到目标主机的 TCP 连接
|
59 |
+
# ---------------------------
|
60 |
+
try:
|
61 |
+
reader, writer = await asyncio.open_connection(dest_host, dest_port)
|
62 |
+
except Exception as e:
|
63 |
+
err_msg = f"HTTP/1.1 502 Bad Gateway\r\n\r\n无法连接 {dest_host}:{dest_port},错误:{e}"
|
64 |
+
await websocket.send_text(err_msg)
|
65 |
+
await websocket.close()
|
66 |
+
return
|
67 |
+
|
68 |
+
# ---------------------------
|
69 |
+
# 3. 向客户端返回 200 成功响应
|
70 |
+
# ---------------------------
|
71 |
+
await websocket.send_text("HTTP/1.1 200 Connection Established\r\n\r\n")
|
72 |
+
|
73 |
+
# ---------------------------
|
74 |
+
# 4. 双向数据转发
|
75 |
+
# ---------------------------
|
76 |
+
async def tcp_to_ws():
|
77 |
+
"""
|
78 |
+
从 TCP 连接中读取数据,通过 WebSocket 以二进制方式发送给客户端
|
79 |
+
"""
|
80 |
+
try:
|
81 |
+
while True:
|
82 |
+
data = await reader.read(1024)
|
83 |
+
if not data:
|
84 |
+
break
|
85 |
+
await websocket.send_bytes(data)
|
86 |
+
except Exception as e:
|
87 |
+
# 读取异常或对方关闭连接时退出
|
88 |
+
print("tcp_to_ws 异常:", e)
|
89 |
+
|
90 |
+
async def ws_to_tcp():
|
91 |
+
"""
|
92 |
+
从客户端通过 WebSocket 发送的数据写入 TCP 连接
|
93 |
+
"""
|
94 |
+
try:
|
95 |
+
while True:
|
96 |
+
message = await websocket.receive()
|
97 |
+
# 接收到的数据可能是文本或二进制,这里尽量以二进制方式处理
|
98 |
+
if "bytes" in message:
|
99 |
+
data = message["bytes"]
|
100 |
+
elif "text" in message:
|
101 |
+
# 若收到文本数据,则转换为 bytes(可能只在握手阶段出现)
|
102 |
+
data = message["text"].encode("utf-8")
|
103 |
+
else:
|
104 |
+
break
|
105 |
+
writer.write(data)
|
106 |
+
await writer.drain()
|
107 |
+
except Exception as e:
|
108 |
+
print("ws_to_tcp 异常:", e)
|
109 |
+
|
110 |
+
# 并发执行数据转发任务,任一方向关闭则结束隧道
|
111 |
+
await asyncio.gather(tcp_to_ws(), ws_to_tcp())
|
112 |
+
except Exception as e:
|
113 |
+
print("WebSocket 隧道处理异常:", e)
|
114 |
+
finally:
|
115 |
+
# 关闭连接
|
116 |
+
await websocket.close()
|
117 |
+
|
118 |
+
# ---------------------------
|
119 |
+
# 启动服务器:监听 0.0.0.0:7860
|
120 |
+
# ---------------------------
|
121 |
+
if __name__ == "__main__":
|
122 |
+
uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=False)
|