FTP 기능 구현

개미는뚠뚠·2024년 7월 19일
0

java

목록 보기
13/15
post-thumbnail

해당 기능은 기상청 API(apihub.kma.go.kr)에서 제공한 한반도 특보 현황 지도를 이미지 파일로 저장하여, FTP를 통해 서버에 전송하는 작업을 구현하였다.

FTP 서버 내 설정도 글로 담고싶지만 보안의 문제로 생략...😒


1. Controller

    public void ftpScheduler() { 

        String imageUrl;
        List<Map<String, Objects>> wrnList = schedulerService.getWrnList(); // 기상특보 이미지 수집 데이터 조회

        if (!wrnList.isEmpty()) {
            for (Map<String, Objects> map : wrnList) {
                String stnId = String.valueOf(map.get("stn_id"));
                String fmFc = String.valueOf(map.get("tm_fc"));
                String fileNum = String.valueOf(map.get("id"));

                imageUrl = ScheduleVO.WRN_IMG_URL.replace("{stnId}", stnId).replace("{tmFc}", fmFc).trim(); // 이미지 다운로드 URL 할당
                String ftpPath = "/kma/downloaded" + fileNum + ".png"; // FTP 서버의 저장 경로

                log.info("IMG URL => {}", imageUrl);

                // FTP 실행 및 기상특보 테이블 업데이트
                try (InputStream in = new BufferedInputStream(new URL(imageUrl).openStream())) {
                    String ftp = schedulerService.uploadToFTP(ScheduleVO.FTP_SERVER, ScheduleVO.FTP_PORT, ScheduleVO.FTP_ID, ScheduleVO.FTP_PW, in, ftpPath);
                    schedulerService.updateWrnInfo(ftp, fileNum);
                    log.info("이미지가 성공적으로 FTP 서버에 업로드되었습니다 => {} ", ftpPath);
                } catch (IOException e) {
                    log.error("이미지를 FTP 서버에 업로드하는 데 실패했습니다 => {} ", e.getMessage());
                }
            }
        }else{
            log.info("업데이트 특보 리스트 없음");
        }
    }

Controller에서의 작업
1. API 호출에 필요한 stnId(지점), tmFc(시간)을 통해 URL을 생성하고, API를 호출한다.
2. ftp 로직을 수행할 service를 호출한다(FTP에 필요한 host, port id, pw, inputStream을 통해 전달 받은 데이터, 파일 저장경로).
3. 작업 상태 업데이트 service 호출(상세 생략).


2. SERVICE

    @Override
    public String uploadToFTP(String server, int port, String user, String pass, InputStream inputStream, String ftpPath) throws IOException {

        FTPSClient ftpsClient = new FTPSClient("TLS", false);
        String resultPath = null;

        try {
            /* 1. FTP Connection(서버 정보, 포트 정보) */
            ftpsClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
            ftpsClient.connect(server, port);

            /* 2. FTP 연결 및 데이터 전송 시간 설정(연결-10초, 데이터 전송-30초) */
            ftpsClient.setConnectTimeout(10000);
            ftpsClient.setDataTimeout(30000);

            /* 3. FTP 서버의 연결 상태 확인 */
            int reply = ftpsClient.getReplyCode();

            if (!FTPReply.isPositiveCompletion(reply)) {
                ftpsClient.disconnect();
                log.error("FTP server refused connection.");
            }

            /* 4. FTP 로그인(서버 접속 계정, 비밀번호) */
            if (!ftpsClient.login(user, pass)) {
                ftpsClient.logout();
                log.error("FTP login failed.");
            }

            /* 5. TLS 세션 보호 수준 및 데이터 채널 보호 설정 */
            ftpsClient.execPBSZ(0);
            ftpsClient.execPROT("P");

            /* 6. PassiveMode 설정 및 파일 타입 지정 */
            ftpsClient.enterLocalPassiveMode();
            ftpsClient.setFileType(FTPSClient.BINARY_FILE_TYPE);

            /* 7. 파일 전송(done = ture 전송 완료 || done = false 전송 실패) */
            boolean done = ftpsClient.storeFile(ftpPath, inputStream);

            if (done) {
                log.info("The file is uploaded successfully.");
                log.info("Save File Path => {}", ftpPath);
                resultPath = ftpPath;
            } else {
                log.error("Failed to upload the file.");
            }
        } catch (IOException ex) {
            log.error("I/O error => {} ", ex.getMessage());
        } finally {
            /* 8. FTP 세션 종료 */
            if (ftpsClient.isConnected()) {
                ftpsClient.logout();
                ftpsClient.disconnect();
            }
            log.info("FTP Scheduler Finish");
        }
        return resultPath;
    }

Service에서의 작업
1. FTPSClient 호출하고 IP, PORT를 통해 FTP 서버와 연결한다.
2. ID, PW 를 통해 FTP 권한을 가진 계정으로 접속한다.
3. 패시브모드와 데이터 보호모드를 수준을 설정한다.
4. store를 통한 실질적인 파일 전송을 진행하고, 성공 여부에 따른 처리를 진행한다.
5. 작업이 완료되면 FTP 서버와 연결을 종료한다.


결과

성공적으로 잘 저장되었다!

FTP 로그

FTP 서버 내 파일


문제해결

553 Could not create file.
라는 에러와 함께 FTP 전송이 중간중간 계속 실패하는 현상이 있었다.
많은 구글링과 AI의 도움도 받았지만, 이틀 간 문제는 해결하지 못했고, 서버 로그를 보게 되었다.

정상적으로 전송된 파일의 로그와 실패한 로그의 byte의 크기가 차이가 나는 것이였다.
뭔가 데이터 전송 시간에 있어서 디폴트로 뭔가 걸려있나? 라는 의문이 들었다.

하지만 관련 자료는 찾지 못했고, 나는 service 코드 중 최대 전송유지 시간을 늘리는 방법을 선택하였다.

/* 2. FTP 연결 및 데이터 전송 시간 설정(연결-10초, 데이터 전송-30초) */
            ftpsClient.setConnectTimeout(10000);
            ftpsClient.setDataTimeout(30000);

그랬더니 성공했다!

원인을 정확히 파악하기 위해 구글링을 했던 내용들을 다 점검하기 시작하였다.

  • FTP 설정파일 문제
  • 권한 문제
  • 네트워크 / 서버 방화벽

등 여러가지 시도했었지만 원인은 파악하지 못했다.

하지만 지속적으로 모니터링을 진행한 결과 문제 없이 정상적인 기능을 수행하고 있고, 기능 구현하면서 새로운 사실도 많이 알았으니 완전 럭키비키잖아~~~

0개의 댓글

Powered by GraphCDN, the GraphQL CDN