We have a use case to download files from FTP, and there is a strange behavior that the ftp inbound adapter stops to work after network resets, here is the steps to reproduce the problem:
start application
application starts to download files from ftp server to local
there are filename.writing file appearing in defined local directory
pull out the network cable (to simulate a network reset situation)
application stops to download file (obviously no network connection)
plug in the network cable.
download is not restarted or reset, application stays still..
there is no LOG at all to identify this problem.
Thanks in advance!
UPDATE
This problem should be fixed by adding timeout defSession.setConnectTimeout(Integer.valueOf(env.getProperty("ftp.timeout.connect")));
AND The code below is a WORKING EXAMPLE on FTP reading client.
Here are the code snippet:
#Bean
public DefaultFtpSessionFactory ftpSessionFactory() {
DefaultFtpSessionFactory defSession = new DefaultFtpSessionFactory();
defSession.setUsername(env.getProperty("ftp.username"));
defSession.setPassword(env.getProperty("ftp.password"));
defSession.setPort(21);
defSession.setHost(env.getProperty("ftp.host"));
defSession.setClientMode(FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE);
defSession.setControlEncoding("UTF-8");
return defSession;
}
#Bean
PollableChannel ftpChannel() {
return new QueueChannel(Integer.valueOf(env.getProperty("ftp.channel.size")));
}
#Bean
public FtpInboundFileSynchronizer ftpInboundFileSynchronizer() {
FtpInboundFileSynchronizer ftpInboundFileSynchronizer = new FtpInboundFileSynchronizer(ftpSessionFactory());
ftpInboundFileSynchronizer.setDeleteRemoteFiles(Boolean.valueOf(env.getProperty("ftp.directory.delete")));
FtpRegexPatternFileListFilter ftpRegexPatternFileListFilter = new FtpRegexPatternFileListFilter(pattern);
ftpInboundFileSynchronizer.setFilter(ftpRegexPatternFileListFilter);
ftpInboundFileSynchronizer.setRemoteDirectory(env.getProperty("ftp.directory.remote"));
return ftpInboundFileSynchronizer;
}
#Bean
#InboundChannelAdapter(value = "ftpChannel")
public FtpInboundFileSynchronizingMessageSource ftpInboundFileSynchronizingMessageSource() {
FtpInboundFileSynchronizingMessageSource ftpInboundFileSynchronizingMessageSource = new FtpInboundFileSynchronizingMessageSource(ftpInboundFileSynchronizer());
ftpInboundFileSynchronizingMessageSource.setLoggingEnabled(true);
ftpInboundFileSynchronizingMessageSource.setCountsEnabled(true);
ftpInboundFileSynchronizingMessageSource.setAutoCreateLocalDirectory(true);
ftpInboundFileSynchronizingMessageSource.setLocalDirectory(new File(env.getProperty("ftp.directory.local")));
return ftpInboundFileSynchronizingMessageSource;
}
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata defaultPoller() {
PollerMetadata pollerMetadata = new PollerMetadata();
pollerMetadata.setErrorHandler(t -> log.error("Failed to retrieve data from FTP: {}", t.getMessage(), t));
pollerMetadata.setTrigger(new PeriodicTrigger(60, TimeUnit.SECONDS));
return pollerMetadata;
}
Most likely the thread is still hanging on the read - if you pull the cable from the actual adapter on the computer, the network stack should notify the java process that the socket is gone, but if you pull the cable from a downstream router, there may be no signal. jstack will show what the thread is doing.
You need to set timeouts on the session factory - see the documentation and the FtpClient javadocs - the dataTimeout is used for reads.
Related
https://medium.com/nerd-for-tech/retrieving-files-from-ftp-server-using-spring-integration-5ccc4a972eaf
I'm implementing FTP behavior based on the above site.
In the above site, I've only added passive mode to the ftp settings.
#Bean
public DefaultFtpSessionFactory sf() {
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
sf.setHost(host);
sf.setPort(port);
sf.setUsername(username);
sf.setPassword(password);
sf.setClientMode(FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE);
return sf;
}
And I want to check the passive port when communicating in practice.
#ServiceActivator(inputChannel = "ftpMGET")
#Bean
public FtpOutboundGateway getFiles() {
DefaultFtpSessionFactory ftpSessionFactory = sf();
FtpOutboundGateway gateway = new FtpOutboundGateway(ftpSessionFactory, "mget", "payload");
gateway.setAutoCreateDirectory(true);
gateway.setLocalDirectory(new File(downloadPath));
gateway.setFileExistsMode(FileExistsMode.APPEND);
gateway.setFilter(new AcceptOnceFileListFilter<>());
gateway.setOutputChannelName("fileResults");
gateway.setOption(AbstractRemoteFileOutboundGateway.Option.RECURSIVE);
System.out.println(ftpSessionFactory.getSession().getClientInstance().getPassivePort());
return gateway;
}
At the end, I added a passivePort with System.out.println.
It keeps saying -1, but how do I check the passive port when downloading a file from a real server?!
The FTPClient.getPassivePort cannot return any actual value, before you transfer anything. Each transfer uses a different (passive) port. Until you actually transfer anything, no passive port is involved.
As the documentation of the FTPClient.getPassivePort method says:
This method only returns a valid value AFTER a data connection has been opened after a call to enterLocalPassiveMode(). This is because FTPClient sends a PASV command to the server only just before opening a data connection, and not when you call enterLocalPassiveMode().
Background: I am calling a REST API to download a text file from a spring boot app. The problem is that the server takes time to generate the file and then make it ready for download.
So, I am using RetryTemplate to wait for 1 minute and attempt 60 times (60 attempts in 1 hour). When the download file is not ready, the server response will be empty, but when the download file is ready, the server will respond with the file URL.
Problem: Now the problem is, the way I've configured RetryTemplate, it'll keep calling the API even if there is an exception from the server.
Desired behavior: RetryTemplate should retry calling the API only when the server response is EMPTY and should NOT retry if the server response contains the download file URL or on a server-side exception.
RetryTemplate bean configuration:
#Bean
public RetryTemplate retryTemplate() {
final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(60);
final FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(60000L);
final RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
Service class method where it's used:
#Override
public DownloadResult getDownloadFile(final int id) {
// retry for 1 hour to download the file. Call to EBL will be made every minute for 60 minutes until the file is ready
final DownloadResult result = retryTemplate.execute(downloadFile -> dataAccess.getFile(id));
final InputStream inputStream = Try.of(result::getFileUrl)
.map(this::download)
.onFailure(throwable -> log.error("MalformedURLException: {}", result.getFileUrl()))
.get();
return result.toBuilder().downloadFileInputStream(inputStream).build();
}
The server response when download file is ready for download looks like this:
{
fileUrl:"www.download.com/fileId"
}
Empty server response if download file is not ready for download:
{
}
Thanks in advance for your help!
spring-retry is entirely based on exceptions. You need to check the result within the lambda and throw an exception...
retryTemplate.execute(downloadFile -> {
DownloadResult result = dataAccess.getFile(id));
if (resultIsEmpty(result)) {
throw new SomeException();
}
return result;
}
I am using Spring integration sftp to put files on a remote server and below is configuration.
<spring-integration.version>5.2.5.RELEASE</spring-integration.version>
I have configurated #MessagingGateway.
#MessagingGateway
public interface SftpMessagingGateway {
#Gateway(requestChannel = "sftpOutputChannel")
void sendToFTP(Message<?> message);
}
I have configured the MessageHandler as below,
#Bean
#ServiceActivator(inputChannel = "sftpOutputChannel" )
public MessageHandler genericOutboundhandler() {
SftpMessageHandler handler = new SftpMessageHandler(outboundTemplate(), FileExistsMode.APPEND);
handler.setRemoteDirectoryExpressionString("headers['remote_directory']");
handler.setFileNameGenerator((Message<?> message) -> ((String) message.getHeaders().get(Constant.FILE_NAME_KEY)));
handler.setUseTemporaryFileName(false);
return handler;
}
I have configured SftpRemoteFileTemplate as below
private SftpRemoteFileTemplate outboundTemplate;
public SftpRemoteFileTemplate outboundTemplate(){
if (outboundTemplate == null) {
outboundTemplate = new SftpRemoteFileTemplate(sftpSessionFactory());
}
return outboundTemplate;
}
This is the configuration for SessionFactory
public SessionFactory<LsEntry> sftpSessionFactory() {
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory();
factory.setHost(host);
factory.setPort(port);
factory.setUser(username);
factory.setPassword(password);
factory.setAllowUnknownKeys(true);
factory.setKnownHosts(host);
factory.setSessionConfig(configPropertiesOutbound());
CachingSessionFactory<LsEntry> csf = new CachingSessionFactory<LsEntry>(factory);
csf.setSessionWaitTimeout(1000);
csf.setPoolSize(10);
csf.setTestSession(true);
return csf;
}
I have configured all this in one of the service.
Now the problem is,
Sometimes the entire operation takes more than 15 min~ specially if the service is ideal for few hours and I am not sure what is causing this issue.
It looks like it is spending time on getting the active session from CachedSessionFactory the after operations are pretty fast below is the snap from one of the tool where I have managed to capture the time.
It usually takes few miliseconds to transfer files.
I have recently made below changes but before that as well I was getting the same issue,
I have set isShareSession to false earlier it was DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
There was no pool size I have set it to 10
I think I have configured something incorrectly and that's why I end up piling connection ? Or there is something else ?
Observation :
The time taking to complete the operation is somewhat similar all the time when issue occurs i.e 938000 milliseconds +
If I restart the application daily it works perfectly fine.
I use spring integration sftp to download and upload files.In the document ,I found
Spring Integration supports sending and receiving files over SFTP by providing three client side endpoints: Inbound Channel Adapter, Outbound Channel Adapter, and Outbound Gateway
When I want to download files I must assign the local directory and when I want to upload files I must assign the remote directory.But if I can't assign the directory when I write the code such as my directory is association with date.How can I assign the directory at runtime?
Here is my code:
#Bean
public SessionFactory<LsEntry> sftpSessionFactory(){
DefaultSftpSessionFactory defaultSftpSessionFactory = new DefaultSftpSessionFactory();
defaultSftpSessionFactory.setHost(host);
defaultSftpSessionFactory.setPort(Integer.parseInt(port));
defaultSftpSessionFactory.setUser(username);
defaultSftpSessionFactory.setPassword(password);
defaultSftpSessionFactory.setAllowUnknownKeys(true);
return new CachingSessionFactory<LsEntry>(defaultSftpSessionFactory);
}
#Bean
public SftpRemoteFileTemplate sftpRemoteFileTemplate(){
SftpRemoteFileTemplate sftpRemoteFileTemplate = new SftpRemoteFileTemplate(sftpSessionFactory());
return sftpRemoteFileTemplate;
}
#Bean
#ServiceActivator(inputChannel = "sftpChannel")
public MessageHandler handlerGet() {
SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "mget", "payload");
sftpOutboundGateway.setLocalDirectory(new File(localDirectory));
sftpOutboundGateway.setFilter(new SftpSimplePatternFileListFilter("*.txt"));
sftpOutboundGateway.setSendTimeout(1000);
return sftpOutboundGateway;
}
In the messageHandler,I must assign the localDirectory in the outboundGateway. And when I want change my localDirectory by days.I must download the file to the localDirectory and move to the target directory. How can I assign the localDirectory at runtime .such as today I download to 20170606/ and tomorrow I download to 20170607 ?
edit
this is my option and test
public interface OutboundGatewayOption {
#Gateway(requestChannel = "sftpChannel")
public List<File> getFiles(String dir);
}
#Test
public void test2(){
outboundGatewayOption.getFiles("upload/20160920/");
}
sftpOutboundGateway.setLocalDirectoryExpression(
new SpelExpressionParser().parseExpression("headers['whereToPutTheFiles']");
or parseExpression("#someBean.getDirectoryName(payload)")
etc.
The expression must evaluate to a String representing the directory absolute path.
While evaluating the expression, the remote directory is available as a variable #remoteDirectory.
I'm new to Spring Framework and, indeed, I'm learning and using Spring Boot. Recently, in the app I'm developing, I made Quartz Scheduler work, and now I want to make Spring Integration work there: FTP connection to a server to write and read files from.
What I want is really simple (as I've been able to do so in a previous java application). I've got two Quartz Jobs scheduled to fired in different times daily: one of them reads a file from a FTP server and another one writes a file to a FTP server.
I'll detail what I've developed so far.
#SpringBootApplication
#ImportResource("classpath:ws-config.xml")
#EnableIntegration
#EnableScheduling
public class MyApp extends SpringBootServletInitializer {
#Autowired
private Configuration configuration;
//...
#Bean
public DefaultFtpsSessionFactory myFtpsSessionFactory(){
DefaultFtpsSessionFactory sess = new DefaultFtpsSessionFactory();
Ftp ftp = configuration.getFtp();
sess.setHost(ftp.getServer());
sess.setPort(ftp.getPort());
sess.setUsername(ftp.getUsername());
sess.setPassword(ftp.getPassword());
return sess;
}
}
The following class I've named it as a FtpGateway, as follows:
#Component
public class FtpGateway {
#Autowired
private DefaultFtpsSessionFactory sess;
public void sendFile(){
// todo
}
public void readFile(){
// todo
}
}
I'm reading this documentation to learn to do so. Spring Integration's FTP seems to be event driven, so I don't know how can I execute either of the sendFile() and readFile() from by Jobs when the trigger is fired at an exact time.
The documentation tells me something about using Inbound Channel Adapter (to read files from a FTP?), Outbound Channel Adapter (to write files to a FTP?) and Outbound Gateway (to do what?):
Spring Integration supports sending and receiving files over FTP/FTPS by providing three client side endpoints: Inbound Channel Adapter, Outbound Channel Adapter, and Outbound Gateway. It also provides convenient namespace-based configuration options for defining these client components.
So, I haven't got it clear as how to follow.
Please, could anybody give me a hint?
Thank you!
EDIT:
Thank you #M. Deinum. First, I'll try a simple task: read a file from the FTP, the poller will run every 5 seconds. This is what I've added:
#Bean
public FtpInboundFileSynchronizer ftpInboundFileSynchronizer() {
FtpInboundFileSynchronizer fileSynchronizer = new FtpInboundFileSynchronizer(myFtpsSessionFactory());
fileSynchronizer.setDeleteRemoteFiles(false);
fileSynchronizer.setPreserveTimestamp(true);
fileSynchronizer.setRemoteDirectory("/Entrada");
fileSynchronizer.setFilter(new FtpSimplePatternFileListFilter("*.csv"));
return fileSynchronizer;
}
#Bean
#InboundChannelAdapter(channel = "ftpChannel", poller = #Poller(fixedDelay = "5000"))
public MessageSource<File> ftpMessageSource() {
FtpInboundFileSynchronizingMessageSource source = new FtpInboundFileSynchronizingMessageSource(inbound);
source.setLocalDirectory(new File(configuracion.getDirFicherosDescargados()));
source.setAutoCreateLocalDirectory(true);
source.setLocalFilter(new AcceptOnceFileListFilter<File>());
return source;
}
#Bean
#ServiceActivator(inputChannel = "ftpChannel")
public MessageHandler handler() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
Object payload = message.getPayload();
if(payload instanceof File){
File f = (File) payload;
System.out.println(f.getName());
}else{
System.out.println(message.getPayload());
}
}
};
}
Then, when the app is running, I put a new csv file intro "Entrada" remote folder, but the handler() method isn't run after 5 seconds... I'm doing something wrong?
Please add #Scheduled(fixedDelay = 5000) over your poller method.
You should use SPRING BATCH with tasklet. It is far easier to configure bean, crone time, input source with existing interfaces provided by Spring.
https://www.baeldung.com/introduction-to-spring-batch
Above example is annotation and xml based both, you can use either.
Other benefit Take use of listeners and parallel steps. This framework can be used in Reader - Processor - Writer manner as well.