SpringMvc之term.js 实现webshell来访问后台-yellowcong

这个webshell,是通过websocket的协议进行开发的,前台是基于term.js的,这个玩意挺好用的,term.js可以用来直接模拟ssh终端,而且还有颜色。开发思路:1、先搭建好websocket环境,2、配置好websocket服务,3、配置前端的term。js,4、配置后端ssh的消息处理。5、测试是否能正常处理,是否有乱码报错的问题。

项目地址

#这个是term.js的项目地址
https://github.com/chjj/term.js

#代码地址
https://gitee.com/yellowcong/bos.git

实现效果

SpringMvc之term.js 实现webshell来访问后台-yellowcong_第1张图片

准备

SpringMvc之websocket学习-yellowcong

目录结构

名称 功能
Server 模型类,用于存储连接机器信息
SshClient ssh连接客户端,基于原始的客户端进行封装
SshWriteThread 输出类,一直给服务器端输出数据信息
HandshakeInterceptor socket拦截器类,用于处理握手
SshShellHandler socket请求处理类,用于处理socket请求
shell.js term的socket连接处理类

代码结构图,项目的基本框架是SSM,权限是shiro
SpringMvc之term.js 实现webshell来访问后台-yellowcong_第2张图片

配置websocket

这个websocket必须在tomcat7以上版本运行,才好使。

pom.xml

需要导入socket和 ganymed 两个核心包


<dependency>
    <groupId>ch.ethz.ganymedgroupId>
    <artifactId>ganymed-ssh2artifactId>
    <version>262version>
dependency>


<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-websocketartifactId>
    <version>${spring.version}version>
dependency>

配置处理socket的类

配置spring-mvc.xml配置文件,添加处理socket的类和握手包类

<beans xmlns="http://www.springframework.org/schema/beans"
      ...
    xmlns:websocket="http://www.springframework.org/schema/websocket"  
    xsi:schemaLocation=" http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd
    ">
<websocket:handlers>
       <websocket:mapping path="/websocket/test" handler="sshShellHandler" />
       <websocket:mapping path="/websocket/test2" handler="sshShellHandler" />
       <websocket:handshake-interceptors>
           
           <bean class="com.yellowcon.webssh.websocket.HandshakeInterceptor" />
       websocket:handshake-interceptors>
   websocket:handlers>

   <bean id="sshShellHandler" class="com.yellowcon.webssh.websocket.SshShellHandler" />
   ...
beans>

Socket的拦截器处理

package com.yellowcon.webssh.websocket;

import java.util.Map;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

/**
 * 创建日期:2018年1月11日
* 创建时间:下午10:15:48
* 创建者 :yellowcong
* 机能概要:创建握手,类似与http的连接 三次握手一次挥手 */
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{ @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { super.afterHandshake(request, response, wsHandler, ex); } @Override public boolean beforeHandshake(ServerHttpRequest arg0, ServerHttpResponse arg1, WebSocketHandler arg2, Map arg3) throws Exception { return super.beforeHandshake(arg0, arg1, arg2, arg3); } }

socket连接处理类

这个连接类里面,我的ssh连接地址是写死的,没有做参数传递机器信息的操作,可以进一步的扩展。

package com.yellowcon.webssh.websocket;

import java.util.Arrays;
import java.util.Date;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.yellowcon.webssh.Server;
import com.yellowcon.webssh.SshClient;

