Реализация FTP-клиента на Java

Реализация FTP-клиента на Java

1. обзор

В этом руководстве мы рассмотрим, как использовать библиотекуApache Commons Net для взаимодействия с внешним FTP-сервером.

2. Настроить

При использовании библиотек, которые используются для взаимодействия с внешними системами, часто рекомендуется написать дополнительные интеграционные тесты, чтобы убедиться, что мы используем библиотеку правильно.

В настоящее время мы обычно используем Docker, чтобы развернуть эти системы для наших интеграционных тестов. Однако, особенно при использовании в пассивном режиме, FTP-сервер - не самое простое приложение для прозрачного запуска внутри контейнера, если мы хотим использовать динамические сопоставления портов (что часто необходимо для возможности запуска тестов на общем CI-сервере. ).

Вот почему мы будем использовать вместо этогоMockFtpServer, FTP-сервер Fake / Stub, написанный на Java, который предоставляет обширный API для удобного использования в тестах JUnit:


    commons-net
    commons-net
    3.6


    org.mockftpserver
    MockFtpServer
    2.7.1
    test

Рекомендуется всегда использовать самую последнюю версию. Их можно найтиhere иhere.

3. Поддержка FTP в JDK

Удивительно, но в некоторых вариантах JDK уже есть базовая поддержка FTP в видеsun.net.www.protocol.ftp.FtpURLConnection.

Однако мы не должны использовать этот класс напрямую, вместо этого можно использовать класс JDKjava.net.URL в качестве абстракции.

Эта поддержка FTP очень проста, но с использованием удобных APIjava.nio.file.Files, этого может быть достаточно для простых случаев использования:

@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 уже отсутствуют основные функции, такие как списки файлов, мы будем использовать поддержку FTP в библиотеке Apache Net Commons в следующих примерах.

4. соединительный

Сначала нам нужно подключиться к FTP-серверу. Начнем с создания классаFtpClient.

Он будет служить API абстракции для реального FTP-клиента Apache Commons Net:

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-серверу с помощью инструментов командной строки в stdout.

Поскольку наши интеграционные тесты будут иметь некоторый шаблонный код, такой как запуск / остановка 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, мы запускаем фиктивный сервер и свободный случайный порт.

Вот почему мы должны получить фактический порт при созданииFtpClient после запуска сервера, используяfakeFtpServer.getServerControlPort().

5. Список файлов

Первый фактический вариант использования будет список файлов.

Начнем сначала с теста в стиле TDD:

@Test
public void givenRemoteFile_whenListingRemoteFiles_thenItIsContainedInList() throws IOException {
    Collection files = ftpClient.listFiles("");
    assertThat(files).contains("foobar.txt");
}

Сама реализация одинаково проста. Чтобы упростить возвращаемую структуру данных для этого примера, мы преобразуем возвращаемый массивFTPFile в списокStrings, используя Java 8Streams:

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

FTP-клиент Apache Net Commons содержит удобный API, который будет напрямую писать в определенныйOutputStream.. Это означает, что мы можем использовать его напрямую:

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-сервером как для чтения, так и для записи.

Как обычно, полный код этой статьи доступен в нашихGitHub repository.