티스토리 뷰
반응형
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)
반응형