1. Información general
En este tutorial, veremos cómo aprovechar la biblioteca Apache Commons Net para interactuar con un servidor FTP externo.
2. Configuración
Cuando se usan bibliotecas, que se usan para interactuar con sistemas externos, a menudo es una buena idea escribir algunas pruebas de integración adicionales para asegurarnos de que estamos usando la biblioteca correctamente.
Hoy en día, normalmente usamos Docker para poner en marcha esos sistemas para nuestras pruebas de integración. Sin embargo, especialmente cuando se usa en modo pasivo, un servidor FTP no es la aplicación más fácil de ejecutar de forma transparente dentro de un contenedor si queremos hacer uso de asignaciones de puertos dinámicas (que a menudo es necesario para que las pruebas se puedan ejecutar en un servidor CI compartido). ).
Es por eso que usaremos MockFtpServer en su lugar, un servidor FTP falso / stub escrito en Java, que proporciona una API extensa para un uso fácil en las pruebas JUnit:
commons-net commons-net 3.6 org.mockftpserver MockFtpServer 2.7.1 test
Se recomienda utilizar siempre la última versión. Se pueden encontrar aquí y aquí.
3. Soporte FTP en JDK
Sorprendentemente, ya existe soporte básico para FTP en algunas versiones de JDK en forma de sun.net.www.protocol.ftp.FtpURLConnection .
Sin embargo, no deberíamos usar esta clase directamente y en su lugar es posible usar java.net del JDK . Clase de URL como una abstracción.
Este soporte FTP es muy básico, pero aprovechando las API de conveniencia de java.nio.file.Files, podría ser suficiente para casos de uso 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 }
Dado que a este soporte FTP básico ya le faltan características básicas como listados de archivos, usaremos el soporte FTP en la biblioteca Apache Net Commons en los siguientes ejemplos.
4. Conexión
Primero necesitamos conectarnos al servidor FTP. Comencemos creando una clase FtpClient.
Servirá como una API de abstracción para el cliente FTP de 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(); } }
Necesitamos la dirección del servidor y el puerto, así como el nombre de usuario y la contraseña. Después de conectarse, es necesario verificar el código de respuesta para asegurarse de que la conexión se haya realizado correctamente. También agregamos un PrintCommandListener , para imprimir las respuestas que normalmente veríamos cuando nos conectamos a un servidor FTP usando herramientas de línea de comandos para stdout.
Desde nuestras pruebas de integración tendrán algún código repetitivo, como iniciar / detener el MockFtpServer y conexión / desconexión de nuestro cliente, podemos hacer estas cosas en las @Before y @After métodos:
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(); } }
Al establecer el puerto de control del servidor simulado en el valor 0, estamos iniciando el servidor simulado y un puerto aleatorio libre.
Es por eso que tenemos que recuperar el puerto real al crear el FtpClient después de que se haya iniciado el servidor, usando fakeFtpServer.getServerControlPort () .
5. Listado de archivos
El primer caso de uso real será una lista de archivos.
Comencemos con la prueba primero, estilo TDD:
@Test public void givenRemoteFile_whenListingRemoteFiles_thenItIsContainedInList() throws IOException { Collection files = ftpClient.listFiles(""); assertThat(files).contains("foobar.txt"); }
La implementación en sí es igualmente sencilla. Para simplificar un poco la estructura de datos devueltos por el bien de este ejemplo, transformamos la matriz FTPFile devuelta en una lista de cadenas utilizando 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. Descarga
Para descargar un archivo del servidor FTP, estamos definiendo una API.
Aquí definimos el archivo de origen y el destino en el sistema de archivos 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 }
El cliente FTP de Apache Net Commons contiene una API conveniente, que escribirá directamente en un OutputStream definido . Esto significa que podemos usar esto directamente:
void downloadFile(String source, String destination) throws IOException { FileOutputStream out = new FileOutputStream(destination); ftp.retrieveFile(source, out); }
7. Subiendo
MockFtpServer proporciona algunos métodos útiles para acceder al contenido de su sistema de archivos. Podemos usar esta función para escribir una prueba de integración simple para la funcionalidad de carga:
@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(); }
La carga de un archivo funciona de manera muy similar a la API de descargarlo, pero en lugar de usar un OutputStream , necesitamos proporcionar un InputStream en su lugar:
void putFileToPath(File file, String path) throws IOException { ftp.storeFile(path, new FileInputStream(file)); }
8. Conclusión
Hemos visto que usar Java junto con Apache Net Commons nos permite interactuar fácilmente con un servidor FTP externo, tanto para lectura como para escritura.
Como de costumbre, el código completo de este artículo está disponible en nuestro repositorio de GitHub.