Apache Mina Sshd を使った sftp クライアント
2023/09/08, last updated 2024/10/10 - ~2 Minutes
あるプロジェクトで sftp でファイルを持ってくるために jsch を使っていたのだが、使い方が悪いのか、転送に失敗したりファイルが消えたりすることがあった。 また、jsch は更新が止まっているので別のものを使ったほうが良いのでは?という指摘も受けた。
そのため、apache mina sshd の sshd-sftp を評価することにした。(使っている spring framework も最新でないので、spring integration sftp も使えなかったという事情もある)
sshd-sftp だが、ネット検索の方法が悪いのか、sftp クライアント のサンプルを見つけられなかった。
そこで、試行錯誤したコードを添付する。YOUR_* の部分は適当に変更すること。
TestSftp.java
package YOUR_PACKAGE_NAME;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.Collection;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.io.IOUtils;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.sftp.client.SftpClient;
import org.apache.sshd.sftp.client.SftpClient.CloseableHandle;
import org.apache.sshd.sftp.client.SftpClient.DirEntry;
import org.apache.sshd.sftp.client.SftpClientFactory;
public class TestSftp {
static private String passphrase = "YOUR_PASS_PHRASE";
static private String key = "-----BEGIN RSA PRIVATE KEY-----\n" +
"YOUR_KEY" +
"YOUR_KEY" +
"YOUR_KEY" +
"YOUR_KEY" +
"YOUR_KEY" +
"-----END RSA PRIVATE KEY-----";
public static void main(String args[]) {
KeyPairResourceLoader loader = SecurityUtils.getKeyPairResourceParser();
try (SshClient client = SshClient.setUpDefaultClient()) {
Collection<KeyPair> kps = loader.loadKeyPairs(null, null, FilePasswordProvider.of(passphrase), key);
// client.addPublicKeyIdentity(kps.stream().findFirst().get()); // 鍵の追加はここでも良い?
client.start();
try (ClientSession session = client.connect("YOUR_ID", "YOUR_HOST", 22).verify(60 * 1000).getSession()) {
System.out.println("session started");
session.addPublicKeyIdentity(kps.stream().findFirst().get());
session.auth().verify(60 * 1000);
try (SftpClient sftp = SftpClientFactory.instance().createSftpClient(session)) {
try (CloseableHandle handle = sftp.openDir(".")) {
Iterable<DirEntry> entries = sftp.listDir(handle);
for (DirEntry entry: entries) {
System.out.println(entry.getFilename());
System.out.println(entry.getLongFilename());
String filename = entry.getFilename();
if (StringUtils.equals(filename, "index.html")) {
System.out.println("index.html found");
try (InputStream is = sftp.read(filename)) {
FileOutputStream os = new FileOutputStream(new File(filename));
IOUtils.copy(is, os);
}
try {
sftp.mkdir("tmp");
} catch (IOException e) {
System.out.println("IOException is ignored");
}
sftp.rename(filename, "tmp/" + filename);
}
}
}
}
}
} catch (IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
}
}
API については、 sshd javadoc も参考にする必要があるだろう。
Sftpd のドキュメントだけ見ていても何のことかわからなかった。SshClient が必要。SshClient で ClientSession を生成して、ClientSession 上に SftpClient を生成する。 (ssh 自体そのような構造)
このサンプルは、main に全部書いてしまっているが(申し訳ない)、 公式ドキュメントの Client-side SFTP に、以下のような記述がある。
// The underlying session will also be closed when the client is
try (SftpClient client = createSftpClient(....)) {
... use the SFTP client...
}
SftpClient createSftpClient(...) {
ClientSession session = ...obtain session...
SftpClientFactory factory = ...obtain factory...
SftpClient client = factory.createSftpClient(session);
return client.singleSessionInstance();
}
SftpClient の生成をサブルーチンにする場合、singleSessionInstance() が返す SftpClient を使えば、ClientSession が自動的に開放されるようだ。 さすがに、SshClient はどこかに保持しておく必要があるだろうか。client.start() が必要なことを考えると、SshClient を解放してしまうと予期しない動きになるのだろうか?良くわからなかった。
[PR]