Spaces:
Runtime error
Runtime error
remote_uploader.py
Browse files- websocket_uploader.py +210 -204
websocket_uploader.py
CHANGED
@@ -1,227 +1,233 @@
|
|
1 |
import asyncio
|
2 |
import websockets
|
|
|
3 |
import json
|
4 |
import logging
|
5 |
import sys
|
|
|
6 |
import base64
|
7 |
import os
|
8 |
-
import
|
9 |
-
import
|
10 |
from urllib.parse import urlparse
|
11 |
|
12 |
-
#
|
13 |
-
logging.basicConfig(level=logging.
|
14 |
logger = logging.getLogger(__name__)
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
try:
|
19 |
-
# 解析原始URL
|
20 |
-
parsed = urlparse(website_url)
|
21 |
-
|
22 |
-
# 构建WebSocket URL
|
23 |
-
if parsed.scheme == 'https':
|
24 |
-
ws_scheme = 'wss'
|
25 |
-
else:
|
26 |
-
ws_scheme = 'ws'
|
27 |
-
|
28 |
-
# 提取主机名,移除端口号
|
29 |
-
hostname = parsed.hostname
|
30 |
-
|
31 |
-
# 构建WebSocket URL,使用端口8765
|
32 |
-
ws_url = f"{ws_scheme}://{hostname}:8765/ws"
|
33 |
-
|
34 |
-
logger.info(f"原始URL: {website_url}")
|
35 |
-
logger.info(f"WebSocket URL: {ws_url}")
|
36 |
-
|
37 |
-
return ws_url
|
38 |
-
|
39 |
-
except Exception as e:
|
40 |
-
logger.error(f"构建WebSocket URL时出错: {e}")
|
41 |
-
# 回退方案:简单替换
|
42 |
-
if website_url.startswith('https://'):
|
43 |
-
return website_url.replace('https://', 'wss://').replace(':5001', ':8765') + '/ws'
|
44 |
-
else:
|
45 |
-
return website_url.replace('http://', 'ws://').replace(':5001', ':8765') + '/ws'
|
46 |
|
47 |
-
async def
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
# 获取所有文件
|
56 |
-
files_to_upload = []
|
57 |
-
for root, dirs, files in os.walk(output_dir):
|
58 |
-
for file in files:
|
59 |
-
file_path = os.path.join(root, file)
|
60 |
-
if os.path.isfile(file_path):
|
61 |
-
files_to_upload.append(file_path)
|
62 |
-
|
63 |
-
if not files_to_upload:
|
64 |
-
logger.info("输出目录中没有找到任何文件")
|
65 |
-
return True
|
66 |
-
|
67 |
-
logger.info(f"找到 {len(files_to_upload)} 个文件需要上传")
|
68 |
-
|
69 |
-
# 构建WebSocket URL
|
70 |
-
ws_url = build_websocket_url(website_url)
|
71 |
-
|
72 |
try:
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
# 设置连接超时
|
84 |
-
connect_timeout = 10
|
85 |
-
|
86 |
-
async with websockets.connect(
|
87 |
-
ws_url,
|
88 |
-
ssl=ssl_context,
|
89 |
-
ping_interval=20,
|
90 |
-
ping_timeout=10,
|
91 |
-
close_timeout=10
|
92 |
-
) as websocket:
|
93 |
-
logger.info("WebSocket 连接成功")
|
94 |
-
|
95 |
-
# 发送注册消息
|
96 |
-
register_msg = {
|
97 |
-
'type': 'register',
|
98 |
-
'space_id': space_id,
|
99 |
-
'user_token': user_token
|
100 |
-
}
|
101 |
-
await websocket.send(json.dumps(register_msg))
|
102 |
-
logger.info("已发送注册消息")
|
103 |
-
|
104 |
-
# 等待注册确认
|
105 |
-
try:
|
106 |
-
response = await asyncio.wait_for(websocket.recv(), timeout=10)
|
107 |
-
response_data = json.loads(response)
|
108 |
-
logger.info(f"收到响应: {response_data}")
|
109 |
-
except asyncio.TimeoutError:
|
110 |
-
logger.error("等待注册确认超时")
|
111 |
-
return False
|
112 |
-
|
113 |
-
if response_data.get('type') == 'registered':
|
114 |
-
logger.info("注册成功,开始上传文件")
|
115 |
-
|
116 |
-
# 上传文件
|
117 |
-
success_count = 0
|
118 |
-
failed_count = 0
|
119 |
-
|
120 |
-
for file_path in files_to_upload:
|
121 |
try:
|
122 |
-
|
123 |
-
logger.info(f"正在上传文件: {filename}")
|
124 |
-
|
125 |
-
# 读取文件内容并编码为base64
|
126 |
-
with open(file_path, 'rb') as f:
|
127 |
file_content = f.read()
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
upload_msg = {
|
135 |
-
'type': 'file_upload',
|
136 |
-
'space_id': space_id,
|
137 |
-
'user_token': user_token,
|
138 |
-
'filename': filename,
|
139 |
-
'content': file_b64
|
140 |
}
|
141 |
-
|
142 |
-
|
143 |
-
logger.info(f"已发送文件 {filename}")
|
144 |
-
|
145 |
-
# 等待上传结果
|
146 |
-
try:
|
147 |
-
upload_response = await asyncio.wait_for(websocket.recv(), timeout=30)
|
148 |
-
upload_result = json.loads(upload_response)
|
149 |
-
logger.info(f"上传响应: {upload_result}")
|
150 |
-
except asyncio.TimeoutError:
|
151 |
-
logger.error(f"等待文件 {filename} 上传结果超时")
|
152 |
-
failed_count += 1
|
153 |
-
continue
|
154 |
-
|
155 |
-
if upload_result.get('type') == 'upload_success':
|
156 |
-
logger.info(f"文件 {filename} 上传成功")
|
157 |
-
success_count += 1
|
158 |
-
else:
|
159 |
-
logger.error(f"文件 {filename} 上传失败: {upload_result}")
|
160 |
-
failed_count += 1
|
161 |
-
|
162 |
-
# 稍微延迟避免服务器压力过大
|
163 |
-
await asyncio.sleep(0.5)
|
164 |
-
|
165 |
except Exception as e:
|
166 |
-
logger.error(f"
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
try:
|
173 |
-
|
174 |
-
|
175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
except Exception as e:
|
177 |
-
logger.error(f"
|
178 |
-
|
179 |
-
return success_count > 0
|
180 |
-
|
181 |
-
elif response_data.get('type') == 'error':
|
182 |
-
logger.error(f"注册失败: {response_data.get('message', '未知错误')}")
|
183 |
-
return False
|
184 |
-
else:
|
185 |
-
logger.error(f"未知的响应类型: {response_data}")
|
186 |
-
return False
|
187 |
-
|
188 |
-
except websockets.exceptions.InvalidURI as e:
|
189 |
-
logger.error(f"无效的WebSocket URI: {e}")
|
190 |
-
return False
|
191 |
-
except websockets.exceptions.ConnectionClosed as e:
|
192 |
-
logger.error(f"WebSocket连接被关闭: {e}")
|
193 |
-
return False
|
194 |
except Exception as e:
|
195 |
-
logger.error(f"
|
196 |
-
return False
|
197 |
|
198 |
-
def
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
|
226 |
if __name__ == "__main__":
|
227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import asyncio
|
2 |
import websockets
|
3 |
+
import subprocess
|
4 |
import json
|
5 |
import logging
|
6 |
import sys
|
7 |
+
import urllib.parse
|
8 |
import base64
|
9 |
import os
|
10 |
+
import threading
|
11 |
+
import requests
|
12 |
from urllib.parse import urlparse
|
13 |
|
14 |
+
# Configure logging
|
15 |
+
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
16 |
logger = logging.getLogger(__name__)
|
17 |
|
18 |
+
# 全局变量,用于在用户输入时访问 WebSocket
|
19 |
+
global_websocket = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
+
async def connect(space_id, machine_secret, token, upload_file=None, upload_dir=None):
|
22 |
+
global global_websocket
|
23 |
+
# 使用查询参数传递 machine_secret 和 token
|
24 |
+
encoded_secret = urllib.parse.quote(machine_secret)
|
25 |
+
encoded_token = urllib.parse.quote(token)
|
26 |
+
uri = f"wss://remote-terminal-worker.nianxi4563.workers.dev/terminal/{space_id}?secret={encoded_secret}&token={encoded_token}"
|
27 |
+
logger.info(f"Attempting to connect to {uri}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
try:
|
29 |
+
async with websockets.connect(uri, ping_interval=20, ping_timeout=60) as websocket:
|
30 |
+
global_websocket = websocket
|
31 |
+
logger.info("Connected to WebSocket")
|
32 |
+
machine_info = {"type": "machine", "space_id": space_id, "token": token}
|
33 |
+
await websocket.send(json.dumps(machine_info))
|
34 |
+
logger.debug(f"Sent machine registration: {machine_info}")
|
35 |
+
|
36 |
+
# 如果指定了上传文件,发送文件内容
|
37 |
+
if upload_file:
|
38 |
+
if os.path.exists(upload_file):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
try:
|
40 |
+
with open(upload_file, 'rb') as f:
|
|
|
|
|
|
|
|
|
41 |
file_content = f.read()
|
42 |
+
file_b64 = base64.b64encode(file_content).decode('utf-8')
|
43 |
+
file_msg = {
|
44 |
+
"type": "file_upload",
|
45 |
+
"filename": os.path.basename(upload_file),
|
46 |
+
"content": file_b64,
|
47 |
+
"token": token
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
}
|
49 |
+
await websocket.send(json.dumps(file_msg))
|
50 |
+
logger.info(f"Uploaded file: {upload_file}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
except Exception as e:
|
52 |
+
logger.error(f"Error uploading file {upload_file}: {e}")
|
53 |
+
else:
|
54 |
+
logger.error(f"File not found: {upload_file}")
|
55 |
+
|
56 |
+
# 如果指定了上传目录,扫描并上传所有文件
|
57 |
+
if upload_dir:
|
58 |
+
if os.path.exists(upload_dir):
|
59 |
+
all_files = []
|
60 |
+
for root, dirs, files in os.walk(upload_dir):
|
61 |
+
for file in files:
|
62 |
+
file_path = os.path.join(root, file)
|
63 |
+
if os.path.isfile(file_path):
|
64 |
+
all_files.append(file_path)
|
65 |
+
if all_files:
|
66 |
+
logger.info(f"Found {len(all_files)} files in {upload_dir}, starting upload...")
|
67 |
+
for file_path in all_files:
|
68 |
+
try:
|
69 |
+
with open(file_path, 'rb') as f:
|
70 |
+
file_content = f.read()
|
71 |
+
file_b64 = base64.b64encode(file_content).decode('utf-8')
|
72 |
+
file_msg = {
|
73 |
+
"type": "file_upload",
|
74 |
+
"filename": os.path.basename(file_path),
|
75 |
+
"content": file_b64,
|
76 |
+
"token": token
|
77 |
+
}
|
78 |
+
await websocket.send(json.dumps(file_msg))
|
79 |
+
logger.info(f"Uploaded file from dir: {file_path}")
|
80 |
+
except Exception as e:
|
81 |
+
logger.error(f"Error uploading file {file_path}: {e}")
|
82 |
+
else:
|
83 |
+
logger.info(f"No files found in {upload_dir}")
|
84 |
+
else:
|
85 |
+
logger.error(f"Directory not found: {upload_dir}")
|
86 |
+
|
87 |
+
# 创建下载目录
|
88 |
+
os.makedirs("./downloads", exist_ok=True)
|
89 |
+
|
90 |
+
# 启动用户输入监听(非阻塞)
|
91 |
+
asyncio.create_task(listen_user_input())
|
92 |
+
|
93 |
+
while True:
|
94 |
try:
|
95 |
+
message = await websocket.recv()
|
96 |
+
logger.debug(f"Received message: {message}")
|
97 |
+
data = json.loads(message)
|
98 |
+
if data["type"] == "command":
|
99 |
+
command = data["command"]
|
100 |
+
logger.info(f"Executing command: {command}")
|
101 |
+
try:
|
102 |
+
process = subprocess.run(command, shell=True, capture_output=True, text=True)
|
103 |
+
output = process.stdout + process.stderr
|
104 |
+
logger.debug(f"Command output: {output}")
|
105 |
+
if process.returncode == 0:
|
106 |
+
await websocket.send(json.dumps({"type": "output", "data": output}))
|
107 |
+
else:
|
108 |
+
await websocket.send(json.dumps({"type": "error", "data": output}))
|
109 |
+
except Exception as e:
|
110 |
+
error_message = f"Error executing command: {e}"
|
111 |
+
logger.error(error_message)
|
112 |
+
await websocket.send(json.dumps({"type": "error", "data": error_message}))
|
113 |
+
elif data["type"] == "file_download_url":
|
114 |
+
# 处理从服务器发来的文件下载URL
|
115 |
+
url = data["url"]
|
116 |
+
filename = data["filename"]
|
117 |
+
logger.info(f"Received file download URL: {url}")
|
118 |
+
try:
|
119 |
+
download_dir = "./downloads"
|
120 |
+
os.makedirs(download_dir, exist_ok=True)
|
121 |
+
response = requests.get(url)
|
122 |
+
if response.status_code == 200:
|
123 |
+
file_path = os.path.join(download_dir, filename)
|
124 |
+
with open(file_path, 'wb') as f:
|
125 |
+
f.write(response.content)
|
126 |
+
success_msg = f"File downloaded successfully: {file_path}"
|
127 |
+
logger.info(success_msg)
|
128 |
+
await websocket.send(json.dumps({
|
129 |
+
"type": "output",
|
130 |
+
"data": success_msg
|
131 |
+
}))
|
132 |
+
else:
|
133 |
+
error_msg = f"Failed to download file. Status code: {response.status_code}"
|
134 |
+
logger.error(error_msg)
|
135 |
+
await websocket.send(json.dumps({
|
136 |
+
"type": "error",
|
137 |
+
"data": error_msg
|
138 |
+
}))
|
139 |
+
except Exception as e:
|
140 |
+
error_msg = f"Error downloading file from URL: {e}"
|
141 |
+
logger.error(error_msg)
|
142 |
+
await websocket.send(json.dumps({
|
143 |
+
"type": "error",
|
144 |
+
"data": error_msg
|
145 |
+
}))
|
146 |
+
elif data["type"] == "ping":
|
147 |
+
logger.debug("Received ping, sending pong")
|
148 |
+
await websocket.send(json.dumps({"type": "pong"}))
|
149 |
+
except websockets.exceptions.ConnectionClosed:
|
150 |
+
logger.error("WebSocket connection closed")
|
151 |
+
break
|
152 |
except Exception as e:
|
153 |
+
logger.error(f"Error processing message: {e}")
|
154 |
+
await websocket.send(json.dumps({"type": "error", "data": str(e)}))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
155 |
except Exception as e:
|
156 |
+
logger.error(f"Failed to connect or maintain connection: {e}")
|
|
|
157 |
|
158 |
+
async def listen_user_input():
|
159 |
+
global global_websocket
|
160 |
+
logger.info("Started listening for user input. Use ' --upload' to upload files to server.")
|
161 |
+
while True:
|
162 |
+
try:
|
163 |
+
user_input = await asyncio.to_thread(input, "Enter command or file path to upload: ")
|
164 |
+
if user_input.strip():
|
165 |
+
parts = user_input.strip().split()
|
166 |
+
if len(parts) >= 2 and parts[-1] == "--upload":
|
167 |
+
filename = " ".join(parts[:-1])
|
168 |
+
if os.path.exists(filename):
|
169 |
+
try:
|
170 |
+
with open(filename, 'rb') as f:
|
171 |
+
file_content = f.read()
|
172 |
+
file_b64 = base64.b64encode(file_content).decode('utf-8')
|
173 |
+
file_msg = {
|
174 |
+
"type": "file_upload",
|
175 |
+
"filename": os.path.basename(filename),
|
176 |
+
"content": file_b64
|
177 |
+
}
|
178 |
+
if global_websocket:
|
179 |
+
await global_websocket.send(json.dumps(file_msg))
|
180 |
+
logger.info(f"Uploaded file via input: {filename}")
|
181 |
+
else:
|
182 |
+
logger.error("WebSocket not connected")
|
183 |
+
except Exception as e:
|
184 |
+
logger.error(f"Error uploading file {filename}: {e}")
|
185 |
+
else:
|
186 |
+
logger.error(f"File not found: {filename}")
|
187 |
+
elif len(parts) >= 2 and parts[-1] == "--download":
|
188 |
+
url = " ".join(parts[:-1])
|
189 |
+
try:
|
190 |
+
parsed_url = urlparse(url)
|
191 |
+
filename = os.path.basename(parsed_url.path)
|
192 |
+
download_dir = "./downloads"
|
193 |
+
os.makedirs(download_dir, exist_ok=True)
|
194 |
+
logger.info(f"Downloading file from URL: {url}")
|
195 |
+
response = requests.get(url)
|
196 |
+
if response.status_code == 200:
|
197 |
+
file_path = os.path.join(download_dir, filename)
|
198 |
+
with open(file_path, 'wb') as f:
|
199 |
+
f.write(response.content)
|
200 |
+
logger.info(f"File downloaded successfully: {file_path}")
|
201 |
+
else:
|
202 |
+
logger.error(f"Failed to download file. Status code: {response.status_code}")
|
203 |
+
except Exception as e:
|
204 |
+
logger.error(f"Error downloading from URL {url}: {e}")
|
205 |
+
else:
|
206 |
+
if global_websocket:
|
207 |
+
await global_websocket.send(json.dumps({
|
208 |
+
"type": "command",
|
209 |
+
"command": user_input
|
210 |
+
}))
|
211 |
+
logger.info(f"Sent command: {user_input}")
|
212 |
+
else:
|
213 |
+
logger.error("WebSocket not connected")
|
214 |
+
await asyncio.sleep(0.1)
|
215 |
+
except Exception as e:
|
216 |
+
logger.error(f"Error in user input listener: {e}")
|
217 |
+
await asyncio.sleep(1)
|
218 |
|
219 |
if __name__ == "__main__":
|
220 |
+
if len(sys.argv) < 4:
|
221 |
+
print("Usage: python remote_client.py <space_id> <machine_secret> <token> [--upload <file_path>] [--upload-dir <dir_path>]")
|
222 |
+
sys.exit(1)
|
223 |
+
space_id = sys.argv[1]
|
224 |
+
machine_secret = sys.argv[2]
|
225 |
+
token = sys.argv[3]
|
226 |
+
upload_file = None
|
227 |
+
upload_dir = None
|
228 |
+
if len(sys.argv) > 4:
|
229 |
+
if sys.argv[4] == "--upload" and len(sys.argv) > 5:
|
230 |
+
upload_file = sys.argv[5]
|
231 |
+
elif sys.argv[4] == "--upload-dir" and len(sys.argv) > 5:
|
232 |
+
upload_dir = sys.argv[5]
|
233 |
+
asyncio.run(connect(space_id, machine_secret, token, upload_file, upload_dir))
|