본문 바로가기
Front-End

[FE] s3 버켓으로부터 csv 파일 다운로드 받기

by SeanK 2023. 6. 9.

안녕하세요 :)

 

오늘은 사이드프로젝트로 만드는 웹어플에서 파일 다운로드 기능을 만들어 보았는데요,

생각보다 복잡한 것 같아 기록으로 남겨봅니다.

 

우선 일반적인 파일 다운로드 경로는 아래와 같습니다.

일반적 다운로드 방법

보안문제 때문에 보통은 위처럼 클라이언트와 s3 버킷사이에 서버가 위치해 파일 다운로드를 지원해 줍니다.

하지만 이번 사이드프로젝트에서는 핸들링하는 서버가 없습니다. 따라서 클라이언트에서 직접 s3의 csv파일을 다운로드해야 했는데, 이 부분에 문제가 발생했습니다.

직접 s3에 접근할 경우

아래와 같이 코드를 작성하고 이벤트를 발생시키는 순간

<a href={`https://${process.env.REACT_APP_AWS_S3_BUCKET}.s3.ap-northeast-2.amazonaws.com/....csv`} download='.....csv'>
  <Button type="primary" disabled={false}>Download</Button>
</a>

위와 같이 access denied 에러가 발생합니다.

이유는 보안상 문제 때문입니다.

클라이언트는 보안에 취약할 수 밖에 없는 구조이기 때문에 만약 client에서 접근을 시도하면 위처럼 막아버리게 됩니다.

그렇다면 어떻게 해결할 수 있을까요?

해결방법

우선 s3-client를 통해 원하는 파일의 데이터를 버킷으로부터 받아옵니다.

그리고 그 데이터를 transformToString 메서드로 스트링타입으로 변환해 줍니다.

여기서 한 단계 더 나아가 스트링타입의 데이터를 blob으로 변환합니다.

 

여기서 blob이란?

Blob 객체는 이뮤터블한 raw 데이터로 이루어진 파일과 유사한 객체입니다. Blob은 텍스트 또는 이진 데이터로 읽을 수 있으며, ReadableStream으로 변환하여 해당 메서드를 사용하여 데이터를 처리할 수 있습니다.
Blob은 반드시 Javascript 네이티브 형식일 필요는 없습니다. File 인터페이스는 Blob을 기반으로 하며, Blob 기능을 상속하고 사용자의 시스템에 있는 파일을 지원하도록 확장합니다.

 

그리고 URL 객체를 생성해줍니다.

 

여기서 URL 객체란?

URL.createObjectURL()은 매개변수로 주어진 객체를 나타내는 URL을 포함하는 문자열을 생성하는 정적 메서드입니다. URL의 수명은 생성된 창의 문서에 따라 다릅니다. 새 개체 URL은 지정된 파일 객체 또는 Blob 객체를 나타냅니다.

 

그리고 a 태그 엘리먼트를 만들고 해당 태그의 href 속성에 이전에 만든 URL 객체를 넣어줍니다. 마지막으로 a 태그의 클릭 이벤트를 발생시키면 다운로드가 진행됩니다.

 

아래는 그 코드입니다.

import { GetObjectCommand } from "@aws-sdk/client-s3";
import { s3Client } from "aws";

export const getS3Data = async (filepath: string) => {
  try {
    const res = await s3Client.send(new GetObjectCommand({
      Bucket: process.env.REACT_APP_AWS_S3_BUCKET,
      Key: filepath,
    }))
    return res;
  } catch {
    console.error('Could not get files from bucket');
    return undefined;
  }
}

export const downloadS3File = async (filepath: string) => {
  try{
    const res = await getS3Data(filepath)
    const body = res?.Body
    if (body) {
      const _file = await body.transformToString();
      const csvBlob = new Blob([_file], {
        type: 'text/csv;charset=utf-8;'
      })

      const blobUrl = URL.createObjectURL(csvBlob)
      const link = document.createElement('a')
      link.href = blobUrl
      link.download = filepath
      document.body.appendChild(link)
      link.dispatchEvent(
        new MouseEvent('click', {
          bubbles: true,
          cancelable: true,
          view: window,
        })
      )
      document.body.removeChild(link)
    }
  } catch {
    console.error('Could not get files from bucket');
    return undefined;
  }
}