LoginSignup
1
0

More than 1 year has passed since last update.

[Java]FTPs通信のアイドルタイムアウト回避

Last updated at Posted at 2022-12-27

起きた問題

spring-integration のFTPSアダプター(MessageSource および OutboundGateway)を利用したFTPs通信を行うバッチ処理で、巨大なファイルの送受信を行うと 送受信完了後に例外が発生 して異常終了した。

直接原因

FTPsの 制御チャネルのアイドル状態が長時間化 し、通信経路中にある ロードバランサのアイドルタイムアウトに抵触 したため。

根本原因

DefaultFtpsSessionFactory#createClientInstance() で生成される FTPSClient のインスタンスには、controlKeepAliveTimeout (※)が設定されていないため。

※ controlKeepAliveTimeout を設定すると、一定間隔で制御チャネルから無害なコマンド(NOOP)を送信するようになり、通信経路中にあるネットワーク機器のアイドルタイマーをリセットする効果がある

回避方法

DefaultFtpsSessionFactory の拡張クラスで postProcessClientBeforeConnect メソッドをオーバーライドして、その中で controlKeepAliveTimeout を設定する。

FtpsSessionFactory.java
// 要lombok.jar

import org.apache.commons.net.ftp.FTPSClient;
import org.springframework.integration.ftp.session.DefaultFtpsSessionFactory;
import org.springframework.integration.util.JavaUtils;
import lombok.Setter;

public class FtpsSessionFactory extends DefaultFtpsSessionFactory {

    @Setter
    private Long controlKeepAliveTimeout;

    @Override
    protected void postProcessClientBeforeConnect(FTPSClient ftpsClient) {
        super.postProcessClientBeforeConnect(ftpsClient);
        JavaUtils.INSTANCE
                .acceptIfNotNull(this.controlKeepAliveTimeout,
                        ftpsClient::setControlKeepAliveTimeout);
    }

}
FtpsConfig.java
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.file.remote.session.SessionFactory;

@Configuration
public class FtpsConfig {

    @Value("${ftps.config.host:127.0.0.1}")
    private String host;
    @Value("${ftps.config.port:21}")
    private int port;
    @Value("${ftps.config.connectTimeout:1000}")
    private int connectTimeout;
    @Value("${ftps.config.defaultTimeout:1000}")
    private int defaultTimeout;
    @Value("${ftps.config.dataTimeout:1000}")
    private int dataTimeout;
    @Value("${ftps.config.controlKeepAliveTimeout:5}")
    private long controlKeepAliveTimeout;
    @Value("${ftps.config.username:hoge}")
    private String username;
    @Value("${ftps.config.password:none}")
    private String password;
    @Value("${ftps.config.usePassive:true}")
    private boolean usePassive;

    @Bean
    public SessionFactory<FTPFile> ftpsSessionFactory() {
        FtpsSessionFactory factory = new FtpsSessionFactory();
        factory.setHost(host);
        factory.setPort(port);
        factory.setConnectTimeout(connectTimeout);
        factory.setDefaultTimeout(defaultTimeout);
        factory.setDataTimeout(dataTimeout);
        factory.setControlKeepAliveTimeout(controlKeepAliveTimeout);
        factory.setUsername(username);
        factory.setPassword(password);
        if (usePassive) {
            factory.setClientMode(FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE);
        }
        return new CachingSessionFactory<>(factory);
    }

}

参考

Control channel keep-alive feature: より
During file transfers, the data connection is busy, but the control connection is idle. FTP servers know that the control connection is in use, so won't close it through lack of activity, but it's a lot harder for network routers to know that the control and data connections are associated with each other. Some routers may treat the control connection as idle, and disconnect it if the transfer over the data connection takes longer than the allowable idle time for the router.
One solution to this is to send a safe command (i.e. NOOP) over the control connection to reset the router's idle timer.
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0