티스토리 뷰

반응형

 

FTP 접속을 위해서 라이브러리 사용을 위해 build.gradle.kts에 아래와 같이 의존성을 주입해준다.

implementation("commons-net:commons-net:3.9.0")

 

 

우선 로그인까지 해본다.

"hostname.com"에는 접속할 FTP 주소를, "username"에는 아이디, "password"에는 비밀번호를 입력한다.

import org.apache.commons.net.ftp.FTP
import org.apache.commons.net.ftp.FTPClient
import org.apache.commons.net.ftp.FTPReply

class Service {

    fun download() {
        val ftpClient = FTPClient()

        try {
            // 서버와 연결
            // port 기본값은 21이므로 서버 포트가 21인 경우 hostname 파라미터만 넣어도 됨
            ftpClient.connect("hostname.com", 21)

            // 서버와의 응답이 정상인지 확인
            if (!FTPReply.isPositiveCompletion(ftpClient.replyCode)) {
                throw RuntimeException("FTP server connection failed.")
            }
            
            // 로그인
            if (!ftpClient.login("username", "password")) {
                var message = ""
                for (s in ftpClient.replyStrings) {
                    if (message.isNotEmpty()) {
                        message += "\n"
                    }
                    message += s
                }
                throw RuntimeException("FTP server login failed : $message")
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            try {
                if (ftpClient.isConnected) {
                    ftpClient.logout()
                    ftpClient.disconnect()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
    
}

 

 

 

커넥션은 잘 됐는데 로그인에서 아래와 같이 에러가 발생한다.

530 sessions must use encryption

 

 

 

통신하려는 서버는 SSL 인증서를 사용해야하기 때문에 FTPClient가 아닌 FTPSClient를 사용해야한다고 한다.

import org.apache.commons.net.ftp.FTP
import org.apache.commons.net.ftp.FTPReply
import org.apache.commons.net.ftp.FTPSClient

class Service {

    fun download() {
        // TLS/SSL을 위해 FTPClient가 아닌 FTPSClient 사용
        val ftpClient = FTPSClient()

        try {
            // 서버와 연결
            // port 기본값은 21이므로 서버 포트가 21인 경우 hostname 파라미터만 넣어도 됨
            ftpClient.connect("hostname.com", 21)

            // 서버와의 응답이 정상인지 확인
            if (!FTPReply.isPositiveCompletion(ftpClient.replyCode)) {
                throw RuntimeException("FTP server connection failed.")
            }
            
            // 로그인
            if (!ftpClient.login("username", "password")) {
                var message = ""
                for (s in ftpClient.replyStrings) {
                    if (message.isNotEmpty()) {
                        message += "\n"
                    }
                    message += s
                }
                throw RuntimeException("FTP server login failed : $message")
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            try {
                if (ftpClient.isConnected) {
                    ftpClient.logout()
                    ftpClient.disconnect()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
    
}

 

 

아주 잘 통신한다.

이제 FTP 디렉터리에 접근해 필요한 파일을 다운로드 하려 한다.

 

 

 

"/directory"에 접근할 디렉터리 경로를 입력하면 해당 디렉터리에서 파일 목록을 불러오려 한다.

import org.apache.commons.net.ftp.FTP
import org.apache.commons.net.ftp.FTPSClient
import org.apache.commons.net.ftp.FTPReply
import java.io.FileOutputStream

class Service {

    fun download() {
        // TLS/SSL을 위해 FTPClient가 아닌 FTPSClient 사용
        val ftpClient = FTPSClient()

        try {
            // 서버와 연결
            // port 기본값은 21이므로 서버 포트가 21인 경우 hostname 파라미터만 넣어도 됨
            ftpClient.connect("hostname.com", 21)

            // 서버와의 응답이 정상인지 확인
            if (!FTPReply.isPositiveCompletion(ftpClient.replyCode)) {
                throw RuntimeException("FTP server connection failed : ${ftpClient.replyStrings}")
            }

            // 로그인
            if (!ftpClient.login("username", "password")) {
                throw RuntimeException("FTP server login failed : ${ftpClient.replyStrings}")
            }

            // Passive Mode : 클라이언트가 서버로부터 데이터를 내려받음
            // Active Mode : 클라이언트가 서버에 데이터 전송 요청을 하면 서버가 클라이언트에 접속해 파일을 올려줌
            ftpClient.enterLocalPassiveMode()
            // 모든 유형의 파일이 데이터 변형 없이 전송되기 위해 Binary File Type 전송 설정
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE)
            // controlEncoding 기본값이 "ISO-8859-1" 때문에 파일명이 한글인 경우 깨짐
            // 파일명이 한글이 아닌 경우 UTF-8 설정이 따로 필요하지 않음
            ftpClient.controlEncoding = "UTF-8"

            // 디렉터리 변경
            if (!ftpClient.changeWorkingDirectory("/directory")) {
                throw RuntimeException("Remote directory not found.")
            }

            // 파일 목록 불러오기
            val files = ftpClient.listFiles()
            
            // 파일 다운로드
            files.forEach { file ->
                val fileName = file.name
                val downloadPath = "src/main/resources/files/$fileName"
                val outputStream = FileOutputStream(downloadPath)

                println("Downloading $fileName...")

				// 서버의 파일을 다운로드
                if (ftpClient.retrieveFile(fileName, outputStream)) {
                    println("File $fileName has been downloaded successfully.")
                } else {
                    println("File $fileName download failed.")
                }

                outputStream.close()
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            try {
                if (ftpClient.isConnected) {
                    ftpClient.logout()
                    ftpClient.disconnect()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
    
}

 

 

 

파일 목록을 불러오는데 에러가 발생한다.

데이터 연결 또한 암호화가 되어야 한다는 것이다.

522 Data connections must be encrypted

 

 

 

이렇게 PROT 명령을 "P"로 설정해줘야 암호화 처리가 되어 파일 목록을 불러올 수 있다.

import org.apache.commons.net.ftp.FTP
import org.apache.commons.net.ftp.FTPSClient
import org.apache.commons.net.ftp.FTPReply
import java.io.FileOutputStream

class Service {

    fun download() {
        // TLS/SSL을 위해 FTPClient가 아닌 FTPSClient 사용
        val ftpClient = FTPSClient()

        try {
            // 서버와 연결
            // port 기본값은 21이므로 서버 포트가 21인 경우 hostname 파라미터만 넣어도 됨
            ftpClient.connect("hostname.com", 21)

            // 서버와의 응답이 정상인지 확인
            if (!FTPReply.isPositiveCompletion(ftpClient.replyCode)) {
                throw RuntimeException("FTP server connection failed : ${ftpClient.replyStrings}")
            }

            // 로그인
            if (!ftpClient.login("username", "password")) {
                throw RuntimeException("FTP server login failed : ${ftpClient.replyStrings}")
            }

            // Passive Mode : 클라이언트가 서버로부터 데이터를 내려받음
            // Active Mode : 클라이언트가 서버에 데이터 전송 요청을 하면 서버가 클라이언트에 접속해 파일을 올려줌
            ftpClient.enterLocalPassiveMode()
            // 모든 유형의 파일이 데이터 변형 없이 전송되기 위해 Binary File Type 전송 설정
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE)
            // controlEncoding 기본값이 "ISO-8859-1" 때문에 파일명이 한글인 경우 깨짐
            // 파일명이 한글이 아닌 경우 UTF-8 설정이 따로 필요하지 않음
            ftpClient.controlEncoding = "UTF-8"
            // PROT 명령 P : Private을 나타내며 데이터 연결이 TLS로 암호화됨
            // PROT 명령 C : Clear를 나타내며 데이터 연결이 암호화되지 않음을 의미
            ftpClient.execPROT("P")

            // 디렉터리 변경
            if (!ftpClient.changeWorkingDirectory("/directory")) {
                throw RuntimeException("Remote directory not found.")
            }

            // 파일 목록 불러오기
            val files = ftpClient.listFiles()
            
            // 파일 다운로드
            files.forEach { file ->
                val fileName = file.name
                val downloadPath = "src/main/resources/files/$fileName"
                val outputStream = FileOutputStream(downloadPath)

                println("Downloading $fileName...")

                // 서버의 파일을 다운로드
                if (ftpClient.retrieveFile(fileName, outputStream)) {
                    println("File $fileName has been downloaded successfully.")
                } else {
                    println("File $fileName download failed.")
                }

                outputStream.close()
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            try {
                if (ftpClient.isConnected) {
                    ftpClient.logout()
                    ftpClient.disconnect()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
    
}

 

(참고 : https://kldp.org/node/70843)

 

 

 

 

 

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함