Java FTPClient를 통한 ftp 파일 업로드 내용 정리
Spring Boot 프로젝트에서 FTPClient를 통한 ftp 파일 업로드를 구현하며 정리한 내용입니다.
파일 업로드 과정에서 FTPClient에 설정이 필요한 부분들이 정리되어 있으며, 포스팅 맨 하단에 github 주소도 함께 링크해 놓았으니 참고하시면 좋을 것 같습니다.
Java FTPClient를 통한 FTP 파일 업로드
@Slf4j
@Component
public class FtpUtils {
@Value("${ttotw.ftp.server}")
private String server;
@Value("${ttotw.ftp.port}")
private int port;
@Value("${ttotw.ftp.username}")
private String username;
@Value("${ttotw.ftp.password}")
private String password;
private FTPClient ftp;
//FTPClient 객체를 통한 ftp 서버 연결
public void open() { ... }
//파일 업로드 또는 다운로드 동작 완료 후 ftp 서버 연결 종료
public void close() { ... }
//파일 업로드
public void upload(MultipartFile file) { ... }
}
FTP 파일 업로드를 위한 FtpUtils 클래스를 만들고 @Component 어노테이션을 통해 해당 클래스를 빈 등록, 필요한 곳에서 바로 사용할 수 있도록 하였습니다.
또 upload, download, delete 등의 메서드 안에서 open, close를 각각 처리할 수 있지만 해당 부분은 공통적으로 사용되는 부분이기 때문에 메서드를 통해 따로 빼서 처리할 수 있게 구현하였습니다.
그러면 이어지는 내용을 통해 open(), close(), upload() 각각의 메서드에 대해 자세하게 살펴보겠습니다.
***
해당 방식이 아닌 생성자를 통해 필요한 데이터(server, port, username, password) 등을 넣어 해당 객체를 만들어 사용하는 방법도 있으며, 이 부분은 상황에 맞게 사용하면 될 것 같습니다.
open() method
public void open() {
ftp = new FTPClient();
//default controlEncoding 값이 "ISO-8859-1" 때문에 한글 파일의 경우 파일명이 깨짐
//ftp server 에 저장될 파일명을 uuid 등의 방식으로 한글을 사용하지 않고 저장할 경우 UTF-8 설정이 따로 필요하지 않다.
ftp.setControlEncoding("UTF-8");
//PrintCommandListener 를 추가하여 표준 출력에 대한 명령줄 도구를 사용하여 FTP 서버에 연결할 때 일반적으로 표시되는 응답을 출력
ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
try {
//ftp 서버 연결
ftp.connect(server, port);
//ftp 서버에 정상적으로 연결되었는지 확인
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
log.error("FTPClient:: server connection failed.");
}
//socketTimeout 값 설정
ftp.setSoTimeout(1000);
//ftp 서버 로그인
ftp.login(username, password);
//file type 설정 (default FTP.ASCII_FILE_TYPE)
ftp.setFileType(FTP.BINARY_FILE_TYPE);
} catch (IOException e) {
e.printStackTrace();
log.error("FTPClient:: server connection failed.");
}
}
파일 업로드, 다운로드 등의 로직에 앞서 필수적으로 수행되어야 하는 FTPClient 객체를 통한 ftp server 연결 과정입니다.
FTPClient 객체의 default _controlEncoding 값은 "ISO-8859-1"로 설정되어 있습니다.
만약 파일명을 들어오는 파일명 그대로 저장한다고 하였을 때, 파일 명이 영문 또는 숫자인 경우에는 문제가 없지만 한글일 경우 저장 시 ???? 등의 값으로 깨지게 되는데요.
따라서 파일명을 한글로 저장하고 싶은 경우 setControlEncoding() 메서드를 통해 인코딩 설정을 변경해주어야 합니다.
이어서 addProtocolCommandListener() 메서드를 통해 PrintCommandListener를 설정하는 부분인데요.
예시 코드와 같이 PrintCommandListnener를 추가하게 되면, 위 이미지와 같이 FTPClient 동작에 대한 응답을 출력할 수 있습니다.
ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
new PrintWriter() 부분에는 위코드와 같이 System.out 하나만을 인자로 줄 수도 있는데요.
위 코드에 적용된 두 번째 boolean 인자 'true'는 ftp server 접속 시 출력되는 응답에서 password 부분을 노출시키지 않기 위한 옵션입니다.
//socketTimeout 값 설정
ftp.setSoTimeout(1000);
이어서 socketTimeout을 설정하는 부분입니다. FTPClient에는 기본적으로 1분이라는 connectTimeout 설정이 적용되어 있는데요.
(상속 관계를 타고 내려가 SocketClient 클래스를 확인해 보면 'DEFAULT_CONNECT_TIMEOUT = 60000'으로 설정되어 있는 것을 확인할 수 있습니다.)
connection timeout은 서버와 연결이 되기까지 설정된 시간 동안 연결이 이루어지지 않았을 때 발생하는 예외이며, socket timeout은 서버 연결 이후 데이터를 받아오는 과정에서 발생하는 예외로 이 둘은 서로 다른 설정인데요.
socketTimeout에 대한 설정이 필요한 경우 해당 메서드를 통해 적용할 수 있습니다.
2023.02.01 - [Programming/Java] - FTP로 살펴보는 SocketTimeout(soTimeout)과 ConnectionTimeout 차이점
(socketTimeout과 ConnectionTimeout에 대한 조금 더 자세한 내용은 위 포스팅을 참고해 주시면 좋을 것 같습니다.)
//file type 설정 (default FTP.ASCII_FILE_TYPE)
ftp.setFileType(FTP.BINARY_FILE_TYPE);
해당 설정은 데이터 전송 방식에 대한 설정인데요.
FTPClient에 기본으로 설정된 전송 방식은 'ASCII_FILE_TYPE'이며, 해당 방식의 경우 텍스트를 전송하기 위해 사용하는 방식이기 때문에 'ASCII_FILE_TYPE'으로 이미지를 전송하게 되면 이미지 파일이 깨져서 열리지 않게 됩니다.
따라서 이미지 파일을 전송할 경우에는 다음과 같이 'BINARY_FILE_TYPE'으로 전송 타입 변경이 필요합니다.
(binary 전송 방식의 경우 파일을 바이트 단위로 정확히 보내는 방식으로 이미지, 압축 파일, 실행 프로그램 등을 보낼 때 해당 방식이 사용됩니다.)
close() method
public void close() {
try {
ftp.logout();
ftp.disconnect();
} catch (IOException e) {
e.printStackTrace();
log.error("FTPClient:: server close failed.");
}
}
파일 업로드, 다운로드, 삭제 등의 요청이 끝나고 FTPClient의 연결을 종료하는 close() 메서드입니다.
해당 메서드의 경우 logout() 메서드와 disconnect() 메서드를 통해 접속을 종료하며, 해당 부분에서 발생할 수 있는 IOException에 대한 처리를 해주는 간단한 로직입니다.
upload() method
public void upload(MultipartFile file) {
open();
InputStream inputStream = null;
try {
inputStream = file.getInputStream();
ftp.storeFile(file.getOriginalFilename(), inputStream);
} catch (IOException e) {
e.printStackTrace();
log.error("FTPClient:: file upload failed.");
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
log.error("FTPClient:: file upload failed.");
}
close();
}
}
파일 업로드 메서드입니다. 해당 메서드에는 위에서 구현된 open(), close() 메서드가 사용되는 것을 볼 수 있습니다.
여기서 실질적인 파일 업로드 메서드는 storeFile() 메서드이며, 해당 메서드의 첫 번째 인자로는 파일 이름을 포함한 파일이 올라갈 전체 경로가 들어가며, 두 번째 인자로는 파일의 inputstream instance가 들어갑니다.
위 코드에서는 자원(inputstream)을 사용하는 부분을 try-finally 방식으로 처리하고 있는데요.
JDK1.7부터는 try-with-resources 방식을 사용할 수 있기 때문에 해당 방식을 적용하면 아래와 같은 코드로도 사용할 수 있다는 점 참고하면 좋을 것 같습니다.
public void upload(MultipartFile file) {
open();
try (InputStream inputStream = file.getInputStream()) {
ftp.storeFile(file.getOriginalFilename(), inputStream);
} catch (IOException e) {
e.printStackTrace();
log.error("FTPClient:: file upload failed.");
} finally {
close();
}
}
여기까지가 java FTPClient를 통한 ftp 파일 업로드의 기본적인 구현인데요.
이 기본 구현을 바탕으로 'changeWorkingDirectory()', 'makeDirectory()' 메서드를 사용하여 파일을 저장할 폴더를 생성하는 등의 커스텀 과정을 추가할 수 있고, 이 과정에서 필요한 경우 'sendSiteCommand()' 메서드를 통해 생성된 폴더의 권한을 주는 작업도 추가할 수 있습니다.
파일 다운로드 및 삭제의 경우에도 핵심이 되는 메서드만 파악하여 위 open(), close() 기반으로 어렵지 않게 구현할 수 있습니다.
//파일 삭제의 경우 deleteFile() 메서드, 파일 다운로드의 경우 retrieveFile() 메서드
< java ftp 관련된 자료 및 코드 github 주소>
2023.02.06 - [Programming/Java] - Java 파일 확장자 구하는 방법 (filename extension)