基于websocket和java实现webshell访问docker容器
基于websocket和java实现webshell访问docker容器需求:PaaS平台展示容器信息,并在web端实现与该容器webshell交互。介绍:通过ws请求与主机建立websocket连接,执行docker exec -i [containerid]/bin/bash命令进入docker容器。进入容器是可以理解为进入进程,通过进程的输入输出流进行交
基于websocket和java实现webshell访问docker容器
基于websocket和java实现webshell访问docker容器
需求:PaaS平台展示容器信息,并在web端实现与该容器webshell交互。
介绍:通过ws请求与主机建立websocket连接,执行docker exec -i [containerid] /bin/bash命令进入docker容器。进入容器是可以理解为进入进程,通过进程的输入输出流进行交互,并且process的InputStream的在read的时候是一个阻塞,因此在建立连接后启动输入流输出流线程来执行命令和获取结果。前端通过websocket的websocket。send(message)发送执行命令至后台@onmessage通过输出流线程在容器内执行,并且执行结果通过输入流响应到web端。注:需设置process.waitfor();这样输入流阻塞时存在有执行命令结果会响应到webshell,同时输出流在向容器写命令时最后需加上“\n”回车,这样容器才会执行命令
web代码(webshell界面代码从网上down,效果体验很好):
:
` python
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>控制台</title>
<script type="text/javascript" src="js/jquery.js"> </script>
<script type="text/javascript" src="js/jquery- fieldselection.min.js"></script>
<style>
*{
margin:0px;
padding:0px;
}
body{
overflow:hidden;
}
#log-container{
width:100%;
height:800px;
}
#log-container textarea.crtP{
width:100%;
height:100%;
background:#000;
color:#fff;
font-size: 13px;
font-family: Lucida Console;
resize:none;
border-radius: 6px;
-webkit-transition: box-shadow 0.30s ease-in-out;
border: #87C6F9 2px solid;
box-shadow: 0 0 8px rgba(103, 166, 217, 1);
}
#log-container textarea.crtP::selection {
background:rgb(0,255,0);
color:#000;
}
#log-container textarea.crtP::-moz-selection {
background:rgb(0,255,0);
color:#000;
}
#log-container textarea.crtP::-webkit-selection {
background:rgb(0,255,0);
color:#000;
}
</style>
</head>
<body>
<div id="log-container">
<textarea class="crtP" spellcheck="false"></textarea>
</div>
</body>
<script>
$(document).ready(function() {
var hisCmdArray = new Array();
var cmdIndex = 0;
var cmdSize = 0;
var tmpCmd = "";
var minRangeLength = 0;
var termShellStr = "";
var textareaCont = "";
var $textarea = $("#log-container textarea.crtP");
var maxLine = 200;
var fromX,fromY,toX,toY;
var TimeFn = null;
var isDbl = false;
//var dblCt = 0;
// 指定websocket路径
var websocket = new WebSocket(*'ws://192.168.111.133:8006/websockettail/webSecureCRT');*
websocket.onmessage = function(event) {
//console.log("返回结果="+event.data);
var context = "";
// 接收服务端的实时日志并添加到HTML页面中
if(event.data.substring(0,9) === "startFlag"){
termShellStr = event.data.split("=")[1];
termShellStr = termShellStr.substring(0,termShellStr.length);
//console.log(termShellStr);
context = termShellStr;
}else {
context = event.data+"\n";
}
tmpCmd = "";
inputCmd($textarea,context);
minRangeLength = $textarea.val().length;
//console.log(minRangeLength);
// 滚动条滚动到最低部
$textarea.scrollTop($textarea[0].scrollHeight);
};
function inputCmd(inputObj,cmd){
var hisCont = inputObj.val();
var array = $textarea.val().split("\n");
if(array.length>=maxLine){
/* var hasIndex = 0;
var deleteLength = 0;
while( array[hasIndex].length === 0){
deleteLength += array[hasIndex].length;
hasIndex++;
}
deleteLength += array[hasIndex].length;
console.log(deleteLength);
hisCont = hisCont.substring(deleteLength);
console.log(hisCont.length); */
for(var i=0;i<=array.length-maxLine;i++){
hisCont = hisCont.substring(array[i].length);
}
//console.log(hisCont.length);
}
hisCont = hisCont.substring(0,hisCont.length-tmpCmd.length);
inputObj.val(hisCont+cmd);
}
$.fn.setCursorPosition = function(position){
if(this.lengh == 0) return this;
return $(this).setSelection(position, position);
}
$.fn.setSelection = function(selectionStart, selectionEnd) {
if(this.lengh == 0) return this;
input = this[0];
if (input.createTextRange) {
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', selectionEnd);
range.moveStart('character', selectionStart);
range.select();
} else if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
}
return this;
}
$.fn.focusEnd = function(){
this.setCursorPosition(this.val().length);
}
$textarea.click(function(event){
clearTimeout(TimeFn);
TimeFn = setTimeout(function(){
console.log("click");
isDbl = false;
if(toX===fromX && toY===fromY && !isDbl){
$textarea.focusEnd();
}
},300);
});
$textarea.mouseup(function(event){
toX = event.pageX;
toY = event.pageY;
});
/* $textarea.mousemove(function(event){
console.log(event)
console.log("mousemove");
}); */
$textarea.mousedown(function(event){
//console.log(event.which);
if(event.which === 2){//如果按下鼠标中键
var range = $textarea.getSelection();
console.log(range.text);
inputCmd($textarea,range.text);
return false;
}
fromX = event.pageX;
fromY = event.pageY;
});
$textarea.dblclick(function(event){
clearTimeout(TimeFn);
isDbl = true;
console.log("dblclick");
});
$(document).keydown(function(event){
//console.log(event.keyCode);
var range = $textarea.getSelection();
//console.log(range);
if(event.keyCode == 13){
var array = $textarea.val().split("\n");
//alert(array[array.length-1]);
//send();
var message = array[array.length-1];
if(message.length>=termShellStr.length){
message = message.substring(termShellStr.length);
console.log(message);
if(message === "clear"){
$textarea.val(termShellStr);
//$textarea.focusEnd();
return false;
}else {
send(message);
}
if(message === ""){
}else {
hisCmdArray.push(message);
cmdIndex = hisCmdArray.length;
cmdSize = hisCmdArray.length;
}
}else{
send(message);
}
console.log(hisCmdArray);
}else if(event.keyCode == 8){//删除键
var array = $textarea.val().split("\n");
//alert(array[array.length-1]);
//send();
var currLine = array[array.length-1];
//console.log(currLine+"--"+termShellStr);
if(currLine.substring(0,termShellStr.length) === termShellStr){
}else {
return false;
}
if(currLine === termShellStr){
return false;
}
}else if(event.keyCode == 38){//向上键
cmdIndex--;
if(cmdIndex < 0){
cmdIndex = 0;
return false;
}
if(cmdIndex < cmdSize && cmdIndex >= 0){
var lastCmd = hisCmdArray[cmdIndex];
console.log(lastCmd);
inputCmd($textarea,lastCmd);
tmpCmd = lastCmd;
}
return false;
}else if(event.keyCode == 40){//向下键
cmdIndex++;
if(cmdIndex >= cmdSize){
cmdIndex = cmdSize-1;
return false;
}
if(cmdIndex < cmdSize && cmdIndex >= 0){
var nextCmd = hisCmdArray[cmdIndex];
console.log(nextCmd);
inputCmd($textarea,nextCmd);
tmpCmd = nextCmd;
}
return false;
}else if(event.keyCode == 37){//向左键
//console.log(minRangeLength);
if(range.end > minRangeLength){
}else {
return false;
}
}
if (event.keyCode == 67 && event.ctrlKey) { //这里只能用alt,shift,ctrl等去组合其他键event.altKey、event.ctrlKey、event.shiftKey 属性
send("Ctrl+C");
}
});
//发送消息
function send(message){
websocket.send(message);
}
$textarea.focusEnd();
/* $("#log-container textarea").change( function() {
var array = $("#log-container textarea").val().split("\n");
var currLine = array[array.length-1];
console.log(currLine+"--"+termShellStr);
if(currLine === termShellStr){
return false;
}
}); */
});
</script>
</body>
</html>
后台代码与容器建立进程,启动输入线程和输出线程进行交互:
package com.xxg.websocket;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/webSecureCRT")
public class WebSecureCRT {
protected Runtime runtime = Runtime.getRuntime();
protected Process process;
protected InputStream inputStream;
protected InputStream errStream;
public static String termStr = "pp=`whoami`'@'`hostname`':'`pwd`'>' ";
public String pwd = "";
protected String termShowStr = "";
public static String pwdCmdStr = "tt='pwd='`pwd` && echo $tt";
public static String hostType = "/bin/sh -c"; //unix /usr/bin/ksh
public static String hhStr = "\n";
protected Map<String,String> envMap = System.getenv();
protected String[] envStrArray;
public static List<String> cmdList = new ArrayList<String>();
static {
cmdList.add("tail");
cmdList.add("more");
}
public WebSecureCRT() {
super();
System.out.println("创建WebSecureCRT对象");
}
/**
* 新的WebSocket请求开启
*/
@OnOpen
public void onOpen(Session session) {
String message="pp=`whoami`'@'`hostname` && echo $pp ";
try {
System.out.println("connect to server start...");
String command = "docker exec -i b2873e708667 /bin/bash ";
String[] commandArray = {"/bin/sh","-c",command};
process =Runtime.getRuntime().exec(commandArray,null);
System.out.println(process);
onMessage(message, session);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* WebSocket请求关闭
*/
@OnClose
public void onClose() {
try {
if(inputStream != null)
inputStream.close();
if(errStream != null)
errStream.close();
} catch (Exception e) {
e.printStackTrace();
}
if(process != null)
process.destroy();
}
@OnError
public void onError(Throwable thr) {
thr.printStackTrace();
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
* @param session 可选的参数
* @throws ExecutionException
* @throws InterruptedException
* @throws Exception
*/
@OnMessage
public void onMessage(String message, Session session) throws InterruptedException, ExecutionException, Exception {
System.out.println("from clinet message start [" + message+"]");
System.out.println(process);
StreamOutput outGobbler = new StreamOutput(process.getOutputStream(), "OUTPUT",message);
outGobbler.start();
StreamGobbler infoGobbler = new StreamGobbler(process.getInputStream(), "INPUT",session);
infoGobbler.start();
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR",session);
errorGobbler.start();
System.out.println("from clinet message end...[" + message+"]");
}
}
输入流线程代码读取流响应到前端:
package com.xxg.websocket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import javax.websocket.Session;
/**
* 用于处理Runtime.getRuntime().exec产生的错误流及输出流
* @author shaojianye
*
*/
public class StreamGobbler extends Thread {
InputStream is;
String type;
Session session;
StreamGobbler(InputStream is, String type,Session session) {
this.is = is;
this.type = type;
this.session=session;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isr);
String line=null;
if("INPUT".equals(type)){
while((line = reader.readLine()) != null) {
System.out.println("---------");
System.out.println("in:"+line);
if(line.length()==0){
session.getBasicRemote().sendText("\n");
}else{
session.getBasicRemote().sendText(line);
}
}
}
if("ERROR".equals(type)){
while((line = reader.readLine()) != null) {
System.out.println("in:"+line);
if(line.length()==0){
session.getBasicRemote().sendText("\n");
}else{
session.getBasicRemote().sendText(line);
}
}
}
} catch (Exception ioe) {
ioe.printStackTrace();
}
}
}
获取前端传入linux命令通过输出流线程响应到容器
package com.xxg.websocket;
import java.io.OutputStream;
import java.io.PrintWriter;
/**
* 用于处理Runtime.getRuntime().exec产生的错误流及输出流
* @author shaojianye
*
*/
public class StreamOutput extends Thread {
OutputStream out ;
String type;
String message;
StreamOutput(OutputStream out, String type,String message) {
this.out = out;
this.type = type;
this.message=message;
}
public void run() {
try {
PrintWriter pw = null;
String val=message+" \n";
System.out.println(val);
//判断exit是否可以退出,待测试
if (out!=null){
pw = new PrintWriter(out);
pw.println(val);
pw.flush();
}
} catch (Exception ioe) {
ioe.printStackTrace();
}
}
}
部署服务结果如图:
因为执行的docker exec -i containerid /bin/bash命令,没有-t参数(查相关资料-t参数在io流传输是会失效具体原因不太清楚)因此响应的webshell没有虚拟化终端样式
测试demo在github地址https://github.com/SHAOLBJ/Webshell
访问宿主机的效果实现思路基本一致:
更多推荐
所有评论(0)