精华内容
下载资源
问答
  • web终端是什么
    千次阅读
    2019-03-30 20:14:01

    一句话概括:其实Web原本并非跨终端,但应用场景多了,就变成了”跨终端“。

    既然说是Web,我就把范围划定在互联网相关的技术栈内。

    任何基于TCP/IP模型应用层协议的Web应用的请求方,都可视为“Web终端”,与此相对的就是Web Service。终端的核心价值是Web Service的表现形式与交互形式。

    来讨论终端,就暂且不说Web Service的事情了。
    所以这里就涉及硬件、软件和网络协议三个部分。

    硬件

    这个非常好理解,就是我们日常使用的各种智能设备,包括电脑、平板、智能手机等传统设备。但仅仅是这样还不够,电视盒子、智能手环、智能灯饰等智能家居都在“Web终端”的范围内。

    为什么这么说?

    传统设备自不必多说。新兴的物联网智能设备,我可以总结为:通过用户操作,使用传统的或较新的网络传输技术(包括WLAN、蓝牙、红外、RFID等),向对应的Web Service发出请求,等待请求应答,并根据应答内容向用户反馈信息。

    是不是有点物联网的意思?

    软件

    这是大家印象最深刻的部分,尤其是在移动设备带动响应式交互大行其道的今天。做Web开发的程序员,尤其是前端,需要同时考虑页面在多种浏览器和多种设备的兼容性,因此响应式布局确实是跨终端Web的一个体现。

    但我认为这还不够,因为对于互联网而言,Web的终端不只有浏览器,还有其他应用程序。最简单的,比如一个Web Service同时提供Web站点和iOS App两种服务,再比如魔兽世界网游,我的账号数据被同时提供给游戏客户端和Web版的英雄榜,这里面的区别就是软件终端的不同。

    网络协议

    让事情变得简单一点,先考虑TCP/IP模型中应用层的协议。考虑这些是因为,这些协议支撑起来的应用都遵循”客户-服务器“范式。

    这些协议的存在让同一个Web Service支持不同软件终端成为可能,比如HTTP协议支持浏览器直接访问服务,也支持App中调用API向服务端申请服务,比如DHCP协议支持给不同操作系统的PC、平板、智能手机、电视盒子、其他智能家居提供IP地址,SNMP协议的跨系统、跨设备的支持更不用说,等等。

    从应用层再深入思考,比如运输层、网络层的协议,是与硬件平台和传输信道紧密相关的。

    跨终端Web

    回到最开始,”跨终端Web“,我觉得这真的不是单一一门技术就能做完的事情。

    根据实际需求,终端分硬件和软件两个层次,每个层次包含多种类型,每种类型用到的技术又各不相同,所以”终端“的差异是不可避免、无法消除的。

    但我们开发者也是人,也想尽可能的省事,于是就有了跨终端的Web:

    以通信协议为基础,以Web服务的最终表现形式与交互形式为核心的,基于各类操作系统的软件技术集。

     

    更多相关内容
  • Web Shell 由 、 和。 Windows 需要 。 安装 从源代码 git clone github.com/jiangklijna/web-shell cd web-shell make gen make 从发布 帮助 $ web-shell -h Usage: web-shell [-s server mode] [-c client mode] ...
  • 很酷的Web-RTC Web终端,可与实用程序很好地配合以建立P2P连接。 尝试正在运行的 ! 这如何运作 启动器(r​​tc-shell)将生成一个“要约”。 将此粘贴到Web终端中,按回车。 Web终端生成一个“答案”。 将此粘贴...
  • 终端Web

    2019-07-22 20:47:37
    第1章提出了跨终端Web的概念以及实现跨终端Web的多重途径,第2章主要介绍Mobile Web的技术基础,第3~7章是全书的核心,按照开发流程组织逐步讲解了实现跨终端Web所需要的各类技术基础设施,第8章主要介绍了Hybrid ...
  • Termbox 通过Web界面提供即时Linux终端访问
  • 如何制作一个简单的Web终端

    千次阅读 2022-03-29 11:32:38
    web终端可以在浏览器上模拟xshell的功能,本文源代码在Github: How to create a simple web terminal 效果如下所示: 架构 现在的web终端基本都是基于xterm.js开发的。xterm是一个前端的终端组件。 web终端的实现是...

    概括

    web终端可以在浏览器上模拟xshell的功能,本文源代码在Github: How to create a simple web terminal

    效果如下所示:
    在这里插入图片描述

    架构

    现在的web终端基本都是基于xterm.js开发的。xterm是一个前端的终端组件。

    web终端的实现是客户端-服务器架构:

    在这里插入图片描述

    通信协议

    • 客户端和服务器之间使用的是websocket协议
    • 服务器通过本地fork一个bash或者powershell进程,充当传话筒角色

    数据结构

    服务器会维持一个,客户端的<socketId,pty>的Map, 用来记录客户端连接和终端进程的对应关系。

    客户端

    客户端主要使用xterm和socketIO这两个库,xterm是终端组件,socketIO是和服务器通信的组件。

    客户端显示仅仅需要一个DOM元素

    • index.html
    <!DOCTYPE html>
    <html>
      <head>
        <title>How to create web terminals</title>
        <meta charset="UTF-8" />
        <style>
            /**设置满屏展示**/
            #terminal-container{
              position: absolute;
              margin: 0;
              padding: 0;
              height: 100%;
              width: 100%;
            }
        </style>
      </head>
    
      <body>
         <!--终端关联的DOM元素-->
        <div id="terminal-container"></div>
        <script src="src/index.js"></script>
      </body>
    </html>
    
    
    • index.js
    
    import { TerminalUI } from "./TerminalUI";
    import io from "socket.io-client";
    
    // 连接服务端,并给终端设置关联的DOM元素。
    
    // IMPORTANT: 服务端的监听地址
    const serverAddress = "http://localhost:8080";
    
    // 连接服务端并返回一个socket
    function connectToSocket(serverAddress) {
      return new Promise(res => {
        const socket = io(serverAddress);
        res(socket);
      });
    }
    // 开启一个终端
    function startTerminal(container, socket) {
      // Create an xterm.js instance (TerminalUI class is a wrapper with some utils. Check that file for info.)
      const terminal = new TerminalUI(socket);
    
      // Attach created terminal to a DOM element.
      terminal.attachTo(container);
    
      // When terminal attached to DOM, start listening for input, output events.
      // Check TerminalUI startListening() function for details.
      terminal.startListening();
    }
    
    function start() {
      // get DOM element as an terminal container
      const container = document.getElementById("terminal-container");
      // Connect to socket and when it is available, start terminal.
      connectToSocket(serverAddress).then(socket => {
        startTerminal(container, socket);
      });
    }
    
    • TerminalUI.js

    这个文件主要设置了xterm实例如何发送客户端输入和接收服务端输出。

    // TerminalUI.js
    
    import { Terminal } from "xterm";
    import { FitAddon } from 'xterm-addon-fit';
    
    
    import "xterm/css/xterm.css";
    
    export class TerminalUI {
      // xterm实例构造器,设置了背景和字体颜色
      constructor(socket) {
        this.terminal = new Terminal({
          theme: {
            background: "black",
            foreground: "#F5F8FA"
          },
        });
        // xterm实例一一对应一个socket连接
        this.socket = socket;
      }
    
      /**
       * Attach event listeners for terminal UI and socket.io client
       */
      startListening() {
    
        this.terminal.onData(data =>{
          this.sendInput(data)
        });
    
        this.socket.on("output", data => {
          // When there is data from PTY on server, print that on Terminal.
          this.write(data);
        });
    
      }
    
      /**
       * Print something to terminal UI.
       */
      write(text) {
    
        this.terminal.write(text);
      }
    
      /**
       * Utility function to print new line on terminal.
       */
      prompt() {
        this.terminal.write(`\r\n$ `);
    
      }
    
      /**
       * Send whatever you type in Terminal UI to PTY process in server.
       * @param {*} input Input to send to server
       */
      sendInput(input) {
        this.socket.emit("input", input);
      }
    
      /**
       *
       * @param {HTMLElement} container HTMLElement where xterm can attach terminal ui instance.
       */
      attachTo(container) {
        // 关联DOM元素并加载Fit组件(用于设置终端适应DOM元素大小,不然终端有固定尺寸很难看)
        let fitAddon = new FitAddon();
        this.terminal.loadAddon(fitAddon);
        this.terminal.open(container);
        fitAddon.fit()
        // Default text to display on terminal.
        this.terminal.write("Terminal Connected");
        this.terminal.write("");
        this.prompt();
    
      }
    
      clear() {
        this.terminal.clear();
      }
    }
    

    服务端

    服务端会给每个客户端连接socket生成一个终端进程,终端进程一般情况下linux是bash, windows是powershell.

    会有一个全局的Map保存socket和终端进程的映射关系。

    • SocketService.js
    // SocketService.js
    
    // Manage Socket.IO server
    const socketIO = require("socket.io");
    const PTYService = require("./PTYService");
    
    class SocketService {
      constructor() {
          // 全局的Map保存socket和终端进程的映射关系
          this.SocketBook=new Map()
      }
    
      attachServer(server) {
        if (!server) {
          throw new Error("Server not found...");
        }
    
        const io = socketIO(server);
        console.log("Created socket server. Waiting for client connection.");
        // "connection" event happens when any client connects to this io instance.
        // 每个socket都有一个唯一的socketID
        io.on("connection", socket => {
          console.log("Client connect to socket.", socket.id);
    
          // Just logging when socket disconnects.
          socket.on("disconnect", () => {
            console.log("Disconnected Socket: ", socket.id);
            this.SocketBook.delete(socket.id)
          });
    
          // Create a new pty service when client connects.
          let pty = new PTYService(socket);
    
          // 如何是容器终端,取消这行注释,使用kubectl exec 或者docker exec
          //pty.write("kubectl exec -it cicd-dev-metric-service-b85cc4cdd-zpcrm /bin/bash \r")
    
    
          // add <socket.id,pty> to map
          this.SocketBook.set(socket.id,pty)
    
          // Attach any event listeners which runs if any event is triggered from socket.io client
          // For now, we are only adding "input" event, where client sends the strings you type on terminal UI.
          // 每当客户端有输入事件到来,根据socket id 查找对应shell进程,并向shell进程写入客户端的输入字符
          socket.on("input", input => {
            //Runs this event function socket receives "input" events from socket.io client
            let pty= this.SocketBook.get(socket.id)
            pty.write(input);
          });
    
    
        });
      }
    }
    
    module.exports = SocketService;
    
    
    • PTYService.js

    封装本地终端进程的相关操作,使用了node-pty库来fork终端进程。

    // PTYService.js
    
    const os = require("os");
    const pty = require("node-pty");
    
    class PTY {
      constructor(socket) {
        // Setting default terminals based on user os
        this.shell = os.platform() === "win32" ? "powershell.exe" : "bash";
        this.ptyProcess = null;
        this.socket = socket;
    
        // Initialize PTY process.
        this.startPtyProcess();
      }
    
      /**
       * Spawn an instance of pty with a selected shell.
       */
      startPtyProcess() {
        this.ptyProcess = pty.spawn(this.shell, [], {
          name: "xterm-color",
          cwd: process.env.HOME, // Which path should terminal start
          env: process.env // Pass environment variables
        });
    
        // Add a "data" event listener.
        this.ptyProcess.on("data", data => {
          // Whenever terminal generates any data, send that output to socket.io client to display on UI
          this.sendToClient(data);
        });
      }
    
      /**
       * Use this function to send in the input to Pseudo Terminal process.
       * @param {*} data Input from user like command sent from terminal UI
       */
    
      write(data) {
        this.ptyProcess.write(data);
      }
    
      sendToClient(data) {
        // Emit data to socket.io client in an event "output"
        this.socket.emit("output", data);
      }
    }
    
    module.exports = PTY;
    
    

    总结

    不足:

    • xterm的样式很难调节,本文示例中,我一直想要让输入字符输入达到DOM元素的长度再换行,但是没有做到。
    • 如何写一个容器的web终端的话,如何将会话一开始的kubectl exec等信息清除。

    成果:

    • 实现了一个web终端的基础功能。
    • 多客户端同时可以连接
    展开全文
  • 网络终端 基于Tornado和term.js的Web终端
  • 主要介绍了node.js支持多用户web终端实现方案以及web终端安全性保证的解决方法,一起学习参考下。
  • 终端Web pdf

    热门讨论 2015-02-05 10:41:15
    第1章提出了跨终端Web的概念以及实现跨终端Web的多重途径,第2章主要介绍Mobile Web的技术基础,第3~7章是全书的核心,按照开发流程组织逐步讲解了实现跨终端Web所需要的各类技术基础设施,第8章主要介绍了Hybrid ...
  • WebSocket实现web 终端

    千次阅读 2020-08-27 09:21:31
    在做自动化运维时,不可避免的要用到web终端,而web终端主要体现在前后端的实时性的交互,那么websocket是一个很好的选择 其结构如下 流程为: 1、xterm.js 在浏览器端模拟 shell 终端, 监听用户输入通过 websocket...

    前言

    在做自动化运维时,不可避免的要用到web终端,而web终端主要体现在前后端的实时性的交互,那么websocket是一个很好的选择
    其结构如下
    在这里插入图片描述
    流程为:
    1、xterm.js 在浏览器端模拟 shell 终端, 监听用户输入通过 websocket 将用户输入的内容上传到 django
    2、django 接受到用户上传的内容, 将用户在前端页面输入的内容通过 paramiko 建立的 ssh 通道上传到远程服务器执行
    3、paramiko 将远程服务器的处理结果返回给 django
    4、django 将 paramiko 返回的结果通过 websocket 返回给用户
    5、xterm.js 接收 django 返回的数据并将其写入前端页面

    前端

    前端一般选择用xterm.js包,大致页面如下
    在这里插入图片描述
    终端页面代码如下

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>远程终端</title>
        <link href="/static/my_assets/css/xterm.css" rel="stylesheet">
    </head>
    <body>
    <div>
        <p style="position:absolute; padding: 0; right:0; margin-right:19px; text-align: center; z-index: 999">
            <button class="btn btn-danger " id="id-close-conn">
                <span class="ti-power-off"></span>
            </button>
        </p>
        <div id="terminal"></div>
    </div>
    <script src="/static/assets/js/lib/jquery.min.js"></script>
    <script src="/static/my_assets/js/xterm.js"></script>
    <script>
        //获取window的长宽
        function get_box_size() {
            let init_width = 9;
            let init_height = 17;
    
            let windows_width = $(window).width();
            let windows_height = $(window).height();
            return {
                cols: Math.floor(windows_width / init_width),
                rows: Math.floor(windows_height / init_height),
            }
        }
    
        //连接后端websocket
        function connect_ssh() {
            let cols = get_box_size().cols;
            let rows = get_box_size().rows;
    
            //根据div的大小初始化终端,待WebSocket连接上后,显示终端
            let term = new Terminal(
                {
                    cols: cols,
                    rows: rows,
                    useStyle: true,
                    cursorBlink: true,
                    cursorStyle: 'underline',
                    convertEol: true,
                }
            );
    
            let pwd = window.btoa("{{ ssh_info.password }}");
    
            //建立WebSocket连接
            let ssh_args = `user={{ ssh_info.user_name }}&host={{ ssh_info.ip }}&port={{ ssh_info.port }}&pwd=` + pwd;
            let ws_scheme = window.location.protocol === "https:" ? "wss" : "ws"; //获取协议
            let ws = new WebSocket(ws_scheme + '://' + window.location.host + '/ws/webssh/?' + ssh_args + `&width=${cols}&height=${rows}`);
    
    
            //打开websocket连接,并打开终端
            ws.onopen = function () {
                console.log('WebSocket建立连接,打开Web终端');
                term.open(document.getElementById('terminal'));
            };
            ws.onclose = function (e) {
                console.error('WebSocket关闭连接,关闭Web终端');
                window.close();
            };
    
            //读取服务器发送的数据并写入web终端
            ws.onmessage = function (e) {
                console.log('WebSocket接收消息,ssh交互中');
                let data = JSON.parse(e.data);
                console.log(data);
                let message = data['message'];
                if (data.flag === 'msg') {
                    term.write(message);
                } else if (data.flag === 'fail') {
                    term.write(message);  //连接ssh的异常提示
                    swal(
                        {
                            title: "连接失败",
                            text: message,
                            type: "error",
                        });
                    setTimeout(function () {
                        term.write(message);
                    }, 5000);
                } else if (data.flag === 'user') {
                    swal(
                        {
                            title: "消息",
                            text: message,
                            type: "info",
                        });
                } else if (data.flag === 'error') {
                    swal(
                        {
                            title: "连接失败",
                            text: message,
                            type: "error",
                        });
    
                    setTimeout(function () {
                        term.write(message);
                    }, 5000);
    
                }
    
            };
    
            //向服务器发送数据
            term.onData(function (data) {
                //data为每个按键输入内容,例如按A,就传递给后端:{'flag': 1, 'data': 'a', 'cols': None, 'rows': None}
                let send_data = JSON.stringify({
                    'flag': 'entered_key',
                    'entered_key': data,
                    'cols': null,
                    'rows': null
                });
                //向WebSocket发送消息,也就是输入的每一个按键
                ws.send(send_data)
            });
    
            //终端右上角显示的关闭连接安装,当点击是,关闭ws
            $('#id-close-conn').click(function () {
                ws.close();
            });
    
            // 监听浏览器窗口, 根据浏览器窗口大小修改终端大小
            $(window).resize(function () {
                let cols = get_box_size().cols;
                let rows = get_box_size().rows;
                console.log(`更改显示终端窗口大小,行${rows}${cols}`);
                let send_data = JSON.stringify({'flag': 'resize', 'cols': cols, 'rows': rows});
                ws.send(send_data);
                term.resize(cols, rows) //调整页面终端大小
            })
        }
    
        $(function () {
            connect_ssh()
        })
    </script>
    </body>
    </html>
    

    后端

    后端要考虑到websocket以及ssh远程连接服务器
    这里先说ssh连接,创建consumers.py文件

    from asgiref.sync import async_to_sync
    from channels.generic.websocket import WebsocketConsumer
    import json
    import base64
    from django.http.request import QueryDict
    import paramiko
    from threading import Thread
    import os
    from io import StringIO
    from django.conf import settings
    
    
    # 解析密钥文件
    def get_key_obj(pkeyobj, pkey_file=None, pkey_obj=None, password=None):
        if pkey_file:
            with open(pkey_file) as fo:
                try:
                    pkey = pkeyobj.from_private_key(fo, password=password)
                    return pkey
                except:
                    pass
        else:
            try:
                pkey = pkeyobj.from_private_key(pkey_obj, password=password)
                return pkey
            except:
                pkey_obj.seek(0)
    
    
     """
        桥接WebSocket和ssh
     """
    class SSHBridge(object):
      
        def __init__(self, websocket):
            self.websocket = websocket
            self.ssh_channel = None
    
        def connect(self, host, user, pwd=None, key=None, port=22, timeout=6, term='xterm', pty_width=80, pty_height=24):
            # 建立ssh连接
            ssh_client = paramiko.SSHClient()
            ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            try:
                if key:
                    # 密钥方式认证
                    pkey = get_key_obj(paramiko.RSAKey, pkey_obj=key, password=pwd) or \
                           get_key_obj(paramiko.DSSKey, pkey_obj=key, password=pwd) or \
                           get_key_obj(paramiko.ECDSAKey, pkey_obj=key, password=pwd) or \
                           get_key_obj(paramiko.Ed25519Key, pkey_obj=key, password=pwd)
                    ssh_client.connect(username=user, hostname=host, port=port, pkey=pkey, timeout=timeout)
                else:
                    ssh_client.connect(hostname=host, port=port, username=user, password=pwd, timeout=timeout)
            except Exception as e:
                message = json.dumps({'flag': 'fail', 'message': str(e)})
                self.websocket.send_message_or_team(message)
                return
    
            transport = ssh_client.get_transport()
            # 打开一个通道
            self.ssh_channel = transport.open_session()
            # 获取一个终端
            self.ssh_channel.get_pty(term=term, width=pty_width, height=pty_height)
            # 激活终端,这样就可以登录到终端了,就和我们用类似于xshell登录系统一样
            self.ssh_channel.invoke_shell()
    
            # 获取ssh连接主机后的返回内容,例如Linux,会显示上次登录等信息,把这些信息通过WebSocket显示到Web终端。
            # 连接建立一次,之后交互数据不会再进入该方法
            for i in range(2):
                # 这里因为可能出现编码问题
                try:
                    recv = self.ssh_channel.recv(1024).decode('utf-8')
                except Exception as e:
                    recv = self.ssh_channel.recv(1024).decode('gbk')
                message = json.dumps({'flag': 'msg', 'message': recv})
                self.websocket.send_message_or_team(message)
    
        def close(self):
            message = {'flag': 0, 'message': '关闭WebSocket和SSH连接'}
            # 向WebSocket发送一个关闭消息
            self.websocket.send_message_or_team(json.dumps(message))
    
            try:
                # 关闭ssh通道
                self.ssh_channel.close()
                # 关闭WebSocket连接
                self.websocket.close()
            except BaseException as e:
                print('关闭WebSocket和SSH连接产生异常:', e)
                pass
    
        def _ws_to_ssh(self, data):
            # 尝试发送数据到ssh通道,产生异常则关闭所有连接
            try:
                self.ssh_channel.send(data)
            except OSError:
                self.close()
    
        def _ssh_to_ws(self):
            try:
                while not self.ssh_channel.exit_status_ready():
                    try:
                        data = self.ssh_channel.recv(1024).decode('utf-8')
                    except Exception as e:
                        data = self.ssh_channel.recv(1024).decode('gbk')
                    if len(data) != 0:
                        message = {'flag': 'msg', 'message': data}
                        self.websocket.send_message_or_team(json.dumps(message))
                    else:
                        break
            except Exception as e:
                self.close()
    
        def shell(self, data):
            # 开启两个线程,一个接收消息一个发送消息
            Thread(target=self._ws_to_ssh, args=(data,)).start()
            Thread(target=self._ssh_to_ws).start()
    
        def resize_pty(self, cols, rows):
            # 重新改变虚拟终端长和宽
            self.ssh_channel.resize_pty(width=cols, height=rows)
          
    
    class WebsshConsumer(WebsocketConsumer):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.host_id = ''
            self.simple_user = ''
            self.is_team = False
            self.team_name = ''
            self.ssh = None
    
        def connect(self):
            # 建立WebSocket连接,并实例化SSHBridge类,在这个对象中建立SSH连接
            self.host_id = self.scope['url_route']['kwargs'].get('host_id')
            self.accept()
    
            # WebSocket连接成功后,连接ssh
            query_string = self.scope.get('query_string')
            ws_args = QueryDict(query_string=query_string, encoding='utf-8')
            width = ws_args.get('width')
            height = ws_args.get('height')
            width = int(width)
            height = int(height)  # ssh连接要求int类型:required argument is an integer
            user = ws_args.get('user')
            host = ws_args.get('host')
            port = ws_args.get('port')
            port = int(port)
            auth = ws_args.get('auth')
            pwd = ws_args.get('pwd')
            if pwd:
                pwd = base64.b64decode(pwd).decode('utf-8')
            sshkey_filename = ws_args.get('sshkey_filename')
    
            ssh_connect_dict = {
                'host': host,
                'user': user,
                'port': port,
                'timeout': 30,
                'pty_width': width,
                'pty_height': height,
                'pwd': pwd
            }
    
            if auth == 'key':
                sshkey_file = os.path.join(settings.MEDIA_ROOT, 'sshkey', sshkey_filename)
                if not os.path.exists(sshkey_file):
                    self.send(json.dumps({'flag': 'error', 'message': '密钥文件不存在'}))
    
                else:
                    try:
                        f = open(sshkey_file, 'r', encoding='utf-8')
                        key = f.read()
                        string_io = StringIO()
                        string_io.write(key)
                        string_io.flush()
                        string_io.seek(0)
                        ssh_connect_dict['key'] = string_io
    
                        os.remove(sshkey_file)  # 用完之后删除key文件
                    except BaseException as e:
                        pass
    
            # 建立SSH连接
            self.ssh = SSHBridge(websocket=self)
            self.ssh.connect(**ssh_connect_dict)
    
        def disconnect(self, close_code):
            # 断开连接
            try:
                self.ssh.close()
            except BaseException as e:
                pass
    
        def receive(self, text_data=None, bytes_data=None):
            # 从WebSocket中接收消息
            text_data = json.loads(text_data)  # json字符串转字典
            if type(text_data) == dict:
                if text_data.get('flag') == 'entered_key':
                    data = text_data.get('entered_key', '')  # 获取前端传过来输入的按键值,并传递给shell
                    self.ssh.shell(data=data)
                else:
                    cols = text_data['cols']
                    rows = text_data['rows']
                    # 改变通道中终端大小
                    self.ssh.resize_pty(cols=cols, rows=rows)
            else:
                pass
    
        def send_message_or_team(self, message):
            self.send(message)
    
        def team_message(self, event):
            message = event['message']
            self.send(message)
    
    

    以上完成后就要建立路由
    在这里插入图片描述
    在web_ssh下建立routing.py,代码如下

    from django.conf.urls import url
    from utils import consumers # 引入consumers.py文件
    
    #
    websocket_urlpatterns = [
        url(r'^ws/webssh/$', consumers.WebsshConsumer),
    ]
    
    

    在django的settings.py的同级目录下创建routing.py,代码如下:

    from channels.routing import ProtocolTypeRouter, URLRouter
    from channels.auth import AuthMiddlewareStack
    import web_ssh.routing
    
    
    application = ProtocolTypeRouter({
        'websocket': AuthMiddlewareStack(
            URLRouter(
                web_ssh.routing.websocket_urlpatterns,
            ),
        ),
    })
    

    最后在views.py中添加打开前端页面的功能函数就ok了

    展开全文
  • 一个Kubernetes Web终端连接工具

    千次阅读 2022-03-16 16:46:16
    当应用部署到Kubernetes集群中之后,如何提供Web终端的功能,以便开发人员调试? 方案一 该功能的核心就是实现kubernetes executor接口 exec.go package pod import ( "context" "errors" "log" "net/http" ...

    当应用部署到Kubernetes集群中之后,如何提供Web终端的功能,以便开发人员调试?

    方案一

    该功能的核心就是实现kubernetes executor接口

    exec.go

    package pod
    
    import (
    	"context"
    	"errors"
    	"log"
    	"net/http"
    	"sync"
    
    	"github.com/gorilla/websocket"
    	corev1 "k8s.io/api/core/v1"
    	"k8s.io/client-go/kubernetes/scheme"
    	"k8s.io/client-go/tools/remotecommand"
    )
    
    // 封装websocket连接
    type WsConnection struct {
    	wsSocket  *websocket.Conn // 底层websocket
    	inChan    chan *WsMessage // 读取队列
    	outChan   chan *WsMessage // 发送队列
    	mutex     sync.Mutex      // 避免重复关闭管道
    	isClosed  bool
    	closeChan chan byte // 关闭通知
    }
    
    // web终端发来的包
    type xtermMessage struct {
    	MsgType string `json:"type"`  // 类型:resize客户端调整终端, input客户端输入
    	Input   string `json:"input"` // msgtype=input情况下使用
    	Rows    uint16 `json:"rows"`  // msgtype=resize情况下使用
    	Cols    uint16 `json:"cols"`  // msgtype=resize情况下使用
    }
    
    // websocket消息
    type WsMessage struct {
    	MessageType int
    	Data        []byte
    }
    
    // 关闭连接
    func (wsConn *WsConnection) WsClose() {
    	wsConn.wsSocket.Close()
    	wsConn.mutex.Lock()
    	defer wsConn.mutex.Unlock()
    	if !wsConn.isClosed {
    		wsConn.isClosed = true
    		close(wsConn.closeChan)
    	}
    }
    
    // ssh流式处理器
    type streamHandler struct {
    	wsConn      *WsConnection
    	resizeEvent chan remotecommand.TerminalSize
    }
    
    // executor回调获取web是否resize
    func (handler *streamHandler) Next() (size *remotecommand.TerminalSize) {
    	ret := <-handler.resizeEvent
    	size = &ret
    	return
    }
    
    // 发送返回消息到协程
    func (wsConn *WsConnection) WsWrite(messageType int, data []byte) (err error) {
    	select {
    	case wsConn.outChan <- &WsMessage{messageType, data}:
    	case <-wsConn.closeChan:
    		err = errors.New("WsWrite websocket closed")
    		break
    	}
    	return
    }
    
    // 读取协程
    func (wsConn *WsConnection) wsReadLoop() {
    	for {
    		// 读一条message
    		// ReadMessage返回的messageType只可能是:TextMessage BinaryMessage
    		msgType, data, err := wsConn.wsSocket.ReadMessage()
    		if err != nil {
    			log.Println(err)
    			break
    		}
    		//log.Print(string(data))
    		// 放入请求队列
    		wsConn.inChan <- &WsMessage{
    			msgType,
    			data,
    		}
    	}
    }
    
    // 发送协程
    func (wsConn *WsConnection) wsWriteLoop() {
    	// 服务端返回给页面的数据
    	for {
    		select {
    		// 取一个应答
    		case msg := <-wsConn.outChan:
    			//log.Print(string(msg.Data))
    			// 写给web  websocket
    			if err := wsConn.wsSocket.WriteMessage(msg.MessageType, msg.Data); err != nil {
    				log.Println(err)
    				break
    			}
    		case <-wsConn.closeChan:
    			wsConn.WsClose()
    			return
    		}
    	}
    }
    
    func (wsConn *WsConnection) onContextCancel(ctx context.Context) {
    	for {
    		select {
    		case <-ctx.Done():
    			log.Println("web cancel context or time out..........")
    			wsConn.WsClose()
    			return
    		}
    	}
    }
    
    // 读取 页面消息到协程
    func (wsConn *WsConnection) WsRead() (msg *WsMessage, err error) {
    	select {
    	case msg = <-wsConn.inChan:
    		return
    	case <-wsConn.closeChan:
    		err = errors.New("WsRead websocket closed")
    		break
    	}
    	return
    }
    
    // executor回调读取web端的输入
    func (handler *streamHandler) Read(p []byte) (size int, err error) {
    	// 读web发来的输入
    	msg, err := handler.wsConn.WsRead()
    	if err != nil {
    		handler.wsConn.WsClose()
    		return
    	}
    
    	xtermMsg := &xtermMessage{
    		//MsgType: string(msg.MessageType),
    		Input: string(msg.Data),
    	}
    	// 放到channel里,等remotecommand executor调用我们的Next取走
    	handler.resizeEvent <- remotecommand.TerminalSize{Width: xtermMsg.Cols, Height: xtermMsg.Rows}
    	size = len(xtermMsg.Input)
    	copy(p, xtermMsg.Input)
    	return
    
    }
    
    // executor回调向web端输出
    func (handler *streamHandler) Write(p []byte) (size int, err error) {
    	// 产生副本
    	copyData := make([]byte, len(p))
    	copy(copyData, p)
    	size = len(p)
    	err = handler.wsConn.WsWrite(websocket.TextMessage, copyData)
    	return
    }
    
    func ContainerExec(ctx context.Context, r *http.Request, w http.ResponseWriter, cluster, namespace, podID, container string) error {
    
    	// todo 获取k8s信息部分 需要替换成自己的
    	ctxName := meta.GetContextName(cluster)
    	kclient, err := k8s.GetClient(ctxName)
    	if err != nil {
    		log.Println(err)
    		return err
    	}
    	cmds := []string{"sh", "-c", "test -f /bin/bash && bash || sh"}
    	option := &corev1.PodExecOptions{
    		Command:   cmds,
    		Stdin:     true,
    		Stdout:    true,
    		Stderr:    true,
    		TTY:       true,
    		Container: container,
    	}
    	subCtx, cancel := context.WithTimeout(ctx, models.READ_LOG_TIMEOUT)
    	defer cancel()
    	req := kclient.CoreV1().RESTClient().
    		Post().
    		Resource("pods").
    		Name(podID).
    		Namespace(namespace).
    		SubResource("exec").
    		VersionedParams(option, scheme.ParameterCodec).Timeout(models.READ_LOG_TIMEOUT)
    	wsSocket, err := upGrader.Upgrade(w, r, nil)
    	if err != nil {
    		log.Println(err)
    		return err
    	}
    	wsConn := &WsConnection{
    		wsSocket:  wsSocket,
    		inChan:    make(chan *WsMessage, 1000),
    		outChan:   make(chan *WsMessage, 1000),
    		closeChan: make(chan byte),
    		isClosed:  false,
    	}
    	// 获取kube config配置
    	config, err := k8s.GetClientConfig(ctxName)
    	if err != nil {
    		wsConn.WsClose()
    		log.Println(err)
    		return err
    	}
    	// 创建到容器的连接
    	executor, err := remotecommand.NewSPDYExecutor(config, http.MethodPost, req.URL())
    	if err != nil {
    		wsConn.WsClose()
    		log.Println(err)
    		return err
    	}
    	// 页面读入输入 协程
    	go wsConn.wsReadLoop()
    	// 服务端返回数据 协程
    	go wsConn.wsWriteLoop()
    
    	// 监听前端请求
    	go wsConn.onContextCancel(subCtx)
    
    	// 配置与容器之间的数据流处理回调
    	handler := &streamHandler{wsConn: wsConn, resizeEvent: make(chan remotecommand.TerminalSize)}
    	if err = executor.Stream(remotecommand.StreamOptions{
    		Stdin:             handler,
    		Stdout:            handler,
    		Stderr:            handler,
    		TerminalSizeQueue: handler,
    		Tty:               true,
    	}); err != nil {
    		log.Println("handler", err)
    		return err
    	}
    	return err
    }
    
    

    参考资料:

    https://github.com/jiankunking/k8-web-terminal

    方案一存在内存泄漏问题,https://github.com/kubernetes/client-go/issues/884

    方案二

    exec.go

    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"io"
    	"log"
    	"net/http"
    	"time"
    
    	"github.com/gorilla/websocket"
    	corev1 "k8s.io/api/core/v1"
    	"k8s.io/client-go/kubernetes/scheme"
    	"k8s.io/client-go/tools/remotecommand"
    
    	"git.haier.net/console/k8s-ext/k8s"
    	"git.haier.net/console/k8s-ext/pkg/models"
    )
    
    // https://github.com/kubernetes/dashboard/blob/master/src/app/backend/handler/terminal.go
    
    type PtyHandler interface {
    	io.Reader
    	io.Writer
    	remotecommand.TerminalSizeQueue
    }
    
    const END_OF_TRANSMISSION = "\u0004"
    
    // TerminalMessage is the messaging protocol between ShellController and TerminalSession.
    //
    // OP      DIRECTION  FIELD(S) USED  DESCRIPTION
    // ---------------------------------------------------------------------
    // bind    fe->be     SessionID      Id sent back from TerminalResponse
    // stdin   fe->be     Data           Keystrokes/paste buffer
    // resize  fe->be     Rows, Cols     New terminal size
    // stdout  be->fe     Data           Output from the process
    // toast   be->fe     Data           OOB message to be shown to the user
    type TerminalMessage struct {
    	Op, Data string
    	//SessionID  string `json:",omitempty"`
    	Rows, Cols uint16 `json:",omitempty"`
    }
    
    // TerminalSession
    type TerminalSession struct {
    	ID       string
    	wsConn   *websocket.Conn
    	sizeChan chan remotecommand.TerminalSize
    	doneChan chan struct{}
    }
    
    // TerminalSize handles pty->process resize events
    // Called in a loop from remotecommand as long as the process is running
    func (t *TerminalSession) Next() *remotecommand.TerminalSize {
    	select {
    	case size := <-t.sizeChan:
    		return &size
    	case <-t.doneChan:
    		return nil
    	}
    }
    
    // Read handles pty->process messages (stdin, resize)
    // Called in a loop from remotecommand as long as the process is running
    func (t *TerminalSession) Read(p []byte) (int, error) {
    	_, message, err := t.wsConn.ReadMessage()
    	if err != nil {
    		log.Printf("%s: read ws message failed: %v", t.ID, err)
    		return copy(p, END_OF_TRANSMISSION), err
    	}
    	log.Printf("%s: read", t.ID)
    	var msg TerminalMessage
    	if err := json.Unmarshal(message, &msg); err != nil {
    		// TODO: temp workaround for non-json input
    		return 0, nil
    		//log.Printf("%s: json decoded failed: %v", t.ID, err)
    		//return copy(p, END_OF_TRANSMISSION), err
    	}
    	switch msg.Op {
    	case "stdin":
    		return copy(p, msg.Data), nil
    	case "resize":
    		t.sizeChan <- remotecommand.TerminalSize{Width: msg.Cols, Height: msg.Rows}
    		return 0, nil
    	default:
    		log.Printf("%s: unknown message type '%s'", t.ID, msg.Op)
    		return copy(p, END_OF_TRANSMISSION), fmt.Errorf("unknown message type '%s'", msg.Op)
    	}
    }
    
    // Write handles process->pty stdout
    // Called from remotecommand whenever there is any output
    func (t *TerminalSession) Write(p []byte) (int, error) {
    	//msg, err := json.Marshal(TerminalMessage{
    	//    Op:   "stdout",
    	//    Data: string(p),
    	//})
    	//if err != nil {
    	//    log.Printf("json encode failed: %v", err)
    	//    return 0, err
    	//}
    	if err := t.wsConn.WriteMessage(websocket.TextMessage, p); err != nil {
    		log.Printf("%s: write ws message failed: %v", t.ID, err)
    		return 0, err
    	}
    	log.Printf("%s: write", t.ID)
    	return len(p), nil
    }
    
    func (t *TerminalSession) Close() error {
    	close(t.doneChan)
    	return t.wsConn.Close()
    }
    
    func newTerminalSession(id string, r *http.Request, w http.ResponseWriter) (*TerminalSession, error) {
    	conn, err := upGrader.Upgrade(w, r, nil)
    	if err != nil {
    		return nil, err
    	}
    	return &TerminalSession{
    		ID:       id,
    		wsConn:   conn,
    		sizeChan: make(chan remotecommand.TerminalSize),
    		doneChan: make(chan struct{}),
    	}, nil
    }
    
    func ContainerExec(ctx context.Context, r *http.Request, w http.ResponseWriter, cluster, namespace, podName, container string) error {
    	kclient, err := k8s.GetKubeClient(cluster)
    	if err != nil {
    		log.Println(err)
    		return err
    	}
    
    	// 获取kube config配置
    	config, err := k8s.GetClientConfig(cluster)
    	if err != nil {
    		log.Println(err)
    		return err
    	}
    
    	cmd := []string{"sh", "-c", "test -f /bin/bash && bash || sh"}
    	option := &corev1.PodExecOptions{
    		Command:   cmd,
    		Stdin:     true,
    		Stdout:    true,
    		Stderr:    true,
    		TTY:       true,
    		Container: container,
    	}
    	//ctx, cancel := context.WithTimeout(ctx, models.READ_LOG_TIMEOUT)
    	//defer cancel()
    	req := kclient.CoreV1().RESTClient().
    		Post().
    		Resource("pods").
    		Name(podName).
    		Namespace(namespace).
    		SubResource("exec").
    		VersionedParams(option, scheme.ParameterCodec).
    		Timeout(models.READ_LOG_TIMEOUT)
    
    	executor, err := remotecommand.NewSPDYExecutor(config, http.MethodPost, req.URL())
    	if err != nil {
    		log.Println(err)
    		return err
    	}
    
    	sessID := fmt.Sprintf("%s|%s|%s|%s|%v", cluster, namespace, podName, container, time.Now().Unix())
    	t, err := newTerminalSession(sessID, r, w)
    	if err != nil {
    		log.Println(err)
    		return err
    	}
    	defer t.Close()
    
    	if err = executor.Stream(remotecommand.StreamOptions{
    		Stdin:             t,
    		Stdout:            t,
    		Stderr:            t,
    		TerminalSizeQueue: t,
    		Tty:               true,
    	}); err != nil {
    		// http连接已经被hijacked,http.ResponseWriter不能再使用,所以不返回err
    		log.Printf("exec stream failed: %v", err)
    		return nil
    	}
    
    	return nil
    }
    
    
    展开全文
  • 绿色屏幕Web终端的Chrome扩展程序,通过启用本地用户设置来改善用户体验。 绿屏Web终端插件可启用终端宏和基于用户的UI自定义。 支持语言:English (United States)
  • 为了适应智能电网智能用电技术发展要求,满足电网与用户之间交互需要,本文结合嵌入式web技术,提出一种基于ARM11和Android嵌入式系统的全新的用户侧智能终端设计方案。文中阐述了系统的软硬件设计与实现,给出了...
  • Web终端主题 网站的终端主题。 目前仅适用于Google! 支持的浏览器 谷歌浏览器 微软边缘 如何安装此扩展 下载此存储库并解压缩 在浏览器上转到chrome:// extensions或edge:// extensions 开启开发人员模式 单击...
  • 移动终端的多样性及用户的个性化要求是传统Web页面遇到的挑战。为获得与电脑相一致的浏览效果,提出一个服务器端的Web页面自适应方法,通过优化系统处理生成自适应页面。该方法通过用户为设备设置Web页面上项的...
  • 网络内核基于Linux的终端仿真Imagine if you forgot your computer at home and you're about to use your phone and type Linux commands演示版
  • 一个基于vue,xtermjs,tornado,paramiko的web终端
  • 重庆安腾人员定位系统WEB终端软件V1.0说明书资料.pdf
  • 重庆安腾人员定位系统WEB终端软件V1.0说明书定义.pdf
  • 基于反向Ajax技术的终端驱动Web服务.pdf
  • 提出了一个Web Service技术在手机终端的应用模型。利用Sun公司提供的WTK251工具将服务器端生成的WSDL服务文件转化为相应的Java代码,再利用JavaME提供的API封装Java服务方法,根据Soap和Http协议,以及移动网和...
  • 边虫Sidecar,用于使用Web终端打开交互式调试会话
  • WEB服务器】什么是WEB服务器

    千次阅读 2022-02-07 16:15:10
    Web服务器一般指的是“网站服务器”,是某种驻留在因特网上的计算机程序,可以向请求终端提供服务,主要功能时存储、处理和传递网页给“客户”,传递内容一般是HTML文档、图像、样式表或脚本等,也可以放置网站文件...
  • 开发技术-Web
  • 响应式Web设计诞生已久,本文将带你了解响应式Web设计的前世今生。
  • 基于Java EE-Web服务的多终端协同创新管理平台设计.pdf
  • 环景: ubuntu 16.04.06 ...JumpServer新增加windows终端连接出现用户名或密码认证错误,登录失败,账户密码确认是正确无误 解决方案: 1.windows计算机属性远程桌面-只允许使用网络级别…前面√去掉即可 ...
  • 基于 Web 的 Linux 终端 WebTerminal

    千次阅读 2019-09-13 00:06:21
    有时候用公共电脑,或者在没有安装 putty、xshell 之类的终端的电脑上访问或展示服务器上的一些资料数据,甚至是在运维平台开发中想要嵌入 WebTerminal 功能,于是找到了这个项目——基于 Web 的 Linux 终端 webSSH...
  • 新添加资产账户密码资产单独xshell可以登入,web终端登录不了主机连接旧提示用户密码错误 解决方案: 资产列表: 资产用户列表 检查下面有2台一样的资产其中一条没有账户密码信息,应该之前添加没处理好,再次加了一...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 258,220
精华内容 103,288
关键字:

web终端是什么