Programming/Javascript

(Javascript) a 태그의 download 속성을 통한 파일 다운로드 방법 및 예시

Jan92 2024. 7. 14. 01:50
반응형

(Javascript) a tag의 download 속성을 통한 파일 다운로드 방법 및 예시

a tag file download

해당 포스팅은 Javascript에서 '<a>' 태그의 'download' 속성을 활용한 파일 다운로드 방법 및 예시를 정리한 내용입니다.

 


a tag 파일 다운로드 방법

<!-- <a href="/경로/파일명.확장자" download="다운로드되는파일이름.확장자"></a> -->
<a href="/files/example.txt" download="다운로드될파일명.txt">파일 다운로드</a>

 

'<a>' 태그의 경우 'href' 속성에 다운로드할 파일의 URL을 설정하고 'download' 속성을 추가하여 간단하게 파일을 다운로드할 수 있는데요.

 

download 속성의 경우 HTML5에서 추가되었으며, 이 속성을 사용하면 브라우저가 링크를 클릭할 때 파일을 열지 않고 다운로드를 시도하게 됩니다.

download 속성의 값으로는 다운로드될 파일의 이름을 지정할 수 있으며, 값을 지정하지 않은 경우 파일의 원래 이름 그대로 다운로드를 시도하게 됩니다.

 

***

주의할 점으로는 download 속성은 'Same-Origin Policy(동일 출처 정책)'의 영향을 받기 때문에 링크된 파일이 동일 출처에서 제공될 때만 제대로 동작하게 됩니다.

 

 

<!-- javascript function 내부적으로 동작시키는 방식 --> 
<input type="button" value="파일 다운로드" onclick="download_file()">

<script>
    function download_file() {
        const element = document.createElement('a');
        element.setAttribute('href', '/files/example.txt');
        element.setAttribute('download', '다운로드될파일명.txt');
        element.click();
        document.body.removeChile(element);
    }
</script>

 

또는 다음 방식처럼 javascript function 내부적으로 a tag를 생성해서 download 속성을 사용하는 방식이 있습니다.

 

 


(Blob 사용) Javascript에서 api 호출을 통해 파일을 다운로드하는 방법

<input id="download-btn" type="button" value="파일 다운로드" onclick="download_file()">

<script>
    const download_file = async () => {
        // 단순 예시를 위해 filename을 하드코딩 하였으며, 함수의 파라미터로 받아 동적으로 사용할 수 있습니다.
        const filename = 'example.txt';
        // GET 요청 보내기
        fetch('http://localhost:8080/fileDownload?filename=' + filename, {
            method: 'GET',
        })
        .then((response) => response.blob())
        .then((blob) => {
            // blob 객체를 통해 URL 생성
            const url = window.URL.createObjectURL(blob);
            const element = document.createElement('a');

            // 생성한 URL과 다운로드될 파일명 설정
            element.setAttribute('href', url);
            element.setAttribute('download', '다운로드될파일명.txt');

            // 생성된 a tag를 body에 추가
            document.body.appendChild(element);

            // a tag 클릭을 통해 파일 다운로드 시도
            element.click();

            // 다운로드 후 a tag와 URL을 정리
            element.parentNode.removeChild(element);
            window.URL.revokeObjectURL(url);
        });
    }
</script>

 

이어서 Javascript에서 api 호출을 통해 반환된 파일을 Blob을 사용하여 다운로드하는 예시입니다.

 

위 예시에서 사용된 'Blob(Binary Large Object)'는 Javascript에서 이미지나 텍스트 파일, 비디오와 같은 멀티미디어 데이터를 다룰 때 사용됩니다.

또한 Blob는 데이터의 크기 및 MIME 타입 정보를 확인하거나, 파일 전송을 위해 데이터를 Blob 객체로 변환하여 활용됩니다.

 

추가로 'URL.createObjectURL()'은 주어진 객체를 가리키는 URL을 DOM String으로 반환하는 메서드입니다.

 

***

해당 방식의 경우 아래 과정처럼 서버에서 다운로드한 파일이 브라우저를 거쳐 클라이언트에게 다운로드되는 방식이기 때문에 브라우저 내의 메모리 사용량이 늘어난다는 점을 인지해야 하며, 파일의 크기가 너무 큰 경우 브라우저 단에서 문제가 발생할 수 있습니다.

 

