JavaでFTPクライアントを実装する
1.概要
-
このチュートリアルでは、https://commons.apache.org/proper/commons-net/[Apache Commons Net]ライブラリを利用して外部のFTPサーバーとやり取りする方法を見ていきます。**
2.セットアップ
外部システムとのやり取りに使用されるライブラリを使用する場合は、ライブラリを正しく使用していることを確認するために、追加の統合テストを作成することをお勧めします。
今日では、統合テストのためにこれらのシステムをスピンアップするために通常Dockerを使用します。ただし、特にパッシブモードで使用する場合、動的ポートマッピングを使用したい場合はFTPサーバーをコンテナ内で透過的に実行するのが最も簡単なアプリケーションではありません(共有CIサーバーで実行できるテストにはしばしば必要です) )
そのため、JUnitテストで簡単に使用するための広範なAPIを提供する、http://mockftpserver.sourceforge.net/index.html[MockFtpServer](Javaで作成されたFake/Stub FTPサーバー)を代わりに使用します。
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>org.mockftpserver</groupId>
<artifactId>MockFtpServer</artifactId>
<version>2.7.1</version>
<scope>test</scope>
</dependency>
常に最新版を使用することをお勧めします。それらはhttps://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22commons-net%22[here]およびhttps://search.maven.org/classic/#searchを見つけることができます。 %7Cga%7C1%7Ca%3A%22MockFtpServer%22[ここ]。
3. JDKでのFTPサポート
-
驚いたことに、
sun.net.www.protocol.ftp.FtpURLConnection
の形式で、いくつかのJDKフレーバーですでにFTPの基本的なサポートがあります。 -
ただし、このクラスを直接使用するのではなく、代わりにJDKの
_java.net .
_
URLクラスを抽象化として使用することも可能です。**
このFTPサポートは非常に基本的なものですが、
java.nio.file.Filesの便利なAPIを利用して
簡単な使用例には十分かもしれません:
@Test
public void givenRemoteFile__whenDownloading__thenItIsOnTheLocalFilesystem() throws IOException {
String ftpUrl = String.format(
"ftp://user:[email protected]:%d/foobar.txt", fakeFtpServer.getServerControlPort());
URLConnection urlConnection = new URL(ftpUrl).openConnection();
InputStream inputStream = urlConnection.getInputStream();
Files.copy(inputStream, new File("downloaded__buz.txt").toPath());
inputStream.close();
assertThat(new File("downloaded__buz.txt")).exists();
new File("downloaded__buz.txt").delete();//cleanup
}
この基本的なFTPサポートにはファイルリストのような基本的な機能がすでに欠けているので、次の例ではApache Net CommonsライブラリのFTPサポートを使用します。
4.接続する
まずFTPサーバーに接続する必要があります。まず__FtpClientクラスを作成しましょう。
これは、実際のApache Commons Net FTPクライアントに対する抽象化APIとして機能します。
class FtpClient {
private String server;
private int port;
private String user;
private String password;
private FTPClient ftp;
//constructor
void open() throws IOException {
ftp = new FTPClient();
ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
ftp.connect(server, port);
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
throw new IOException("Exception in connecting to FTP Server");
}
ftp.login(user, password);
}
void close() throws IOException {
ftp.disconnect();
}
}
サーバーアドレスとポート、そしてユーザー名とパスワードが必要です。接続後、接続が成功したことを確認するために、返信コードを実際に確認する必要があります。また、標準出力にコマンドラインツールを使用してFTPサーバーに接続するときに通常表示される応答を印刷するために、
PrintCommandListener
を追加します。
MockFtpServerの起動/停止やクライアントの接続/切断など、統合テストには定型コードが含まれているため、
@ Before
および
@ After
メソッドで次のことを実行できます。
public class FtpClientIntegrationTest {
private FakeFtpServer fakeFtpServer;
private FtpClient ftpClient;
@Before
public void setup() throws IOException {
fakeFtpServer = new FakeFtpServer();
fakeFtpServer.addUserAccount(new UserAccount("user", "password", "/data"));
FileSystem fileSystem = new UnixFakeFileSystem();
fileSystem.add(new DirectoryEntry("/data"));
fileSystem.add(new FileEntry("/data/foobar.txt", "abcdef 1234567890"));
fakeFtpServer.setFileSystem(fileSystem);
fakeFtpServer.setServerControlPort(0);
fakeFtpServer.start();
ftpClient = new FtpClient("localhost", fakeFtpServer.getServerControlPort(), "user", "password");
ftpClient.open();
}
@After
public void teardown() throws IOException {
ftpClient.close();
fakeFtpServer.stop();
}
}
モックサーバーのコントロールポートを値0に設定することで、モックサーバーと無料のランダムポートを起動します。
そのため、サーバーの起動後に
fakeFtpServer.getServerControlPort()
を使用して
FtpClient
を作成するときに実際のポートを取得する必要があります。
5.ファイルの一覧表示
最初の実際の使用例はファイルのリストです。
まずTDDスタイルのテストから始めましょう。
@Test
public void givenRemoteFile__whenListingRemoteFiles__thenItIsContainedInList() throws IOException {
Collection<String> files = ftpClient.listFiles("");
assertThat(files).contains("foobar.txt");
}
実装自体も同様に簡単です。この例のために返されるデータ構造をもう少し簡単にするために、返される
FTPFile
配列をJava 8
Streamsを使って
Strings__のリストに変換します。
Collection<String> listFiles(String path) throws IOException {
FTPFile[]files = ftp.listFiles(path);
return Arrays.stream(files)
.map(FTPFile::getName)
.collect(Collectors.toList());
}
6.ダウンロード
FTPサーバーからファイルをダウンロードするために、APIを定義しています。
ここではローカルファイルシステム上でソースファイルと宛先を定義します。
@Test
public void givenRemoteFile__whenDownloading__thenItIsOnTheLocalFilesystem() throws IOException {
ftpClient.downloadFile("/buz.txt", "downloaded__buz.txt");
assertThat(new File("downloaded__buz.txt")).exists();
new File("downloaded__buz.txt").delete();//cleanup
}
Apache Net CommonsのFTPクライアントには、定義済みの__OutputStreamに直接書き込む便利なAPIが含まれています。これは、これを直接使用できることを意味します。
void downloadFile(String source, String destination) throws IOException {
FileOutputStream out = new FileOutputStream(destination);
ftp.retrieveFile(source, out);
}
7.アップロード
MockFtpServerはそのファイルシステムの内容にアクセスするための便利な方法をいくつか提供します。この機能を使って、アップロード機能のための簡単な統合テストを書くことができます。
@Test
public void givenLocalFile__whenUploadingIt__thenItExistsOnRemoteLocation()
throws URISyntaxException, IOException {
File file = new File(getClass().getClassLoader().getResource("baz.txt").toURI());
ftpClient.putFileToPath(file, "/buz.txt");
assertThat(fakeFtpServer.getFileSystem().exists("/buz.txt")).isTrue();
}
ファイルをアップロードするのはAPI的にはダウンロードとほぼ同じですが、
OutputStream
を使用するのではなく、代わりに
InputStream
を提供する必要があります。
void putFileToPath(File file, String path) throws IOException {
ftp.storeFile(path, new FileInputStream(file));
}
8.まとめ
JavaをApache Net Commonsと一緒に使用すると、読み取りおよび書き込みアクセスのために、外部のFTPサーバーと簡単に対話できることがわかりました。
いつものように、この記事の完全なコードはhttps://github.com/eugenp/tutorials/tree/master/libraries[GitHub repository]にあります。