Implementierung eines FTP-Clients in Java

Implementierung eines FTP-Clients in Java

1. Überblick

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

2. Konfiguration

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 stattdessenMockFtpServer, 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 könnenhere undhere gefunden werden.

3. FTP-Unterstützung in JDK

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

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

Diese FTP-Unterstützung ist sehr einfach, aber die Nutzung der Convenience-APIs vonjava.nio.file.Files, könnte 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 KlasseFtpClient.

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

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 außerdem einPrintCommandListener 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@Afterausfü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 wirFtpClient nach dem Start des Servers mitfakeFtpServer.getServerControlPort() erstellen.

5. Dateien auflisten

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

Beginnen wir zunächst 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 unkompliziert. Um die zurückgegebene Datenstruktur für dieses Beispiel etwas zu vereinfachen, transformieren wir das zurückgegebeneFTPFile-Array mit Java 8Streams: in eine Liste vonStrings

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

6. wird heruntergeladen

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

Hier definieren wir die Quelldatei und das Ziel auf dem 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 ein definiertesOutputStream.chreibt. 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 anstattOutputStream zu verwenden, müssen wir stattdessenInputStream 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 die einfache Interaktion mit einem externen FTP-Server für Lese- und Schreibzugriff ermöglicht.

Wie üblich ist der vollständige Code für diesen Artikel in unserenGitHub repository verfügbar.