Apache Mina Sshd を使った sftp クライアント

あるプロジェクトで 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]

1969年生まれ。大学卒業後から15年以上にわたり、通信、カードリーダ、セキュリティ業界においてソフトウェア開発に従事。その後、2012年5月に当社を設立。電力、交通、車載向けの組み込み系システム、旅行業界向けの WEB システム開発、音声合成システム、消防向けのシステム開発等に参画。
低コストかつシンプルで安定稼働するシステムの実現を目指し、アーキテクチャ設計に取り組んでいます。
会社情報と代表者守屋のプロフィール詳細