Programming/Java

Java FTPClient를 통한 ftp 파일 업로드 내용 정리

Jan92 2023. 2. 13. 22:53

java file upload 구현

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() 메서드를 통해 인코딩 설정을 변경해주어야 합니다.

 

 

PrintCommandListener 설정

이어서 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)

https://github.com/JianChoi-Kor/ttotw/blob/master/core-module/src/main/java/com/project/ttotw/lib/FtpUtils.java