FTP-Operationen

Mit der Bibliothek Apache Commons Net lassen sich auf einfache Weise die Funktionen eines FTP-Clients realisieren.

Um FTP-Funktionen in Java zu realisieren, bietet es sich der Einfachheit halber an, auf die Bibliothek Apache Commons Net zurückzugreifen. Sie kapselt eine Vielzahl an FTP-Funktionen in Methoden. Vor der Verwendung der Bibliothek muss diese in den Classpath eingebunden werden.

Da beim Aufbau und der Nutzung einer FTP-Verbindung eine Reihe potentieller Fehler auftreten kann, müssen die meisten Routinen in try-catch-Blöcke gebettet werden. Hieraus kann, durch das Abfangen der Fehler, eine große Menge redundanter Code resultieren, der dadurch umgangen werden soll, dass die einzelnen Demonstrationsbeispiele in Form von Methoden direkt in main() eingebunden werden.
Diese Beispielmethoden werden dort innerhalb eines try-catch-Blockes aufgerufen und reichen (durch die throws-Erweiterung der Methodensignatur) die jeweilige Ausnahmebehandlung nach 'oben' zur aufrufenden main()-Methode weiter.
Dort werden auch die Variablen der Verbindungsdaten deklariert, die dann als Parameter an die jeweilige Methode weitergegeben werden.

public class FTPUpAndDownload {

