Implémentation d’un client FTP en Java

Implémentation d'un client FTP en Java

1. Vue d'ensemble

Dans ce didacticiel, nous allons voir comment tirer parti de la bibliothèqueApache Commons Net pour interagir avec un serveur FTP externe.

2. Installer

Lorsque vous utilisez des bibliothèques, utilisées pour interagir avec des systèmes externes, il est souvent judicieux d’écrire des tests d’intégration supplémentaires, afin de nous assurer que nous utilisons correctement la bibliothèque.

De nos jours, nous utilisons normalement Docker pour faire tourner ces systèmes pour nos tests d'intégration. Cependant, surtout lorsqu'il est utilisé en mode passif, un serveur FTP n'est pas l'application la plus facile à exécuter de manière transparente à l'intérieur d'un conteneur si nous voulons utiliser des mappages de ports dynamiques (ce qui est souvent nécessaire pour que les tests puissent être exécutés sur un serveur CI partagé ).

C'est pourquoi nous utiliserons à la placeMockFtpServer, un serveur FTP Fake / Stub écrit en Java, qui fournit une API complète pour une utilisation facile dans les tests JUnit:


    commons-net
    commons-net
    3.6


    org.mockftpserver
    MockFtpServer
    2.7.1
    test

Il est recommandé de toujours utiliser la dernière version. Ceux-ci peuvent être trouvéshere ethere.

3. Prise en charge FTP dans JDK

Étonnamment, il existe déjà un support de base pour FTP dans certaines versions de JDK sous la forme desun.net.www.protocol.ftp.FtpURLConnection.

Cependant, nous ne devons pas utiliser cette classe directement et il est à la place possible d’utiliser la classejava.net.URL du JDK comme abstraction.

Ce support FTP est très basique, mais en exploitant les API de commodité dejava.nio.file.Files,, cela pourrait suffire pour des cas d'utilisation simples:

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

Comme ce support FTP de base manque déjà de fonctionnalités de base telles que les listes de fichiers, nous allons utiliser le support FTP de la bibliothèque Apache Net Commons dans les exemples suivants.

4. De liaison

Nous devons d’abord nous connecter au serveur FTP. Commençons par créer une classeFtpClient.

Il servira d'API d'abstraction au client FTP actuel d'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();
    }
}

Nous avons besoin de l'adresse du serveur et du port, ainsi que du nom d'utilisateur et du mot de passe. Après la connexion, il est nécessaire de vérifier le code de réponse pour être sûr que la connexion a réussi. Nous ajoutons également unPrintCommandListener, pour imprimer les réponses que nous voyons normalement lors de la connexion à un serveur FTP en utilisant les outils de ligne de commande vers stdout.

Étant donné que nos tests d'intégration auront un code standard, comme démarrer / arrêter le MockFtpServer et connecter / déconnecter notre client, nous pouvons faire ces choses dans les méthodes@Before et@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();
    }
}

En définissant le port de contrôle du serveur fictif sur la valeur 0, nous démarrons le serveur fictif et un port aléatoire libre.

C’est pourquoi nous devons récupérer le port réel lors de la création desFtpClient après le démarrage du serveur, en utilisantfakeFtpServer.getServerControlPort().

5. Liste des fichiers

Le premier cas d'utilisation consistera à lister les fichiers.

Commençons par le test, de style TDD:

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

La mise en œuvre elle-même est également simple. Pour simplifier un peu la structure des données renvoyées dans le cadre de cet exemple, nous transformons le tableauFTPFile renvoyé en une liste deStrings en utilisant Java 8Streams:

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

6. Téléchargement

Pour télécharger un fichier depuis le serveur FTP, nous définissons une API.

Ici, nous définissons le fichier source et la destination sur le système de fichiers local:

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

Le client FTP Apache Net Commons contient une API pratique, qui écrira directement dans unOutputStream. défini.Cela signifie que nous pouvons l'utiliser directement:

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

7. Téléchargement

MockFtpServer fournit des méthodes utiles pour accéder au contenu de son système de fichiers. Nous pouvons utiliser cette fonctionnalité pour écrire un test d'intégration simple pour la fonctionnalité de téléchargement:

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

Le téléchargement d'un fichier fonctionne de manière API assez similaire au téléchargement, mais au lieu d'utiliser unOutputStream, nous devons fournir unInputStream à la place:

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

8. Conclusion

Nous avons vu que l’utilisation de Java avec Apache Net Commons nous permet d’interagir facilement avec un serveur FTP externe, aussi bien en lecture qu’en écriture.

Comme d'habitude, le code complet de cet article est disponible dans nosGitHub repository.