基于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项目


8 回复

基于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()

核心要点:

  1. 使用Paramiko库建立SSH连接
  2. WebSocket实时双向通信
  3. 非阻塞方式处理SSH通道I/O
  4. xterm.js处理终端渲染和用户输入

安全提醒: 实际部署时需要添加身份验证、输入验证和HTTPS加密。

总结建议:注意处理好SSH连接的生命周期和异常情况。

Mark 下,回头有时间再仔细看下。

增加了一个分支 external_staic,依赖的静态资源使用链接引用形式。
欢迎使用和反馈。

hello,想把这个集成到 django 中,不知有没 demo 参考的?

哇,作者居然上 v2, 感谢作者 前两天才把 webssh 集成到了运维后台中去~

老哥你是怎么集成上去的,能给点建议吗

回到顶部