如何实现在线web terminal

2022-09-29 19:22:20 浏览数 (1)

1.前言

随着云服务、DevOps等兴起,我们经常在网站中看到远程登录功能,点击之后会出现一个类似Shell的终端,可以查看相关服务状态,编写代码等功能。web termial让我们和实际的机器通过网络联通了。

在生产环境中进行登录和调试都非常方便,那么这是如何实现的呢,要解决两个问题:实现 Shell 界面以及浏览器与服务器进行通信。

2.如何实现Shell 界面

在react 中有很多模拟 Terminal 组件库,比如 [react-terminal]1

[terminal-in-react] 2等,笔者推荐的是使用xterm 3, 其可以满足以下需求:

  • 丰富的指令
  • 支持多种编码
  • 支持大多数终端程序如:vim,bash等

日常开始使用的IDE VS Code也是用xterm,可见应用的广泛性。使用如下。

2.1 Terminal 构造函数

Terminal 对象作为一个构造函数,用于新建 Terminal 实例。

代码语言:javascript复制
import { Terminal } from "xterm";
let terminal = new Terminal({ cursorBlink: true });
2.2 Terminal.open

将terminal实例附加到指定的dom 元素上。

代码语言:javascript复制
terminal.open(document.getElementById("terminal"));
2.3 Terminal.loadAddon

使用terminal 提供的插件扩展功能

代码语言:javascript复制
terminal.loadAddon(fitAddon);

3.如何进行通信

日常开发中大多数使用的是http协议,但是有个缺点是只能客户端向服务端发送请求,服务器返回结果;无法web terminal 也会出现服务端推送消息的情况。

WebSocket4 协议在2008年诞生,现在浏览器都已经支持。其特点是服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息。

3.1 与服务器建立连接

客户端提供 WebSoket API,通创建一个实例就可以将客户端与服务器端连接。

代码语言:javascript复制
ws = new WebSocket("wss:xxx");
3.2 向服务端发送消息

websocket.send 方法可以发送消息给服务器

代码语言:javascript复制
ws.send("Hello WebSockets!");
3.3 接收服务端消息

websocket.onmessage 可接收服务端消息

代码语言:javascript复制
ws.onmessage = function(event) {
  var data = event.data;
  // 处理数据
};

4. 具体实现

在解决展示和通信问题之后,将两者结合起来,就可实现一个在线web terminal 组件了,大体代码如下。

4.1 创建通信类
代码语言:javascript复制
//TerminalConnector.js
export class TerminalConnector {
 private ws;
  constructor({
    command,
    handleOpen,
    handleMessage,
    handleClose,
    handleError,
  }) {
      this.handleOpen = handleOpen;
      this.handleMessage = handleMessage;
      this.handleClose = handleClose;
      this.handleError = handleError;
      this.ws = new WebSocket("wss:xxx");
      this.ws.onopen = this.onConnOpen;
      this.ws.onmessage = this.onMessage;
      this.ws.onclose = this.onConnClose;
      this.ws.onerror = this.onConnError;
    }
    handleSend(data) {
     this.ws.send(data);
   }
    detachTerminal() {
      try {
        this.ws.close();
        this.ws = null;
      } catch (e) {
        console.warn(e);
      }
    }
    private onConnOpen = () => {
     //建立连接后身份验证
     this.ws.send('authToken相关数据')
    }
    private onMessage = (event) => {
     const dat = event.data || '';
     this.handleMessage(data);
    }
    private onConnClose = (e) => {
       console.log("conn close", e);
       this.handleClose();
    }
    private onConnError = (e) => {
      console.error("conn error", e);
      this.handleError(e);
    }

}
4.2 实现 Teraminal组件
代码语言:javascript复制
import React from "react";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import {
  TerminalConnector
} from "./TerminalConnector";

export function Xterm(props) {
  let { command } = props;

  const [curTerminal, setTerminal] = React.useState(null);
  const [curConnector, setConnector] = React.useState(null);

  React.useEffect(() => {
    if (curTerminal) {
      curTerminal.dispose();
    }
    if (curConnector) {
      curConnector.detachTerminal();
    }
    let terminal = new Terminal({ cursorBlink: true });
    let fitAddon = new FitAddon();
    let connector = new TerminalConnector({
      command,
      handleOpen,
      handleMessage,
      handleClose,
      handleError,
    });
    setTerminal(terminal);
    setConnector(connector);
    function handleOpen() {
      fitAddon.fit();
      terminal.focus();

      window.onresize = function () {
        fitAddon.fit();
      };
    }

    function handleMessage(data) {
     // 接收服务器数据,将数据写入shell
       terminal.write(data);
       
    }

    function handleClose() {
      terminal.writeln("Exit");
    }

    function handleError(e) {
      terminal.writeln("");
      terminal.writeln("Sorry, connection has some error.");
    }

    terminal.loadAddon(fitAddon);
    terminal.open(document.getElementById("terminal"));
    terminal.onResize(({ cols, rows }) => {
      connector.handleSend(
        JSON.stringify({ columns: cols, rows })
      );
    });
    terminal.onData((data) => {
      connector.handleSend(data);
    });
  }, [command]);

  return <div id="terminal"></div>;
}

[1]react-terminal: https://www.npmjs.com/package/react-terminal

[2]terminal-in-react: https://github.com/nitin42/terminal-in-react

[3]xterm:https://xtermjs.org/

[4]WebSoket: https://www.ruanyifeng.com/blog/2017/05/websocket.html

0 人点赞