/**
 * 创建日期:2018年1月11日
* 创建时间:下午10:09:11
* 创建者 :yellowcong
* 机能概要: */
@RequestMapping("/webshh/sshShellHandler") public class SshShellHandler extends TextWebSocketHandler{ //客户端 SshClient client = null; //关闭连接后的处理 @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { // TODO Auto-generated method stub super.afterConnectionClosed(session, status); //关闭连接 if (client != null) { client.disconnect(); } } //建立socket连接 @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // TODO Auto-generated method stub super.afterConnectionEstablished(session); //做多个用户处理的时候,可以在这个地方来 ,判断用户id和 //配置服务器信息 Server server = new Server("192.168.66.100","root","yellowcong"); //初始化客户端 client = new SshClient(server, session); //连接服务器 client.connect(); } //处理socker发送过来的消息 @Override public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { super.handleMessage(session, message); //处理连接 try { TextMessage msg = (TextMessage) message; //当客户端不为空的情况 if (client != null) { //receive a close cmd ? if (Arrays.equals("exit".getBytes(), msg.asBytes())) { if (client != null) { client.disconnect(); } session.close(); return ; } //写入前台传递过来的命令,发送到目标服务器上 client.write(new String(msg.asBytes(), "UTF-8")); } } catch (Exception e) { e.printStackTrace(); session.sendMessage(new TextMessage("An error occured, websocket is closed.")); session.close(); } } }

后台的shell

这个shell是依赖于ganymed,我们在初始化的时候,会单独创建一个线程,用来输出连接信息到控制台。

ssh的客户端

package com.yellowcon.webssh;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;

import org.springframework.web.socket.WebSocketSession;

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;

public class SshClient {

    // 服务器信息
    private Server server;
    // 与客户端连接的socket回话
    private WebSocketSession socket;
    // 连接信息
    private Connection conn = null;
    // term的session信息
    private Session session = null;
    // 向服务器外面写数据的线程
    private SshWriteThread writeThread = null;

    //写命令到服务器
    private BufferedWriter out =  null;

    public SshClient(Server server, WebSocketSession socket) {
        super();
        this.server = server;
        this.socket = socket;
    }

