Spring/대용량 파일 전송

[5] 대용량 파일 전송 시스템 개발 - 압축 / 무결성 검증, gzip, pako, MessageDigest

carrotweb 2026. 8. 21. 23:43
728x90
반응형

청크 데이터(이진 데이터)를 그래도 보내도 되지만 압축하면 전송 효율이 좋아지기 때문에 압축 라이브러인 pako(파코)를 사용하여 압축하여 전송할 수 있습니다.

 

Blob(Binary Large Object)를 압축

pako (https://github.com/nodeca/pako)

pako(파코)는 Zlib 라이브러리의 JavaScript 구현체입니다.

 

1. pako(파코) 라이브러리를 CDN으로 가져옵니다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script>

 

pako.gzip()은 Blob 객체를 직접 입력받을 수 없기 때문에 Uint8Array 같은 이진 배열(TypedArray) 형태로 데이터를 변환해서 사용해야 합니다.

 

ArrayBuffer (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)

ArrayBuffer는 원시 바이너리 데이터 버퍼 객체(고정된 크기의 연속된 메모리 바이트를 직접 할당받는 객체)입니다. (바이트 배열)

 

Uint8Array (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)

Uint8Array는 부호 없는 8비트 정수의 배열(0부터 255 사이의 정수 배열) 객체입니다.

 

변환하는 방법

Blob → ArrayBuffer → Uint8Array

// Blob
const blob = new Blob(["Hello"], { type: 'text/plain' });

// Blob를 ArrayBuffer로 변환
const arrayBuffer = await blob.arrayBuffer();

// ArrayBuffer를 Uint8Array로 변환
const uint8Array = new Uint8Array(arrayBuffer);

 

Uint8Array  ArrayBuffer,  Uint8Array → Blob

// Uint8Array "Hello"
const uint8Array = new Uint8Array([72, 101, 108, 108, 111]);

// Uint8Array를 ArrayBuffer로 변환
const arrayBuffer = uint8Array.buffer;

// Uint8Array를 Blob으로 변환
const blob = new Blob([uint8Array], { type: 'application/octet-stream' });

 

2. 클라이언트에서 pako.gzip()를 사용하여 청크 데이터를 압축합니다.

// Blob을 ArrayBuffer로 변환
const arrayBuffer = await chunkData.arrayBuffer();

// ArrayBuffer를 Uint8Array로 변환
const uint8Array = new Uint8Array(arrayBuffer);

// pako로 압축
let compressedData = pako.gzip(uint8Array);

console.log(chunkData);
console.log(chunkData.size);
console.log(compressedData);
console.log(compressedData.length);

 

10240Byte가 8858Byte로 압축되는 것을 확인할 수 있습니다.

 

3. 전송을 위해 압축된 데이터를 다시 Blob으로 변환하고 전송합니다.

// 압축된 데이터를 다시 Blob으로 변환
compressedData = new Blob([compressedData], { type: 'application/gzip' });

console.log(compressedData);
console.log(compressedData.size);

chunkData = compressedData;

 

 

무결성 검증

데이터가 전송 중에 변하지 않았는지 검증하기 위해서 무결성 확인(Checksum) 용도로 단방향 해시 함수를 사용합니다.

 

암호화 JavaScript 라이브러리 (https://github.com/brix/crypto-js)

 

4. 암호화 라이브러리를 CDN으로 가져옵니다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>

 

5. 청크 데이터를 기반으로 단방향 해시 함수를 사용하여 무결성 검증을 위한 코드를 생성합니다.

// Blob을 ArrayBuffer로 변환
const blobBuffer = await chunkData.arrayBuffer();

// 단방향 해시 함수(MessageDigest) 사용, 리턴 값은 ArrayBuffer 객체
const hashBuffer = await crypto.subtle.digest('SHA-256', blobBuffer);

// 배열로 변환
const hashArray = Array.from(new Uint8Array(hashBuffer));

// 16진수 2자리 문자로 변환
verificationCode = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
console.log(verificationCode);

 

6. 서버에서 전송된 파일(청크 데이터)을 읽습니다.

// 청크 파일 읽기
InputStream chunkFileInputStream = chunkFile.getInputStream();

byte[] buffer = new byte[1024]; // 버퍼 크기 설정
int bytesRead = -1;

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while ((bytesRead = chunkFileInputStream.read(buffer)) != -1) {
    byteArrayOutputStream.write(buffer, 0, bytesRead);
}

byte[] compressedData = byteArrayOutputStream.toByteArray();

 

서버에서 전송된 청크 데이터로 단방향 해시 함수를 사용하여 무결성 검증을 위한 코드를 생성하고 클라이언트에 전달 받은 무결성 코드와 비교하여 검증합니다.

// 해시 코드
String hashCode = encryptSHA256(compressedData);

// 무결성 검증
if (verificationCode.equals(hashCode)) {
:
}

 

java.security의 MessageDigest를 사용하여 데이터를 해시한 후 16진수 문자열로 변환합니다.

private String encryptSHA256(byte[] bytes) {
    String ciphertext = "";
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] encodedHash = md.digest(bytes);

        StringBuilder builder = new StringBuilder();
        for (byte b : encodedHash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                builder.append('0');
            }
            builder.append(hex);
        }
        ciphertext = builder.toString();
    } catch (NoSuchAlgorithmException e) {
        ciphertext = "";
    }

    return ciphertext;
}

 

7. 서버에서 Gzip를 사용하여 압축된 청크 데이터를 해제한 후 기존 파일에 이어서 저장합니다.

// 파일 쓰기 스트림
FileOutputStream fos = new FileOutputStream(tempFile, append);

// Gzip으로 압축 해제 후 저장
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedData);
GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream);
while ((bytesRead = gzipInputStream.read(buffer)) != -1) {
	fos.write(buffer, 0, bytesRead);
}

 

728x90
반응형