나의 개발일지

[Java] Gdal 라이브러리 사용하기 - 1. InputStream to TempFile 변환 및 후처리 본문

Language/Java

[Java] Gdal 라이브러리 사용하기 - 1. InputStream to TempFile 변환 및 후처리

사각분무기 2025. 4. 28. 20:15

개요

InputStream으로 받은 파일은 보통 디스크에 저장하지 않고 그대로 활용하는 것이 속도 측면에서 좋습니다. 하지만 사용하고자 하는 라이브러리에서 InputStream을 받지 않는 경우에는 디스크에 저장할 수 밖에 없습니다. 이 글은 디스크에 저장한 InputStream을 통해 저장한 임시 파일을 안정적으로 삭제까지 하는 방법을 기록합니다.


발단

최근 이력서를 넣은 한 회사에서 과제를 주었습니다. 파일을 Gdal이라는 라이브러리로 변환하는 것이 주요 골자입니다. 이번 글의 핵심은 Gdal의 API 중 하나인 gdal.Open 메소드에 있습니다.

public class gdal {
    // name(=path)의 위치에 있는 파일을 Gdal에서 정의한 파일 형식인 Dataset 객체로 불러옵니다.
    public static Dataset Open(String name) {}
}

문제

GdalInputStream이 아닌 파일 경로를 받습니다. 말인즉 S3 같은 경로를 통해 받은 InputStream을 변환해야 할 경우 디스크에 저장한 후 사용해야 합니다. InputStream을 바로 사용할 수 있다면 할 필요 없는 걱정이지만, 디스크에 저장해서 사용하는 만큼 자칫 임시 파일들이 제대로 삭제되지 못하고 무분별하게 쌓일 위험이 있습니다. 이를 방지하기 위해 저장한 파일을 삭제하는 코드도 철저히 작성해야 합니다. 저는 아래와 같은 순서로 파일을 처리했습니다.

  1. 임시 파일 생성
  2. 생성한 파일에 InputStream 저장
  3. Gdal 라이브러리 사용 (이번 포스트에선 다루지 않습니다.)
  4. 파일 삭제

해결

임시 파일 생성

먼저 경로를 지정합니다. 직접 temp 파일을 지정하고 파일명 생성기도 만들 수 있겠지만 java.io 패키지에서 제공하는 File 클래스의 메소드를 활용합니다. 또한 이 방식을 통해 추후 삭제도 쉽게 할 수 있습니다.

public class File implements Serializable, Comparable<File> {
    public static File createTempFile(String prefix, String suffix) {}
}
  • prefix는 세글자 이상이여야 합니다.
  • suffix에 null 값을 입력할 경우 자동으로 .tmp를 접미어를 사용합니다.
  • prefix + 난수 + suffix 의 파일이름을 배정해줍니다.
    • 파일명은 거의 unique 합니다.

윈도우 기준 C:\Users\사용자이름\AppData\Local\Temp 경로로 파일이 저장됩니다.

생성한 파일에 InputStream 저장

InputStream을 저장합니다. 앞서 생성해 둔 임시 파일의 경로에 그대로 덮어씁니다. 이번에는 java.nio.file 패키지의 Files 클래스의 메소드를 활용합니다.

public final class Files {
    public static long copy(InputStream in, Path target, CopyOption... options) {}
}
  • InputStream inInputStream을 넣어줍니다.
  • Path target에 앞서 생성해 둔 filePath를 넣어줍니다.
    • FiletoPath() 메소드를 통해 Path 객체를 반환 받을 수 있습니다.
  • CopyOption... optionsStandardCopyOption.REPLACE_EXISTING을 넣어줍니다.
    • 덮어쓰기 옵션이 추가됩니다.

파일 삭제

가장 중요한 부분입니다. 이 부분을 누락할 경우 일정 주기마다 터지는 저주 받은 서버가 완성됩니다. 혹자는 주말마다 서버가 터져서 골머리를 썩이는 헤프닝을 겪었다고 합니다.
java.nio.file 패키지의 Files 클래스를 다시 사용해줍니다.

public final class Files {
    public static void delete(Path path) throws IOException) {}
}
  • Path path에 생성했던 파일의 Path를 넣어줍니다.

File의 삭제 메소드가 아닌 Files 메소드를 사용합니다. Filedelete 메소드의 경우 삭제에 실패했을 경우 false를 반환하기 때문에 확실하게 예외를 반환해주는 Filesdelete 메소드를 활용합니다.

여담이지만 FiledeleteOnExit 메소드도 제대로 먹히지 않았습니다. 메소드의 생명 주기에 맞춰 삭제되었으면 싶지만 애플리케이션 자체가 멈췄을 경우에 삭제가 되기 때문에 주말마다 서버가 터지는 일은 막을 수 없을 것 같습니다.

전체 코드

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class TempFile implements Closeable {

    private final File tempFile;

    public static TempFile withInputStream(InputStream inputStream, String filename) {
        try {
            File file = File.createTempFile("first-temp_", "." + FilenameUtils.getExtension(filename));
            Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING);

            return new TempFile(file);

        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    @Override
    public void close() {
        try {
            Files.delete(tempFile.toPath());
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}
  • File을 직접 사용하기보다 File을 한번 감싼 래퍼 클래스를 활용했습니다.
    • Closeable을 Implement 하기 위함입니다.
    • 이를 통해 try-with-resources문법을 활용하여 더 안전한 사용이 가능합니다.