    /**
     * 连接到目标服务器
     * 
     * @return
     */
    public boolean connect() {
        try {
            String hostname = this.server.getHostname();
            String username = this.server.getUsername();
            String password = this.server.getPassword();
            // 建立连接
            conn = new Connection(hostname, 22);
            // 连接
            conn.connect();

            // 授权
            boolean isAuthenticate = conn.authenticateWithPassword(username, password);
            if (isAuthenticate) {

                // 打开连接
                session = conn.openSession();
                // 打开bash
                //session.requestPTY("bash");
                session.requestPTY("xterm", 90, 30, 0, 0, null);

                // 启动shell
                session.startShell();

                // 向客户端写数据
                startWriter();

                // 输出流 
                out = new BufferedWriter(new OutputStreamWriter(session.getStdin(), "utf-8")); 

                // 开启term
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向服务器端写数据
     */
    private void startWriter() {
        // 启动多线程,来获取我们运行的结果
        // 第一个参数 输入流
        // 第二个参数 输出流,这个直接输出的是控制台
        writeThread = new SshWriteThread(session.getStdout(), socket);
        new Thread(writeThread).start();

    }

    /**
     * 写数据到服务器端,让机器执行命令
     * @param cmd
     * @return 
     */
    public boolean write(String cmd) {
        try {
            this.out.write(cmd );
            this.out.flush();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }

    }
    /**
     * 关闭连接
     */
    public void disconnect() {
        try {
            //将与服务器端的连接关闭掉,并设置为空
            conn.close();
            session.close();
            session = null;
            conn = null;
            writeThread.stopThread();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

存储连接信息的model类

package com.yellowcon.webssh;

public class Server {

    //主机名称
    private String hostname;
    //用户名
    private String username;
    //密码
    private String password;

    public Server(String hostname, String username, String password) {
        super();
        this.hostname = hostname;
        this.username = username;
        this.password = password;
    }

    public String getHostname() {
        return hostname;
    }

    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

输出线程

输出的时候,我做了一次补码操作,不然就会乱码,而且读取数据的时候,所设定的buff的数组大小刚好是8192,这是upd返回的包大小。如果设置为别的,会产生乱码的情况

package com.yellowcon.webssh;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

/**
 * 创建日期:2018年1月11日
* 创建时间:下午10:15:48
* 创建者 :yellowcong
* 机能概要:用于读取ssh输出的 */
public class SshWriteThread implements Runnable { //定义一个flag,来停止线程用 private boolean isStop = false; //接入输入流数据 private InputStream in; //用于输出数据 private WebSocketSession session; private static final String ENCODING = "UTF-8"; //停止线程 public void stopThread() { this.isStop = true; } public SshWriteThread(InputStream in, WebSocketSession session) { super(); this.in = in; this.session = session; } public void run() { try { //读取数据 while (!isStop && //线程是否停止 session != null && //session 不是空的 session.isOpen()) { //session是打开的状态 //获取到我们的session // session.sendMessage(new TextMessage(new String(result.toString().getBytes("ISO-8859-1"),"UTF-8"))); //写数据到服务器端 // session.sendMessage(new TextMessage(result)); //写数据到客户端 writeToWeb(in); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 写数据到web控制界面 * @param in */ private void writeToWeb(InputStream in) { try { //定义一个缓存 //一个UDP 的用户数据报的数据字段长度为8192字节 byte [] buff = new byte[8192]; int len =0; StringBuffer sb = new StringBuffer(); while((len = in.read(buff)) > 0) { //设定从0 开始 sb.setLength(0); //读取数组里面的数据,进行补码 for (int i = 0; i < len; i++){ //进行补码操作 char c = (char) (buff[i] & 0xff); sb.append(c); } //写数据到服务器端 session.sendMessage(new TextMessage(new String(sb.toString().getBytes("ISO-8859-1"),"UTF-8"))); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }

前端代码

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">


<script type="text/javascript" src="<%=request.getContextPath()%>/resources/plugins/term/term.js">script>


<script type="text/javascript" src="<%=request.getContextPath()%>/resources/js/web/shell.js">script>
head>
<body class="layui-layout-body">
<input type="hidden" id="wsHost" value="<%=request.getLocalAddr()+":"+request.getServerPort()+"/"+request.getContextPath() %>/websocket/test2"/>
脚本

<div id="content_term">div>
body>
html>

shell.js脚本

设置连接的时候Terminal的创建,是在连接之后创建,不然就会导致Term失效的问题,打死就是不好使的情况。

$(function(){
    //创建websocket连接
    createWebSocket();
});
//--------------------------------------------------------------
//创建Websocket连接
//--------------------------------------------------------------
function createWebSocket(){

    //获取服务器socket连接地址
    var wsHost = $("#wsHost").val();
    //连接服务器
    var client =  new WebSocket("ws://"+wsHost);  
    var term  = null;

    //刚刚打开连接
    client.onopen = function(evt) { 
      console.log("Connection open ..."); 
        //计算控制台的高度和宽度
        $width = $('.layui-body').width();
        $height = $('.layui-body').height();

        //创建Term的控制台]
        term = new Terminal({
            cols: Math.floor($width / 7.25),
            rows: Math.floor($height / 17.42),
            screenKeys: false,
            useStyle: true,
            cursorBlink: true,
            convertEol: true
          });
        //打开指定的term
        term.open($("#content_term").empty()[0]);

        //term.on方法就是实时监控输入的字段,
        term.on('data', function(data) {
            client.send(data);
        });
    };

    //接收消息的情况
    client.onmessage = function(evt) {
      console.log( "Received Message: " + evt.data);
      //写数据到term 控制台
      term.write(evt.data);
    };

    //关闭连接的情况
    client.onclose = function(evt) {
      console.log("连接关闭");
    }; 

    //连接失败的情况
    client.onerror = function(event) {
        console.log("连接失败");
    };





}

问题集合

E437: terminal capability “cm” required

当我们使用web的终端,进行远程访问的时候,结果发现报错了,这个问题的原因是,我们打开终端的方式是bash,需要修改为 xterm
这里写图片描述

打开后的文件就乱码了
这里写图片描述

//session.requestPTY("bash");
//第一个参数:模拟终端类型
//第二个参数:模拟终端宽度
//第三个参数:模拟终端高度
session.requestPTY("xterm", 90, 30, 0, 0, null);

第二次打开后,就不会报错了,由于宽度和高度没弄好,导致没有完全显示的问题。
SpringMvc之term.js 实现webshell来访问后台-yellowcong_第3张图片

参考文章

https://www.cnblogs.com/zhenfei-jiang/p/7065038.html

你可能感兴趣的:(springmvc,shell)