[자바] apache-commons-net FtpClient 래퍼 및 파일 다운로드

2023. 1. 18. 19:45프로그래밍

728x90

사실 별 재미있는 작업은 아니지만, 없으면 또 만들어야 하고 해서 잊어버릴까 한번 다시 만들어 봅니다.

#아파치 #commons-net 을 사용해서 #FtpClient #래퍼 클래스를 만들어 보는 것이 오늘의 할 일입니다.

우선 고민이 되는 부분은 사용자 접속 정보를 외부 환경 설정에서 가져 오되, 이 접속 정보를 다시 외부에 오픈 할 것인가에 대한 부분입니다.

 

결론은 외부에 오픈하지 않는다는 것으로 가닥을 잡았습니다.

 

내부 연결 변수는 다 private 으로 정리하고, 생성자가 호출 될 때, FtpClient 클래스 인스턴스를 하나 만들어서 사용 합니다. 인스턴스가 하나 생성 될 때마다, 클라이언트를 하나씩 만들어 주겠죠.

private final String ip;
private final String port;
private final String user;
private final String passwd;

public FtpClientWrapper(final String ip, final String port, final String user, final String passwd)
{
	this.ip = ip;
	this.port = port;
	this.user = user;
	this.passwd = passwd;
	LOG.setLevel(Level.INFO);

	initFtpClient();
}
 

파일이 많지 않을 경우를 예상 했기 때문에 파일을 하나씩 원격에서 로컬로 옮겨 주는 메서드를 다음과 같이 만들어 주었습니다.

public boolean saveRemoteFileToCurrentDir(final String remoteFile, final String saveFile)
{
	boolean isSuccess = false;

	InputStream remoteFileStream = null;
	OutputStream saveToLocationStream = null;
	int flushCount = 100;
	LOG.debug("saveRemoteFileToCurrentDir: {}/{}", ftpClient.isAvailable(), ftpClient.isConnected());
	
	try {
		saveToLocationStream = new BufferedOutputStream(new FileOutputStream(saveFile));
		
		remoteFileStream = new BufferedInputStream(ftpClient.retrieveFileStream(remoteFile));
		
		int returnCode = ftpClient.getReplyCode();
        if (remoteFileStream == null || returnCode == 550) {
        	if(remoteFileStream!=null)  remoteFileStream.close();
            return false;
        }
		
		byte[] buffer = new byte[4096];
		int byteRead = -1;
		
		while((byteRead = remoteFileStream.read(buffer)) != -1)
		{
			saveToLocationStream.write(buffer, 0, byteRead);
			//LOG.info("byteRead: {}", byteRead);
			flushCount--;
			
			if(flushCount == 0)
			{
				saveToLocationStream.flush();
				flushCount = 100;
			}
		}
		
		saveToLocationStream.flush();
        saveToLocationStream.close();
		remoteFileStream.close();
		
		boolean success = ftpClient.completePendingCommand();
        if (success) {
           LOG.info("File has been downloaded successfully.{}", remoteFile);
        }
        
		isSuccess = true;
	} catch (IOException e) {
		e.printStackTrace();
		LOG.info("IOException File: {}", remoteFile);
		isSuccess = false;
	}
	finally
	{
		try {
			if(saveToLocationStream!=null) saveToLocationStream.close();
			if(remoteFileStream!=null) remoteFileStream.close();
		} catch (IOException e) {
			//e.printStackTrace();
		}
	}

	return isSuccess;
}
 

아주 전통적인 방법인 Input/Ouput 스트림 방식으로 구현 했기 때문에 일반적인 JDK에서는 구동이 되지 않을까 합니다.

 

위의 메서드를 적용 할 경우, 옮겨놓을 디렉토리를 만들어 놓지 않은 경우

java.io.FileNotFoundException 오류가 날 것이 뻔합니다.따라서, 먼저 디렉토리를 만들어 놓고 옮겨야만 한다는 것입니다.

 

그리고 하나 더 유의 할 점은 #completePendingCommand 메서드를 호출 하기 전에 이미 파일 입출력 처리를 닫아야 한다는 것입니다.

saveToLocationStream.close();
remoteFileStream.close();
		
boolean success = ftpClient.completePendingCommand();
if (success) {
    LOG.info("File has been downloaded successfully.{}", remoteFile);
}
 

그리고, completePendingCommand 메서드를 호출 하지 않으면 오작동을 할 수 있다고 API에 설명이 있습니다. 가급적이면 이 메서드를 호출 해서 #다운로드가 끝났는 지 여부를 확인 해 볼 필요는 있어 보입니다.

 

이 부분만 주의 한다면, 그냥 쓸만하지 않을까 합니다.

 

