Ensiná-lo a implementar uma versão web de Xshell (com fonte) puro Java

prefácio

Devido às recentes exigências do projeto, necessidade projecto de implementação de um terminal de conexão função WebSSH, por causa de sua primeira vez para fazer este tipo de função, então em primeiro lugar no GitHub olhando para encontrar não há rodas de ready-made pode ser usado para diretamente, que viu um monte de presente aspectos do projeto, tais como: GateOne, webssh, shellinabox, estes projectos podem realizar a função webssh.

Mas em última análise, não adotou, a razão subjacente é que estes são na sua maioria escrito em python, necessidade de contar com um monte de arquivos, você pode usar o tempo que eles usaram este esquema,-rápida reviravolta, mas o tempo para fazer um projeto para os usuários, não pode exigir que o usuário a fazer o servidor deve ser incluído nestes dependência subjacente, o que obviamente não é razoável, então eu decidi escrever-se uma função WebSSH, e como um fora projeto de código aberto independente.

github endereço projeto open source: https: //github.com/NoCortY/WebSSH

Seleção tecnologia

Porque a troca de dados em tempo real necessidade webssh, desde usará conexão WebSocket, a fim de facilitar o desenvolvimento do quadro escolhido SpringBoot, além de seu conhecimento de xterm.js usuários Java para ssh e implementação de jsch a página shell frente de conexão.

Portanto, a seleção de tecnologia final é WebSocket + jsch + + SpringBoot xterm.js .

dependência das importações

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
    <!-- Web相关 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- jsch支持 -->
    <dependency>
        <groupId>com.jcraft</groupId>
        <artifactId>jsch</artifactId>
        <version>0.1.54</version>
    </dependency>
    <!-- WebSocket 支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <!-- 文件上传解析器 -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.4</version>
    </dependency>
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
</dependencies>

A xterm caso simples

Desde o xterm é uma tecnologia popular, então um monte de alunos não suportam esse conhecimento, eu também sou, a fim de atingir essa funcionalidade para que a escola temporária, de modo que este falar com vocês.

xterm.js WebSocket é uma baseada em contêiner, ele pode nos ajudar a alcançar linha de comando estilo na extremidade frontal. E então, como nós normalmente como SecureCRT ou Xshell ao conectar ao servidor.

Aqui é o caso de inscrição oficial on-line:

<!doctype html>
 <html>
  <head>
    <link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
    <script src="node_modules/xterm/lib/xterm.js"></script>
  </head>
  <body>
    <div id="terminal"></div>
    <script>
      var term = new Terminal();
      term.open(document.getElementById('terminal'));
      term.write('Hello from x1B[1;3;31mxterm.jsx1B[0m $ ')
    </script>
  </body>
 </html>

O teste final, a página será parecido com:

Ensiná-lo a implementar uma versão web de Xshell (com fonte) puro Javaentrada xterm

Você pode ver a página tem havido semelhante ao estilo shell, em seguida, com base nessa continuar em profundidade, para conseguir um webssh.

implementação de back-end

Porque, enquanto xterm apenas para alcançar um front-end estilo, realmente não interagir com o servidor, os interage servidor com o principalmente confiar em nosso backend Java para o controle, então começamos a partir do final de volta, usando jsch + websocket alcançar esta parte.

Configuração WebSocket

Desde que a mensagem de envio em tempo real para a necessidade frente ao uso WebSocket, não entendo os estudantes WebSocket pode ir por conta própria para descobrir, não há muita descrição, nós imediatamente iniciar a configuração WebSocket.

  /**
  * @Description: websocket配置
  * @Author: NoCortY
  * @Date: 2020/3/8
  */
  @Configuration
  @EnableWebSocket
  public class WebSSHWebSocketConfig implements WebSocketConfigurer{
      @Autowired
      WebSSHWebSocketHandler webSSHWebSocketHandler;
      @Override
      public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
          //socket通道
          //指定处理器和路径,并设置跨域
          webSocketHandlerRegistry.addHandler(webSSHWebSocketHandler, "/webssh")
                  .addInterceptors(new WebSocketInterceptor())
                  .setAllowedOrigins("*");
      }
  }

Processor (Handler) eo interceptor (Interceptor) implementação

Acabamos de completar a configuração WebSocket, e especificar um processador e interceptores. Então, o próximo passo é implementar o processador e interceptor.

