这个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之websocket学习-yellowcong
名称 | 功能 |
---|---|
Server | 模型类,用于存储连接机器信息 |
SshClient | ssh连接客户端,基于原始的客户端进行封装 |
SshWriteThread | 输出类,一直给服务器端输出数据信息 |
HandshakeInterceptor | socket拦截器类,用于处理握手 |
SshShellHandler | socket请求处理类,用于处理socket请求 |
shell.js | term的socket连接处理类 |
这个websocket必须在tomcat7以上版本运行,才好使。
需要导入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>
配置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>
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);
}
}
这个连接类里面,我的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是依赖于ganymed,我们在初始化的时候,会单独创建一个线程,用来输出连接信息到控制台。
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();
}
}
}
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>
设置连接的时候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("连接失败");
};
}
当我们使用web的终端,进行远程访问的时候,结果发现报错了,这个问题的原因是,我们打开终端的方式是bash,需要修改为 xterm
//session.requestPTY("bash");
//第一个参数:模拟终端类型
//第二个参数:模拟终端宽度
//第三个参数:模拟终端高度
session.requestPTY("xterm", 90, 30, 0, 0, null);
第二次打开后,就不会报错了,由于宽度和高度没弄好,导致没有完全显示的问题。
https://www.cnblogs.com/zhenfei-jiang/p/7065038.html