    public static void main(String[] args) {
        String host = "javabeginners.de";
        String user = "Benutzer";
        String pw = "Passwort";
        try {
            // hier Methodenaufruf
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FTP-Status auslesen

Für die Nutzung grundlegender Client-seitiger Funktionen stellt die Klasse org.apache.commons.net.ftp.FTPClient die zentrale dar. Von SocketClient wird die Methode connect() geerbt, die durch Übergabe des Servernamens die Verbindung herstellt. Die Methode ist mehrfach überladen und kann wahlweise mit oder ohne Portangabe als zweitem Parameter und/oder anstatt eines Host-Strings auch mit einem Objekt vom Typ InetAdress aufgerufen werden.
Nach dem Versuch des Verbindungsaufbaus sendet der FTP-Server einen entsprechenden Code und ggf. eine Textmitteilung. Beides kann durch die Methoden getReplyCode() bzw. getReplyString() abgefragt werden. Beginnt der dreistellige Code mit einer 2, ist die Verbindung erfolgreich zustande gekommen. Dies kann durch die statische Methode isPositiveCompletion() der Klasse ReplyCode abgefragt werden [6].

private static void getFtpStatus(String host) throws IOException {
    FTPClient ftp = new FTPClient();
    ftp.connect(host);
    String s = ftp.getReplyString();
    int code = ftp.getReplyCode();
    if (FTPReply.isPositiveCompletion(code)) {
        String stat = ftp.getStatus();
        System.out.println(s);
        System.out.println("Code: " + code);
        System.out.println(stat);
    } else {
        System.out.println("Verbindung nicht erfolgreich, Replycode: " + code);
    }
    ftp.disconnect();
}

Die Ausgabe sieht wie folgt aus:

220---------- Welcome to Pure-FTPd [privsep] [TLS] ----------
220-You are user number 1 of 50 allowed.
220-Local time is now 11:52. Server port: 21.
220-This is a private system - No anonymous login
220-IPv6 connections are also welcome on this server.
220 You will be disconnected after 15 minutes of inactivity.

Code: 220
null

Hier zeigt sich, dass der Status des Servers offensichtlich nicht auslesbar ist. Dies ändert sich, nachdem sich ein authorisierter User angemeldet hat:

private static void getFtpStatus(String host, String user, String pw) throws IOException {
    FTPClient ftp = new FTPClient();
    ftp.connect(host);
    ftp.login(user, pw);
    String s = ftp.getReplyString();
    int code = ftp.getReplyCode();
    if (FTPReply.isPositiveCompletion(code)) {
        String stat = ftp.getStatus();
        System.out.println(s);
        System.out.println("Code: " + code);
        System.out.println(stat);
    } else {
        System.out.println("Verbindung nicht erfolgreich, Replycode: " + code);
    }
    ftp.logout();
    ftp.disconnect();
}

Die Ausgabe ändert sich wie folgt:

230 OK. Current restricted directory is /

Code: 230
211 https://www.pureftpd.org/

Navigation in der Verzeichnishierarchie

Für die Navigation in der entfernten Verzeichnishierarchie werden im Wesentlichen die Methoden changeWorkingDirectory(), changeToParentDirectory() und printWorkingDirectory() der Klasse FTPClient verwendet. Im Beispiel wird dies anhand der Methode listFiles() demonstriert.
In ihr loggt sich der FTP-Benutzer ein und wechselt in das als Parameter übergebene Verzeichnis. Nach Auflistung und Ausgabe der dort vorhandenen Datei- und Verzeichnis-Pfade wird erst in das Elternverzeichnis und dann wiederum in ein weiter unten gelegenens Verzeichnis gewechselt.

Ohne gesonderte Pfadangabe befindet sich der angemeldete FTP-User üblicherweise im Root-Verzeichnis des ihm zugänglichen Bereiches des FTP-Servers. Um das Arbeitsverzeichnis zu wechseln, wird der absolute Zielpfad1 als Parameter an changeWorkingDirectory() übergeben [8]. Die dort gelisteten Verzeichnisse und Dateien werden dann durch die Methode listNames() ermittelt. Sie liefert die zugehörigen Pfade. Deren Ausgabe findet im Beispiel durch die Hilfsmethode printFileNames() statt [10, 27].
In das übergeordnete Verzeichnis gelangt man durch changeToParentDirectory() [11], und um den Pfad des aktuellen Arbeitsverzeichnisses zu ermitteln, kann printWorkingDirectory() verwendet werden [9, 12].

private static void listFiles(String host, String user, String pw, String remotePath) throws IOException {
    String[] fileNames = null;
    FTPClient ftp = null;
    ftp = new FTPClient();
    ftp.connect(host);
    if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
        if (ftp.login(user, pw)) {
            ftp.changeWorkingDirectory(remotePath);
            fileNames = ftp.listNames(ftp.printWorkingDirectory());
            printFileNames(fileNames);
            if (ftp.changeToParentDirectory()) {
                fileNames = ftp.listNames(ftp.printWorkingDirectory());
                printFileNames(fileNames);
                if (ftp.changeWorkingDirectory(ftp.printWorkingDirectory() + "/Swing-Elemente/Tabellen")) {
                    fileNames = ftp.listNames(ftp.printWorkingDirectory());
                    printFileNames(fileNames);
                }
            }
            if (ftp.logout()) {
                 System.out.println("Logout erfolgreich");
            }
        }
    }
    ftp.disconnect();
}

private static void printFileNames(String[] fileNames) {
    if (fileNames == null)
        return;
    for (String s : fileNames) {
        System.out.println(s);
    }
    System.out.println();
}

Dateigröße und -typ ermitteln

Im folgenden Beispiel wird durch die o.a. Methode FTPClient#listFiles() ein Array des Typs FTPFile erzeugt [8]. Es enthält die Datei- und Verzeichnisobjekte des durch den Parameter remotePath übergebenen Pfades. In einer Schleife wird das Array durchlaufen und für jedes Objekt dessenn Typ abgefragt [17]. FTPFile definiert eine Reihe an int-Konstanten, die nachfolgend innerhalb einer switch-case-Verzweigung verwendet werden, um bei den ermittelten Typen zwischen Dateien, Verzeichnissen und Symbolischen Links zu differenzieren.
Abschließend erfolgt die Ausgabe des Namens unter Angabe des Typs und der Größe in byte auf die Konsole.

private static void listFileSizes(String host, String user, String pw, String remotePath) throws IOException {
    FTPFile[] files = null;
    FTPClient ftp = null;
    ftp = new FTPClient();
    ftp.connect(host);
    if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
        if (ftp.login(user, pw)) {
            files = ftp.listFiles(remotePath);
        }
    }
    ftp.disconnect();
    if (files == null)
        return;
    int type;
    String s;
    for (FTPFile f : files) {
        type = f.getType();
        switch (type) {
        case FTPFile.FILE_TYPE:
            s = "(Datei)";
            break;
        case FTPFile.DIRECTORY_TYPE:
            s = "(Verzeichnis)";
            break;
        case FTPFile.SYMBOLIC_LINK_TYPE:
            s = "(Symbolischer Link)";
            break;
        default:
            s = "(Unbekannter Typ)";
        }
        System.out.println(f.getName() + " " + s + ": " + f.getSize() + " bytes");
    }
}

Download

Um einen Download vom FTP-Server durchzuführen, werden der Methode loadDown() neben dem Hostnamen, dem Nutzernamen und seinem Passwort noch jeweils ein Pfad zur lokalen und zur entfernten Datei übergeben. Beide Dateien müssen existieren. Auf die Gültigkeitsprüfung wird hier jedoch aus Gründen der Übersichtlichkeit verzichtet.
Mittels Übergabe des lokalen Dateinamens an den Konstruktor wird ein FileOutputStream erzeugt [9]. Dieser wird gemeinsam mit dem entfernten Pfad der Methode retrieveFile() als Parameter übergeben [10]. Die Methode gibt nach erfolgreicher Übertragung true zurück, sodass die Existenz der heruntergeladenen Datei nach Erzeugung eines File-Objektes überprüft werden kann [12].

private static void loadDown(String host, String user, String pw, String remoteFile, String localFile)
        throws IOException {
    FTPClient ftp = null;
    ftp = new FTPClient();
    ftp.connect(host);
    if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
        if (ftp.login(user, pw)) {
            System.out.println("Login erfolgreich");
            FileOutputStream out = new FileOutputStream(localFile);
            if (ftp.retrieveFile(remoteFile, out)) {
                File f = new File(localFile);
                if (f.exists() && f.length() > 0) {
                    System.out.println("Download erfolgreich: " + f.getName() + ", " + f.length() + " bytes");
                }
            }
            if (ftp.logout()) {
                System.out.println("Logout erfolgreich");
            }
        }
    }
}

Upload

Der Upload auf einen FTP-Server ähnelt hinsichtlich seiner Durchführung stark dem Download. Statt eines FileOutputStream wird hier jedoch ein FileInputStream gebildet, der anschließend gemeinsam mit dem entfernten Dateipfad an die Methode storeFile() des FTPClient-Objektes übergeben wird.

private static void loadUp(String host, String user, String pw, String remoteFile, String localFile)
            throws IOException {
    FTPClient ftp = null;
    ftp = new FTPClient();
    ftp.connect(host);
    if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
        if (ftp.login(user, pw)) {
            InputStream in = new FileInputStream(localFile);
            if (ftp.storeFile(remoteFile, in)) {
                System.out.println("Upload erfolgreich: " + localFile + " -> " + remoteFile);
            }
            if (ftp.logout()) {
                System.out.println("Logout erfolgreich");
            }
        }
    }
}

1) Hier ist der absolute Pfad des FTP-Servers gemeint: Liegen die virtuellen Hosts auf einem Server z.B. unter /var/www/ und dort das Verzeichnis javabeginners als Host, so ist dies oft auch das Wurzelverzeichnis des FTP-Servers. Befindet sich dort das Verzeichnis Swing-Elemente im Verzeichnis Swing, so lautet der absolute Pfad /Swing/Swing-Elemente.

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.