前段時間做一個hadoop+Spark的性能監控頁面時,需要一個web控制檯遠程登陸到master節點上去,後來發現這方面資料太少,於是自己參照着零散的東西修修改改,終於做出了一個簡單的web shell,記錄一下以免時間長了忘記。大概像這個樣子的:
這樣就可以在網頁上直接訪問linux服務器了,初衷是用來遠程關閉正在運行的spark任務的,做發現出來之後一般的linux命令都能執行。
首先講一下後臺實現:
1.建立ssh連接,並定義一些流用於收發命令。
2.其次定義是一個接收命令和返回結果的方法,因爲linux每次返回一行,所以我這裏存入List<String>返回給前臺處理。
代碼都如下:package com.java.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;
public class SSHLinux
{
private String hostname = "11.11.11.15";
private int port = 22;
private String username = "root";
private String password = "123456";
private Connection conn;
private Session sess;
private BufferedReader stdout;
private BufferedReader stderr;
private PrintWriter pw;
public SSHLinux()
{
//使用默認值
long t1 = System.currentTimeMillis();
initSession();
long t2 = System.currentTimeMillis();
System.out.println("遠程登陸linux,連接耗時:"+(t2-t1)/1000.0+"s");
}
public SSHLinux(String hostname, int port, String username, String password)
{
this.hostname = hostname;
this.port = port;
this.username = username;
this.password = password;
long t1 = System.currentTimeMillis();
initSession();
long t2 = System.currentTimeMillis();
System.out.println("遠程登陸linux,連接耗時:"+(t2-t1)/1000.0+"s");
}
//初始化連接並建立虛擬終端
public void initSession()
{
try
{
conn = new Connection(hostname,port);
conn.connect();
boolean isAuthenticated = conn.authenticateWithPassword(username, password);
if (isAuthenticated == false)
throw new IOException("Authentication failed.");
sess = conn.openSession();
sess.requestDumbPTY();//建立虛擬終端
sess.startShell();//打開一個Shell
stdout = new BufferedReader(new InputStreamReader(new StreamGobbler(sess.getStdout()), StandardCharsets.UTF_8));
stderr = new BufferedReader(new InputStreamReader(new StreamGobbler(sess.getStderr()), StandardCharsets.UTF_8));
pw = new PrintWriter(sess.getStdin(),true);// 準備輸入命令
}
catch (IOException e)
{
System.out.println("------建立ssh linux連接錯誤------");
}
}
public void close()
{
try
{
stdout.close();
stderr.close();
pw.close();
sess.close();
conn.close();
}
catch (Exception e)
{
System.out.println("------關閉ssh連接錯誤------");
}
}
public List<String> execute(String strcmd)
{
System.out.println("在execute()中接收到命令:"+ strcmd );
List<String> rstList = new ArrayList<String>();
try
{
if(strcmd.equals("exit"))
{
pw.println(strcmd);// 輸入待執行命令
close();
rstList.add("用戶退出,關閉連接!!!");
return rstList;
}
pw.println(strcmd);// 輸入待執行命令
while (true)
{
String line = stdout.readLine();
if (line == null || line.trim().endsWith("#"))
break;
System.out.println(line);
rstList.add(line);
}
}
catch (IOException e)
{
System.out.println("------連接已經關閉或命令出錯------");
}
return rstList;
}
public static void main(String[] args)
{
SSHLinux ssh = new SSHLinux("192.168.0.160",22,"root","123456");
List<String> list = ssh.execute("pwd");
System.out.println("list----: "+list);
//ssh.execute("exit");
}
}
3.頁面後臺調用前面初始化方法和執行命令的方法,這個方法叫getLinux(),主要是和前臺頁面交互。我在這個方法命名爲linux,即@RequestMapping("/linux"),前臺就可以通過linux.do識別了。
首先創建彈出框並初始化:
/*
* 創建按鈕彈出框
*/
@RequestMapping("/addWindow")
public ModelAndView goNewAddServer(HttpServletRequest req)
{
//ssh連接linux---------從xml配置文件中讀取參數
ApplicationContext ct = new ClassPathXmlApplicationContext("applicationContext.xml");
Permission per = (Permission) ct.getBean("linux");
String hostname = per.getIp();
int port = per.getPort();
String username = per.getUsername();
String password = per.getPassword();
System.out.println("ssh連接linux---------Permission="+per.toString());
try {
ssh = new SSHLinux(hostname,port,username,password);
//或者將賬號寫死
//ssh = new SSHLinux("192.168.1.12",22,"root","123456");
} catch (Exception e) {
e.printStackTrace();
}
return new ModelAndView("/admin/cloudcompute/box");///對應web console頁面
}
其次執行命令,主要是和前臺頁面交互: /*
* 在linux web shell中執行命令
*/
@RequestMapping("/linux")
@ResponseBody
private void getLinux(HttpServletRequest request, HttpServletResponse response)throws Exception{
System.out.println("\n-----------------Linux Shell-----------------\n");
String cmd = request.getParameter("code");
System.out.println("執行命令: "+cmd);
if(ssh!=null)
{
List<String> list = new ArrayList<String>();
if(cmd.length()==0)
{
System.out.println("輸入爲空");
//list.add("輸入爲空");
}
else if(cmd!="" || cmd.length()!=0 || cmd!=null)
{
list = ssh.execute(cmd);
}
System.out.println("返回結果list----: "+list);
String result = ""; //{'data:','
for(String str : list)
{
result += str.trim()+"<br/>";
}
//result +="'}";
ResponseUtil.write(response, result);
//JsonBinder buildNormalBinder = JsonBinder.buildNormalBinder();
//return buildNormalBinder.toJson(result);
}
else
{
System.out.println("沒有連接,不執行命令");
//return "";
}
}//
4.接下來看頁面,頁面很簡單:主要是定義<ul>列表來接收命令,因爲發送的命令佔一行,回來的結果爲一行或多行,所以<ul>列表很合適。
<!-- 按鈕觸發模態框 -->
<span class="annnys">
<a id="linuxWindow" class="pve-tabletack active-tabletack" href="javascript:establishDialog()" style="cursor:pointer;">控制檯</a>
</span>
<!-- 模態框(Modal) -->
<div class="modal fade" id="myModal"
tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" style="width:703px;height:410px;">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
<p class="modal-title" id="myModalLabel">Linux 控制檯</p>
</div>
<div class="modal-body" style="padding:0px;">
<!-- <iframe id="box" src="box.jsp" style="width:600px;height:400px;"></iframe> -->
<div class="wingb" id="msg">
<ul class="myul">
<li>----------------------命令Demo----------------------------</li>
<li>關閉進程方式1:</li>
<li>ps -aux | grep test.jar //根據提交的jar包名字來查找進程,再kill -9 [pid]殺掉進程</li>
<li>關閉進程方式2:</li>
<li>ps -aux | grep test.jar | kill -9 `awk '{print $2}'` //查找進程id並立即殺掉</li>
<li>關閉進程方式3:</li>
<li>kill -9 $(cat my.pid) //前提:提交任務時獲取進程號: spark-submit --class demo.WordCount test.jar & echo $!>my.pid</li>
<li>----------------------開始操作----------------------------</li>
</ul>
<!-- <div class="incmd" contentEditable="true" id='in'>$</div> -->
<input class="incmd" type="text" value="$" id='in'>
</div>
</div><!-- modal-body -->
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<script>
$("#in").keyup(function(event) {
if (event.keyCode == 13) { //回車
$.ajax({
async: false,
type : "POST",
url : "${pageContext.request.contextPath}/admin/cloudcompute/linux.do",
data : "code=" + $("#in").val().substring(1),
success : function(data) {
$("ul").append("<li>" + $("#in").val() + "</li>"); //將輸入的輸出到界面
$("ul").append("<li>" + data + "</li>"); //獲取返回值並輸出
$("#in").val("$"); //清空輸入框
$("#msg").scrollTop($("#msg").scrollTop() + 9999);//滾動條拉到最下面,顯示出輸入框
}
});
}
else if(event.ctrlKey && event.which == 81){ //ctrl+Q 中斷
alert("ctrl+Q 中斷");
CloseWebPage();
}
});
$("#in")[0].focus();
function CloseWebPage() {
if (navigator.userAgent.indexOf("MSIE") > 0) {
if (navigator.userAgent.indexOf("MSIE 6.0") > 0) {
window.opener = null;
window.close();
} else {
window.open('', '_top');
window.top.close();
}
} else if (navigator.userAgent.indexOf("Firefox") > 0) {
window.location.href = 'about:blank ';
} else {
window.opener = null;
window.open('', '_self', '');
window.close();
}
}
//創建按鈕 彈出框
function establishDialog(){
xajax.iframeLAYER('${pageContext.request.contextPath}/admin/cloudcompute/addWindow.do',
"linux控制檯",
'620px',
'430px',
function(){
});
}
</script>
這是原來的頁面中增加的一個模態框,通過按鈕觸發。當然也可以新建一個頁面專門來做控制檯,代碼都一樣,如圖:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>命令行窗口</title>
<script type="text/javascript" src="${pageContext.request.contextPath}/static/public-js/jquery-1.8.0.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/static/layer/layer.js"></script>
<style type="text/css">
body{
background-color: #424242; /*背景顏色*/
font-size:14px;
font: "微軟雅黑";
}
.incmd {
background-color: #9FB6CD; /*輸入行顏色*/
border: 0;
color: #FFFFFF; /*輸入字體顏色*/
outline: none;
font-size:14px;
width: 99%;
}
.panel {/*暫時未用*/
background-color: #424242; /*背景顏色*/
border-top: #424242 outset 2px;/*上邊框*/
width: 700px;
height: 500px;
overflow-y: scroll;
overflow-x:visible;
font-size:14px;
}
ul {
margin: 0px;
padding: 0px;
list-style: none;
color:#7CFC00; /*顯示字體顏色*/
}
input {
background-color: #9FB6CD; /*輸入行顏色*/
border: 0;
color: #FFFFFF; /*輸入字體顏色*/
outline: none;
font-size:14px;
width: 99%;
}
</style>
<script>
$(function(){
$("#in").keyup(function(event) {
if (event.keyCode == 13) { //回車
$.ajax({
async: false,
type : "POST",
url : "${pageContext.request.contextPath}/admin/cloudcompute/linux.do",
data : "code=" + $("#in").val().substring(1),
success : function(data) {
$("ul").append("<li>" + $("#in").val() + "</li>"); //將輸入的輸出到界面
$("ul").append("<li>" + data + "</li>"); //獲取返回值並輸出
$("#in").val("$"); //清空輸入框
$("#msg").scrollTop($("#msg").scrollTop() + 32);//滾動條拉到最下面,顯示出輸入框
}
});
}
/* else if(event.ctrlKey && event.which == 81){ //ctrl+Q 中斷
alert("ctrl+Q 中斷");
}
else if(event.which == 27){ //ESC 退出
alert("ESC終端");
CloseWebPage();
}
*/
});
$("#in")[0].focus();
});
function CloseWebPage() {
if (navigator.userAgent.indexOf("MSIE") > 0) {
if (navigator.userAgent.indexOf("MSIE 6.0") > 0) {
window.opener = null;
window.close();
} else {
window.open('', '_top');
window.top.close();
}
} else if (navigator.userAgent.indexOf("Firefox") > 0) {
window.location.href = 'about:blank ';
} else {
window.opener = null;
window.open('', '_self', '');
window.close();
}
}
</script>
</head>
<body>
<div id="msg">
<ul>
<li>----------------------命令Demo----------------------------</li>
<li>關閉進程方式1:</li>
<li>ps -aux | grep test.jar //根據提交的jar包名字來查找進程,再kill -9 [pid]殺掉進程</li>
<li>關閉進程方式2:</li>
<li>ps -aux | grep test.jar | kill -9 `awk '{print $2}'` //查找進程id並立即殺掉</li>
<li>關閉進程方式3:</li>
<li>kill -9 $(cat my.pid) //前提:提交任務時獲取進程號: spark-submit --class demo.WordCount test.jar & echo $!>my.pid</li>
<li>----------------------開始操作----------------------------</li>
</ul>
<!-- <div class="incmd" contentEditable="true" id='in'>$</div> -->
<input type="text" value="$" id='in'>
</div>
</body>
</html>
5.頁面調用後臺代碼的部分都是一樣的,linux.do就會調轉到前面提到的getLinux()方法。
ok, 本文主要從後臺到前臺的順序講了如何實現一個簡單web shell。
完。
我準備寫一個公衆號技術博客,回顧我學大數據以來的個人經驗,希望和大家一起每天進步一點點!剛剛開始寫,請大家多多支持,如有不足之處多多包含,最後多多關注哈哈哈。