[Error/Spring] Caused by: java.io.IOException: entry 'sample.txt' closed at '0' before the '10000' bytes specified in the header were written

2025. 7. 2. 23:31Backend/Spring

 

특정 데이터를 파일(.tar)로 압축할 때 생길 수 있는 에러에 관한 글입니다.

 

tar는 tape archive의 약자로, 파일 형식 혹은 명령어를 의미합니다. tar는 여러 파일이나 디렉토리를 묶을 때 사용하는데요. 놀라운 건 tar는 zip 등과 달리 파일 압축을 하지는 않습니다. tar를 만들기 위해 spring 에서 TarArchiveEntry를 사용할 수 있습니다. TarArchiveEntry는 파일(또는 디렉토리)에 대한 메타데이터를 헤더에 기록하게 됩니다. 

 

리눅스에서 archive로 묶는 tar를 생성하고 싶다면

  리눅스 명령어
파일(또는 디렉토리) archive tar -cvf [생성파일.tar] [디렉토리 or 파일]
파일(또는 디렉토리) archive + 압축 tar -czvf [생성파일.tar] [디렉토리 or 파일]
파일(또는 디렉토리) archive 해제 tar -xvf [생성파일.tar]
파일(또는 디렉토리) archive + 압축 해제 tar -xzvf [생성파일.tar]

 

TarArhiveEntry를 이용하여 tar를 만들고자 한다면, 다음과 같은 라이브러리를 추가해줘야 합니다.

implementation 'org.apache.commons:commons-compress' // 버전 생략

 

GPT를 활용하여 확인한 예시 코드입니다.

File tar = new File("test.tar");
File file = new File("sample.txt");

try (
    FileOutputStream fos = new FileOutputStream(outputTar);
    BufferedOutputStream bos = new BufferedOutputStream(fos);
    TarArchiveOutputStream tarOut = new TarArchiveOutputStream(bos)) {

    TarArchiveEntry tarArhiveEntry = new TarArchiveEntry(file, file.getName());
    tarArhiveEntry.setSize(file.length());
    tarOut.putArchiveEntry(tarArhiveEntry);

	//코드 생략 (file을 write하거나 copy하거나)

    tarOut.closeArchiveEntry();
}

//이하 생략

 

위 코드에서 주요 특징은 다음과 같습니다.

 

1) TarArchiveOutputStream : .tar 파일에 데이터를 작성할 때 사용하는 stream

2) TarArchiveEntry : tar 에 각 파일의 메타데이터를 가지고 있는 Entry

 

 

😂원인

Caused by: java.io.IOException: entry 'sample.txt' closed at '0' before the '10000' bytes specified in the header were written

 

TarArchiveEntry를 이용하게 되면, 위와 같은 에러가 간혹 발생할 수 있는데요! 

 

에러가 발생했던 원인은, TarArchiveEntry를 이용하여 파일 크기를 명시하게 되는데, 명시된 사이즈와 TarArchiveEntry를 close 할 때 확인되는 파일 크기와 달랐기 때문입니다.

 

tarOut.putArchiveEntry()를 통해 tar 파일에 기록된 파일 크기가 10000 bytes였으나,  tarOut.closeArchiveEntry() 에서는 지금까지 사용한 bytes가 0이었다는 겁니다. 이렇게 크기가 차이가 날 경우에 IOException이 발생하게 됩니다.

 

추가로, 

Caused by: java.io.IOException: request to write '@@@' bytes exceeds size in header of '10000' bytes for entry 'sample.txt'

 

다음과 같은 에러가 발생한다면, putArchiveEntry() 시점에서는 파일 크기가 10000 bytes였으나, closeArchiveEntry() 시점에서는 훨씬 큰 파일 크기가 사용되었음을 확인해서 IOException이 발생하게 됩니다.

put(10000) > close(0) entry 'sample.txt' closed at '0' before the '10000' bytes specified in the header were written
put(10000) < close(more) equest to write '@@@' bytes exceeds size in header of '10000' bytes for entry 'sample.txt'

 

📝해결

 

tar 파일로 만든다는 건, 기존에 계속 업데이트되는 파일(또는 디렉토리)이 존재함을 가정합니다. 

 

그래야 업데이트되는 파일들을 붙이고 모아서 tar 파일로 만들 수 있고, 해당 tar 파일을 클라이언트에 제공할 수 있을 테니까요.

 

그래서 개인적으로 고민했던 몇 가지 방안을 아래에 적어봤습니다.

 

1. 클라이언트 측의 코드 수정

 

파일이 업데이트되는 찰나에, 파일의 크기가 0이 될 수 있습니다. 서버 측에서는 파일을 생성해놓고 기록하는 중인데, 클라이언트에서는 파일을 가져가려고 한 겁니다. 

 

그래서 사이즈가 0인 경우, 한 번 더 요청할 수 있게 전략을 수정하면 좋을 것 같습니다.

 

2. 서버 측 코드 수정

 

반대로, 서버측에서는 클라이언트가 가져가는 파일의 크기가 0이 되지 않도록, 파일 생성 전략을 고민해야 될 것으로 파악됩니다.

 

예를 들어, 1개의 파일만 가져갈 수 있다고 한다면, 임시 디렉토리를 만들어 tar 파일을 미리 생성해주는 겁니다.

 

또는 기존 파일(예: A.txt)에서 업데이트된 파일(A.txt.tmp)을 생성하고, renameTo()를 이용하여 빠르게 교체하는 방법이 있을 것으로 파악됩니다.

 

 

틀린 정보가 있다면, 말씀해주시면 감사하겠습니다😊