[AWS] S3버킷 파일 zip파일로 묶어서 전송하기

홍종훈·2023년 12월 18일
0

AWS

목록 보기
8/9
post-thumbnail

Download Version Data 버튼을 클릭했을 때 특정 URL에서 모든 데이터를 다운로드하는 기능을 구현하려면, 먼저 해당 URL에서 사용 가능한 모든 파일의 목록을 가져와야 합니다. 그런 다음, 각 파일에 대해 개별적으로 다운로드 요청을 수행해야 합니다.

하지만, 브라우저 보안 정책 때문에 직접적으로 다수의 파일을 동시에 다운로드하는 것은 제한됩니다. 사용자가 각 다운로드에 명시적으로 동의해야만 합니다. 그러므로, 여러 파일을 한 번에 다운로드하는 것은 자동화하기 어렵습니다.

다음은 가능한 해결책 중 하나입니다:

  1. 서버에서 모든 파일을 하나의 압축 파일로 묶은 후, 그 압축 파일을 다운로드하는 링크를 제공합니다. 이렇게 하면 사용자는 단 한 번의 클릭으로 모든 데이터를 받을 수 있습니다.
  2. 사용자에게 각 파일을 개별적으로 다운로드하도록 안내합니다. 이 방법은 사용자 경험 측면에서 이상적이지 않을 수 있습니다.

위의 해결책을 구현하기 위해서는 서버 측에서 추가적인 처리가 필요합니다. 하지만, 이는 클라이언트 사이드에서 해결할 수 있는 부분이 아니므로, 서버 개발자와 협력하여 이를 구현해야 합니다.

아래는 클라이언트 사이드에서 할 수 있는 onClickDownloadVersionData 함수의 예시입니다. 이 함수는 현재 서버로부터 파일 목록을 가져와서, 사용자에게 각 파일을 개별적으로 다운로드할 수 있는 옵션을 제공합니다:

const onClickDownloadVersionData = async () => {
  try {
    const { data } = await axios.get(`${process.env.REACT_APP_SERVER_URL}/data/list`, {
      params: { serverType, dataType, dataVersion },
    });

    data.forEach(file => {
      const url = `https://${process.env.S3_BUCKET_NAME}.s3.ap-northeast-2.amazonaws.com/${file.Key}`;
      const link = document.createElement('a');
      link.href = url;
      link.download = file.Key.split('/').pop();
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    });
  } catch (error) {
    message.error(`${error} failed to load data.`);
  }
};

여러 파일을 압축하여 다운로드 받는 기능을 구현하기 위해서는 서버 측에서 추가 작업이 필요합니다.

이를 위해 archiver 라이브러리를 사용할 수 있습니다. archiver는 파일과 스트림을 ZIP 형식으로 압축하는 데 사용할 수 있는 Node.js 라이브러리입니다.

npm install archiver

그런 다음, S3 버킷에서 파일들을 읽어 ZIP 파일로 압축하고 클라이언트에게 스트림으로 전송하는 함수를 S3Service 클래스에 추가할 수 있습니다. 아래는 이를 위한 예시 코드입니다:

const onClickDownloadVersionData = async () => {
  if (dataVersion === '') {
    Swal.fire('Check', 'Please input data version', 'question');
    return;
  }

  try {
    const body = { serverType, dataType, dataVersion };
    const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/data/version`, {
      params: body,
      responseType: 'blob' // 파일을 Blob 형태로 받기
    });

    // Blob을 다운로드 링크로 변환하여 사용자가 저장할 수 있게 함
    const url = window.URL.createObjectURL(new Blob([response.data]));
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('download', `${dataVersion}.zip`); // 파일명 설정
    document.body.appendChild(link);
    link.click();

    message.success('Download successful.');
  } catch (error) {
    message.error('Download failed: ' + error.message);
  }
};
async downloadAndZipFiles(serverType: string, dataType: string, dataVersion: string, response) {
        try {
            const bucket = dataType === 'server' ? this.s3ServerBucket : this.s3ClientBucket;
            const folderPath = `${serverType}/${dataVersion}/`;

            const listParams = {
                Bucket: bucket,
                Prefix: folderPath
            };
            const listedObjects = await this.s3.send(new ListObjectsV2Command(listParams));

            if (listedObjects.Contents.length === 0) {
                throw new Error('No files found to zip');
            }

            const archive = archiver('zip', { zlib: { level: 9 } });
            archive.on('error', err => {
                throw err;
            });

            response.attachment(`${dataVersion}.zip`);
            archive.pipe(response);

            for (const object of listedObjects.Contents) {
                const stream = await this.getObjectStream(bucket, object.Key);
                archive.append(stream, { name: object.Key.split('/').pop() });
            }

            archive.finalize().then(() => {
                response.end();
            });
        } catch (err) {
            this.logger.error('Error', err);
            response.status(500).send('Error occurred during file download');
        }
    }

    async getObjectStream(bucket: string, key: string): Promise<Readable> {
        const params = { Bucket: bucket, Key: key };
        const command = new GetObjectCommand(params);
        const { Body } = await this.s3.send(command);

        if (Body instanceof Readable) {
            return Body;
        } else {
            throw new Error('Failed to get a valid stream from S3');
        }
    }

위의 코드는 다음과 같이 작동합니다:

  1. 지정된 S3 버킷과 경로에서 파일 목록을 가져옵니다.
  2. archiver를 사용하여 ZIP 파일을 생성합니다.
  3. 각 파일에 대해 S3에서 스트림을 가져와서 ZIP 파일에 추가합니다.
  4. ZIP 파일을 클라이언트에 스트림으로 전송합니다.

이 기능을 사용하기 위해서는 클라이언트 사이드에서도 해당 API 엔드포인트로 요청을 보내고, 응답으로 받은 스트림을 사용하여 파일을 다운로드해야 합니다. 또한, 이 기능을 사용하기 위해서는 서버의 해당 라우트에서 적절한 HTTP 헤더 설정이 필요합니다 (예: Content-Typeapplication/zip으로 설정).

이 방법은 특히 대용량 파일이나 많은 수의 파일을 처리할 때 유용합니다. 그러나 대용량 파일의 경우 메모리 사용량과 네트워크 대역폭을 고려해야 할 수 있습니다.

profile
Search Engineer

0개의 댓글