전체 소스

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketException;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;

public class FtpClientWrapper {
	private final static Logger LOG = (Logger) LoggerFactory.getLogger(FtpClientWrapper.class);
	
	private FTPClient ftpClient;
	private static final String FTP_ENC="EUC-KR";

	private final String ip;
	private final String port; 
	private final String user; 
	private final String passwd;
	
	private static final int SOCK_TIME_OUT = 2 * 60 * 1000;
	
	public FtpClientWrapper(final String ip, final String port, final String user, final String passwd)
	{
		this.ip = ip;
		this.port = port;
		this.user = user;
		this.passwd = passwd;
		LOG.setLevel(Level.INFO);
		
		initFtpClient();
	}

	private void initFtpClient() {
		ftpClient = new FTPClient();
		ftpClient.setControlEncoding(FTP_ENC);
		LOG.info("FTP server connection to.{}/{}", ip, port); 
		
		try {
			ftpClient.connect(ip, Integer.parseInt(port));
		} catch (NumberFormatException | IOException e) {
			
			LOG.info("FTP server connection failed.{}/{}", ip, port); 
			
			e.printStackTrace();
		}
		
		int reply = ftpClient.getReplyCode(); // 응답코드가 비정상이면 종료합니다

		if (!FTPReply.isPositiveCompletion(reply)) {
			try {
				ftpClient.disconnect();
			} catch (IOException e) {
				e.printStackTrace();
			}
			LOG.info("FTP server refused connection."); 
			
		} else {
			LOG.info("Success to connect : {} \n", ftpClient.getReplyString());
			try {
				ftpClient.setSoTimeout(SOCK_TIME_OUT);
			} catch (SocketException e) {
				LOG.error("Time out : {}", e);
			} // 현재 커넥션 timeout을 millisecond 값으로 입력합니다

			try {
				ftpClient.login(user, passwd);
				LOG.info("Success to login by following user : {}", user);
			} catch (IOException e) {
				LOG.info("Fail to login by following user : {}", user);
				e.printStackTrace();
			} // 로그인 유저명과 비밀번호를 입력 합니다
			
			ftpClient.enterLocalPassiveMode();
			
            try {
				ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public boolean saveRemoteFileToCurrentDir(final String remoteFile, final String saveFile)
	{
		boolean isSuccess = false;

		InputStream remoteFileStream = null;
		OutputStream saveToLocationStream = null;
		int flushCount = 100;
		LOG.debug("saveRemoteFileToCurrentDir: {}/{}", ftpClient.isAvailable(), ftpClient.isConnected());
		
		try {
			saveToLocationStream = new BufferedOutputStream(new FileOutputStream(saveFile));
			
			remoteFileStream = new BufferedInputStream(ftpClient.retrieveFileStream(remoteFile));
			
			int returnCode = ftpClient.getReplyCode();
	        if (remoteFileStream == null || returnCode == 550) {
	        	if(remoteFileStream!=null)  remoteFileStream.close();
	            return false;
	        }
			
			byte[] buffer = new byte[4096];
			int byteRead = -1;
			
			while((byteRead = remoteFileStream.read(buffer)) != -1)
			{
				saveToLocationStream.write(buffer, 0, byteRead);
				//LOG.info("byteRead: {}", byteRead);
				flushCount--;
				
				if(flushCount == 0)
				{
					saveToLocationStream.flush();
					flushCount = 100;
				}
			}
			
			saveToLocationStream.flush();
            if(saveToLocationStream!=null) saveToLocationStream.close();
			if(remoteFileStream!=null) remoteFileStream.close();
			
			boolean success = ftpClient.completePendingCommand();
            if (success) {
               LOG.info("File has been downloaded successfully.{}", remoteFile);
            }
            
			isSuccess = true;
		} catch (IOException e) {
			e.printStackTrace();
			LOG.info("IOException File: {}", remoteFile);
			isSuccess = false;
		}
		finally
		{
			try {
				if(saveToLocationStream!=null) saveToLocationStream.close();
				if(remoteFileStream!=null) remoteFileStream.close();
			} catch (IOException e) {
				//e.printStackTrace();
			}
		}

		return isSuccess;
	}
	
	public boolean releaseConnection()
	{
		boolean isSuccessToRelease = false;
		try {
			if(ftpClient.isConnected())
			{
				ftpClient.logout();
				ftpClient.disconnect();
				
			}
			isSuccessToRelease = true;
		} catch (IOException e) {
			e.printStackTrace();
			isSuccessToRelease = false;
		}
		
		return isSuccessToRelease;
	}

}
 

이상.

 

728x90