さようなら、xShell、私はJavaを使用してWebバージョンを作成し、ネチズンは666を呼び出します。

舞台裏で公式アカウントをフォローして、支払いまたはモールに返信し、実際のプロジェクト情報とビデオ入手してください

序文

最近、プロジェクトの要件により、プロジェクトでWebSSH接続端末機能を実装する必要があります。このタイプの機能を初めて実行するので、最初にGitHubにアクセスして、可能性のある既製のホイールがあるかどうかを確認しました。 GateOne、webssh、shellinaboxなどのいくつかのプロジェクトは、websshの機能を非常にうまく実現できますが、最終的に採用されませんでした。理由基盤となるレイヤーのほとんどはPythonで記述されており、多くのファイルに依存してそれらを単独で使用する必要があるということです。このソリューションは、プロジェクトのユーザーが使用する場合でも、すばやく簡単に使用できる場合があります。 、ユーザーがこれらの基礎となる依存関係をサーバーに含めることを常に要求できるとは限りません。これは明らかに合理的ではないため、WebSSH関数を自分で作成することにしました。そして独立したプロジェクトとしてオープンソース。

githubプロジェクトのオープンソースアドレス:https://github.com/NoCortY/WebSSH

テクノロジーの選択

websshはリアルタイムのデータ対話を必要とするため、長く接続されたWebSocketを選択します。開発の便宜のために、フレームワークはSpringBootを選択します。さらに、Javaユーザーがsshおよびxterm.jsに接続するためのjschも理解しています。フロントエンドシェルページ。

したがって、最終的なテクノロジーの選択はSpringBoot + Websocket + jsch + xterm.jsです。

依存関係をインポートする

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
    <!-- Web相关 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- jsch支持 -->
    <dependency>
        <groupId>com.jcraft</groupId>
        <artifactId>jsch</artifactId>
        <version>0.1.54</version>
    </dependency>
    <!-- WebSocket 支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <!-- 文件上传解析器 -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.4</version>
    </dependency>
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
</dependencies>
1234567891011121314151617181920212223242526272829303132333435

単純なxtermの場合

xtermは人気のない技術であるため、多くの学生がこの分野の知識サポートを持っていません。この機能を実現するために一時的にこれも学びましたので、ここで皆さんに紹介します。

xterm.jsはWebSocketベースのコンテナーであり、フロントエンドにコマンドラインスタイルを実装するのに役立ちます。通常、SecureCRTまたはXShellを使用してサーバーに接続する場合と同じです。

以下は公式ウェブサイトでの紹介事例です。

<!doctype html>
 <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>
123456789101112131415

最終テストでは、ページは次のようになります。

ページがすでにシェルスタイルに似ていることがわかります。その後、さらに深く掘り下げて、これに基づいてwebsshを実装します。

バックエンドの実装

xtermがフロントエンドスタイルのみを実装している限り、サーバーと実際に対話することはできません。サーバーとの対話は主にJavaバックエンドによって制御されるため、バックエンドから開始し、jsch + websocketを使用して実現します。コンテンツのこの部分。

WebSocket構成

フロントエンドへのメッセージのリアルタイムプッシュには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("*");
    }
}
12345678910111213141516171819

ハンドラーとインターセプターの実装

これで、WebSocketの構成が完了し、プロセッサーとインターセプターを指定しました。したがって、次のステップはプロセッサとインターセプターの実装です。

インターセプター

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<String, Object> 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) {

    }
}
12345678910111213141516171819202122232425262728

プロセッサー

/**
* @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("用户:{},发送命令:{}", 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: 出现错误的回调
     * @Param: [webSocketSession, throwable]
     * @return: void
     * @Author: Object
     * @Date: 2020/3/8
     */
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
        logger.error("数据传输错误");
    }

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

インターセプターで追加したユーザーIDはランダムなUUIDを使用することに注意してください。これは、独立したWebSocketプロジェクトとして、ユーザーモジュールがないためです。このプロジェクトを独自のプロジェクトに統合する必要がある場合は、これを変更する必要があります。コードの一部として、独自のプロジェクトでユーザーを識別するために使用されるユーザーIDに変更します。

WebSSHビジネスロジックの実装(コア)

ちょうど今、websocketの構成を実装しました。これはすべてデッドコードです。インターフェースを実装した後、必要に応じて実装できます。次に、バックエンドのメインビジネスロジックを実装します。このロジックを実装する前に、考えてみましょう。WebSSH、主に効果を提示したいと思います。

私はここに要約をしました:

1.まず、端末に接続する必要があります(接続を初期化します)

2.次に、サーバーはフロントエンドからのメッセージを処理する必要があります(フロントエンドメッセージを受信して​​処理します)

3.端末から返されたメッセージをフロントエンドに書き戻す必要があります(データ書き戻しフロントエンド)

4.接続を閉じます

これらの4つの要件に従って、要件を明確にするために、最初にインターフェイスを定義します。

