This commit is contained in:
Looly 2023-09-24 13:24:48 +08:00
parent 8a0e477f69
commit ed0b9a16ef
5 changed files with 180 additions and 725 deletions

View File

@ -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方式
* <p>
* 此方法单次发送一个命令到服务端不读取环境变量执行结束后自动关闭Session不会产生阻塞
* 此方法单次发送一个命令到服务端不读取环境变量不会产生阻塞
* </p>
*
* @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命令
* <p>
* 此方法单次发送一个命令到服务端自动读取环境变量执行结束后自动关闭Session可能产生阻塞
* 此方法单次发送一个命令到服务端自动读取环境变量可能产生阻塞
* </p>
*
* @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);
}
/**

View File

@ -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);

View File

@ -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会话池keyhostvalueSession对象
*/
private final SimpleCache<String, Session> 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<Entry<String, Session>> iterator = this.cache.iterator();
Entry<String, Session> entry;
while (iterator.hasNext()) {
entry = iterator.next();
if (session.equals(entry.getValue())) {
iterator.remove();
break;
}
}
}
}
/**
* 关闭所有SSH连接会话
*/
public void closeAll() {
Session session;
for (final Entry<String, Session> entry : this.cache) {
session = entry.getValue();
if (session != null && session.isConnected()) {
session.disconnect();
}
}
cache.clear();
}
}

View File

@ -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工具类<br>
* Jsch是Java Secure Channel的缩写JSch是一个SSH2的纯Java实现<br>
* 它允许你连接到一个SSH服务器并且可以使用端口转发X11转发文件传输等<br>
*
* @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端口上. <br>
* 即数据从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方式
* <p>
* 此方法单次发送一个命令到服务端不读取环境变量执行结束后自动关闭channel不会产生阻塞
* </p>
*
* @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命令
* <p>
* 此方法单次发送一个命令到服务端自动读取环境变量执行结束后自动关闭channel不会产生阻塞
* </p>
*
* @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();
}
}

View File

@ -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;
/**
* 基于SSHJhttps://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方式
* <p>
* 此方法单次发送一个命令到服务端不读取环境变量不会产生阻塞
* </p>
*
* @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命令
* <p>
* 此方法单次发送一个命令到服务端自动读取环境变量可能产生阻塞
* </p>
*
* @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);
}
}