最近做了个项目,是要和哥伦比亚当地的软件商做接口传凭证。
那边给了接口方式,用sftp的方式给他们指定的服务器目录传txt格式的报文。然后他们再自己读取到自己系统里。
虽然不知道为什么要这么做,webservice它不香吗…还能实时返回调用结果…难道他们不会用?…那句俗话,乳香随俗吧。
现在自己电脑上搭了个ftp服务器,然后网上找了段轮子当demo,一测,还挺好使。就继续写业务逻辑了。
结果等他们给提供了ftp服务器地址之后,怎么连也连不上。然后给他们发了封邮件,过了两天回过来消息。说FTPClient不行,得用FTPSClient,然后丢过来一个stackoverflow网站的连接,一看就是谷歌刚查出来的那种。然后是他们用filezilla连接成功的截图。
我去下了个filezilla试了下,竟然能传。这整的就很尴尬了,刚开始我还心里想他们难道webservice都不会用,这会儿自己连个ftp都整不成功。没办法,自己研究吧。
用FTPSClient之后,确实登录不报错了,但是 ftpsClient.storeFile()就卡那儿了,网上查了下,要在前边加ftpsClient.enterLocalPassiveMode(); 设置成被动模式。
不报错了,程序直接就走完了。
真好。
去filezilla上刷新一看,毛都没有。
等了个寂寞。然后ftpsClient.storeFile()的值打印出来看。false。这就是没上传成功。难道是目录切错了?
listFiles一下,也是程序跑完,结果listFiles的数组里是空的。明明有文件。这接不到值。日狗。
遂去查 listFiles为空。刚好网上有人说两个服务器,什么跨国两台服务器语言不同,导致传日期格式的时候有问题。我一看,跨国,语言不同,为空,这不就是说的我嘛。
打起精神看人家怎么解决的:找到源码,继承某某类。看了半天把代码搬过来用也不行。
人家是跟源码解决的,那我这是不是跟下源码就能解决了。遂找地儿下来了commons源码。着重跟了半天博主说的语言格式的地儿。
一是工作原理没弄懂,二是md那么多源码绕来绕去的,咱这水平也跟不上。真较真就下班了。
中场休息。
下午回来一想,我是为了上传,应该去解决storeFile的问题啊。整半天listfile回头解决了也没啥用啊。
转头跟storeFile的源码逻辑。回主线。
发现FTPSClient是继承FTPClient的,然后在源码中socket是空的,所以下边判断返回了false
然后这里返回了521
这里的方法判断返回的值必须是在100-200,而此时sendcommmand返回的是521.
public static boolean isPositivePreliminary(int reply)
{
return (reply >= 100 && reply < 200);
}
关键是sendcommand我也不知道是干啥的啊,再去网上查,然后查ftp返回521是什么意思,是我爱你的意思吗
写不下去了。。。太tm曲折了,也没时间再写了。
最后用谷到stackoverflow上有说
jre1.7和jre1.8对session的缓存处理有差异。
synchronized String getHost() {
// Note that the host may be null or empty for localhost.
if (host == null || host.length() == 0) {
host = getInetAddress().getHostName();
}
return host;
}
synchronized String getHost() {
// Note that the host may be null or empty for localhost.
if (host == null || host.length() == 0) {
if (!trustNameService) {
// If the local name service is not trustworthy, reverse host
// name resolution should not be performed for endpoint
// identification. Use the application original specified
// hostname or IP address instead.
host = getOriginalHostname(getInetAddress());
} else {
host = getInetAddress().getHostName();
}
}
return host;
}
1.7.0中,返回的字符串是主机名称,类似"ec2-…compute.amazonaws.com"。但是,在 1.8.0 中,默认情况下会阻止反向主机名称,并返回主机ip,类似"123.45.67.89"。问题是,总是使用主机名称作为密钥的第一部分存储到缓存中.
大概就是错误的host导致要上传的时候算新会话,服务器不认。
自己定义一个类继承FTPSClient,重载_prepareDataSocket_(final Socket socket)方法
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;
public class SSLSessionReuseFTPSClient extends FTPSClient {
// adapted from:
// https://trac.cyberduck.io/browser/trunk/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java
@Override
protected void _prepareDataSocket_(final Socket socket) throws IOException {
if (socket instanceof SSLSocket) {
// Control socket is SSL
final SSLSession session = ((SSLSocket) _socket_).getSession();
if (session.isValid()) {
final SSLSessionContext context = session.getSessionContext();
try {
final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache");
sessionHostPortCache.setAccessible(true);
final Object cache = sessionHostPortCache.get(context);
final Method method = cache.getClass().getDeclaredMethod("put", Object.class, Object.class);
method.setAccessible(true);
method.invoke(cache, String
.format("%s:%s", socket.getInetAddress().getHostName(), String.valueOf(socket.getPort()))
.toLowerCase(Locale.ROOT), session);
method.invoke(cache, String
.format("%s:%s", socket.getInetAddress().getHostAddress(), String.valueOf(socket.getPort()))
.toLowerCase(Locale.ROOT), session);
} catch (NoSuchFieldException e) {
throw new IOException(e);
} catch (Exception e) {
throw new IOException(e);
}
} else {
throw new IOException("Invalid SSL Session");
}
}
}
}
不想写了,就这样的。
原问题链接:
https://stackoverflow.com/questions/32398754/how-to-connect-to-ftps-server-with-data-connection-using-same-tls-session
回头对计算机网络这块还是要认真学一下。
哦,还有两个事儿要交待
1.System.setProperty(“jdk.tls.useExtendedMasterSecret”, “false”);
这句话加代码里。
2.用eclipse跑demo自己加载个jdk,我用eclipse自带的跑不成.