序文
このたくさんのを見て何も既製の車輪を直接に使用することはできませんがある見つけるために探して、最近のプロジェクトの要件なので、最初にすべてのGitHubの上、理由の関数のこのタイプを行うには彼の最初の時間で、機能WebSSH接続端子を実装するために、プロジェクトの必要性に起因します以下のようなプロジェクトの側面、:GateOne、webssh、shellinabox、これらのプロジェクトはwebssh機能を実現することができます。
しかし、最終的に採用しなかった、根本的な理由は、これらのほとんどは、Pythonで書かれているということである、たくさんのファイルに依存する必要性、あなたは彼らがこの方式では、クイックターンアラウンドが、ユーザーのためのプロジェクトを実行する時間を使用する時間を使うことができ、それは何をするユーザーに要求することはできませんサーバーは、私は自分自身WebSSH関数を書くことにしたので、明らかに合理的なされていない、これらの基本的な依存関係に含まれており、独立したオープンソースプロジェクトとして実施されなければなりません。
githubのオープンソースプロジェクトのアドレスします。https://github.com/NoCortY/WebSSH
テクノロジーの選択
websshの必要性、リアルタイムのデータ交換なので、そう長く接続sshとJSCHフロントシェルページの実装にxterm.jsのJavaユーザーの知識に加えて、SpringBoot選ばれたフレームワークの開発を容易にするために、WebSocketの接続を使用します。
そのため、最終的な技術の選択は、WebSocketを+ JSCH + + SpringBoot 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>
シンプルなケースの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.jsx1B[0m $ ')
</script>
</body>
</html>
最終テストでは、ページは次のようになります。
xtermのエントリ
あなたはこれに基づいて、その後、シェルのスタイルに似てがなされているページがwebsshを達成するために、中・深引き続き見ることができます。
バックエンドの実装
そのため、我々は、バックエンドからスタート限りのxtermとしてのみスタイルのフロントエンドを達成するために、サーバーには本当に相互作用し、主に使用してサーバーの相互作用は、この部分を達成JSCH +のWebSocketを使用して、コントロールに対する当社のJavaのバックエンドに依存しているとして。
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("*");
}
}
プロセッサ(ハンドラ)とインターセプター(インターセプター)の実装
私達はちょうどの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) {
}
}
プロセッサ:
/**
* @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;
}
}
私が使用しているユーザーIDで迎撃に参加した、ことを注意ランダムUUIDをあなたのプロジェクトには、このプロジェクトが必要な場合は、独立したWebSocketのプロジェクト、ない、モジュールとして、あなたはそれを変更する必要があるため、ユーザによって使用されるプログラムで自分自身を識別するために、ユーザを識別するコードの一部。
WebSSHビジネスロジックの実装(コア)
我々はいくつかのデッドコードされ、WebSocketの構成を実現した、その後、実装インタフェースを実現することができ、ニーズに応じて、そして今、私たちはこのロジックを実装する前に、のはそれについて考えてみましょう、主要なバックエンドのビジネス・ロジックを達成する、WebSSH私たちは、主にほとんど効果を示したいと思います。
私は要約を行うためにここにいます:
1.まず、端子(初期接続)を接続する必要があります
2.第二には、我々はフロントエンド(フロントエンド受信すると、プロセスメッセージ)からのメッセージサーバを処理する必要があります
3.我々は、端末のフロントエンドから返されたメッセージに書き戻される必要がある(データライトバックフロントエンド)
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);
}
今、私たちはこのインタフェースに基づいて定義された機能を実現するために行くことができます。
1.初期接続
私たちが達成JSCH根本的に依存しているので、ので、ここで接続を確立するためにJSCH使用する必要があります。接続の初期化、いわゆるは、実際には、任意の実際の接続作業がない、Mapに保存し、我々は必要な情報を接続することです。なぜ直接接続はありませんか?そこの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);
}
2.クライアントから送信されたデータを処理
このステップでは、我々は2つのブランチに分割されます。
-
最初の分岐:クライアントは、端末のユーザ名とパスワードの情報に送信された場合、我々は、端末を接続してください。
-
第二分岐:クライアントはターミナルコマンド操作に送信された場合、我々は、端末に直接転送し、端末の結果を取得します。
特定のコードの実装:
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);
}
}
3.データは用WebSocketによってフロントエンドに送信されます
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);
}
}
この時点で、私達の全体のバックエンドの実装では、紙面の都合で、終わって、ここでいくつかの操作は、メソッドをパッケージになるだろう、あまりにも多くのショーをしない、それを達成するためにいくつかの論理的な思考を強調表示します。次に、我々は、フロントエンドを実現します。
フロントエンドの実装
フロントエンドの作業は非常に少ないのステップに分かれています。
-
ページの実現
-
WebSocketの接続とデータと書き込みバックの受信を完了
-
データを送信します
したがって、我々はそれを達成するためのステップバイステップ。
ページの実装
ページが非常に簡単で実装私たちはスタイルと書き込み何も、ちょうどdiv要素によって、このインスタンスにDIV、後にターミナルにxtermを作成しないので、我々だけで、フルスクリーンに大きな黒い端末画面の種類を表示する必要がありますこれを達成することができます。
<!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>
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 + 'rn');
},
onConnect: function () {
//连接成功回调
client.sendInitData(options);
},
onClose: function () {
//连接关闭回调
term.write("rconnection closed");
},
onData: function (data) {
//收到数据时回调
term.write(data);
}
});
}
結果は
接続
接続
接続が成功します
接続が成功します
コマンド動作
LSはコマンド:
lsコマンド
vimのエディタ:
vimのエディタ
topコマンド:
topコマンド
エピローグ
その後、我々はwebsshプロジェクトの実施を完了し、他のコンポーネントのいずれかに依存することなく、バックエンドは完全に展開するために非常に簡単、SpringBootの使用により、Javaで実装されています。
しかし、我々はまた、新しいアップロードまたはダウンロードファイルとして、プロジェクトに同様Xftpのような、することができます簡単にドラッグ&ドロップのアップロードとダウンロードファイルを拡張することができます。
このプロジェクトの後、私は継続的に更新され、これらの機能は徐々に、GitHubのを実感しますします。https://github.com/NoCortY/WebSSH
私はああ〜スターを与えることができますのように