Implémentation d'un client FTP en Java

1. Vue d'ensemble

Dans ce didacticiel, nous verrons comment tirer parti de la bibliothèque Apache Commons Net pour interagir avec un serveur FTP externe.

2. Configuration

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 vous 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 plutôt MockFtpServer, 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és ici et ici.

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 de sun.net.www.protocol.ftp.FtpURLConnection .

Cependant, nous ne devons pas utiliser cette classe directement et il est à la place possible d'utiliser java.net du JDK . Classe d'URL en tant qu'abstraction.

Ce support FTP est très basique, mais en tirant parti des API pratiques de java.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 dans la bibliothèque Apache Net Commons dans les exemples suivants.

4. Connexion

Nous devons d'abord nous connecter au serveur FTP. Commençons par créer une classe FtpClient.

Il servira d'API d'abstraction pour le client 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(); } }

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 un PrintCommandListener , pour imprimer les réponses que nous verrions normalement lors de la connexion à un serveur FTP en utilisant des outils de ligne de commande pour 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 du FtpClient après le démarrage du serveur, en utilisant fakeFtpServer.getServerControlPort () .

5. Liste des fichiers

Le premier cas d'utilisation réel sera de 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 tout aussi simple. Pour simplifier un peu la structure des données renvoyées dans le cadre de cet exemple, nous transformons le tableau FTPFile renvoyé en une liste de chaînes à l' aide de Java 8 Streams:

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 un OutputStream 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

Le 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 un OutputStream , nous devons fournir un InputStream à 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 notre référentiel GitHub.