diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/ganymed/GanymedSession.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/ganymed/GanymedSession.java
index 69c523bb4..86474114d 100644
--- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/ganymed/GanymedSession.java
+++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/ganymed/GanymedSession.java
@@ -16,6 +16,7 @@ import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.StreamGobbler;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.io.IoUtil;
+import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.extra.ssh.Connector;
import org.dromara.hutool.extra.ssh.Session;
@@ -65,7 +66,7 @@ public class GanymedSession implements Session {
/**
* 执行Shell命令(使用EXEC方式)
*
- * 此方法单次发送一个命令到服务端,不读取环境变量,执行结束后自动关闭Session,不会产生阻塞。
+ * 此方法单次发送一个命令到服务端,不读取环境变量,不会产生阻塞。
*
*
* @param cmd 命令
@@ -73,24 +74,31 @@ public class GanymedSession implements Session {
* @param errStream 错误信息输出到的位置
* @return 执行返回结果
*/
- public String exec(final String cmd, final Charset charset, final OutputStream errStream) {
- final String result;
+ public String exec(final String cmd, Charset charset, final OutputStream errStream) {
+ if (null == charset) {
+ charset = CharsetUtil.UTF_8;
+ }
+
+ // 发送命令
try {
this.raw.execCommand(cmd, charset.name());
- result = IoUtil.read(new StreamGobbler(this.raw.getStdout()), charset);
-
- // 错误输出
- IoUtil.copy(new StreamGobbler(this.raw.getStderr()), errStream);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
- return result;
+
+ // 错误输出
+ if(null != errStream){
+ IoUtil.copy(new StreamGobbler(this.raw.getStderr()), errStream);
+ }
+
+ // 结果输出
+ return IoUtil.read(new StreamGobbler(this.raw.getStdout()), charset);
}
/**
* 执行Shell命令
*
- * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。
+ * 此方法单次发送一个命令到服务端,自动读取环境变量,可能产生阻塞。
*
*
* @param cmd 命令
@@ -98,21 +106,27 @@ public class GanymedSession implements Session {
* @param errStream 错误信息输出到的位置
* @return 执行返回结果
*/
- public String execByShell(final String cmd, final Charset charset, final OutputStream errStream) {
- final String result;
+ public String execByShell(final String cmd, Charset charset, final OutputStream errStream) {
+ if (null == charset) {
+ charset = CharsetUtil.UTF_8;
+ }
+
try {
this.raw.requestDumbPTY();
- IoUtil.write(this.raw.getStdin(), charset, true, cmd);
-
- result = IoUtil.read(new StreamGobbler(this.raw.getStdout()), charset);
- if(null != errStream){
- // 错误输出
- IoUtil.copy(new StreamGobbler(this.raw.getStderr()), errStream);
- }
} catch (final IOException e) {
throw new IORuntimeException(e);
}
- return result;
+
+ // 发送命令
+ IoUtil.write(this.raw.getStdin(), charset, true, cmd);
+
+ // 错误输出
+ if (null != errStream) {
+ IoUtil.copy(new StreamGobbler(this.raw.getStderr()), errStream);
+ }
+
+ // 结果输出
+ return IoUtil.read(new StreamGobbler(this.raw.getStdout()), charset);
}
/**
diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSession.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSession.java
index ab907a169..31677124c 100644
--- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSession.java
+++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSession.java
@@ -205,6 +205,7 @@ public class JschSession implements Session {
final ChannelExec channel = (ChannelExec) createChannel(ChannelType.EXEC);
channel.setCommand(ByteUtil.toBytes(cmd, charset));
channel.setInputStream(null);
+
channel.setErrStream(errStream);
InputStream in = null;
try {
@@ -234,7 +235,7 @@ public class JschSession implements Session {
* @return {@link ChannelExec}
* @since 5.2.5
*/
- public static String execByShell(final String cmd, final Charset charset) {
+ public String execByShell(final String cmd, final Charset charset) {
final ChannelShell shell = openShell();
// 开始连接
shell.setPty(true);
diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSessionPool.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSessionPool.java
deleted file mode 100644
index 92a4df792..000000000
--- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSessionPool.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (c) 2023 looly(loolly@aliyun.com)
- * Hutool is licensed under Mulan PSL v2.
- * You can use this software according to the terms and conditions of the Mulan PSL v2.
- * You may obtain a copy of Mulan PSL v2 at:
- * https://license.coscl.org.cn/MulanPSL2
- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
- * See the Mulan PSL v2 for more details.
- */
-
-package org.dromara.hutool.extra.ssh.engine.jsch;
-
-import org.dromara.hutool.core.cache.SimpleCache;
-import org.dromara.hutool.core.text.StrUtil;
-import com.jcraft.jsch.Session;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map.Entry;
-
-/**
- * Jsch会话池
- *
- * @author looly
- */
-public enum JschSessionPool {
- INSTANCE;
-
- /**
- * SSH会话池,key:host,value:Session对象
- */
- private final SimpleCache cache = new SimpleCache<>(new HashMap<>());
-
- /**
- * 获取Session,不存在返回null
- *
- * @param key 键
- * @return Session
- */
- public Session get(final String key) {
- return cache.get(key);
- }
-
- /**
- * 获得一个SSH跳板机会话,重用已经使用的会话
- *
- * @param sshHost 跳板机主机
- * @param sshPort 跳板机端口
- * @param sshUser 跳板机用户名
- * @param sshPass 跳板机密码
- * @return SSH会话
- */
- public Session getSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass) {
- final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort);
- return this.cache.get(key, Session::isConnected, ()-> JschUtil.openSession(sshHost, sshPort, sshUser, sshPass));
- }
-
- /**
- * 获得一个SSH跳板机会话,重用已经使用的会话
- *
- * @param sshHost 跳板机主机
- * @param sshPort 跳板机端口
- * @param sshUser 跳板机用户名
- * @param prvkey 跳板机私钥路径
- * @param passphrase 跳板机私钥密码
- * @return SSH会话
- */
- public Session getSession(final String sshHost, final int sshPort, final String sshUser, final String prvkey, final byte[] passphrase) {
- final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort);
- return this.cache.get(key, Session::isConnected, ()->JschUtil.openSession(sshHost, sshPort, sshUser, prvkey, passphrase));
- }
-
- /**
- * 加入Session
- *
- * @param key 键
- * @param session Session
- */
- public void put(final String key, final Session session) {
- this.cache.put(key, session);
- }
-
- /**
- * 关闭SSH连接会话
- *
- * @param key 主机,格式为user@host:port
- */
- public void close(final String key) {
- final Session session = get(key);
- if (session != null && session.isConnected()) {
- session.disconnect();
- }
- this.cache.remove(key);
- }
-
- /**
- * 移除指定Session
- *
- * @param session Session会话
- * @since 4.1.15
- */
- public void remove(final Session session) {
- if (null != session) {
- final Iterator> iterator = this.cache.iterator();
- Entry entry;
- while (iterator.hasNext()) {
- entry = iterator.next();
- if (session.equals(entry.getValue())) {
- iterator.remove();
- break;
- }
- }
- }
- }
-
- /**
- * 关闭所有SSH连接会话
- */
- public void closeAll() {
- Session session;
- for (final Entry entry : this.cache) {
- session = entry.getValue();
- if (session != null && session.isConnected()) {
- session.disconnect();
- }
- }
- cache.clear();
- }
-}
diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschUtil.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschUtil.java
deleted file mode 100644
index 6f3aed9db..000000000
--- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschUtil.java
+++ /dev/null
@@ -1,574 +0,0 @@
-/*
- * Copyright (c) 2023 looly(loolly@aliyun.com)
- * Hutool is licensed under Mulan PSL v2.
- * You can use this software according to the terms and conditions of the Mulan PSL v2.
- * You may obtain a copy of Mulan PSL v2 at:
- * https://license.coscl.org.cn/MulanPSL2
- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
- * See the Mulan PSL v2 for more details.
- */
-
-package org.dromara.hutool.extra.ssh.engine.jsch;
-
-import com.jcraft.jsch.*;
-import org.dromara.hutool.core.io.IORuntimeException;
-import org.dromara.hutool.core.io.IoUtil;
-import org.dromara.hutool.core.lang.Assert;
-import org.dromara.hutool.core.net.LocalPortGenerator;
-import org.dromara.hutool.core.text.StrUtil;
-import org.dromara.hutool.core.util.ByteUtil;
-import org.dromara.hutool.core.util.CharsetUtil;
-import org.dromara.hutool.extra.ssh.Connector;
-import org.dromara.hutool.extra.ssh.SshException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-
-/**
- * Jsch工具类
- * Jsch是Java Secure Channel的缩写。JSch是一个SSH2的纯Java实现。
- * 它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等。
- *
- * @author Looly
- * @since 4.0.0
- */
-public class JschUtil {
-
- /**
- * 不使用SSH的值
- */
- public final static String SSH_NONE = "none";
-
- /**
- * 本地端口生成器
- */
- private static final LocalPortGenerator portGenerater = new LocalPortGenerator(10000);
-
- /**
- * 生成一个本地端口,用于远程端口映射
- *
- * @return 未被使用的本地端口
- */
- public static int generateLocalPort() {
- return portGenerater.generate();
- }
-
- /**
- * 获得一个SSH会话,重用已经使用的会话
- *
- * @param sshHost 主机
- * @param sshPort 端口
- * @param sshUser 用户名
- * @param sshPass 密码
- * @return SSH会话
- */
- public static Session getSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass) {
- return JschSessionPool.INSTANCE.getSession(sshHost, sshPort, sshUser, sshPass);
- }
-
- /**
- * 获得一个SSH会话,重用已经使用的会话
- *
- * @param sshHost 主机
- * @param sshPort 端口
- * @param sshUser 用户名
- * @param privateKeyPath 私钥路径
- * @param passphrase 私钥密码
- * @return SSH会话
- */
- public static Session getSession(final String sshHost, final int sshPort, final String sshUser, final String privateKeyPath, final byte[] passphrase) {
- return JschSessionPool.INSTANCE.getSession(sshHost, sshPort, sshUser, privateKeyPath, passphrase);
- }
-
- /**
- * 打开一个新的SSH会话
- *
- * @param sshHost 主机
- * @param sshPort 端口
- * @param sshUser 用户名
- * @param sshPass 密码
- * @return SSH会话
- */
- public static Session openSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass) {
- return openSession(sshHost, sshPort, sshUser, sshPass, 0);
- }
-
- /**
- * 打开一个新的SSH会话
- *
- * @param sshHost 主机
- * @param sshPort 端口
- * @param sshUser 用户名
- * @param sshPass 密码
- * @param timeout Socket连接超时时长,单位毫秒
- * @return SSH会话
- * @since 5.3.3
- */
- public static Session openSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass, final int timeout) {
- final Session session = createSession(sshHost, sshPort, sshUser, sshPass);
- try {
- session.setTimeout(timeout);
- session.connect(timeout);
- } catch (final JSchException e) {
- throw new SshException(e);
- }
- return session;
- }
-
- /**
- * 打开一个新的SSH会话
- *
- * @param sshHost 主机
- * @param sshPort 端口
- * @param sshUser 用户名
- * @param privateKeyPath 私钥的路径
- * @param passphrase 私钥文件的密码,可以为null
- * @return SSH会话
- */
- public static Session openSession(final String sshHost, final int sshPort, final String sshUser, final String privateKeyPath, final byte[] passphrase) {
- final Session session = createSession(sshHost, sshPort, sshUser, privateKeyPath, passphrase);
- try {
- session.connect();
- } catch (final JSchException e) {
- throw new SshException(e);
- }
- return session;
- }
-
- /**
- * 打开一个新的SSH会话
- *
- * @param sshHost 主机
- * @param sshPort 端口
- * @param sshUser 用户名
- * @param privateKeyPath 私钥的路径
- * @param passphrase 私钥文件的密码,可以为null
- * @param timeout 超时时间,单位毫秒
- * @return SSH会话
- */
- public static Session openSession(final String sshHost, final int sshPort, final String sshUser, final String privateKeyPath, final byte[] passphrase, final int timeout) {
- final Session session = createSession(sshHost, sshPort, sshUser, privateKeyPath, passphrase);
- try {
- session.setTimeout(timeout);
- session.connect(timeout);
- } catch (final JSchException e) {
- throw new SshException(e);
- }
- return session;
- }
-
- /**
- * 新建一个新的SSH会话,此方法并不打开会话(既不调用connect方法)
- *
- * @param sshHost 主机
- * @param sshPort 端口
- * @param sshUser 用户名,如果为null,默认root
- * @param sshPass 密码
- * @return SSH会话
- * @since 4.5.2
- */
- public static Session createSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass) {
- final JSch jsch = new JSch();
- final Session session = createSession(jsch, sshHost, sshPort, sshUser);
-
- if (StrUtil.isNotEmpty(sshPass)) {
- session.setPassword(sshPass);
- }
-
- return session;
- }
-
- /**
- * 新建一个新的SSH会话,此方法并不打开会话(既不调用connect方法)
- *
- * @param sshHost 主机
- * @param sshPort 端口
- * @param sshUser 用户名,如果为null,默认root
- * @param privateKeyPath 私钥的路径
- * @param passphrase 私钥文件的密码,可以为null
- * @return SSH会话
- * @since 5.0.0
- */
- public static Session createSession(final String sshHost, final int sshPort, final String sshUser, final String privateKeyPath, final byte[] passphrase) {
- Assert.notEmpty(privateKeyPath, "PrivateKey Path must be not empty!");
-
- final JSch jsch = new JSch();
- try {
- jsch.addIdentity(privateKeyPath, passphrase);
- } catch (final JSchException e) {
- throw new SshException(e);
- }
-
- return createSession(jsch, sshHost, sshPort, sshUser);
- }
-
- /**
- * 创建一个SSH会话,重用已经使用的会话
- *
- * @param jsch {@link JSch}
- * @param sshHost 主机
- * @param sshPort 端口
- * @param sshUser 用户名,如果为null,默认root
- * @return {@link Session}
- * @since 5.0.3
- */
- public static Session createSession(JSch jsch, final String sshHost, final int sshPort, String sshUser) {
- Assert.notEmpty(sshHost, "SSH Host must be not empty!");
- Assert.isTrue(sshPort > 0, "SSH port must be > 0");
-
- // 默认root用户
- if (StrUtil.isEmpty(sshUser)) {
- sshUser = "root";
- }
-
- if (null == jsch) {
- jsch = new JSch();
- }
-
- final Session session;
- try {
- session = jsch.getSession(sshUser, sshHost, sshPort);
- } catch (final JSchException e) {
- throw new SshException(e);
- }
-
- // 设置第一次登录的时候提示,可选值:(ask | yes | no)
- session.setConfig("StrictHostKeyChecking", "no");
-
- return session;
- }
-
- /**
- * 绑定端口到本地。 一个会话可绑定多个端口
- *
- * @param session 需要绑定端口的SSH会话
- * @param remoteHost 远程主机
- * @param remotePort 远程端口
- * @param localPort 本地端口
- * @return 成功与否
- * @throws SshException 端口绑定失败异常
- */
- public static boolean bindPort(final Session session, final String remoteHost, final int remotePort, final int localPort) throws SshException {
- return bindPort(session, remoteHost, remotePort, "127.0.0.1", localPort);
- }
-
- /**
- * 绑定端口到本地。 一个会话可绑定多个端口
- *
- * @param session 需要绑定端口的SSH会话
- * @param remoteHost 远程主机
- * @param remotePort 远程端口
- * @param localHost 本地主机
- * @param localPort 本地端口
- * @return 成功与否
- * @throws SshException 端口绑定失败异常
- * @since 5.7.8
- */
- public static boolean bindPort(final Session session, final String remoteHost, final int remotePort, final String localHost, final int localPort) throws SshException {
- if (session != null && session.isConnected()) {
- try {
- session.setPortForwardingL(localHost, localPort, remoteHost, remotePort);
- } catch (final JSchException e) {
- throw new SshException(e, "From [{}:{}] mapping to [{}:{}] error!", remoteHost, remotePort, localHost, localPort);
- }
- return true;
- }
- return false;
- }
-
-
- /**
- * 绑定ssh服务端的serverPort端口, 到host主机的port端口上.
- * 即数据从ssh服务端的serverPort端口, 流经ssh客户端, 达到host:port上.
- *
- * @param session 与ssh服务端建立的会话
- * @param bindPort ssh服务端上要被绑定的端口
- * @param host 转发到的host
- * @param port host上的端口
- * @return 成功与否
- * @throws SshException 端口绑定失败异常
- * @since 5.4.2
- */
- public static boolean bindRemotePort(final Session session, final int bindPort, final String host, final int port) throws SshException {
- if (session != null && session.isConnected()) {
- try {
- session.setPortForwardingR(bindPort, host, port);
- } catch (final JSchException e) {
- throw new SshException(e, "From [{}] mapping to [{}] error!", bindPort, port);
- }
- return true;
- }
- return false;
- }
-
-
- /**
- * 解除端口映射
- *
- * @param session 需要解除端口映射的SSH会话
- * @param localPort 需要解除的本地端口
- */
- public static void unBindPort(final Session session, final int localPort) {
- try {
- session.delPortForwardingL(localPort);
- } catch (final JSchException e) {
- throw new SshException(e);
- }
- }
-
- /**
- * 打开SSH会话,并绑定远程端口到本地的一个随机端口
- *
- * @param sshConn SSH连接信息对象
- * @param remoteHost 远程主机
- * @param remotePort 远程端口
- * @return 映射后的本地端口
- * @throws SshException 连接异常
- */
- public static int openAndBindPortToLocal(final Connector sshConn, final String remoteHost, final int remotePort) throws SshException {
- final Session session = openSession(sshConn.getHost(), sshConn.getPort(), sshConn.getUser(), sshConn.getPassword());
- final int localPort = generateLocalPort();
- bindPort(session, remoteHost, remotePort, localPort);
- return localPort;
- }
-
- /**
- * 打开SFTP连接
- *
- * @param session Session会话
- * @return {@link ChannelSftp}
- * @since 4.0.3
- */
- public static ChannelSftp openSftp(final Session session) {
- return openSftp(session, 0);
- }
-
- /**
- * 打开SFTP连接
- *
- * @param session Session会话
- * @param timeout 连接超时时长,单位毫秒
- * @return {@link ChannelSftp}
- * @since 5.3.3
- */
- public static ChannelSftp openSftp(final Session session, final int timeout) {
- return (ChannelSftp) openChannel(session, ChannelType.SFTP, timeout);
- }
-
- /**
- * 创建Sftp
- *
- * @param sshHost 远程主机
- * @param sshPort 远程主机端口
- * @param sshUser 远程主机用户名
- * @param sshPass 远程主机密码
- * @return {@link Sftp}
- * @since 4.0.3
- */
- public static Sftp createSftp(final String sshHost, final int sshPort, final String sshUser, final String sshPass) {
- return new Sftp(sshHost, sshPort, sshUser, sshPass);
- }
-
- /**
- * 创建Sftp
- *
- * @param session SSH会话
- * @return {@link Sftp}
- * @since 4.0.5
- */
- public static Sftp createSftp(final Session session) {
- return new Sftp(session);
- }
-
- /**
- * 打开Shell连接
- *
- * @param session Session会话
- * @return {@link ChannelShell}
- * @since 4.0.3
- */
- public static ChannelShell openShell(final Session session) {
- return (ChannelShell) openChannel(session, ChannelType.SHELL);
- }
-
- /**
- * 打开Channel连接
- *
- * @param session Session会话
- * @param channelType 通道类型,可以是shell或sftp等,见{@link ChannelType}
- * @return {@link Channel}
- * @since 4.5.2
- */
- public static Channel openChannel(final Session session, final ChannelType channelType) {
- return openChannel(session, channelType, 0);
- }
-
- /**
- * 打开Channel连接
- *
- * @param session Session会话
- * @param channelType 通道类型,可以是shell或sftp等,见{@link ChannelType}
- * @param timeout 连接超时时长,单位毫秒
- * @return {@link Channel}
- * @since 5.3.3
- */
- public static Channel openChannel(final Session session, final ChannelType channelType, final int timeout) {
- final Channel channel = createChannel(session, channelType);
- try {
- channel.connect(Math.max(timeout, 0));
- } catch (final JSchException e) {
- throw new SshException(e);
- }
- return channel;
- }
-
- /**
- * 创建Channel连接
- *
- * @param session Session会话
- * @param channelType 通道类型,可以是shell或sftp等,见{@link ChannelType}
- * @return {@link Channel}
- * @since 4.5.2
- */
- public static Channel createChannel(final Session session, final ChannelType channelType) {
- final Channel channel;
- try {
- if (!session.isConnected()) {
- session.connect();
- }
- channel = session.openChannel(channelType.getValue());
- } catch (final JSchException e) {
- throw new SshException(e);
- }
- return channel;
- }
-
- /**
- * 执行Shell命令
- *
- * @param session Session会话
- * @param cmd 命令
- * @param charset 发送和读取内容的编码
- * @return {@link ChannelExec}
- * @since 4.0.3
- */
- public static String exec(final Session session, final String cmd, final Charset charset) {
- return exec(session, cmd, charset, System.err);
- }
-
- /**
- * 执行Shell命令(使用EXEC方式)
- *
- * 此方法单次发送一个命令到服务端,不读取环境变量,执行结束后自动关闭channel,不会产生阻塞。
- *
- *
- * @param session Session会话
- * @param cmd 命令
- * @param charset 发送和读取内容的编码
- * @param errStream 错误信息输出到的位置
- * @return 执行结果内容
- * @since 4.3.1
- */
- public static String exec(final Session session, final String cmd, Charset charset, final OutputStream errStream) {
- if (null == charset) {
- charset = CharsetUtil.UTF_8;
- }
- final ChannelExec channel = (ChannelExec) createChannel(session, ChannelType.EXEC);
- channel.setCommand(ByteUtil.toBytes(cmd, charset));
- channel.setInputStream(null);
- channel.setErrStream(errStream);
- InputStream in = null;
- try {
- channel.connect();
- in = channel.getInputStream();
- return IoUtil.read(in, charset);
- } catch (final IOException e) {
- throw new IORuntimeException(e);
- } catch (final JSchException e) {
- throw new SshException(e);
- } finally {
- IoUtil.closeQuietly(in);
- close(channel);
- }
- }
-
- /**
- * 执行Shell命令
- *
- * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭channel,不会产生阻塞。
- *
- *
- * @param session Session会话
- * @param cmd 命令
- * @param charset 发送和读取内容的编码
- * @return {@link ChannelExec}
- * @since 5.2.5
- */
- public static String execByShell(final Session session, final String cmd, final Charset charset) {
- final ChannelShell shell = openShell(session);
- // 开始连接
- shell.setPty(true);
- OutputStream out = null;
- InputStream in = null;
- try {
- out = shell.getOutputStream();
- in = shell.getInputStream();
-
- out.write(ByteUtil.toBytes(cmd, charset));
- out.flush();
-
- return IoUtil.read(in, charset);
- } catch (final IOException e) {
- throw new IORuntimeException(e);
- } finally {
- IoUtil.closeQuietly(out);
- IoUtil.closeQuietly(in);
- close(shell);
- }
- }
-
- /**
- * 关闭SSH连接会话
- *
- * @param session SSH会话
- */
- public static void close(final Session session) {
- if (session != null && session.isConnected()) {
- session.disconnect();
- }
- JschSessionPool.INSTANCE.remove(session);
- }
-
- /**
- * 关闭会话通道
- *
- * @param channel 会话通道
- * @since 4.0.3
- */
- public static void close(final Channel channel) {
- if (channel != null && channel.isConnected()) {
- channel.disconnect();
- }
- }
-
- /**
- * 关闭SSH连接会话
- *
- * @param key 主机,格式为user@host:port
- */
- public static void close(final String key) {
- JschSessionPool.INSTANCE.close(key);
- }
-
- /**
- * 关闭所有SSH连接会话
- */
- public static void closeAll() {
- JschSessionPool.INSTANCE.closeAll();
- }
-
-}
diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSession.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSession.java
new file mode 100644
index 000000000..99b6149f9
--- /dev/null
+++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSession.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2023 looly(loolly@aliyun.com)
+ * Hutool is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * https://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package org.dromara.hutool.extra.ssh.engine.sshj;
+
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
+import org.dromara.hutool.core.io.IORuntimeException;
+import org.dromara.hutool.core.io.IoUtil;
+import org.dromara.hutool.core.util.CharsetUtil;
+import org.dromara.hutool.extra.ssh.Connector;
+import org.dromara.hutool.extra.ssh.Session;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+/**
+ * 基于SSHJ(https://github.com/hierynomus/sshj)的Session封装
+ *
+ * @author looly
+ */
+public class SshjSession implements Session {
+
+ private SSHClient ssh;
+ private final net.schmizz.sshj.connection.channel.direct.Session raw;
+
+ /**
+ * 构造
+ *
+ * @param connector {@link Connector},保存连接和验证信息等
+ */
+ public SshjSession(final Connector connector) {
+ final SSHClient ssh = new SSHClient();
+ ssh.addHostKeyVerifier(new PromiscuousVerifier());
+ try {
+ ssh.connect(connector.getHost(), connector.getPort());
+ ssh.authPassword(connector.getUser(), connector.getPassword());
+ this.raw = ssh.startSession();
+ } catch (final IOException e) {
+ throw new IORuntimeException(e);
+ }
+
+ this.ssh = ssh;
+ }
+
+ /**
+ * 构造
+ *
+ * @param raw {@link net.schmizz.sshj.connection.channel.direct.Session}
+ */
+ public SshjSession(final net.schmizz.sshj.connection.channel.direct.Session raw) {
+ this.raw = raw;
+ }
+
+ @Override
+ public Object getRaw() {
+ return raw;
+ }
+
+ @Override
+ public void close() throws IOException {
+ IoUtil.closeQuietly(this.raw);
+ IoUtil.closeQuietly(this.ssh);
+ }
+
+ /**
+ * 执行Shell命令(使用EXEC方式)
+ *
+ * 此方法单次发送一个命令到服务端,不读取环境变量,不会产生阻塞。
+ *
+ *
+ * @param cmd 命令
+ * @param charset 发送和读取内容的编码
+ * @param errStream 错误信息输出到的位置
+ * @return 执行返回结果
+ */
+ public String exec(final String cmd, Charset charset, final OutputStream errStream) {
+ if (null == charset) {
+ charset = CharsetUtil.UTF_8;
+ }
+
+ final net.schmizz.sshj.connection.channel.direct.Session.Command command;
+
+ // 发送命令
+ try {
+ command = this.raw.exec(cmd);
+ //command.join();
+ } catch (final IOException e) {
+ throw new IORuntimeException(e);
+ }
+
+ // 错误输出
+ if (null != errStream) {
+ IoUtil.copy(command.getErrorStream(), errStream);
+ }
+
+ // 结果输出
+ return IoUtil.read(command.getInputStream(), charset);
+ }
+
+ /**
+ * 执行Shell命令
+ *
+ * 此方法单次发送一个命令到服务端,自动读取环境变量,可能产生阻塞。
+ *
+ *
+ * @param cmd 命令
+ * @param charset 发送和读取内容的编码
+ * @param errStream 错误信息输出到的位置
+ * @return 执行返回结果
+ */
+ public String execByShell(final String cmd, Charset charset, final OutputStream errStream) {
+ if (null == charset) {
+ charset = CharsetUtil.UTF_8;
+ }
+
+ final net.schmizz.sshj.connection.channel.direct.Session.Shell shell;
+ try {
+ shell = this.raw.startShell();
+ } catch (final IOException e) {
+ throw new IORuntimeException(e);
+ }
+
+ // 发送命令
+ IoUtil.write(shell.getOutputStream(), charset, true, cmd);
+
+ // 错误输出
+ if (null != errStream) {
+ IoUtil.copy(shell.getErrorStream(), errStream);
+ }
+
+ // 结果输出
+ return IoUtil.read(shell.getInputStream(), charset);
+ }
+}