FTPSClient retrievefile()挂起

时间:2016-04-12 18:54:49

标签: java apache ftp ftp-client ftps

我正在创建一个apache FTPS客户端(因为远程服务器不允许普通FTP)。我可以毫无问题地连接和删除文件,但是当使用retrieveFile()或retrieveFileStream()时,它会挂起。

出于某种原因,非常小的文件传输(最多5792个字节),但其他任何东西都提供以下PrintCommandListener输出:

  

运行:
  220 ----------欢迎使用Pure-FTPd [privsep] [TLS] ----------
  220-您允许的用户数为2的50   220-当地时间现在是19:42。服务器端口:21   220-这是一个私人系统 - 没有匿名登录
  此服务器也欢迎220-IPv6连接   220您将在15分钟不活动后断开连接   AUTH TLS
  234 AUTH TLS OK。
  用户
  331用户确定。需要密码
  通过
  230好的当前受限制的目录是/
  A型   200 TYPE现在是ASCII
  EPSV
  229扩展被动模式OK(||| 53360 |)
  RETR test.txt
  150-接受的数据连接
  150 7.3千字节下载

以下是代码:

try {

    FTPSClient ftpClient = new FTPSClient("tls",false);

    ftpClient.addProtocolCommandListener(new PrintCommandListener(new  PrintWriter(System.out)));

    ftpClient.connect(host, port);

    int reply = ftpClient.getReplyCode();

    if (FTPReply.isPositiveCompletion(reply)) {
        ftpClient.enterLocalPassiveMode();
        ftpClient.login(username, password);
        ftpClient.enterLocalPassiveMode();
        FileOutputStream outputStream = new FileOutputStream(tempfile);
        ftpClient.setFileType(FTPClient.ASCII_FILE_TYPE);
        ftpClient.retrieveFile("test.txt", outputStream);
        outputStream.close();
        ftpClient.logout();
        ftpClient.disconnect();
    }
} catch (IOException ioe) {
    System.out.println("FTP client received network error");
}

非常感谢任何想法。

3 个答案:

答案 0 :(得分:0)

通常,FTPS连接的FTP命令序列(每RFC 4217AUTH TLSPBSZ 0然后 USER,{{1} },。因此,您可以尝试:

PASS

明确告诉服务器缓冲数据连接(FTPSClient ftpClient = new FTPSClient("tls",false); ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out))); ftpClient.connect(host, port); int reply = ftpClient.getReplyCode(); if (FTPReply.isPositiveCompletion(reply)) { ftpClient.execPBSZ(0); reply = ftpClient.getReplyCode(); // Check for PBSZ error responses... ftpClient.execPROT("P"); reply = ftpClient.getReplyCode(); // Check for PROT error responses... ftpClient.enterLocalPassiveMode(); ),并使用TLS保护数据传输({{1} })。

能够传输某些字节的事实表明问题是与防火墙/路由器/ NAT的常见复杂问题,这是另一个常见的FTPS问题。

希望这有帮助!

答案 1 :(得分:0)

即使以正确的顺序调用了PBSZ 0PROT P,有时服务器也确实需要SSL会话重用,而客户端默认情况下并非如此。

例如,尝试列出目录时出现以下答复。结果没有返回任何内容列表,这样客户端可以看到目录为空:

LIST /
150 Here comes the directory listing.
522 SSL connection failed; session reuse required: see require_ssl_reuse option in sftpd.conf man page

要解决此问题,需要通过覆盖_prepareDataSocket_()方法来自定义FTPSClient的初始化。

此处详细说明了该解决方案:https://eng.wealthfront.com/2016/06/10/connecting-to-an-ftps-server-with-ssl-session-reuse-in-java-7-and-8/

上面的链接中的工作代码示例:

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.Locale;

import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;

import org.apache.commons.net.ftp.FTPSClient;

import com.google.common.base.Throwables;

public class SSLSessionReuseFTPSClient extends FTPSClient {

  // adapted from: https://trac.cyberduck.io/changeset/10760
  @Override
  protected void _prepareDataSocket_(final Socket socket) throws IOException {
    if(socket instanceof SSLSocket) {
      final SSLSession session = ((SSLSocket) _socket_).getSession();
      final SSLSessionContext context = session.getSessionContext();
      try {
        final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache");
        sessionHostPortCache.setAccessible(true);
        final Object cache = sessionHostPortCache.get(context);
        final Method putMethod = cache.getClass().getDeclaredMethod("put",Object.class, Object.class);
        putMethod.setAccessible(true);
        final Method getHostMethod = socket.getClass().getDeclaredMethod("getHost");
        getHostMethod.setAccessible(true);
        Object host = getHostMethod.invoke(socket);
        final String key = String.format("%s:%s", host, String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
        putMethod.invoke(cache, key, session);
      } catch(Exception e) {
        throw Throwables.propagate(e);
      }
    }
  }

}

答案 2 :(得分:0)

希望几年后有人对我的评论有用。 就我而言,我将retrieveFile替换为retrieveFileStream。它需要更多代码,但至少可以使用。