基于Python的tornado和xterm.js如何实现webssh项目
去年 11 月写了个 webssh 小项目,基于 tornado, paramiko 和 xterm.js 。
大概原理是这样的
browser <–http–> webssh server <–ssh–> ssh server
webssh server: 运行 webssh 的服务器, 起到了桥梁的作用,跟浏览器是用 http(s)或者 websocksets 来通信的,跟 ssh 服务器是用 ssh 协议来通信的。
项目地址是,https://github.com/huashengdun/webssh
欢迎大家来拍砖。
基于Python的tornado和xterm.js如何实现webssh项目
当也欢迎对这个项目 https://github.com/huashengdun/shadowsocks-lab 拍砖
基于Tornado和xterm.js实现WebSSH的核心是建立WebSocket代理,将浏览器中的终端操作转发到真实SSH服务器。以下是关键代码实现:
1. 后端Tornado WebSocket处理器
import tornado.websocket
import paramiko
import threading
import asyncio
class SSHWebSocketHandler(tornado.websocket.WebSocketHandler):
def initialize(self):
self.ssh = None
self.channel = None
async def open(self):
# 连接参数可从URL参数或认证后获取
host = self.get_argument('host', 'localhost')
port = int(self.get_argument('port', 22))
username = self.get_argument('username')
# 创建SSH客户端连接
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 注意:实际生产环境需要安全地处理密码/密钥
await self.run_in_executor(
self.ssh.connect,
host, port, username,
password=self.get_argument('password', '')
)
# 创建交互式通道
self.channel = self.ssh.invoke_shell(term='xterm')
self.channel.setblocking(0)
# 启动数据读取循环
tornado.ioloop.IOLoop.current().add_callback(self.read_ssh_output)
async def read_ssh_output(self):
"""从SSH通道读取数据并发送到WebSocket"""
while self.channel and not self.channel.closed:
try:
if self.channel.recv_ready():
data = await self.run_in_executor(
self.channel.recv, 1024
)
if data:
self.write_message(data, binary=True)
await asyncio.sleep(0.01)
except:
break
def on_message(self, message):
"""接收WebSocket消息并写入SSH通道"""
if self.channel and not self.channel.closed:
self.channel.send(message)
def on_close(self):
"""清理连接"""
if self.channel:
self.channel.close()
if self.ssh:
self.ssh.close()
async def run_in_executor(self, func, *args):
"""将阻塞调用转为异步"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, func, *args)
2. 前端xterm.js集成
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/xterm/css/xterm.css">
</head>
<body>
<div id="terminal"></div>
<script src="https://unpkg.com/xterm/lib/xterm.js"></script>
<script src="https://unpkg.com/xterm-addon-fit/lib/xterm-addon-fit.js"></script>
<script>
const term = new Terminal();
const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
term.open(document.getElementById('terminal'));
fitAddon.fit();
// WebSocket连接
const ws = new WebSocket(
`ws://${location.host}/ssh?host=服务器IP&username=用户&password=密码`
);
// 终端输入转发到WebSocket
term.onData(data => {
ws.send(data);
});
// WebSocket数据输出到终端
ws.onmessage = event => {
term.write(event.data);
};
// 调整终端大小
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'resize',
cols: term.cols,
rows: term.rows
}));
};
</script>
</body>
</html>
3. Tornado应用主程序
import tornado.web
import tornado.ioloop
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("terminal.html") # 上面的HTML页面
app = tornado.web.Application([
(r'/', MainHandler),
(r'/ssh', SSHWebSocketHandler),
])
if __name__ == '__main__':
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
核心要点:
- 使用Paramiko库建立SSH连接
- WebSocket实时双向通信
- 非阻塞方式处理SSH通道I/O
- xterm.js处理终端渲染和用户输入
安全提醒: 实际部署时需要添加身份验证、输入验证和HTTPS加密。
总结建议:注意处理好SSH连接的生命周期和异常情况。
Mark 下,回头有时间再仔细看下。
增加了一个分支 external_staic,依赖的静态资源使用链接引用形式。
欢迎使用和反馈。
hello,想把这个集成到 django 中,不知有没 demo 参考的?
哇,作者居然上 v2, 感谢作者 前两天才把 webssh 集成到了运维后台中去~
老哥你是怎么集成上去的,能给点建议吗

