首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >将文件上载到测试容器FTP服务器失败,连接后拒绝连接

将文件上载到测试容器FTP服务器失败,连接后拒绝连接
EN

Stack Overflow用户
提问于 2021-12-08 12:02:22
回答 2查看 1.3K关注 0票数 3

我正在使用FTPClient来对付使用Testcontainers的FTP服务器。

这里有一个可复制的代码示例:

代码语言:javascript
运行
复制
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers
class FtpUtilsTest {

    private static final int PORT = 21;
    private static final String USER = "user";
    private static final String PASSWORD = "password";
    private static final int FTP_TIMEOUT_IN_MILLISECONDS = 1000 * 60;

    private static final GenericContainer ftp = new GenericContainer(
        new ImageFromDockerfile()
            .withDockerfileFromBuilder(builder ->
                builder
                    .from("delfer/alpine-ftp-server:latest")
                    .build()
            )
    )
        .withExposedPorts(PORT)
        .withEnv("USERS", USER + "|" + PASSWORD);


    @BeforeAll
    public static void staticSetup() throws IOException {
        ftp.start();
    }

    @AfterAll
    static void afterAll() {
        ftp.stop();
    }

    @Test
    void test() throws IOException {
        FTPClient ftpClient = new FTPClient();
        ftpClient.setDataTimeout(FTP_TIMEOUT_IN_MILLISECONDS);
        ftpClient.setConnectTimeout(FTP_TIMEOUT_IN_MILLISECONDS);
        ftpClient.setDefaultTimeout(FTP_TIMEOUT_IN_MILLISECONDS);

        // Log
        ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));

        // Connect
        try {
            ftpClient.connect("localhost", ftp.getMappedPort(PORT));
            ftpClient.setSoTimeout(FTP_TIMEOUT_IN_MILLISECONDS);

            int reply = ftpClient.getReplyCode();
            if (!FTPReply.isPositiveCompletion(reply)) {
                ftpClient.disconnect();
                throw new AssertionError();
            }

            // Login
            boolean loginSuccess = ftpClient.login(USER, PASSWORD);
            if (!loginSuccess) {
                throw new AssertionError();
            }

            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            ftpClient.enterLocalPassiveMode();

        } catch (IOException e) {
            throw new AssertionError(e);
        }

        String remoteFile = "fileonftp";
        try (InputStream targetStream = new ByteArrayInputStream("Hello FTP".getBytes())) {
            assertThat(ftpClient.isConnected()).isTrue();
            ftpClient.storeFile(remoteFile, targetStream);
        }
    }
}

这些指纹:

代码语言:javascript
运行
复制
220 Welcome Alpine ftp server https://hub.docker.com/r/delfer/alpine-ftp-server/
USER *******
331 Please specify the password.
PASS *******
230 Login successful.
TYPE I
200 Switching to Binary mode.
PASV
227 Entering Passive Mode (172,17,0,3,82,15).
[Replacing PASV mode reply address 172.17.0.3 with 127.0.0.1]

然后在以下方面失败:

代码语言:javascript
运行
复制
Connection refused (Connection refused)
java.net.ConnectException: Connection refused (Connection refused)
    ...
    at java.base/java.net.Socket.connect(Socket.java:609)
    at org.apache.commons.net.ftp.FTPClient._openDataConnection_(FTPClient.java:866)
    at org.apache.commons.net.ftp.FTPClient._storeFile(FTPClient.java:1053)
    at org.apache.commons.net.ftp.FTPClient.storeFile(FTPClient.java:3816)
    at org.apache.commons.net.ftp.FTPClient.storeFile(FTPClient.java:3846)

我不明白的是,在成功连接和登录并返回true for isConnected之后,它就会失败。

结果发现,当移除ftpClient.enterLocalPassiveMode();时,它可以工作,但我需要它在被动模式下工作。

我想,当切换到另一个端口进行被动调用时,失败是相关的。

但是,当试图将端口添加到withExposedPorts时,容器无法从以下内容开始:

代码语言:javascript
运行
复制
Caused by: org.testcontainers.containers.ContainerLaunchException: Timed out waiting for container port to open (localhost ports: [55600, 55601, 55602, 55603, 55604, 55605, 55606, 55607, 55608, 55609, 55598, 55599] should be listening)