/**
 * @Description: WebSSH的业务逻辑
 * @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: 处理客户段发的数据
     * @Param:
     * @return:
     * @Author: NoCortY
     * @Date: 2020/3/7
     */
    public void recvHandle(String buffer, WebSocketSession session);

    /**
     * @Description: 数据写回前端 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);
}
123456789101112131415161718192021222324252627282930313233343536373839404142

これで、このインターフェイスに基づいて定義した関数を実装できます。

接続を初期化します

最下層はjschによって実装されているため、ここで接続を確立するにはjschを使用する必要があります。いわゆる初期接続は、実際には、実際の接続操作が実行されないマップに必要な接続情報を保存することです。ここに直接接続してみませんか?ここのフロントエンドはWebSocketにのみ接続されているため、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);
     }
     123456789
     ```

  2. 处理客户端发送的数据

     在这一步骤中,我们会分为两个分支。

     第一个分支:如果客户端发来的是终端的用户名和密码等信息,那么我们进行终端的连接。

     第二个分支:如果客户端发来的是操作终端的命令,那么我们就直接转发到终端并且获取终端的执行结果。

     具体代码实现:

     ```java
     public void recvHandle(String buffer, WebSocketSession session) {
             ObjectMapper objectMapper = new ObjectMapper();
             WebSSHData webSSHData = null;
             try {
                 //转换前端发送的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())) {
                 //如果是发送命令的请求
                 String command = webSSHData.getCommand();
                 SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
                 if (sshConnectInfo != null) {
                     try {
                         //发送命令到终端
                         transToSSH(sshConnectInfo.getChannel(), command);
                     } catch (IOException e) {
                         logger.error("webssh连接异常");
                         logger.error("异常信息:{}", e.getMessage());
                         close(session);
                     }
                 }
             } else {
                 logger.error("不支持的操作");
                 close(session);
             }
     }
     123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
     ```

  3. 数据通过websocket发送到前端

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

  4. 关闭连接

     ```java
     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);
             }
     }
     1234567891011
     ```

  至此,我们的整个后端实现就结束了,由于篇幅有限,这里将一些操作封装成了方法,就不做过多展示了,重点讲逻辑实现的思路吧。接下来我们将进行前端的实现。

## 前端实现

前端工作主要分为这么几个步骤:

1. 页面的实现
2. 连接WebSocket并完成数据的接收并回写
3. 数据的发送

所以我们一步一步来实现它。

- ### 页面实现

  页面的实现很简单,我们只不过需要在一整个屏幕上都显示终端那种大黑屏幕,所以我们并不用写什么样式,只需要创建一个div,之后将terminal实例通过xterm放到这个div中,就可以实现了。

  ```html
  <!doctype html>
  <html>
  <head>
      <title>WebSSH</title>
      <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>
  123456789101112131415

WebSocketに接続して、データの送信、受信、書き戻しを完了します

openTerminal( {
    //这里的内容可以写死,但是要整合到项目中时,需要通过参数的方式传入,可以动态连接某个终端。
        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) {
            //键盘输入时的回调函数
            client.sendClientData(data);
        });
        term.open(document.getElementById('terminal'));
        //在页面上显示连接中...
        term.write('Connecting...');
        //执行连接操作
        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) {
                //收到数据时回调
                term.write(data);
            }
        });
    }
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647

結果を示す

接続

接続に成功しました

コマンド操作

lsコマンド:

vimエディター:

トップコマンド:

結論

このようにして、他のコンポーネントに依存することなくwebsshプロジェクトの実装を完了し、バックエンドは完全にJavaで実装され、SpringBootを使用するため非常に簡単にデプロイできます。

ただし、Xftpと同様に、新しいアップロードまたはダウンロードファイルを追加するなど、このプロジェクトを拡張することもできます。アップロードおよびダウンロードファイルを簡単にドラッグアンドドロップできます。

このプロジェクトの後、私は更新を続け、上記の機能はゆっくりと実装されます、Github:https://github.com/NoCortY/WebSSH

著者:ObjectSpace

ソース:https://sourl.cn/apXiEy

さようなら、xShell、私はJavaを使用してWebバージョンを作成し、ネチズンは666を呼び出します。




有热门推荐????
2020 国内互联网公司的薪酬排名,加班时长排名 !IDEA这样 配置注释模板,让你高出一个逼格!!
Java后端线上问题排查常用命令收藏SpringBoot+Prometheus+Grafana实现应用监控和报警10个解放双手实用在线工具,有些代码真的不用手写!微信小程序练手实战:前端+后端(Java)
又一神操作,SpringBoot2.x 集成百度 uidgenerator搞定全局ID为什么要在2021年放弃Jenkins?我已经对他失去耐心了...Docker + FastDFS + Spring Boot 一键式搭建分布式文件服务器

点击阅读原文,前往学习SpringCloud实战项目

おすすめ

転載: blog.csdn.net/qq_17231297/article/details/114986499