django-websocket-ssh
django-websocket-ssh
实现一个web ssh连接服务器
1.1 安装依赖
pip install channels
pip install channels-redis
pip install websockets
1.2 配置settings.py
# 配置channels layer
ASGI_APPLICATION = 'layui_admin_api.asgi.application' # 自己routing的路径
CHANNEL_LAYERS = {
# 真实上线使用redis
# 'default': {
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
# 'CONFIG': {
# "hosts": [('127.0.0.1', 6379)], # 需修改redis的地址
# },
# },
# 测试阶段,放到内存中即可
'default': {
'BACKEND': 'channels.layers.InMemoryChannelLayer',
},
}
1.3 配置routing路由
# 在项目settings文件同级目录中新增routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from webssh import routing
application = ProtocolTypeRouter({
# 'websocket': AuthMiddlewareStack( # 使用AuthMiddlewareStack,后续在视图类中可以取出self.scope,相当于request对象
'websocket': URLRouter(
routing.websocket_urlpatterns # 指明路由文件是django_websocket/routing.py,类似于路由分发
),
})
1.4 配置app.routing(app的routing.py)
from django.urls import path
from webssh import consumers
websocket_urlpatterns = [
path('ws/webssh/<str:host>/<str:username>/<str:password>/', consumers.SSHConsumer.as_asgi()),
]
1.5 配置asgi.py
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import URLRouter, ProtocolTypeRouter
from django.core.asgi import get_asgi_application
from webssh.websocket import websocket_application
from webssh import routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'layui_admin_api.settings')
application_asgi = get_asgi_application()
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
routing.websocket_urlpatterns
)
),
})
1.6 写消费者类(consumers.py)
import json
import paramiko
from channels.generic.websocket import WebsocketConsumer
from threading import Thread
from urllib.parse import parse_qs
import time
class SSHConsumer(WebsocketConsumer):
def connect(self):
# 1.接收socket连接
self.accept()
query_params = self.scope['url_route']['kwargs']
host = query_params['host']
username = query_params['username']
password = query_params['password']
# 2.建立ssh连接
self.ssh_client = paramiko.SSHClient()
self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
self.ssh_client.connect(
hostname=host,
port=22,
username=username,
password=password
)
except Exception as e:
# 如果报错就发送报错信息给前端
self.send(text_data=json.dumps({
'message': str(e)
}))
self.close()
return
# 3.建立ssh传输通道
transport = self.ssh_client.get_transport()
# 4.打开新会话
self.ssh_channel = transport.open_session()
# 5.获取一个伪终端
self.ssh_channel.get_pty(term='xterm')
# 6.启动一个交互式的Shell会话,允许执行命令并获取输出
self.ssh_channel.invoke_shell()
self.command_history = []
self.last_output = ""
# 启动接收远程命令结果的线程
Thread(target=self.recv_from_host).start()
def disconnect(self, close_code):
self.ssh_client.close()
def receive(self, text_data=None):
text_data = json.loads(text_data)
command = text_data.get('data', '')
self.command_history.append(command.strip())
# 在新线程中发送命令
Thread(target=self.send_command, args=[command + '\n']).start()
def send_command(self, command):
self.ssh_channel.send(command)
time.sleep(0.1) # 延迟,以确保接收到的是命令执行结果
def recv_from_host(self):
try:
buffer = ""
while not self.ssh_channel.exit_status_ready():
if self.ssh_channel.recv_ready():
data = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
buffer += data
if buffer.endswith(('# ', '$ ', '% ')): # 检查常见的命令提示符
# 如果当前缓冲区包含最新命令,去除命令部分
for command in self.command_history:
if command in buffer:
buffer = buffer.replace(command, '').strip()
self.command_history.remove(command)
if buffer:
message = {'flag': 'success', 'message': buffer.strip()}
self.send(json.dumps(message))
buffer = "" # 清空缓冲区
except Exception as e:
print(f"Error in receiving data: {str(e)}")
1.7 启动项目
python -m uvicorn layui_admin_api.asgi:application