interceptor :

  public class WebSocketInterceptor implements HandshakeInterceptor {
      /**
       * @Description: Handler处理前调用
       * @Param: [serverHttpRequest, serverHttpResponse, webSocketHandler, map]
       * @return: boolean
       * @Author: NoCortY
       * @Date: 2020/3/1
       */
      @Override
      public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
          if (serverHttpRequest instanceof ServletServerHttpRequest) {
              ServletServerHttpRequest request = (ServletServerHttpRequest) serverHttpRequest;
              //生成一个UUID,这里由于是独立的项目,没有用户模块,所以可以用随机的UUID
              //但是如果要集成到自己的项目中,需要将其改为自己识别用户的标识
              String uuid = UUID.randomUUID().toString().replace("-","");
              //将uuid放到websocketsession中
              map.put(ConstantPool.USER_UUID_KEY, uuid);
              return true;
          } else {
              return false;
          }
      }

      @Override
      public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

      }
  }

processador :

  /**
  * @Description: WebSSH的WebSocket处理器
  * @Author: NoCortY
  * @Date: 2020/3/8
  */
  @Component
  public class WebSSHWebSocketHandler implements WebSocketHandler{
      @Autowired
      private WebSSHService webSSHService;
      private Logger logger = LoggerFactory.getLogger(WebSSHWebSocketHandler.class);

      /**
       * @Description: 用户连接上WebSocket的回调
       * @Param: [webSocketSession]
       * @return: void
       * @Author: Object
       * @Date: 2020/3/8
       */
      @Override
      public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
          logger.info("用户:{},连接WebSSH", webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY));
          //调用初始化连接
          webSSHService.initConnection(webSocketSession);
      }

      /**
       * @Description: 收到消息的回调
       * @Param: [webSocketSession, webSocketMessage]
       * @return: void
       * @Author: NoCortY
       * @Date: 2020/3/8
       */
      @Override
      public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
          if (webSocketMessage instanceof TextMessage) {
              logger.info("用户:{},发送命令:{}", webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY), webSocketMessage.toString());
              //调用service接收消息
              webSSHService.recvHandle(((TextMessage) webSocketMessage).getPayload(), webSocketSession);
          } else if (webSocketMessage instanceof BinaryMessage) {

          } else if (webSocketMessage instanceof PongMessage) {

          } else {
              System.out.println("Unexpected WebSocket message type: " + webSocketMessage);
          }
      }

      /**
       * @Description: 出现错误的回调
       * @Param: [webSocketSession, throwable]
       * @return: void
       * @Author: Object
       * @Date: 2020/3/8
       */
      @Override
      public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
          logger.error("数据传输错误");
      }

      /**
       * @Description: 连接关闭的回调
       * @Param: [webSocketSession, closeStatus]
       * @return: void
       * @Author: NoCortY
       * @Date: 2020/3/8
       */
      @Override
      public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
          logger.info("用户:{}断开webssh连接", String.valueOf(webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY)));
          //调用service关闭连接
          webSSHService.close(webSocketSession);
      }

      @Override
      public boolean supportsPartialMessages() {
          return false;
      }
  }

Note-se que, entrei para o interceptor na ID de usuário está usando um UUID aleatório , porque, como um projeto websocket independente, sem módulo de usuário, se você precisa este projeto em seu projeto, você precisa modificá-lo parte do código que identifica o usuário a identificar-se em um programa utilizado pelo usuário.

WebSSH implementação lógica de negócio (core)

Nós só percebi configuração websocket, são alguns código morto e implementos, em seguida, a interface pode ser realizada de acordo com as suas necessidades, e agora vamos alcançar grande lógica de negócios back-end, antes de implementar essa lógica, vamos pensar sobre isso, WebSSH nós principalmente, quero mostrar um pouco efeito.

Eu estou aqui para fazer um resumo:

1. Em primeiro lugar, temos de ligar o terminal (conexão de inicialização)

2. Em segundo lugar, temos de lidar com o servidor de mensagens da extremidade frontal (front-end para receber e processar mensagens)

3. Precisamos ser escrito de volta para a mensagem voltou do front-end terminal (write-back de dados front-end)

4. Feche a conexão