1. 데이터 수신: 브라우저가 서버에서 파일 데이터를 수신

2. Blob 객체 생성: 수신된 파일 데이터를 기반으로 Blob 객체 생성

3. Blob URL 생성: Blob 객체를 참조하는 URL을 생성

4. 다운로드 트리거: Blob URL을 통해 파일 다운로드를 시도

 

 


(Blob 사용하지 않고) Javascript에서 api 호출을 통해 파일을 다운로드하는 방법

<input id="download-btn" type="button" value="Download File" onclick="download_file()">

<script>
    function download_file() {
        const filename = 'example.txt';
        const apiUrl = '/fileDownload?filename=' + filename;

        const element = document.createElement('a');
        element.href = apiUrl;
        element.download = filename;
        document.body.appendChild(element);
        element.click();
        document.body.removeChild(element);
    }
</script>

(html 및 Javascript 코드)

 

@GetMapping("/fileDownload")
public void fileDownload(@RequestParam String filename,
                                HttpServletRequest request,
                                HttpServletResponse response) throws IOException {

    Resource resource = new ClassPathResource("static/files/" + filename);
    File file = resource.getFile();

    if (file.exists()) {
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
        response.setContentLengthLong(file.length());

        try (FileInputStream fis = new FileInputStream(file);
             OutputStream os = response.getOutputStream()) {

            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        } catch (IOException ex) {
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());     // 에러 처리
        }
    } else {
        response.setStatus(HttpStatus.NOT_FOUND.value());
    }
}

(파일 반환 api)

 

이전 예시의 Blob를 사용하는 방식처럼 api 요청을 통해 반환받은 파일이 브라우저를 거쳐 메모리가 낭비되지 않게 하기  위해서는 다음과 같이 클라이언트에게 파일을 직접 전송하는 방식을 사용할 수 있는데요.

 

response.setHeader("Content-Disposition", "attachment; filename=" + file.getName());

 

위 코드에서 주목해야 할 부분은 바로 HttpServletResponse 인스턴스에 setHeader() 메서드를 통해 응답의 헤더를 설정하는 부분입니다.

'Content-Disposition'은 HTTP 응답 헤더 중 파일 다운로드를 제어하기 위해 사용되는 헤더이며 'inline' 또는 'attachment'로 디렉티브를 설정할 수 있습니다.

 

1. inline: 브라우저가 내용을 인라인으로 표시해야 함을 나타내는 디렉티브

2. attachment: 브라우저가 내용을 다운로드로 처리해야 함을 나타내는 디렉티브, 파일이 직접 열리지 않고 다운로드 창이 나타나며 클라이언트가 파일을 저장할 수 있음

 

 

추가적으로 예시와 같이 'Content-Disposition' 헤더에 filename 값을 설정하는 경우 download에 적용된 filename보다 헤더에 설정된 filename 값이 우선순위를 가진다는 특징이 있습니다.

 

 


Blob를 통한 방식이 사용되는 이유

Blob를 사용하지 않으면 브라우저의 메모리 낭비도 안되고 다운로드 방식도 간단한데, Blob를 사용하는 이유는 무엇일까요?

 

 

1. api 응답 데이터를 파일로 만들어서 다운로드하는 경우

api 호출로 파일이 아닌 데이터를 반환받는 경우 해당 데이터를 다운로드하려는 파일 형식에 맞게 변환한 후 blob 객체로 변환하여 파일 다운로드 과정을 진행할 수 있습니다.

 

2. Javascript 자체적으로 파일을 만들어서 다운로드하는 경우

api 호출이 아닌 사용자가 입력한 데이터를 기반으로 text 또는 csv 파일 등을 만들어서 다운로드하는 경우, 입력된 데이터를 다운로드하려는 파일의 형식에 맞게 변환한 후 blob 객체로 변환하여 다운로드 과정을 진행할 수 있습니다.

 

 

 

< 참고 자료 >

https://jinminkim-50502.medium.com/file-download-in-javascript-5dc250184a12
https://jjester.tistory.com/106

반응형