nextjs 파일,이미지 다운로드(S3 등 버킷에서 권한 허용없이 URL에 fetch로 접근하는법 feat.Cors 막기)

김철준·2024년 7월 14일
0

next.js

목록 보기
7/7

이미지 버킷 URL로부터 파일 다운로드

프로젝트 중 첨부파일을 다운로드하는 기능을 개발을 하고 있다.
프로젝트할 때마다 대부분 있는 기능이어서 보통 개발할 때, 이미지 버킷 URL에서 fetch를 해와서 a 태그 download 기능을 이용해 다운로드하게 한다.

다음은 코드예시이다.

예시 코드

export default async function downloadImage(url: string, name: string): Promise<void> {
  try {
    // 지정된 URL에서 이미지 데이터를 가져옵니다
    const resp = await fetch(url); // https://s3.~~~~~.com
    
    // 가져온 데이터를 Blob으로 변환합니다
    const blob = await resp.blob();

    // Blob을 이미지를 나타내는 URL로 변환합니다
    const imageUrl = window.URL.createObjectURL(blob);
    
    // 다운로드를 위한 가상의 <a> 요소를 생성합니다
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = imageUrl;
    a.download = name; // 다운로드될 파일의 원하는 이름을 설정합니다
    document.body.appendChild(a);
    
    // 가상의 <a> 요소를 클릭하여 다운로드를 시작합니다
    a.click();

    // 다운로드가 완료되면 생성한 URL을 해제합니다
    window.URL.revokeObjectURL(imageUrl);
  } catch (error) {
    // 다운로드 중에 오류가 발생하면 알림을 표시합니다
    alert('다운로드 중 오류가 발생했습니다. 죄송합니다.');
  }
}

보통 이런식으로 파일 다운로드를 한다.
하지만 url에서 fetch를 할 때,이미지 버킷(ex.S3)에서 권한 허용을 해주지 않으면 CORS오류가 발생한다.

CORS 오류는 요청 도메인과 응답 도메인이 다를 때, 클라이언트측(프론트)에서 네트워크 요청을 하면 서버측에서 내뱉는 오류이다.
서버측에서 CORS 허용 설정을 해주지 않으면 발생한다.

"use client"

import React from 'react';

export interface Portfolio {
  fileName: string;
    filePath: string;
  inquiryPortfolioId: number;
}
/**
 * 첨부파일
 */
const AttachedFile = ({files}:{files:Portfolio[]}) => {


    async function exampleDownload(file: Portfolio){

      const rseponse =   await fetch(file.filePath)
        console.log("response", rseponse)

    }

    return (
        <div className={"flex flex-col gap-[10px]"}>
            <span className={"text-title3Normal text-achromatic-alternative w-[160px]"}>첨부파일</span>
            {files.map((file, index) => <div
                key={index}
                className="w-[200px] h-10 border border-achromatic-alternative rounded px-2 flex items-center">
                <button
                    className={"text-achromatic-alternative w-full h-full flex items-center justify-center"}
                    onClick={() => exampleDownload(file)}

                >
                    {decodeURIComponent(file.fileName)}
                </button>
            </div>)}
        </div>
    );
};

export default AttachedFile;

response 값

서버에서 no-cors라고 지정해줘야 파일에 접근가능하다라고 에러 메시지에서 말해주고 있다.

그렇다면 이는 사용하고 있는 버킷으로 가서 접근 가능하도록 CORS 허용 설정을 해주면 된다.

S3 버킷 CORS 허용 설정

  1. aws S3 홈으로 가서 해당 버킷으로 들어간다.

  2. 권한 탭으로 이동한다.

  3. 쭉 내리면 CORS 편집하는 부분이 있다.

  4. 해당 부분에 다음과 같이 모든 도메인이 접근 가능하도록 해준다.

S3 버킷 URL에서 CORS 허용없이 fetch 접근

위와 같이 S3나 다른 버킷에 들어가서 허용해주면 되지만 백엔드 개발자나 데브옵스 하시는 분들께 허용해달라고 요청을 해야한다.

그러면 버킷에서 CORS 허용 설정없이 접근할 수 있는 방법은 뭐가 있을까?

CORS 오류는 브라우저단에서 발생하는 오류다.
즉, 서버에서 접근을 한다면 발생하지 않는다는 것이다.
nextjs도 하나의 서버이므로 서버에서 접근해보자.

nextjs 서버에서 접근하는 방법은 2가지 정도가 있다.
2가지 방법으로 테스트를 해보겠다.

  1. server action으로 접근

서버사이드에서 접근하기위하여 action.ts라는 파일을 별도로 만든다.
그 뒤에 fetch 접근하여 자원을 가져온다.

action.ts

"use server"