De acordo com estes quatro requisitos, primeiro definir uma interface, para que possa fazer os requisitos esclarecer.

  /**
   * @Description: WebSSH的业务逻辑
   * @Author: NoCortY
   * @Date: 2020/3/7
   */
  public interface WebSSHService {
      /**
       * @Description: 初始化ssh连接
       * @Param:
       * @return:
       * @Author: NoCortY
       * @Date: 2020/3/7
       */
      public void initConnection(WebSocketSession session);

      /**
       * @Description: 处理客户段发的数据
       * @Param:
       * @return:
       * @Author: NoCortY
       * @Date: 2020/3/7
       */
      public void recvHandle(String buffer, WebSocketSession session);

      /**
       * @Description: 数据写回前端 for websocket
       * @Param:
       * @return:
       * @Author: NoCortY
       * @Date: 2020/3/7
       */
      public void sendMessage(WebSocketSession session, byte[] buffer) throws IOException;

      /**
       * @Description: 关闭连接
       * @Param:
       * @return:
       * @Author: NoCortY
       * @Date: 2020/3/7
       */
      public void close(WebSocketSession session);
  }

Agora podemos ir para realizar a função que definimos de acordo com esta interface.

1. conexão de Inicialização

Uma vez que somos dependentes do subjacente jsch alcançar, então aqui é necessário para usar jsch para estabelecer uma conexão. O chamado inicializar a conexão, na verdade, é para ligar as informações que precisamos, salvo em um mapa, não há qualquer operação de conexão real. Por que não há conexão direta? Porque só existe a conexão dica sobre o WebSocket, mas também precisamos de nos mandar para o nome da frente linux terminal de usuário e senha, sem esta informação, nós somos incapazes de se conectar.

     public void initConnection(WebSocketSession session) {
             JSch jSch = new JSch();
             SSHConnectInfo sshConnectInfo = new SSHConnectInfo();
             sshConnectInfo.setjSch(jSch);
             sshConnectInfo.setWebSocketSession(session);
             String uuid = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
             //将这个ssh连接信息放入map中
             sshMap.put(uuid, sshConnectInfo);
     }

2. processar os dados enviados pelo cliente

Nesta etapa, que será dividida em dois ramos.

  • O primeiro ramo: Se o cliente é enviado para o nome de usuário e senha informações do terminal, então conectar o terminal.

  • O segundo ramo: Se o cliente é enviado para as operações de comando do terminal, então será encaminhada diretamente ao terminal e obter os resultados do terminal.

implementação de código específico:

     public void recvHandle(String buffer, WebSocketSession session) {
             ObjectMapper objectMapper = new ObjectMapper();
             WebSSHData webSSHData = null;
             try {
                 //转换前端发送的JSON
                 webSSHData = objectMapper.readValue(buffer, WebSSHData.class);
             } catch (IOException e) {
                 logger.error("Json转换异常");
                 logger.error("异常信息:{}", e.getMessage());
                 return;
             }
         //获取刚才设置的随机的uuid
             String userId = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
             if (ConstantPool.WEBSSH_OPERATE_CONNECT.equals(webSSHData.getOperate())) {
                 //如果是连接请求
                 //找到刚才存储的ssh连接对象
                 SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
                 //启动线程异步处理
                 WebSSHData finalWebSSHData = webSSHData;
                 executorService.execute(new Runnable() {
                     @Override
                     public void run() {
                         try {
                             //连接到终端
                             connectToSSH(sshConnectInfo, finalWebSSHData, session);
                         } catch (JSchException | IOException e) {
                             logger.error("webssh连接异常");
                             logger.error("异常信息:{}", e.getMessage());
                             close(session);
                         }
                     }
                 });
             } else if (ConstantPool.WEBSSH_OPERATE_COMMAND.equals(webSSHData.getOperate())) {
                 //如果是发送命令的请求
                 String command = webSSHData.getCommand();
                 SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
                 if (sshConnectInfo != null) {
                     try {
                         //发送命令到终端
                         transToSSH(sshConnectInfo.getChannel(), command);
                     } catch (IOException e) {
                         logger.error("webssh连接异常");
                         logger.error("异常信息:{}", e.getMessage());
                         close(session);
                     }
                 }
             } else {
                 logger.error("不支持的操作");
                 close(session);
             }
     }

3. Os dados é enviada para a extremidade frontal por websocket

     public void sendMessage(WebSocketSession session, byte[] buffer) throws IOException {
             session.sendMessage(new TextMessage(buffer));
     }

