1. 概要

このチュートリアルでは、 Apache CommonsNetライブラリを活用して外部FTPサーバーと対話する方法を見ていきます。

2. 設定

外部システムとの対話に使用されるライブラリを使用する場合、ライブラリが正しく使用されていることを確認するために、追加の統合テストを作成することをお勧めします。

今日では、通常、Dockerを使用して、統合テストのためにこれらのシステムを起動します。 ただし、特にパッシブモードで使用する場合、動的ポートマッピング(共有CIサーバーでテストを実行できるようにするために必要になることが多い)を利用する場合、FTPサーバーはコンテナー内で透過的に実行するのが最も簡単なアプリケーションではありません。 )。

そのため、代わりに MockFtpServer を使用します。これは、Javaで記述された偽の/スタブFTPサーバーであり、JUnitテストで簡単に使用できる広範なAPIを提供します。

<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>

常に最新バージョンを使用することをお勧めします。 それらはここここで見つけることができます。

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:password@localhost:%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サポートには、ファイルリストなどの基本的な機能がすでに欠けているため、次の例では、ApacheNetCommonsライブラリでFTPサポートを使用します。

4. 接続する

まず、FTPサーバーに接続する必要があります。 クラスFtpClient。を作成することから始めましょう。

これは、実際のApache CommonsNetFTPクライアントへの抽象化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();
    }
}

サーバーアドレスとポート、およびユーザー名とパスワードが必要です。 接続後、実際に応答コードをチェックして、接続が成功したことを確認する必要があります。 また、 PrintCommandListener を追加して、コマンドラインツールを使用してFTPサーバーに接続したときに通常表示される応答を出力します。

統合テストには、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配列をJava8 Streams:[を使用してStringsのリストに変換します。 X201X]

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をApacheNetCommonsと一緒に使用すると、読み取りアクセスと書き込みアクセスのために、外部FTPサーバーと簡単に対話できることがわかりました。

いつものように、この記事の完全なコードは、GitHubリポジトリで入手できます。