<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          教你用純Java實現(xiàn)一個網(wǎng)頁版的Xshell(附源碼)

          共 13466字,需瀏覽 27分鐘

           ·

          2021-01-12 17:56

          點擊上方?好好學java?,選擇?星標?公眾號

          重磅資訊、干貨,第一時間送達

          今日推薦:今天給大家推薦 6 個 Spring Boot 項目,拿來就可以賺錢!

          個人原創(chuàng)100W+訪問量博客:點擊前往,查看更多

          前言

          最近由于項目需求,項目中需要實現(xiàn)一個WebSSH連接終端的功能,由于自己第一次做這類型功能,所以首先上了GitHub找了找有沒有現(xiàn)成的輪子可以拿來直接用,當時看到了很多這方面的項目,例如:GateOne、webssh、shellinabox等,這些項目都可以很好地實現(xiàn)webssh的功能。

          但是最終并沒有采用,原因是在于這些底層大都是python寫的,需要依賴很多文件,自己用的時候可以使用這種方案,快捷省事,但是做到項目中供用戶使用時,總不能要求用戶做到服務器中必須包含這些底層依賴,這顯然不太合理,所以我決定自己動手寫一個WebSSH的功能,并且作為一個獨立的項目開源出來。

          github項目開源地址:https://github.com/NoCortY/WebSSH

          技術選型

          由于webssh需要實時數(shù)據(jù)交互,所以會選用長連接的WebSocket,為了開發(fā)的方便,框架選用SpringBoot,另外還自己了解了Java用戶連接ssh的jsch和實現(xiàn)前端shell頁面的xterm.js.

          所以,最終的技術選型就是 SpringBoot+Websocket+jsch+xterm.js。

          導入依賴

          <parent>
          ????<groupId>org.springframework.bootgroupId>
          ????<artifactId>spring-boot-starter-parentartifactId>
          ????<version>2.1.7.RELEASEversion>
          ????<relativePath?/>?
          parent>
          <dependencies>
          ????
          ????<dependency>
          ????????<groupId>org.springframework.bootgroupId>
          ????????<artifactId>spring-boot-starter-webartifactId>
          ????dependency>
          ????
          ????<dependency>
          ????????<groupId>com.jcraftgroupId>
          ????????<artifactId>jschartifactId>
          ????????<version>0.1.54version>
          ????dependency>
          ????
          ????<dependency>
          ????????<groupId>org.springframework.bootgroupId>
          ????????<artifactId>spring-boot-starter-websocketartifactId>
          ????dependency>
          ????
          ????<dependency>
          ????????<groupId>commons-iogroupId>
          ????????<artifactId>commons-ioartifactId>
          ????????<version>1.4version>
          ????dependency>
          ????<dependency>
          ????????<groupId>commons-fileuploadgroupId>
          ????????<artifactId>commons-fileuploadartifactId>
          ????????<version>1.3.1version>
          ????dependency>
          dependencies>

          一個簡單的xterm案例

          由于xterm是一個冷門技術,所以很多同學并沒有這方面的知識支撐,我也是為了實現(xiàn)這個功能所以臨時學的,所以在這給大家介紹一下。

          xterm.js是一個基于WebSocket的容器,它可以幫助我們在前端實現(xiàn)命令行的樣式。就像是我們平常再用SecureCRT或者XShell連接服務器時一樣。

          下面是官網(wǎng)上的入門案例:


          ?<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.js\x1B[0m?$?')
          ????
          script>
          ??body>
          ?html>

          最終測試,頁面就是下面這個樣子:

          xterm入門

          可以看到頁面已經(jīng)出現(xiàn)了類似與shell的樣式,那就根據(jù)這個繼續(xù)深入,實現(xiàn)一個webssh。

          后端實現(xiàn)

          由于xterm只要只是實現(xiàn)了前端的樣式,并不能真正地實現(xiàn)與服務器交互,與服務器交互主要還是靠我們Java后端來進行控制的,所以我們從后端開始,使用jsch+websocket實現(xiàn)這部分內容。

          WebSocket配置

          由于消息實時推送到前端需要用到WebSocket,不了解WebSocket的同學可以先去自行了解一下,這里就不過多介紹了,我們直接開始進行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("*");
          ??????}
          ??}

          處理器(Handler)和攔截器(Interceptor)的實現(xiàn)

          剛才我們完成了WebSocket的配置,并指定了一個處理器和攔截器。所以接下來就是處理器和攔截器的實現(xiàn)。

          攔截器

          ??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?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)?{

          ??????}
          ??}

          處理器

          ??/**
          ??*?@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("用戶:{},發(fā)送命令:{}",?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:?出現(xiàn)錯誤的回調
          ???????*?@Param:?[webSocketSession,?throwable]
          ???????*?@return:?void
          ???????*?@Author:?Object
          ???????*?@Date:?2020/3/8
          ???????*/

          ??????@Override
          ??????public?void?handleTransportError(WebSocketSession?webSocketSession,?Throwable?throwable)?throws?Exception?{
          ??????????logger.error("數(shù)據(jù)傳輸錯誤");
          ??????}

          ??????/**
          ???????*?@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;
          ??????}
          ??}

          需要注意的是,我在攔截器中加入的用戶標識是使用了隨機的UUID,這是因為作為一個獨立的websocket項目,沒有用戶模塊,如果需要將這個項目集成到自己的項目中,需要修改這部分代碼,將其改為自己項目中識別一個用戶所用的用戶標識。

          WebSSH的業(yè)務邏輯實現(xiàn)(核心)

          剛才我們實現(xiàn)了websocket的配置,都是一些死代碼,實現(xiàn)了接口再根據(jù)自身需求即可實現(xiàn),現(xiàn)在我們將進行后端主要業(yè)務邏輯的實現(xiàn),在實現(xiàn)這個邏輯之前,我們先來想想,WebSSH,我們主要想要呈現(xiàn)一個什么效果。

          我這里做了一個總結:

          1.首先我們得先連接上終端(初始化連接)

          2.其次我們的服務端需要處理來自前端的消息(接收并處理前端消息)

          3.我們需要將終端返回的消息回寫到前端(數(shù)據(jù)回寫前端)

          4.關閉連接

          根據(jù)這四個需求,我們先定義一個接口,這樣可以讓需求明了起來。

          ??/**
          ???*?@Description:?WebSSH的業(yè)務邏輯
          ???*?@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:?處理客戶段發(fā)的數(shù)據(jù)
          ???????*?@Param:
          ???????*?@return:
          ???????*?@Author:?NoCortY
          ???????*?@Date:?2020/3/7
          ???????*/

          ??????public?void?recvHandle(String?buffer,?WebSocketSession?session);

          ??????/**
          ???????*?@Description:?數(shù)據(jù)寫回前端?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);
          ??}

          現(xiàn)在我們可以根據(jù)這個接口去實現(xiàn)我們定義的功能了。

          1.初始化連接

          由于我們的底層是依賴jsch實現(xiàn)的,所以這里是需要使用jsch去建立連接的。而所謂初始化連接,實際上就是將我們所需要的連接信息,保存在一個Map中,這里并不進行任何的真實連接操作。為什么這里不直接進行連接?因為這里前端只是連接上了WebSocket,但是我們還需要前端給我們發(fā)來linux終端的用戶名和密碼,沒有這些信息,我們是無法進行連接的。

          ?????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.處理客戶端發(fā)送的數(shù)據(jù)

          在這一步驟中,我們會分為兩個分支。

          • 第一個分支:如果客戶端發(fā)來的是終端的用戶名和密碼等信息,那么我們進行終端的連接。

          • 第二個分支:如果客戶端發(fā)來的是操作終端的命令,那么我們就直接轉發(fā)到終端并且獲取終端的執(zhí)行結果。

          具體代碼實現(xiàn):

          ?????public?void?recvHandle(String?buffer,?WebSocketSession?session)?{
          ?????????????ObjectMapper?objectMapper?=?new?ObjectMapper();
          ?????????????WebSSHData?webSSHData?=?null;
          ?????????????try?{
          ?????????????????//轉換前端發(fā)送的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()))?{
          ?????????????????//如果是發(fā)送命令的請求
          ?????????????????String?command?=?webSSHData.getCommand();
          ?????????????????SSHConnectInfo?sshConnectInfo?=?(SSHConnectInfo)?sshMap.get(userId);
          ?????????????????if?(sshConnectInfo?!=?null)?{
          ?????????????????????try?{
          ?????????????????????????//發(fā)送命令到終端
          ?????????????????????????transToSSH(sshConnectInfo.getChannel(),?command);
          ?????????????????????}?catch?(IOException?e)?{
          ?????????????????????????logger.error("webssh連接異常");
          ?????????????????????????logger.error("異常信息:{}",?e.getMessage());
          ?????????????????????????close(session);
          ?????????????????????}
          ?????????????????}
          ?????????????}?else?{
          ?????????????????logger.error("不支持的操作");
          ?????????????????close(session);
          ?????????????}
          ?????}

          3.數(shù)據(jù)通過websocket發(fā)送到前端

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

          4.關閉連接

          ?????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);
          ?????????????}
          ?????}

          至此,我們的整個后端實現(xiàn)就結束了,由于篇幅有限,這里將一些操作封裝成了方法,就不做過多展示了,重點講邏輯實現(xiàn)的思路吧。接下來我們將進行前端的實現(xiàn)。

          前端實現(xiàn)

          前端工作主要分為這么幾個步驟:

          1. 頁面的實現(xiàn)

          2. 連接WebSocket并完成數(shù)據(jù)的接收并回寫

          3. 數(shù)據(jù)的發(fā)送

          所以我們一步一步來實現(xiàn)它。

          頁面實現(xiàn)

          頁面的實現(xiàn)很簡單,我們只不過需要在一整個屏幕上都顯示終端那種大黑屏幕,所以我們并不用寫什么樣式,只需要創(chuàng)建一個div,之后將terminal實例通過xterm放到這個div中,就可以實現(xiàn)了。

          ??
          ??<html>
          ??<head>
          ??????<title>WebSSHtitle>
          ??????<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>

          連接WebSocket并完成數(shù)據(jù)的發(fā)送、接收、回寫

          ??openTerminal(?{
          ??????//這里的內容可以寫死,但是要整合到項目中時,需要通過參數(shù)的方式傳入,可以動態(tài)連接某個終端。
          ??????????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)?{
          ??????????????//鍵盤輸入時的回調函數(shù)
          ??????????????client.sendClientData(data);
          ??????????});
          ??????????term.open(document.getElementById('terminal'));
          ??????????//在頁面上顯示連接中...
          ??????????term.write('Connecting...');
          ??????????//執(zhí)行連接操作
          ??????????client.connect({
          ??????????????onError:?function?(error)?{
          ??????????????????//連接失敗回調
          ??????????????????term.write('Error:?'?+?error?+?'\r\n');
          ??????????????},
          ??????????????onConnect:?function?()?{
          ??????????????????//連接成功回調
          ??????????????????client.sendInitData(options);
          ??????????????},
          ??????????????onClose:?function?()?{
          ??????????????????//連接關閉回調
          ??????????????????term.write("\rconnection?closed");
          ??????????????},
          ??????????????onData:?function?(data)?{
          ??????????????????//收到數(shù)據(jù)時回調
          ??????????????????term.write(data);
          ??????????????}
          ??????????});
          ??????}

          效果展示

          連接

          連接

          連接成功

          連接成功

          命令操作

          ls命令:

          ls命令

          vim編輯器:

          vim編輯器

          top命令:

          top命令

          結語

          這樣我們就完成了一個webssh項目的實現(xiàn),沒有依賴其它任何的組件,后端完全使用Java實現(xiàn),由于用了SpringBoot,非常容易部署。

          但是,我們還可以對這個項目進行擴展,比如新增上傳或下載文件,就像Xftp一樣,可以很方便地拖拽式上傳下載文件。

          這個項目之后我會持續(xù)更新,上述功能也會慢慢實現(xiàn),Github:https://github.com/NoCortY/WebSSH

          喜歡可以給個Star哦~

          推薦文章

          原創(chuàng)電子書

          歷時整整一年總結的?Java 面試 + Java 后端技術學習指南,這是本人這幾年及校招的總結,各種高頻面試題已經(jīng)全部進行總結,按照章節(jié)復習即可,已經(jīng)拿到了大廠offer。

          原創(chuàng)思維導圖

          掃碼或者微信搜?程序員的技術圈子?回復?面試?領取原創(chuàng)電子書和思維導圖。

          瀏覽 86
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜精品久久久久久久免费APP | 蜜桃视频色五月婷婷 | 操逼三级片 | 亚洲中文视频在线观看 | 中国老太卖婬HD视频 |