Vue+websocket+django实现WebSSH demo

目标:

        通过实例说明,能够应用至自身应用中




效果:

        如下图,实现了webssh,可以通过web方式,进行实时命令处理




前端:

        包依赖:"xterm": "^4.14.1",   "xterm-addon-fit": "^0.5.0"

        封装SSH组件:/components/SSH/index.vue

<template>
  <div ref="xterm" class="terminal" :style="styleVar" />
</template>

<script>
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'

export default {
  name: 'xterm',
  props: {
    ip: { type: String },   // 通过父组件传递登录ip
    height: {
      type: Number,  // xterm显示屏幕,高度
      default: 100,
    },
  },
  data() {
    return {
      term: null,
      socket: null,
    }
  },
  computed: {   // 动态设置xterm显示屏幕高度
    styleVar() {
      return {
        '--terminal-height': this.height + "vh"
      }
    }
  },
  mounted() {  // 初始化链接
    this.init()
    this.initSocket()
  },
  beforeDestroy() {  // 退出销毁链接
    this.socket.close()
    this.term.dispose()
  },
  methods: {
    init() {  // 初始化Terminal
      this.term = new Terminal({
        fontSize: 18,
        convertEol: true, // 启用时,光标将设置为下一行的开头
        rendererType: 'canvas', // 渲染类型
        cursorBlink: true, // 光标闪烁
        cursorStyle: 'bar', // 光标样式 underline
        theme: {
          background: '#060101', // 背景色
          cursor: 'help' // 设置光标
        }
      })
    },
    initSocket() {  // 初始化Websocket
      const fitPlugin = new FitAddon()
      this.term.loadAddon(fitPlugin)

      const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
      this.socket = new WebSocket(`${protocol}//${window.location.host}/socket/ws/ssh/${this.ip}`)

      this.socket.onmessage = e => {
        const reader = new window.FileReader()
        reader.onload = () => this.term.write(reader.result)
        reader.readAsText(e.data, 'utf-8')
      }

      this.socket.onopen = () => {
        this.term.open(this.$refs.xterm)
        this.term.focus()
        fitPlugin.fit()
      }

      this.socket.onclose = e => {
        if (e.code === 1234) {  // 结束标记
          window.location.href = 'about:blank'
          window.close()
        } else {
          setTimeout(() => this.term.write('\r\nConnection is closed.\r\n'), 200)
        }
      }

      this.term.onData(data => this.socket.send(JSON.stringify({ data })))
      this.term.onResize(({ cols, rows }) => {
        this.socket.send(JSON.stringify({ resize: [cols, rows] }))
      })

      window.onresize = () => fitPlugin.fit()
    }
  }
}
</script>
<style lang="scss" scoped>
.terminal {
  display: flex;
  width: 100%;
  min-height: var(--terminal-height);
  flex: 1;
  background-color: #000;
}
.terminal > div {
  flex: 1;
}
</style>

        父组件引用:

<template>
  <SSH :ip="ip" :height="100" />
</template>

<script>

export default {
  name: 'Xterm',
  components: {
    SSH: () => import('@/components/SSH')
  },
  data() {
    return {
      ip: ''
    }
  },
  mounted() {
    this.ip = this.$route.query.ip  //http://localhost:8011/ssh?ip=xxx
  }
}
</script>



后台:

        Django==3.1.10  daphne==3.0.2  paramiko==2.8.0  channels==3.0.4

         假定目录结构如下:

        (1) 修改asgi.py 

        Tutorial Part 1: Basic Setup — Channels 3.0.4 documentation  

import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')

application = ProtocolTypeRouter({  # 区分http or ws请求
    "http": get_asgi_application(),
})

         (2) 修改settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels'  # 添加
]


ASGI_APPLICATION = 'project.asgi.application'
CHANNEL_LAYERS = {
    'default': {
        "BACKEND": "channels.layers.InMemoryChannelLayer"  # 默认用内存
    },
}

 信道中间层用redis 可参加 https://channels.readthedocs.io/en/stable/topics/channel_layers.html

        (3)配置路由

        settings.py

import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter

from consumer import routing  # 添加路由

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    'websocket': routing.ws_router  # ws请求入口
})

        routing.py

from channels.auth import AuthMiddlewareStack
from channels.routing import URLRouter
from django.urls import path

from consumer.consumers import SSHConsumer

ws_router = AuthMiddlewareStack(
    URLRouter([
        path(r'ws/ssh/<str:ip>', SSHConsumer.as_asgi()),
    ])
)

        consumers.py