与停靠者(docker run -d -p 21:21 -p 21000-21010:21000-21010 -e USERS="user|password" delfer/alpine-ftp-server)进行竞争是可行的。

本地码头版本:

  • Docker版本20.10.11,构建dea9396
  • 码头桌面4.3.1

测试容器-在1.16.2和1.15.3上的行为似乎都是一样的

链接到测试容器讨论

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-12-16 00:06:21

正如您在注释中已经指出的,FTP被动模式的棘手部分是服务器使用另一个端口(而不是21个)进行通信。在您使用的坞映像中,默认情况下它是来自21000-21010范围的一个端口。因此,您需要发布(公开)这些额外的容器端口。在docker run命令中,您使用了-p 21000-21010:21000-21010

但是,Testcontainers库的设计是为了在主机端已经占用了所需的固定端口(或一系列端口)时,将其发布到随机主机端口,以避免出现此问题。如果是FTP被动模式,主机端的随机端口会导致问题,因为afaik无法指示ftp客户端覆盖FTP服务器返回的端口。除了被动模式端口之外,您还需要类似于ftpClient.connect("localhost", ftp.getMappedPort(PORT));的东西。

因此,我在这里看到的唯一解决方案是使用FixedHostPortContainer。尽管由于被占用端口的问题,它被标记为不推荐使用,但我认为这是这里的一个有效用例。FixedHostPortGenericContainer允许在主机端发布固定端口。类似于:

代码语言:javascript
运行
复制
    private static final int PASSIVE_MODE_PORT = 21000;
    ...
    private static final FixedHostPortGenericContainer ftp = new FixedHostPortGenericContainer<>(
            "delfer/alpine-ftp-server:latest")
            .withFixedExposedPort(PASSIVE_MODE_PORT, PASSIVE_MODE_PORT)
            .withExposedPorts(PORT)
            .withEnv("USERS", USER + "|" + PASSWORD)
            .withEnv("MIN_PORT", String.valueOf(PASSIVE_MODE_PORT))
            .withEnv("MAX_PORT", String.valueOf(PASSIVE_MODE_PORT));

请记住,此解决方案依赖于21000端口始终是免费的假设。如果您要在没有保证的环境中运行它,那么您需要对其进行调整,以便首先找到一个空闲的主机端口。比如:

代码语言:javascript
运行
复制
    private static FixedHostPortGenericContainer ftp = new FixedHostPortGenericContainer<>(
            "delfer/alpine-ftp-server:latest")
            .withExposedPorts(PORT)
            .withEnv("USERS", USER + "|" + PASSWORD);

    @BeforeAll
    public static void staticSetup() throws Exception {
        Integer freePort = 0;
        try (ServerSocket socket = new ServerSocket(0)) {
            freePort = socket.getLocalPort();
        }
        ftp = (FixedHostPortGenericContainer)ftp.withFixedExposedPort(freePort, freePort)
                .withEnv("MIN_PORT", String.valueOf(freePort))
                .withEnv("MAX_PORT", String.valueOf(freePort));

        ftp.start();
    }
票数 9
EN

Stack Overflow用户

发布于 2022-06-22 15:37:32

类似于已接受的答案,但不使用不推荐的功能。注意,我们仍然必须使用固定端口21000。

代码语言:javascript
运行
复制
public static class FTPContainer extends GenericContainer<FTPContainer>
{
   private static FTPContainer container;

   private FTPContainer()
   {
     super(DockerImageName.parse("delfer/alpine-ftp-server:latest"));
   }

   @SuppressWarnings("resource")
   public static FTPContainer getInstance()
   {
      if (container == null)
      {
         container = new FTPContainer().withEnv("USERS", "test|test|/home/").withExposedPorts(21)
              .withCreateContainerCmdModifier(e -> e.getHostConfig()
                    .withPortBindings(new PortBinding(Ports.Binding.bindPort(21000), new ExposedPort(21000))))
              .withEnv("MIN_PORT", "21000").withEnv("MAX_PORT", "21000");
      }
      return container;
   }

   @Override
   public void start()
   {
      super.start();
   }

   @Override
   public void stop()
   {
      // Handled when JVM stops
   }
}

然后您可以使用FTP服务器实例,如

代码语言:javascript
运行
复制
FTPContainer FTP = FTPContainer.getInstance();
FTP.start();
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70274689

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档