我正在创建一个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");
}
非常感谢任何想法。
答案 0 :(得分:0)
通常,FTPS连接的FTP命令序列(每RFC 4217)AUTH TLS
,PBSZ 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 0
和PROT 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
。它需要更多代码,但至少可以使用。