import json
from threading import Thread

from channels.generic.websocket import WebsocketConsumer
import paramiko


class SSHConsumer(WebsocketConsumer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.ip = None
        self.chan = None
        self.ssh = None

    def connect(self):
        self.ip = self.scope['url_route']['kwargs']['ip']

        self.accept()
        self._init()

    def disconnect(self, close_code):
        self.chan.close()
        self.ssh.close()

    def get_client(self):
        p_key = paramiko.RSAKey.from_private_key_file("/root/.ssh/id_rsa")  # ssh免密登录私钥
        ssh = paramiko.SSHClient()
        ssh.load_system_host_keys()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname=self.ip, port=22, username='root', pkey=p_key)

        return ssh

    def loop_read(self):
        while True:
            data = self.chan.recv(32 * 1024)
            if not data:
                self.close(1234)
                break

            self.send(bytes_data=data)

    def _init(self):
        self.send(bytes_data=b'Connecting ...\r\n')

        try:
            self.ssh = self.get_client()
        except Exception as e:
            self.send(bytes_data=f'Exception: {e}\r\n'.encode())
            self.close()
            return

        self.chan = self.ssh.invoke_shell(term='xterm')
        self.chan.transport.set_keepalive(30)

        Thread(target=self.loop_read).start()

    def receive(self, text_data=None, bytes_data=None):
        data = text_data or bytes_data
        if data:
            data = json.loads(data)

            resize = data.get('resize')
            if resize and len(resize) == 2:
                self.chan.resize_pty(*resize)
            else:
                self.chan.send(data['data'])

启动命令:

uwsgi --ini project_uwsgi.ini   # 对应8000
daphne project.asgi:application -p 8001 -b 0.0.0.0  --access-log log/asgi.log

Nginx配置:

upstream app {
    server ip:8000;
}

upstream socket {
    server ip:8001;
}

server{
        listen 80;
        server_name _;

        location / {
                root   /usr/share/nginx/html;
                try_files $uri $uri/ /index.html;
        }

        location ~/app/ { # http入口
                rewrite ^/app/(.*) /$1 break;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Host $server_name;
        }
        location ~/socket/ {  # websocket入口
                rewrite ^/socket/(.*) /$1 break;
                proxy_pass http://socket;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Host $server_name;
        }
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这里是一个示例的Spring Boot + Vue.js + WebSocket的在线聊天应用程序的实现。首先,我们可以创建一个Spring Boot工程,然后添加WebSocket的依赖。 在pom.xml文件中添加以下依赖: ```xml <dependencies> <!-- ... --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> </dependencies> ``` 接下来,我们可以编写一个WebSocket配置类,用于注册WebSocket端点和处理器: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new ChatWebSocketHandler(), "/chat"); } } ``` 这里我们注册了一个名为“chat”的WebSocket端点,并将其与一个处理器绑定。 接下来,我们可以编写一个WebSocket处理器类来处理来自客户端的消息: ```java public class ChatWebSocketHandler extends TextWebSocketHandler { private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { for (WebSocketSession s : sessions) { if (s.isOpen()) { s.sendMessage(message); } } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); } } ``` 这个处理器类中,我们定义了一个静态的WebSocketSession列表,用于存储所有连接到服务器的WebSocket会话。在`afterConnectionEstablished`方法中,我们将新的会话添加到列表中。在`handleTextMessage`方法中,我们遍历所有会话并将接收到的消息发送给它们。在`afterConnectionClosed`方法中,我们将关闭的会话从列表中删除。 最后,我们可以编写一个简单的HTML页面,在页面中使用Vue.js和WebSocket API来实现在线聊天功能: ```html <!DOCTYPE html> <html> <head> <title>Chat Room</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div v-for="message in messages">{{ message }}</div> <input v-model="inputMessage" @keyup.enter="send"> </div> <script> var ws = new WebSocket("ws://" + window.location.host + "/chat"); var app = new Vue({ el: '#app', data: { messages: [], inputMessage: '' }, methods: { send: function () { ws.send(this.inputMessage); this.inputMessage = ''; } } }); ws.onmessage = function (event) { app.messages.push(event.data); }; </script> </body> </html> ``` 在这个HTML页面中,我们使用Vue.js来实现数据绑定和事件处理。我们还使用WebSocket API来连接到WebSocket服务器,并在收到消息时更新Vue.js的数据模型。在输入框中按下回车键时,我们将输入框中的内容发送到WebSocket服务器。 以上就是一个简单的Spring Boot + Vue.js + WebSocket的在线聊天应用程序的实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值