이미지 다운로드 기능을 구현할 때 발생한 시행착오

Soyeon·2025년 4월 27일
2

1. a 태그의 문제점: 여러 파일 다운로드

문제 코드

  const downloadSelectedImages = async () => {
    selectedImages.forEach((url, index) => {
      const link = document.createElement('a');
      link.href = url;
      link.download = `phototalk_${index + 1}.jpg`;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    });
  };

a 태그의 download 속성으로 href에 지정한 파일을 다운받을 수 있다.

한 장을 다운 받을 때는 정상적으로 작동하지만, 여러 장을 다운로드 받으면 마지막에 선택된 이미지만 다운로드된다.

→ a.click()을 반복 호출하면, 대부분의 브라우저가 보안상 1개만 허용하거나 무시하기 때문

해결 방법: 반복 호출을 무시하지 않게 하기 (순차 다운)

const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));

const downloadSelectedImages = async () => {
  for (let i = 0; i < selectedImages.length; i++) {
    const url = selectedImages[i];
    const link = document.createElement('a');
    link.href = url;
    link.download = `phototalk_${i + 1}.jpg`
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    // 브라우저가 처리할 시간을 줘야 여러 개가 잘 동작함
    await delay(500);
  }
};

but, 이미지 수가 많으면 느려짐.
이 방법 말고 zip으로 압축받는 방법이 있는데,
사용자를 고려했을 때 3~4장일 경우에도 zip으로 압축되면 불편할 것 같다고 생각했다.
따라서, 이미지 수가 많을 때만 zip으로 다운받게 하고, 적당하면 순차 다운받게 하는 방법 선택

→ 10장 이상일 때만 zip + 10장보다 적으면 순차 다운로드하기



2. zip 문제점: CORS 에러

zip으로 다운받으려고 했더니,, CORS 에러가 발생했다.

S3 이미지 URL을 JS에서 가져오려고 하면, S3가 브라우저 요청을 허용하지 않는 것이다.

즉, S3에서 Access-Control-Allow-Origin 헤더가 없기 때문에 브라우저가 JS에서 fetch 요청을 차단한다.

해결 방법: S3에서 CORS 설정 추가하기

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET"],
    "AllowedOrigins": ["*"], 
    "ExposeHeaders": [],
    "MaxAgeSeconds": 3000
  }
]

AWS S3 → 버킷 → “권한” 탭 → “CORS 설정”



3. 이미지 파일 이름이 원하는대로 안나옴

❎ 첫번째 시도: link.download

const fileName = (index: number) => `phototalk_${index + 1}.jpg`;

export const downloadImage = (url: string, index: number) => {
  const link = document.createElement('a');

  link.href = url;
  link.download = fileName(index);
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
};

❎ 두번째 시도: S3 CORS 설정 추가

 "ExposeHeaders": ["Content-Disposition"],

🅾️ 세번째 시도: Blob으로 변환

const fileName = (index: number) => `phototalk_${index + 1}.jpg`;

export const downloadImage = (url, index) => {
  fetch(url)
    .then(response => response.blob()) // 파일을 Blob으로 변환
    .then(blob => {
      const link = document.createElement('a'); // 링크 요소 생성
      link.href = URL.createObjectURL(blob);  // Blob을 URL로 변환
      link.download = fileName;  // download 속성으로 파일명 설정

      document.body.appendChild(link); // DOM에 추가
      link.click();  // 다운로드 트리거
      document.body.removeChild(link); // DOM에서 삭제
    })
    .catch(err => console.error('Download error:', err));
};

Blob을 사용하면 URL을 클라이언트 측에서 처리할 수 있다.

  • Blob? 실제 바이너리 데이터를 포함하는 객체로, 파일의 실제 내용에 접근할 수 있게 해준다.

fetch(url)을 사용하여 서버에서 파일을 가져온 뒤, 그 파일을 blob()으로 처리하고 URL.createObjectURL()을 사용해 클라이언트 측에서 Blob 객체를 가리키는 URL을 생성한다.

이 URL은 로컬에서 처리되는 객체로, 브라우저의 다운로드 제한을 우회할 수 있다.
즉, 서버가 CORS 정첵을 허용하지 않아도 클라이언트에서 다운로드 가능하게 한다.

따라서, 웹 브라우저에서 직접 파일을 다운로드 받기 위해서는, URL에서 Blob으로 바꾸고 다시 URL로 바꿔서 다운로드 링크를 만드는 방식이 필요하다.

  1. URL -> Blob: 이미지의 URL을 통해 해당 이미지 데이터를 Blob 형태로 변환.
  2. Blob -> URL: URL.createObjectURL(blob)을 사용해 Blob 데이터를 가리키는 URL을 생성하고, 이 URL을 <a> 태그의 href 속성에 넣고, 다운로드 링크를 만든다.
  3. URL -> 다운로드: 링크를 클릭하면 브라우저는 이 URL을 통해 파일을 다운로드.

but, 단점 존재함

  • 대용량 파일 처리 시 메모리 증가, 성능 저하
  • 다운로드 속도에 영향
  • IE에서는 blob이 제한적이다

→ 🅾️ 결론, 서버에서 파일 이름을 제어하는 방법이 제일 안정적이다.

const AWS = require('aws-sdk');
const s3 = new AWS.S3();

const params = {
  Bucket: 'your-bucket-name',
  Key: 'example.jpg', // 파일 키
  Body: fileBuffer,
  ContentDisposition: 'attachment; filename="custom_filename.jpg"' // 파일 이름 설정
};

s3.upload(params, function(err, data) {
  if (err) {
    console.log("Error uploading file:", err);
  } else {
    console.log("File uploaded successfully:", data);
  }
});
profile
탄탄한 개발자로 살아남기🗿

0개의 댓글