fix ws
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from .views.routes import main_bp
|
from .views.routes import main_bp
|
||||||
from .sockets.terminal_service import terminal_bp
|
|
||||||
from .views.file import file_bp
|
from .views.file import file_bp
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .extensions import init_extensions
|
from .extensions import init_extensions,register_namespaces
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__, template_folder="../templates", static_folder="../static",static_url_path="/vsc-like/static")
|
app = Flask(__name__, template_folder="../templates", static_folder="../static",static_url_path="/vsc-like/static")
|
||||||
@@ -14,9 +13,10 @@ def create_app():
|
|||||||
|
|
||||||
# 注册蓝图
|
# 注册蓝图
|
||||||
app.register_blueprint(main_bp)
|
app.register_blueprint(main_bp)
|
||||||
app.register_blueprint(terminal_bp)
|
|
||||||
app.register_blueprint(file_bp)
|
app.register_blueprint(file_bp)
|
||||||
# 初始化SocketIO
|
# 初始化SocketIO
|
||||||
init_extensions(app)
|
init_extensions(app)
|
||||||
|
|
||||||
|
register_namespaces(app)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -3,9 +3,16 @@ from flask_socketio import SocketIO
|
|||||||
from .config import Config
|
from .config import Config
|
||||||
# 初始化扩展
|
# 初始化扩展
|
||||||
cors = CORS()
|
cors = CORS()
|
||||||
socketio = SocketIO()
|
socketio = SocketIO(path='vsc-like-ws',cors_allowed_origins="*",max_payload_size=52428800, async_mode="eventlet") # 不直接传 app
|
||||||
|
|
||||||
session_paths = {}
|
session_paths = {}
|
||||||
def init_extensions(app):
|
def init_extensions(app):
|
||||||
cors.init_app(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
|
cors.init_app(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
|
||||||
socketio.init_app(app, cors_allowed_origins="*", max_payload_size=52428800, async_mode="threading")
|
socketio.init_app(app, cors_allowed_origins="*", max_payload_size=52428800, async_mode="threading")
|
||||||
app.extensions["session_paths"] = session_paths
|
app.extensions["session_paths"] = session_paths
|
||||||
|
|
||||||
|
def register_namespaces(app):
|
||||||
|
"""把所有 Socket.IO namespaces 注册到 socketio"""
|
||||||
|
# 延迟导入以避免循环
|
||||||
|
from .sockets.terminal_service import VSCLikeNameSpace, socketio
|
||||||
|
socketio.on_namespace(VSCLikeNameSpace('/vsc-like'))
|
||||||
@@ -3,16 +3,16 @@ import select
|
|||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
from flask import Blueprint, request, current_app, session
|
from flask import Blueprint, request, current_app, session
|
||||||
from flask_socketio import SocketIO, emit, join_room
|
from flask_socketio import Namespace, join_room, leave_room, emit, SocketIO
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import sys
|
import sys
|
||||||
from ..extensions import socketio
|
from ..extensions import socketio
|
||||||
|
|
||||||
terminal_bp = Blueprint('terminal', __name__, url_prefix='/vsc-like/terminal')
|
terminal_bp = Blueprint('terminal', __name__, url_prefix='/vsc-like')
|
||||||
|
|
||||||
# 存储终端进程
|
# 存储终端进程
|
||||||
terminals = {}
|
terminals = {}
|
||||||
|
terminal_locks = {}
|
||||||
def start_terminal_process(socket_id):
|
def start_terminal_process(socket_id):
|
||||||
"""
|
"""
|
||||||
监听多个子进程的输出,并通过 WebSocket 发给前端
|
监听多个子进程的输出,并通过 WebSocket 发给前端
|
||||||
@@ -52,84 +52,79 @@ def start_terminal_process(socket_id):
|
|||||||
if process.poll() is not None:
|
if process.poll() is not None:
|
||||||
break
|
break
|
||||||
|
|
||||||
@socketio.on('connect', namespace='/vsc-like/terminal')
|
|
||||||
def connect_terminal():
|
|
||||||
"""
|
|
||||||
处理 WebSocket 连接
|
|
||||||
- 验证会话
|
|
||||||
- 使用指定的用户启动 bash 终端
|
|
||||||
- 启动监听子进程输出的线程
|
|
||||||
"""
|
|
||||||
socket_id = session.get('uuid')
|
|
||||||
user_id = session.get('user_id') # 从 session 获取用户 ID
|
|
||||||
path = session.get('path')
|
|
||||||
|
|
||||||
if not path or not user_id:
|
class VSCLikeNameSpace(Namespace):
|
||||||
emit('error', {'message': 'Authentication failed, no path or user found for this session'})
|
def on_connect(self, data):
|
||||||
return False # 拒绝连接
|
"""
|
||||||
|
处理 WebSocket 连接
|
||||||
|
- 验证会话
|
||||||
|
- 使用指定的用户启动 bash 终端
|
||||||
|
- 启动监听子进程输出的线程
|
||||||
|
"""
|
||||||
|
socket_id = session.get('uuid')
|
||||||
|
user_id = session.get('user_id') # 从 session 获取用户 ID
|
||||||
|
path = session.get('path')
|
||||||
|
|
||||||
# 使用 sudo 启动终端进程,以指定的 user_id 用户身份执行
|
if not path or not user_id:
|
||||||
try:
|
emit('error', {'message': 'Authentication failed, no path or user found for this session'})
|
||||||
process = subprocess.Popen(
|
return False # 拒绝连接
|
||||||
['sudo', '-u', user_id, 'bash'], # 使用指定的 user_id 启动 bash
|
# 使用 sudo 启动终端进程,以指定的 user_id 用户身份执行
|
||||||
stdin=subprocess.PIPE,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
terminals[socket_id] = process
|
|
||||||
|
|
||||||
emit('connected', {'message': 'Terminal connected'})
|
|
||||||
join_room(path, namespace='/vsc-like/terminal')
|
|
||||||
|
|
||||||
# 启动监听终端输出的线程
|
|
||||||
thread = Thread(target=start_terminal_process, args=(socket_id,))
|
|
||||||
thread.daemon = True
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
emit('error', {'message': f'Error starting terminal process: {str(e)}'})
|
|
||||||
return False
|
|
||||||
|
|
||||||
terminal_locks = {} # 存储终端的锁,用于线程同步
|
|
||||||
|
|
||||||
@socketio.on('send_input', namespace='/vsc-like/terminal')
|
|
||||||
def send_input(data):
|
|
||||||
socket_id = session.get('uuid')
|
|
||||||
input_data = data.get('input')
|
|
||||||
|
|
||||||
# 检查当前 socket_id 是否有对应的终端
|
|
||||||
if socket_id in terminals:
|
|
||||||
# 获取该终端的锁进行线程同步
|
|
||||||
lock = terminal_locks.get(socket_id)
|
|
||||||
if not lock:
|
|
||||||
lock = threading.Lock()
|
|
||||||
terminal_locks[socket_id] = lock
|
|
||||||
|
|
||||||
with lock: # 在锁的保护下操作终端
|
|
||||||
try:
|
|
||||||
terminals[socket_id].stdin.write(input_data + '\n')
|
|
||||||
terminals[socket_id].stdin.flush()
|
|
||||||
emit('terminal_output', {'data': f'Command sent: {input_data}'}, room=socket_id)
|
|
||||||
except Exception as e:
|
|
||||||
emit('error', {'message': f'Error sending input: {str(e)}'}, room=socket_id)
|
|
||||||
else:
|
|
||||||
emit('error', {'message': 'No terminal found for this session'}, room=socket_id)
|
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('disconnect', namespace='/vsc-like/terminal')
|
|
||||||
def disconnect_terminal():
|
|
||||||
socket_id = session.get('uuid')
|
|
||||||
|
|
||||||
# 检查是否有终端进程需要终止
|
|
||||||
if socket_id in terminals:
|
|
||||||
try:
|
try:
|
||||||
# 终止终端进程
|
process = subprocess.Popen(
|
||||||
terminals[socket_id].terminate()
|
['sudo', '-u', user_id, 'bash'], # 使用指定的 user_id 启动 bash
|
||||||
del terminals[socket_id]
|
stdin=subprocess.PIPE,
|
||||||
# 删除锁
|
stdout=subprocess.PIPE,
|
||||||
if socket_id in terminal_locks:
|
stderr=subprocess.PIPE,
|
||||||
del terminal_locks[socket_id]
|
text=True
|
||||||
emit('terminal_output', {'data': 'Terminal disconnected and terminated'}, room=socket_id)
|
)
|
||||||
|
terminals[socket_id] = process
|
||||||
|
|
||||||
|
emit('connected', {'message': 'Terminal connected'})
|
||||||
|
join_room(path, namespace='/vsc-like')
|
||||||
|
|
||||||
|
# 启动监听终端输出的线程
|
||||||
|
thread = Thread(target=start_terminal_process, args=(socket_id,))
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
emit('error', {'message': f'Error disconnecting terminal: {str(e)}'}, room=socket_id)
|
emit('error', {'message': f'Error starting terminal process: {str(e)}'})
|
||||||
|
return False
|
||||||
|
|
||||||
|
def on_send_input(self, data):
|
||||||
|
socket_id = session.get('uuid')
|
||||||
|
input_data = data.get('input')
|
||||||
|
|
||||||
|
# 检查当前 socket_id 是否有对应的终端
|
||||||
|
if socket_id in terminals:
|
||||||
|
# 获取该终端的锁进行线程同步
|
||||||
|
lock = terminal_locks.get(socket_id)
|
||||||
|
if not lock:
|
||||||
|
lock = threading.Lock()
|
||||||
|
terminal_locks[socket_id] = lock
|
||||||
|
|
||||||
|
with lock: # 在锁的保护下操作终端
|
||||||
|
try:
|
||||||
|
terminals[socket_id].stdin.write(input_data + '\n')
|
||||||
|
terminals[socket_id].stdin.flush()
|
||||||
|
emit('terminal_output', {'data': f'Command sent: {input_data}'}, room=socket_id)
|
||||||
|
except Exception as e:
|
||||||
|
emit('error', {'message': f'Error sending input: {str(e)}'}, room=socket_id)
|
||||||
|
else:
|
||||||
|
emit('error', {'message': 'No terminal found for this session'}, room=socket_id)
|
||||||
|
|
||||||
|
def on_disconnect():
|
||||||
|
socket_id = session.get('uuid')
|
||||||
|
|
||||||
|
# 检查是否有终端进程需要终止
|
||||||
|
if socket_id in terminals:
|
||||||
|
try:
|
||||||
|
# 终止终端进程
|
||||||
|
terminals[socket_id].terminate()
|
||||||
|
del terminals[socket_id]
|
||||||
|
# 删除锁
|
||||||
|
if socket_id in terminal_locks:
|
||||||
|
del terminal_locks[socket_id]
|
||||||
|
emit('terminal_output', {'data': 'Terminal disconnected and terminated'}, room=socket_id)
|
||||||
|
except Exception as e:
|
||||||
|
emit('error', {'message': f'Error disconnecting terminal: {str(e)}'}, room=socket_id)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const socket = io.connect('https://hsamooc.cn/vsc-like/terminal');
|
const socket = io('https://hsamooc.cn/vsc-like',{path:'/vsc-like-ws'});
|
||||||
let term;
|
let term;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user