Перейти к основному содержимому

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

· 5 мин. чтения

1. Обзор

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

2. Настройка

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

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

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

<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. Поддержка FTP в JDK

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

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

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

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

Поскольку наши интеграционные тесты будут иметь некоторый шаблонный код, например запуск/остановку 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<String> files = ftpClient.listFiles("");
assertThat(files).contains("foobar.txt");
}

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

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
}

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 .