1. Overview
1.概述
In this tutorial, we’ll take a look at how to leverage the Apache Commons Net library to interact with an external FTP server.
在本教程中,我们将看看如何利用Apache Commons Net库来与外部FTP服务器进行交互。。
2. Setup
2.设置
When using libraries, that are used to interact with external systems, it’s often a good idea to write some additional integration tests, in order to make sure, we’re using the library correctly.
当使用库时,用于与外部系统交互,通常是一个好主意,编写一些额外的集成测试,以确保我们正确使用该库。
Nowadays, we’d normally use Docker to spin up those systems for our integration tests. However especially when used in passive mode, an FTP server isn’t the easiest application to run transparently inside a container if we want to make use of dynamic port mappings (which is often necessary for tests being able to be run on a shared CI server).
现在,我们通常会使用Docker来为我们的集成测试启动这些系统。然而,特别是在被动模式下使用时,如果我们想利用动态端口映射(这对于测试能够在共享CI服务器上运行通常是必要的),FTP服务器并不是最容易在容器内透明运行的应用程序。
That’s why we’ll use MockFtpServer instead, a Fake/Stub FTP server written in Java, that provides an extensive API for easy use in JUnit tests:
这就是为什么我们要使用MockFtpServer来代替,这是一个用Java编写的Fake/Stub FTP服务器,它提供了一个广泛的API,便于在JUnit测试中使用。
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>org.mockftpserver</groupId>
<artifactId>MockFtpServer</artifactId>
<version>2.7.1</version>
<scope>test</scope>
</dependency>
It’s recommended to always use the latest version. Those can be found here and here.
3. FTP Support in JDK
3.JDK中的FTP支持
Surprisingly, there’s already basic support for FTP in some JDK flavors in the form of sun.net.www.protocol.ftp.FtpURLConnection.
令人惊讶的是,在某些JDK版本中已经有了对FTP的基本支持,其形式为sun.net.www.protocol.ftp.FtpURLConnection。
However, we shouldn’t use this class directly and it’s instead possible to use the JDK’s java.net.URL class as an abstraction.
但是,我们不应该直接使用这个类,而是可以使用JDK的java.net.URL类作为一个抽象的对象。
This FTP support is very basic, but leveraging the convenience APIs of java.nio.file.Files, it could be enough for simple use cases:
这种FTP支持是非常基本的,但利用java.nio.file.Files的便利API,它可能足以满足简单的用例。
@Test
public void givenRemoteFile_whenDownloading_thenItIsOnTheLocalFilesystem() throws IOException {
String ftpUrl = String.format(
"ftp://user:password@localhost:%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
}
Since this basic FTP supports is already missing basic features like file listings, we are going to use FTP support in the Apache Net Commons library in the following examples.
由于这个基本的FTP支持已经缺少了文件列表等基本功能,我们将在下面的例子中使用Apache Net Commons库的FTP支持。
4. Connecting
4.连接
We first need to connect to the FTP server. Let’s start by creating a class FtpClient.
我们首先需要连接到FTP服务器。让我们从创建一个FtpClient.类开始。
It will serve as an abstraction API to the actual Apache Commons Net FTP client:
它将作为实际的Apache Commons Net FTP客户端的一个抽象的API。
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();
}
}
We need the server address and the port, as well as the username and the password. After connecting it’s necessary to actually check the reply code, to be sure connecting was successful. We also add a PrintCommandListener, to print the responses we’d normally see when connecting to an FTP server using command line tools to stdout.
我们需要服务器地址和端口,以及用户名和密码。连接后,有必要实际检查回复代码,以确定连接成功。我们还添加了一个PrintCommandListener,将我们在使用命令行工具连接到FTP服务器时通常会看到的响应打印到stdout。
Since our integration tests will have some boilerplate code, like starting/stopping the MockFtpServer and connecting/disconnecting our client, we can do these things in the @Before and @After methods:
由于我们的集成测试将有一些模板代码,如启动/停止MockFtpServer和连接/断开客户端,我们可以在@Before和@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();
}
}
By setting the mock server control port to the value 0, we’re starting the mock server and a free random port.
通过将模拟服务器的控制端口设置为0,我们就开始了模拟服务器和一个自由的随机端口。
That’s why we have to retrieve the actual port when creating the FtpClient after the server has been started, using fakeFtpServer.getServerControlPort().
这就是为什么我们必须在服务器启动后创建FtpClient时,使用fakeFtpServer.getServerControlPort()来检索实际端口。
5. Listing Files
5.上市文件
The first actual use case will be listing files.
第一个实际用例将是列出文件。
Let’s start with the test first, TDD-style:
让我们先从测试开始,TDD风格。
@Test
public void givenRemoteFile_whenListingRemoteFiles_thenItIsContainedInList() throws IOException {
Collection<String> files = ftpClient.listFiles("");
assertThat(files).contains("foobar.txt");
}
The implementation itself is equally straightforward. To make the returned data structure a bit simpler for the sake of this example, we transform the returned FTPFile array is transformed into a list of Strings using Java 8 Streams:
实现本身也同样简单明了。为了使本例中返回的数据结构更加简单,我们将返回的FTPFile数组转换为一个字符串列表,使用Java 8 Streams:。
Collection<String> listFiles(String path) throws IOException {
FTPFile[] files = ftp.listFiles(path);
return Arrays.stream(files)
.map(FTPFile::getName)
.collect(Collectors.toList());
}
6. Downloading
6.下载
For downloading a file from the FTP server, we’re defining an API.
为了从FTP服务器上下载文件,我们要定义一个API。
Here we define the source file and the destination on the local filesystem:
这里我们定义了本地文件系统中的源文件和目标文件。
@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
}
The Apache Net Commons FTP client contains a convenient API, that will directly write to a defined OutputStream. This means we can use this directly:
Apache Net Commons FTP客户端包含一个方便的API,它将直接写入一个定义的OutputStream.,这意味着我们可以直接使用这个。
void downloadFile(String source, String destination) throws IOException {
FileOutputStream out = new FileOutputStream(destination);
ftp.retrieveFile(source, out);
}
7. Uploading
7.上传
The MockFtpServer provides some helpful methods for accessing the content of its filesystem. We can use this feature to write a simple integration test for the uploading functionality:
MockFtpServer提供了一些有用的方法来访问其文件系统的内容。我们可以利用这个功能为上传功能写一个简单的集成测试。
@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();
}
Uploading a file works API-wise quite similar to downloading it, but instead of using an OutputStream, we need to provide an InputStream instead:
上传文件的API工作原理与下载文件很相似,但我们需要提供一个OutputStream,而不是使用一个InputStream。
void putFileToPath(File file, String path) throws IOException {
ftp.storeFile(path, new FileInputStream(file));
}
8. Conclusion
8.结语
We’ve seen, that using Java together with the Apache Net Commons allows us, to easily interact with an external FTP server, for read as well as write access.
我们已经看到,使用Java和Apache Net Commons可以使我们很容易地与外部FTP服务器进行交互,进行读和写访问。
As usual, the complete code for this article is available in our GitHub repository.
像往常一样,本文的完整代码可在我们的GitHub 仓库中找到。