Triển khai FTP-Client trong Java

1. Khái quát chung

Trong hướng dẫn này, chúng ta sẽ xem xét cách tận dụng thư viện Apache Commons Net để tương tác với máy chủ FTP bên ngoài.

2. Thiết lập

Khi sử dụng thư viện, được sử dụng để tương tác với các hệ thống bên ngoài, bạn thường nên viết một số thử nghiệm tích hợp bổ sung, để đảm bảo rằng chúng ta đang sử dụng thư viện một cách chính xác.

Ngày nay, chúng tôi thường sử dụng Docker để tạo ra các hệ thống đó cho các bài kiểm tra tích hợp của chúng tôi. Tuy nhiên, đặc biệt là khi được sử dụng ở chế độ thụ động, máy chủ FTP không phải là ứng dụng dễ chạy nhất trong suốt bên trong vùng chứa nếu chúng ta muốn sử dụng ánh xạ cổng động (điều này thường cần thiết cho các bài kiểm tra có thể chạy trên máy chủ CI được chia sẻ ).

Đó là lý do tại sao chúng tôi sẽ sử dụng MockFtpServer, một máy chủ FTP Fake / Stub được viết bằng Java, cung cấp một API mở rộng để dễ dàng sử dụng trong các bài kiểm tra JUnit:

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

Bạn nên luôn sử dụng phiên bản mới nhất. Những người có thể được tìm thấy ở đây và ở đây.

3. Hỗ trợ FTP trong JDK

Đáng ngạc nhiên là đã có hỗ trợ cơ bản cho FTP trong một số phiên bản JDK dưới dạng sun.net.www.protocol.ftp.FtpURLConnection .

Tuy nhiên, chúng ta không nên sử dụng trực tiếp lớp này và thay vào đó, có thể sử dụng java.net của JDK . Lớp URL dưới dạng trừu tượng.

Hỗ trợ FTP này rất cơ bản, nhưng tận dụng các API tiện lợi của java.nio.file.Files, nó có thể đủ cho các trường hợp sử dụng đơn giản:

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

Vì hỗ trợ FTP cơ bản này đã thiếu các tính năng cơ bản như danh sách tệp, chúng tôi sẽ sử dụng hỗ trợ FTP trong thư viện Apache Net Commons trong các ví dụ sau.

4. Kết nối

Trước tiên, chúng ta cần kết nối với máy chủ FTP. Hãy bắt đầu bằng cách tạo một lớp FtpClient.

Nó sẽ đóng vai trò là một API trừu tượng cho ứng dụng khách Apache Commons Net FTP thực tế:

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

Chúng tôi cần địa chỉ máy chủ và cổng, cũng như tên người dùng và mật khẩu. Sau khi kết nối, bạn cần thực sự kiểm tra mã trả lời để đảm bảo kết nối thành công. Chúng tôi cũng thêm một PrintCommandListener , để in các phản hồi mà chúng tôi thường thấy khi kết nối với máy chủ FTP bằng cách sử dụng các công cụ dòng lệnh để stdout.

Kể từ khi thử nghiệm hội nhập của chúng tôi sẽ có một số mã soạn sẵn, như bắt đầu / dừng MockFtpServer và kết nối / ngắt kết nối khách hàng của chúng tôi, chúng tôi có thể làm những điều này trong @Before@After phương pháp:

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

Bằng cách đặt cổng điều khiển máy chủ giả thành giá trị 0, chúng tôi đang khởi động máy chủ giả và một cổng ngẫu nhiên miễn phí.

Đó là lý do tại sao chúng ta phải truy xuất cổng thực khi tạo FtpClient sau khi máy chủ đã được khởi động, bằng cách sử dụng fakeFtpServer.getServerControlPort () .

5. Tệp danh sách

Trường hợp sử dụng thực tế đầu tiên sẽ là liệt kê các tệp.

Hãy bắt đầu với bài kiểm tra đầu tiên, kiểu TDD:

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

Bản thân việc thực hiện cũng đơn giản như nhau. Để làm cho cấu trúc dữ liệu trả về đơn giản hơn một chút vì lợi ích của ví dụ này, chúng tôi chuyển đổi mảng FTPFile trả về được chuyển thành danh sách các Chuỗi bằng cách sử dụng 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. Đang tải xuống

Để tải xuống tệp từ máy chủ FTP, chúng tôi đang xác định một API.

Ở đây chúng tôi xác định tệp nguồn và đích trên hệ thống tệp cục bộ:

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

Máy khách Apache Net Commons FTP chứa một API tiện lợi, API này sẽ trực tiếp ghi vào một Dòng đầu ra đã xác định . Điều này có nghĩa là chúng tôi có thể sử dụng trực tiếp:

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

7. Tải lên

MockFtpServer cung cấp một số phương pháp hữu ích để truy cập nội dung của hệ thống tệp của nó. Chúng tôi có thể sử dụng tính năng này để viết một bài kiểm tra tích hợp đơn giản cho chức năng tải lên:

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

Tải lên tệp hoạt động theo API khá giống với tải xuống, nhưng thay vì sử dụng OutputStream , chúng tôi cần cung cấp InputStream thay thế:

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

8. Kết luận

Chúng tôi đã thấy rằng việc sử dụng Java cùng với Apache Net Commons cho phép chúng tôi dễ dàng tương tác với một máy chủ FTP bên ngoài để truy cập đọc cũng như ghi.

Như thường lệ, mã hoàn chỉnh cho bài viết này có sẵn trong kho lưu trữ GitHub của chúng tôi.