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]