Implementierung eines FTP-Clients in Java

1. Übersicht

In diesem Tutorial sehen wir uns an, wie Sie die Apache Commons Net-Bibliothek für die Interaktion mit einem externen FTP-Server nutzen können.

2. Setup

Bei der Verwendung von Bibliotheken, die für die Interaktion mit externen Systemen verwendet werden, empfiehlt es sich häufig, einige zusätzliche Integrationstests zu schreiben, um sicherzustellen, dass die Bibliothek ordnungsgemäß verwendet wird.

Heutzutage verwenden wir normalerweise Docker, um diese Systeme für unsere Integrationstests hochzufahren. Insbesondere im passiven Modus ist ein FTP-Server jedoch nicht die einfachste Anwendung, die transparent in einem Container ausgeführt werden kann, wenn dynamische Portzuordnungen verwendet werden sollen (was häufig erforderlich ist, damit Tests auf einem gemeinsam genutzten CI-Server ausgeführt werden können ).

Aus diesem Grund verwenden wir stattdessen MockFtpServer, einen in Java geschriebenen Fake / Stub-FTP-Server, der eine umfangreiche API für die einfache Verwendung in JUnit-Tests bietet:

 commons-net commons-net 3.6   org.mockftpserver MockFtpServer 2.7.1 test 

Es wird empfohlen, immer die neueste Version zu verwenden. Diese finden Sie hier und hier.

3. FTP-Unterstützung in JDK

Überraschenderweise gibt es in einigen JDK- Varianten bereits grundlegende Unterstützung für FTP in Form von sun.net.www.protocol.ftp.FtpURLConnection .

Wir sollten diese Klasse jedoch nicht direkt verwenden, sondern es ist stattdessen möglich, java.net des JDK zu verwenden . URL-Klasse als Abstraktion.

Diese FTP-Unterstützung ist sehr einfach , aber die Nutzung der praktischen APIs von java.nio.file.Files kann für einfache Anwendungsfälle ausreichen:

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

Da dieser grundlegenden FTP-Unterstützung bereits grundlegende Funktionen wie Dateilisten fehlen, werden wir in den folgenden Beispielen die FTP-Unterstützung in der Apache Net Commons-Bibliothek verwenden.

4. Anschließen

Wir müssen zuerst eine Verbindung zum FTP-Server herstellen. Beginnen wir mit der Erstellung einer Klasse FtpClient.

Es dient als Abstraktions-API für den eigentlichen Apache Commons Net FTP-Client:

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(); } }

Wir benötigen die Serveradresse und den Port sowie den Benutzernamen und das Passwort. Nach dem Herstellen der Verbindung muss der Antwortcode überprüft werden, um sicherzustellen, dass die Verbindung erfolgreich war. Wir fügen auch einen PrintCommandListener hinzu , um die Antworten zu drucken, die normalerweise beim Herstellen einer Verbindung zu einem FTP-Server mithilfe von Befehlszeilentools zum Standardisieren angezeigt werden .

Da unsere Integrationstests einen Boilerplate-Code enthalten, z. B. das Starten / Stoppen des MockFtpServers und das Verbinden / Trennen unseres Clients, können wir diese Dinge mit den Methoden @Before und @After ausführen :

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(); } }

Indem Sie den Mock-Server-Steuerport auf den Wert 0 setzen, starten wir den Mock-Server und einen freien zufälligen Port.

Aus diesem Grund müssen wir den tatsächlichen Port abrufen, wenn wir den FtpClient nach dem Start des Servers mit fakeFtpServer.getServerControlPort () erstellen .

5. Dateien auflisten

Der erste tatsächliche Anwendungsfall ist das Auflisten von Dateien.

Beginnen wir zuerst mit dem Test im TDD-Stil:

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

Die Implementierung selbst ist ebenso einfach. Um die zurückgegebene Datenstruktur für dieses Beispiel etwas zu vereinfachen, transformieren wir das zurückgegebene FTPFile- Array mithilfe von Java 8- Streams in eine Liste von Strings :

Collection listFiles(String path) throws IOException { FTPFile[] files = ftp.listFiles(path); return Arrays.stream(files) .map(FTPFile::getName) .collect(Collectors.toList()); }

6. Herunterladen

Zum Herunterladen einer Datei vom FTP-Server definieren wir eine API.

Hier definieren wir die Quelldatei und das Ziel im lokalen Dateisystem:

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

Der Apache Net Commons FTP-Client enthält eine praktische API, die direkt in einen definierten OutputStream schreibt. Dies bedeutet, dass wir dies direkt verwenden können:

void downloadFile(String source, String destination) throws IOException { FileOutputStream out = new FileOutputStream(destination); ftp.retrieveFile(source, out); }

7. Hochladen

Der MockFtpServer bietet einige hilfreiche Methoden für den Zugriff auf den Inhalt seines Dateisystems. Mit dieser Funktion können wir einen einfachen Integrationstest für die Upload-Funktionalität schreiben:

@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(); }

Das Hochladen einer Datei funktioniert API-ähnlich wie das Herunterladen, aber anstatt einen OutputStream zu verwenden , müssen wir stattdessen einen InputStream bereitstellen :

void putFileToPath(File file, String path) throws IOException { ftp.storeFile(path, new FileInputStream(file)); }

8. Fazit

Wir haben gesehen, dass die Verwendung von Java zusammen mit Apache Net Commons es uns ermöglicht, auf einfache Weise mit einem externen FTP-Server zu interagieren, um sowohl Lese- als auch Schreibzugriff zu erhalten.

Der vollständige Code für diesen Artikel ist wie üblich in unserem GitHub-Repository verfügbar.