Implementazione di un client FTP in Java

1. Panoramica

In questo tutorial, daremo un'occhiata a come sfruttare la libreria Apache Commons Net per interagire con un server FTP esterno.

2. Configurazione

Quando si utilizzano librerie, che vengono utilizzate per interagire con sistemi esterni, è spesso una buona idea scrivere alcuni test di integrazione aggiuntivi, al fine di assicurarsi che stiamo utilizzando la libreria correttamente.

Al giorno d'oggi, normalmente useremmo Docker per avviare questi sistemi per i nostri test di integrazione. Tuttavia, specialmente se utilizzato in modalità passiva, un server FTP non è l'applicazione più semplice da eseguire in modo trasparente all'interno di un contenitore se si desidera utilizzare la mappatura dinamica delle porte (che è spesso necessaria affinché i test possano essere eseguiti su un server CI condiviso ).

Ecco perché useremo invece MockFtpServer, un server FTP Fake / Stub scritto in Java, che fornisce un'API estesa per un facile utilizzo nei test JUnit:

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

Si consiglia di utilizzare sempre l'ultima versione. Questi possono essere trovati qui e qui.

3. Supporto FTP in JDK

Sorprendentemente, esiste già il supporto di base per FTP in alcune versioni JDK sotto forma di sun.net.www.protocol.ftp.FtpURLConnection .

Tuttavia, non dovremmo usare questa classe direttamente ed è invece possibile utilizzare java.net di JDK . Classe URL come astrazione.

Questo supporto FTP è molto semplice, ma sfruttando le pratiche API di java.nio.file.Files, potrebbe essere sufficiente per semplici casi d'uso:

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

Poiché a questo supporto FTP di base mancano già funzionalità di base come l'elenco dei file, utilizzeremo il supporto FTP nella libreria Apache Net Commons nei seguenti esempi.

4. Collegamento

Per prima cosa dobbiamo connetterci al server FTP. Cominciamo creando una classe FtpClient.

Servirà come API di astrazione per l'attuale client FTP di 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(); } }

Abbiamo bisogno dell'indirizzo del server e della porta, così come il nome utente e la password. Dopo la connessione è necessario controllare effettivamente il codice di risposta, per assicurarsi che la connessione abbia avuto successo. Aggiungiamo anche un PrintCommandListener , per stampare le risposte che normalmente vedremmo quando ci connettiamo a un server FTP utilizzando gli strumenti della riga di comando per stdout.

Poiché i nostri test di integrazione avranno un codice boilerplate, come avviare / arrestare MockFtpServer e connettere / disconnettere il nostro client, possiamo fare queste cose nei metodi @Before e @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(); } }

Impostando la porta di controllo del server fittizio sul valore 0, stiamo avviando il server fittizio e una porta casuale libera.

Ecco perché dobbiamo recuperare la porta effettiva quando creiamo FtpClient dopo che il server è stato avviato, usando fakeFtpServer.getServerControlPort () .

5. Elenco dei file

Il primo caso d'uso effettivo sarà elencare i file.

Cominciamo prima con il test, in stile TDD:

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

L'implementazione stessa è altrettanto semplice. Per rendere la struttura dei dati restituiti un po 'più semplice per il bene di questo esempio, trasformiamo l' array FTPFile restituito viene trasformato in un elenco di stringhe utilizzando 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. Download

Per scaricare un file dal server FTP, stiamo definendo un'API.

Qui definiamo il file sorgente e la destinazione sul filesystem locale:

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

Il client FTP di Apache Net Commons contiene una comoda API, che scriverà direttamente su un OutputStream definito . Ciò significa che possiamo usarlo direttamente:

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

7. Caricamento

Il MockFtpServer fornisce alcuni metodi utili per accedere al contenuto del suo filesystem. Possiamo utilizzare questa funzione per scrivere un semplice test di integrazione per la funzionalità di caricamento:

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

Il caricamento di un file funziona in modo API abbastanza simile al download, ma invece di utilizzare un OutputStream , dobbiamo fornire invece un InputStream :

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

8. Conclusione

Abbiamo visto che l'uso di Java insieme ad Apache Net Commons ci consente di interagire facilmente con un server FTP esterno, per l'accesso in lettura e in scrittura.

Come al solito, il codice completo per questo articolo è disponibile nel nostro repository GitHub.