export async function getFile(url:string){
    const response = await fetch(url)
    const blob = await response.blob()
    return {
        type: blob.type,
        arrayBuffer: Object.values(new Uint8Array(await blob.arrayBuffer())),
    }
    // return new Blob([Uint8Array.from(data.data.arrayBuffer)], {type: data.data.type})
}

page.tsx

"use client"

import React from 'react';
import {Portfolio} from "@/app/api/inquiry/types/inquiryDetailsResponse";
import {getFile} from "@/app/(page)/inquiry/[details]/action";

/**
 * 첨부파일
 */
const AttachedFile = ({files}:{files:Portfolio[]}) => {


    async function downloadFile2(file: Portfolio) {
        const response =  await getFile(file.filePath)
        const { type, arrayBuffer } = response;
        const blob =  new Blob([Uint8Array.from(arrayBuffer)], { type });
        const downloadUrl = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = downloadUrl;
        a.download = decodeURIComponent(file.fileName);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        window.URL.revokeObjectURL(downloadUrl);    }

    return (
        <div className={"flex flex-col gap-[10px]"}>
            <span className={"text-title3Normal text-achromatic-alternative w-[160px]"}>첨부파일</span>
            {files.map((file, index) => <div
                key={index}
                className="w-[200px] h-10 border border-achromatic-alternative rounded px-2 flex items-center">
                <button
                    className={"text-achromatic-alternative w-full h-full flex items-center justify-center"}
                    onClick={async() => {
                        await downloadFile2(file)
                    }}

                >
                    {decodeURIComponent(file.fileName)}
                </button>
            </div>)}
        </div>
    );
};

export default AttachedFile;

위와 같이 코드를 구성하면 파일 다운로드가 된다.

하지만 보시다시피 다운로드가 되는 반응속도가 상당히 느리다.

WHY?

  1. route handler로 접근
    두번째 서버사이드로 접근하는 방법은 route handler로 접근하는 방법이다.
    코드는 다음과 같다.

route handler는 app > api 디렉터리에 route.ts를 만들어주면 된다.

app > api > file > route.ts

import {NextRequest} from "next/server";

export async function GET(request:NextRequest) {

    const searchParams = request.nextUrl.searchParams
    const url = searchParams.get('url')

    // 파일 경로에 요청
    const rawData = await fetch(url as string);

    const blob = await rawData.blob();
    return Response.json({ data:{
            type: blob.type,
            arrayBuffer: Object.values(new Uint8Array(await blob.arrayBuffer())),
        } })
}

page.tsx

"use client"

import React from 'react';
import {Portfolio} from "@/app/api/inquiry/types/inquiryDetailsResponse";
import {getFile} from "@/app/(page)/inquiry/[details]/action";

/**
 * 첨부파일
 */
const AttachedFile = ({files}:{files:Portfolio[]}) => {

    async function downloadFile(file: Portfolio) {

       const response =  await fetch(`http://localhost:3000/api/file?url=https://cdn.iden-it.com/${file.filePath}`)

        const data = await response.json()
        const blob =  new Blob([Uint8Array.from(data.data.arrayBuffer)], { type:data.data.type });
        const url = window.URL.createObjectURL(blob)
        const a = document.createElement('a');
        a.href = url;
        a.download = decodeURIComponent(file.fileName);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        window.URL.revokeObjectURL(url);

    }



    return (
        <div className={"flex flex-col gap-[10px]"}>
            <span className={"text-title3Normal text-achromatic-alternative w-[160px]"}>첨부파일</span>
            {files.map((file, index) => <div
                key={index}
                className="w-[200px] h-10 border border-achromatic-alternative rounded px-2 flex items-center">
                <button
                    className={"text-achromatic-alternative w-full h-full flex items-center justify-center"}
                    onClick={async() => {
                        await downloadFile(file)
                    }}

                >
                    {decodeURIComponent(file.fileName)}
                </button>
            </div>)}
        </div>
    );
};

export default AttachedFile;

그리고 다시 파일 다운로드 반응 속도를 확인해보자.

server action으로 할 때보다 훨씬 빠른 것을 확인할 수 있다.

버킷의 cors 허용을 요청하기 번거롭다면 이 방법을 써봐도 좋을 것 같다.

(번외)a태그로 다운로드 하는 법

<a href="https://s3~~~~.com" download="다운로드 되는 파일명">파일 다운로드</a>

이 방법의 경우, 다운로드 가능한 확장자가 달랐다.
확장자에 따라 다운로드가 가능한 경우가 있고 해당 파일 URL로 브라우저에서 이동하는 경우가 있었다.

다운로드 가능한 확장자

  • zip

다운로드 불가능한 확장자

  • pdf, 이미지 확장자(png,jpg...)

참조

Next.js에서 AWS S3 첨부파일 다운로드 구현하기 (CORS 에러 없이)

https://powerku.tistory.com/328

profile
FE DEVELOPER

0개의 댓글