4. Feche a conexão

     public void close(WebSocketSession session) {
         //获取随机生成的uuid
             String userId = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
             SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
             if (sshConnectInfo != null) {
                 //断开连接
                 if (sshConnectInfo.getChannel() != null) sshConnectInfo.getChannel().disconnect();
                 //map中移除该ssh连接信息
                 sshMap.remove(userId);
             }
     }

Neste momento, a nossa implementação back-end inteiro é longo, por causa das limitações de espaço, algumas operações aqui se tornará método de embalagem, não fazer muitas apresentações, destacar alguns pensamento lógico para alcançá-lo. Em seguida, vamos alcançar a extremidade dianteira.

implementação front-end

trabalho front-end é dividido em tão poucos passos:

  1. Realização da página

  2. conexão WebSocket e completar a recepção de dados e escrever de volta

  3. o envio de dados

Por isso, passo a passo para alcançá-lo.

implementação página

Implementar página é muito simples, só precisamos de mostrar o tipo de tela grande terminal preto em uma tela completo, portanto, fazer qualquer coisa que não escrever com estilo, basta criar uma div, após o qual o xterm terminais neste exemplo, pelo div, ele pode ser alcançado.

  <!doctype html>
  <html>
  <head>
      <title>WebSSH</title>
      <link rel="stylesheet" href="../css/xterm.css" />
  </head>
  <body>
  <div id="terminal" style="width: 100%;height: 100%"></div>

  <script src="../lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
  <script src="../js/xterm.js" charset="utf-8"></script>
  <script src="../js/webssh.js" charset="utf-8"></script>
  <script src="../js/base64.js" charset="utf-8"></script>
  </body>
  </html>

conexão WebSocket e completa a transmissão de dados, receber de volta write

  openTerminal( {
      //这里的内容可以写死,但是要整合到项目中时,需要通过参数的方式传入,可以动态连接某个终端。
          operate:'connect',
          host: 'ip地址',
          port: '端口号',
          username: '用户名',
          password: '密码'
      });
      function openTerminal(options){
          var client = new WSSHClient();
          var term = new Terminal({
              cols: 97,
              rows: 37,
              cursorBlink: true, // 光标闪烁
              cursorStyle: "block", // 光标样式  null | 'block' | 'underline' | 'bar'
              scrollback: 800, //回滚
              tabStopWidth: 8, //制表宽度
              screenKeys: true
          });

          term.on('data', function (data) {
              //键盘输入时的回调函数
              client.sendClientData(data);
          });
          term.open(document.getElementById('terminal'));
          //在页面上显示连接中...
          term.write('Connecting...');
          //执行连接操作
          client.connect({
              onError: function (error) {
                  //连接失败回调
                  term.write('Error: ' + error + 'rn');
              },
              onConnect: function () {
                  //连接成功回调
                  client.sendInitData(options);
              },
              onClose: function () {
                  //连接关闭回调
                  term.write("rconnection closed");
              },
              onData: function (data) {
                  //收到数据时回调
                  term.write(data);
              }
          });
      }

Os resultados mostram

conexão

Ensiná-lo a implementar uma versão web de Xshell (com fonte) puro Javaconexão

A conexão é bem sucedida

Ensiná-lo a implementar uma versão web de Xshell (com fonte) puro JavaA conexão é bem sucedida

Operação de comando

comando ls:

Ensiná-lo a implementar uma versão web de Xshell (com fonte) puro Javacomando ls

editor vim:

Ensiná-lo a implementar uma versão web de Xshell (com fonte) puro Javaeditor vim

comando top:

Ensiná-lo a implementar uma versão web de Xshell (com fonte) puro Javacomando top

epílogo

Então concluímos a implementação de um projeto webssh, sem depender de qualquer um dos outros componentes, a extremidade traseira é completamente implementado em Java, devido ao uso de SpringBoot, muito fácil de implantar.

Mas também pode ser estendido para o projeto, como novos arquivos de upload ou download, como Xftp como, você pode facilmente arrastar arquivos e upload queda e download.

Após esse projeto eu vou ser continuamente atualizado, essas funções vai perceber gradualmente, Github: https: //github.com/NoCortY/WebSSH

Como eu posso dar Estrela oh ~

Acho que você gosta

Origin www.cnblogs.com/javazhiyin/p/12468477.html
Recomendado
Clasificación