• 02: tornado结合Websocket进行WebSSH的实现


    1.1 WebSSH介绍

       1、什么是WebSSH?

          1. webssh 泛指一种技术可以在网页上实现一个 SSH 终端。

          2. 从而无需 Xshell 之类的模拟终端工具进行 SSH 连接,将 SSH 这一比较低层的操作也从 C/S 架构扭成了 B/S 架构

          3. 这样的架构常用在运维制作开发一些堡垒机等系统中,或是目前比较新型的在线教育方式

          4. 通过WebSSH 向学生提供一个可以直接使用浏览器进行相关 Linux 操作或代码编写的学习方式

          5. WebSSh 主要是建立客户端与服务端的即时通信

       2、要实现WebSSH技术栈介绍

    # 前端
    vue
    websocket
    xterm.js
    
    # 后端
    tornado
    dwebsocket
    paramiko
    threading

      3、技术介绍

        1)xterm

            前端通过 xterm 插件进行 shell 黑窗口环境的搭建,

            这个插件会自动解析由后台 paramiko 返回的带有标记样式的命令结果,并渲染到浏览器中,非常酷炫
          
        2)websocket

            这里通过 websocket 进行浏览器与 tornado 的双向通信。

        3)paramiko

            paramiko 此时的角色用来承担 tornado与 Linux 环境的交互,

            将前端发来的命令发送给后台,将后台发来的命令结果返回到前端的 xterm 组件中

    1.2 前端实现

            参考代码:https://gitee.com/edushiyanlou/webssh

      1、初始化vue项目

    vue init webpack webssh
    npm install  --save  xterm@3.1.0
    // The Vue build version to load with the `import` command
    // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
    import Vue from 'vue'
    import App from './App'
    import router from './router'
    
    // 引入xterm.css样式
    import 'xterm/dist/xterm.css'
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      components: { App },
      template: '<App/>'
    })
    srcmain.js  文件中引入xterm.css文件
    <template>
      <div class="console" id="terminal"></div>
    </template>
    <script>
      import Terminal from './Xterm'
      export default {
        name: 'Console',
        data () {
          return {
            terminal: {
              pid: 1,
              name: 'terminal',
              cols: 400,
              rows: 400
            },
            term: null,
            terminalSocket: null
          }
        },
        methods: {
          runRealTerminal () {
            console.log('webSocket is finished')
          },
          errorRealTerminal () {
            console.log('error')
          },
          closeRealTerminal () {
            console.log('close')
          }
        },
        mounted () {
          console.log('pid : ' + this.terminal.pid + ' is on ready')
          let terminalContainer = document.getElementById('terminal')
          this.term = new Terminal()                     // 创建一个新的Terminal对象
          this.term.open(terminalContainer)              // 将term挂砸到dom节点上
          // open websocket
          this.terminalSocket = new WebSocket('ws://127.0.0.1:3000/terminals/')  // 创建socket连接
          this.terminalSocket.onopen = this.runRealTerminal          // 当连接成功触发此函数
          this.terminalSocket.onclose = this.closeRealTerminal       // 当断开连接调用此函数
          this.terminalSocket.onerror = this.errorRealTerminal       // 当发生错误调用此函数
          this.term.attach(this.terminalSocket)                      // // 绑定xterm到ws流中
          this.term._initialized = true
          console.log('mounted is going on')
        },
        beforeDestroy () {
          this.terminalSocket.close()
          this.term.destroy()
        }
      }
    </script>
    srccomponentsWebSSH.vue  创建.vue文件
    import { Terminal } from 'xterm'
    import * as fit from 'xterm/lib/addons/fit/fit'
    import * as attach from 'xterm/lib/addons/attach/attach'
    Terminal.applyAddon(fit)
    Terminal.applyAddon(attach)
    
    export default Terminal
    srccomponentsXterm.js  导出xterm相关插件
    import Vue from 'vue'
    import Router from 'vue-router'
    import HelloWorld from '@/components/HelloWorld'
    import WebSSH from '@/components/WebSSH'
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        { path: '/', name: 'HelloWorld', component: HelloWorld },
        { path: '/webssh', name: 'WebSSH', component: WebSSH }
      ]
    })
    src outerindex.js 添加路由

      2、对核心代码解释

    # 1. Xterm.js  初始化 xterm 组件并添加两个插件 
    attach 可以将终端附加到 websocket 流中
    fit 可以调整终端的大小以及行和列适配父级元素
    
    # 2. WebSSH.vue 
    构建 websocket 并绑定到终端, websocket 地址为 ws 协议前缀
    此时使用的是即将在 tornado 中配置 Websocket 后台视图的路由,这一系列行为将挂载到钩子函数下进行
    
    # 当浏览器关闭时,也代表着客户端关闭,此时主动断开连接,交给 vue 的钩子函数来处理这个问题
    beforeDestroy () {
        this.terminalSocket.close()
        this.term.destroy()
    }

     1.3 后端tornado代码(方法一)

      1、安装相关包

    # requirements.txt
    paramiko==2.4.1
    tornado==4.5.2
    requests==2.18.4
    PyJWT==1.6.4

      2、tornado服务端代码

    # -*- coding: utf-8 -*-
    import tornado
    import tornado.websocket
    import paramiko
    import threading
    import time
    
    # 配置服务器信息
    HOSTS = '1.1.1.3'
    PORT = 22
    USERNAME = 'root'
    PASSWORD = 'chnsys@2016'
    
    
    class MyThread(threading.Thread):
        def __init__(self, id, chan):
            '''
            :param id:  线程id这里没有用
            :param chan:  webSSHServer对象
            '''
            threading.Thread.__init__(self)
            self.chan = chan
    
        def run(self):
            thread_num = len(threading.enumerate())
            print("线程数量:", thread_num)
            while not self.chan.chan.exit_status_ready():
                time.sleep(0.1)
                try:
                    data = self.chan.chan.recv(1024)
                    self.chan.write_message(data)
                except Exception as ex:
                    print(str(ex))
            self.chan.sshclient.close()
            return False
    
    
    class webSSHServer(tornado.websocket.WebSocketHandler):
        # 连接websocket服务器时进行的event
        def open(self):
            self.sshclient = paramiko.SSHClient()     # 1 创建SSH对象
            # 通过known_hosts 方式进行认证可以用这个,如果known_hosts 文件未定义还需要定义 known_hosts
            self.sshclient.load_system_host_keys()
            self.sshclient.set_missing_host_key_policy(paramiko.AutoAddPolicy())  # 2 允许连接不在know_hosts文件中的主机
            self.sshclient.connect(HOSTS, PORT, USERNAME, PASSWORD)              # 3 连接服务器
            self.chan = self.sshclient.invoke_shell(term='xterm')               # 4 建立交互式shell连接
            self.chan.settimeout(0)
            t1 = MyThread(999, self)  # 传入webSSHServer对象
            t1.setDaemon(True)       # 守护线程,主线程退出时,需要子线程随主线程退出
            t1.start()
    
        # 收到信息的时候进行的动作(每次从前端收到执行的命令就会执行此函数)
        def on_message(self, message):
            try:
                self.chan.send(message)
            except Exception as ex:
                print(str(ex))
    
        # 主动调用close()函数可以关闭这个连接(关闭浏览器时会触发函数,关闭这个线程)
        def on_close(self):
            self.sshclient.close()
    
        # 每次有浏览器触发新连接调用此函数
        def check_origin(self, origin):
            # 允许跨域访问
            return True
    
    
    if __name__ == '__main__':
        # 定义路由
        app = tornado.web.Application([
            (r"/terminals/", webSSHServer),
        ],
            debug=True
        )
    
        # 启动服务器
        http_server = tornado.httpserver.HTTPServer(app)
        http_server.listen(3000)
        tornado.ioloop.IOLoop.current().start()
    server.py tornado服务代码

     1.4 后端django代码(方法二)

             基于django的WebSSH: https://blog.51cto.com/hequan/2145007

       1、安装相关包

    # requirements.txt
    dwebsocket==0.5.10
    Django==2.0.4
    paramiko==2.4.1

      2、django端代码

    from django.contrib import admin
    from django.urls import path
    from app01 import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('terminals/', views.webssh),  # 前端的 ws 连接地址
    ]
    urls.py
    from django.shortcuts import render
    import paramiko
    import threading
    from dwebsocket.decorators import accept_websocket,require_websocket
    
    
    def _ssh(host,username,password,port=22):
        sh = paramiko.SSHClient()  # 1 创建SSH对象
        sh.set_missing_host_key_policy(paramiko.AutoAddPolicy())    # 2 允许连接不在know_hosts文件中的主机
        sh.connect(host, username=username, password=password)      # 3 连接服务器
        channle = sh.invoke_shell(term='xterm')                    # 4 建立交互式shell连接
        return channle
    
    
    def recv_ssh_msg(channle,ws):
        '''
        channle: 建立好的SSH连接通道
        这个函数会不停的接收ssh通道返回的命令
        返回到前端的ws套接字里
        '''
        while not channle.exit_status_ready():
            try:
                buf = channle.recv(1024)   # 接收命令的执行结果
                ws.send(buf)               #发送消息到客户端
            except:
                break
    
    
    @accept_websocket
    def webssh(request):
        '''
        1: 接收前端(ws)的命令,发给后台(ssh)
        2: 接收后台的返回结果,给到前端
        '''
        # request.is_websocket: 如果是个websocket请求返回True,如果是个普通的http请求返回False, 可以用这个方法区分它们
        if request.is_websocket:
            host = '1.1.1.3'
            username = 'root'
            password = 'chnsys@2016'
            channle = _ssh(host, username=username, password=password)  # 返回交互式shell连接对象
            ws = request.websocket
            t = threading.Thread(target=recv_ssh_msg,args=(channle,ws))  #
            t.setDaemon(True)
            t.start() # 线程开启
            while 1:
                cmd = ws.wait() # 阻塞接收前端发来的命令
                if cmd:
                    channle.send(cmd) # 由SSH通道转交给Linux环境
                else: # 连接断开 跳出循环
                    break
            ws.close() # 释放对应套接字资源
            channle.close()
    app01/views.py

      3、效果图

          

  • 相关阅读:
    02-自定义CALayer
    01-CALayer的基本操作
    抽屉效果
    手势识别
    事件响应
    寻找最合适的view
    hitTest方法与PointInside方法
    02-事件的产生与传递
    OC图标+启动图
    OC多线程操作
  • 原文地址:https://www.cnblogs.com/xiaonq/p/12243024.html
Copyright © 2020-2023  润新知