본문 바로가기
실습/리눅스 서버 + 스프링 부트

[JavaScript/HTML] 새 창에서 열기 시 response에 따른 분기

by 이민우 2024. 3. 31.
728x90
반응형

프로젝트 진행 중 파일 다운로드 기능을 만들며, 한 가지 의문이 생겼다.

 

파일 다운로드 기능의 모든 로직은 성공할 수 없다. 예를 들어 특정 기간 내 데이터를 DB에서 불러와 파일로 생성해 다운로드 하려고 하는데 해당 기간 내에는 적재된 데이터가 존재하지 않을 수도 있다.

 

이런 경우 그냥 404로 띄우면 될 일이긴 하다. 하지만 굳이 그러고 싶지 않았고, 404 페이지로 넘어가기 전에 모달창으로 왜 다운로드 할 수 없는지 이유를 사용자에게 설명해주고 싶었다.

 

골머리를 앓았지만 방법은 의외로 쉬웠다. 그냥 fetch 라는 기능을 사용하면 된다.

 

우선 테스트를 위해 직전 프로젝트에서 사용했던 프로젝트를 불러온다.

https://123okk2.tistory.com/507

 

[JAVA] POI 라이브러리를 사용한 엑셀 파일 만들기

POI 라이브러리를 사용해 사용자에게 업로드된 엑셀 파일을 읽는 포스팅은 직전 포스팅에서 복기했다. 그렇다면 기존 데이터를 엑셀 파일로 만들어서 다운로드하게 해주는 기능은 어떻게 해야

123okk2.tistory.com

 

프로젝트 내 코드는 전부 사용하고, 추가로 간단한 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 코드를 원복해 다운로드도 잘 되는지 확인한다.

 

잘 다운로드 됨을 확인할 수 있다.

728x90
반응형