프로젝트 진행 중 파일 다운로드 기능을 만들며, 한 가지 의문이 생겼다.
파일 다운로드 기능의 모든 로직은 성공할 수 없다. 예를 들어 특정 기간 내 데이터를 DB에서 불러와 파일로 생성해 다운로드 하려고 하는데 해당 기간 내에는 적재된 데이터가 존재하지 않을 수도 있다.
이런 경우 그냥 404로 띄우면 될 일이긴 하다. 하지만 굳이 그러고 싶지 않았고, 404 페이지로 넘어가기 전에 모달창으로 왜 다운로드 할 수 없는지 이유를 사용자에게 설명해주고 싶었다.
골머리를 앓았지만 방법은 의외로 쉬웠다. 그냥 fetch 라는 기능을 사용하면 된다.
우선 테스트를 위해 직전 프로젝트에서 사용했던 프로젝트를 불러온다.
https://123okk2.tistory.com/507
프로젝트 내 코드는 전부 사용하고, 추가로 간단한 HTML과 HTML을 서빙할 컨트롤러를 작성한다.
DownloadController.java
package com.mwlee.test.service;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class DownloadController {
@GetMapping("/download")
public String downloadPage() {
return "download";
}
}
download.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
lang="ko">
<head>
<script src="https://code.jquery.com/jquery.min.js"></script>
</head>
<body>
<!-- 다운로드 버튼 -->
<button id="downloadBtn">download</button>
<script th:inline="javascript">
$("#downloadBtn").click(function() {
window.open("/download/excel");
})
</script>
</body>
</html>
이게 /download에 들어가면 아래와 같은 화면이 나오고, download 버튼을 누르면 파일이 다운로드된다.
그런데 404나 다른 에러가 return되면 어떻게 될까? RestController을 약간 수정해보자.
위와같이 무조건 500이 return되게 설정해놓았다.
이제 다시 한 번 프로젝트를 실행시켜 download 버튼을 눌러보자.
보는 것과 같이 에러임을 알리는 화면이 출력된다.
나는 위와 같은 상황에서 화면이 아니라 alert로 그 이유를 보여주고 싶다.
그래서 사용한 방법이 fetch() 함수를 사용하는 것이다.
fetch
fetch() 함수는 JavaScript에서 네트워크 요청을 생성하고 응답을 처리하기 위한 메서드이다. 즉 HTTP 요청을 보내고 응답을 받아오는 데 사용된다.
기본적인 사용법은 아래와 같다.
- fetch 함수를 파라미터로 요청을 보낼 url을 설정한다.
- then 메서드에서 응답에 대한 분기를 생성한다.
- catch 메서드를 사용해 실패 시에 대한 분기를 처리한다.
말하기 어려우니 코드를 봐보자.
위에서 작성했던 HTML의 btn 클릭 시 메서드를 아래와 같이 변경했다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ko">
<head>
<script src="https://code.jquery.com/jquery.min.js"></script>
</head>
<body>
<!-- 다운로드 버튼 -->
<button id="downloadBtn">download</button>
<script th:inline="javascript">
$("#downloadBtn").click(function() {
// 타임아웃 설정 (10분) : 얼마나 오래걸릴지 모르므로.
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => controller.abort(), 10*60*1000);
fetch("download/excel", { signal }) // 타임아웃을 설정하지 않을 경우 url만 파라미터로 입력
.then(response => {
// http status code에 따라 분기
if(response.status === 200) {
// OK
const disposition = response.headers.get('Content-Disposition');
const match = /filename="?([^"]+)"?;?/i.exec(disposition);
const filename = match ? match[1] : 'file.xlsx'; // 파일명이 없으면 이렇게 생성
return response.blob()
.then(blob => {
if (!blob || blob.size === 0) {
throw new Error("Response Is Empty");
}
// blob이 비어있지 않으면 파일명과 blob을 then으로 전송
return { blob: blob, filename: filename };
});
}
else if(response.status === 404) {
// NOT_FOUND
throw new Error("Data Not Found")
}
else {
// 기타
throw new Error("Interner Error Occured")
}
})
.then(({blob, filename}) => {
// blob은 byte[]라고 생각하면 됨.
// 간단하게 임시 element를 만들어 파일 다운로드 후 삭제
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
link.style.display = 'none';
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url);
link.remove();
})
.catch(error => {
// 위에서 new Error()의 괄호 안 내용 출력
alert(error.message)
});
})
</script>
</body>
</html>
테스트
이제 프로젝트를 재실행시키고 버튼을 눌러보자
alert가 잘 떴으니, RestController 코드를 원복해 다운로드도 잘 되는지 확인한다.
잘 다운로드 됨을 확인할 수 있다.
'실습 > 리눅스 서버 + 스프링 부트' 카테고리의 다른 글
@DependsOn을 사용한 Bean 생성 순서 제어 (0) | 2024.04.10 |
---|---|
[JAVA] OpenCSV를 이용한 CSV 파싱 (0) | 2024.04.01 |
[JAVA] POI 라이브러리를 사용한 엑셀 파일 만들기 (2) | 2024.03.31 |
[JAVA] POI 라이브러리를 사용한 엑셀 파싱 (0) | 2024.03.30 |
[Webflux] Hadoop HTTPFS에 파일 업로드 (0) | 2024.03.30 |