⚙️ Backend/Spring Web Project

[33] 파일 업로드 처리 - 첨부파일의 다운로드 혹은 원본 보여주기 - 스프링

코너(Corner) 2021. 5. 31.
반응형

1. 첨부파일의 다운로드

이미지를 처리하기 전에 우선 좀 더 간단한 첨부파일의 다운로드부터 처리하도록 한다. 첨부파일의 다운로드는 서버에서 MIME 타입을 다운로드 타입으로 지정하고, 적절한 헤더 메시지를 통해서 다운로드 이름을 지정하게 처리한다. 이미지와 달리 다운로드는 MIME 타입이 고정되기 때문에 메서드는 아래와 같이 시작하게 된다.

 

- UploadController의 일부 

@GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public ResponseEntity<Resource> downloadFile(String fileName){
  log.info("download file : " + fileName);
  Resource resource = new FileSystemResource(fileFolder + fileName);

  log.info(resource);
  return null;

}

ResponseEntity<>의 타입은 byte[] 등을 사용할 수 있으나, 이번 예제에서는 org.springframework.core.io.Resource 타입을 이용해서 좀 더 간단히 처리하도록 한다.

테스트를 위해서 C:\upload (기타 개인 폴더 경로, 필자는 final String fileFolder = "/Users/corner/upload/"; 를 통해 전역변수로 경로를 지정해 두었다. 영문 파일을 하나 두고, '/download?fileName=파일이름'의 형태로 호출해 본다.

브라우저에는 아무런 반응이 없지만, 서버에는 로그가 기록되는 것을 확인할 수 있다.

서버에서 파일이 정상적으로 인식되었다는 것이 확인되면 ResponseEntity<>를 처리한다. 이 때 HttpHeaders 객체를 이용해서 다운로드 시 파일의 이름을 처리하도록 한다. 

- UploadController의 일부

@GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public ResponseEntity<Resource> downloadFile(String fileName){
  log.info("download file : " + fileName);
  Resource resource = new FileSystemResource(fileFolder + fileName);

  log.info("resource : " + resource);

  String resourceName = resource.getFilename();

  HttpHeaders headers = new HttpHeaders();

  try {
  headers.add("Content-Disposition", "attachment; filename="+ new String(resourceName.getBytes("UTF-8"), 
  "ISO-8859-1"));
  } catch (UnsupportedEncodingException e) {
  e.printStackTrace();
  }
  return new ResponseEntity<Resource>(resource, headers, HttpStatus.OK);

}

MIME 타입은 다운로드 할 수 있는  'application/octet-stream'으로 지정하고, 다운로드 시 저장되는 이름은 'Content-Disposition'을 이용해서 지정한다. 파일 이름에 대한 문자열 처리는 파일 이름이 한글인 경우 저장할 떄 깨지는 문제를 막기 위해서이다. 크롬 브라우저에서 파일경로 폴더에 있는 파일의 이름과 확장자로 '/download?fileName=xxx'와 같이 호출하면 브라우저는 자동으로 해당 파일을 다운로드 하는 것을 볼 수 있따. IE 계열에서는 파일 다운로드가 호출이 안되는 문제가 발생한다. 이에 대한 처리는 조금 뒤에 살펴보도록 한다.

 

1-1. IE/Edge 브라우저의 문제

첨부파일의 다운로드 시 Chrome 브라우저와 달리 IE에서는 한글 이름이 제대로 다운로드 되지 않는다. 이것은 'Context-Disposition'의 값을 처리하는 방식이 IE의 경우 인코딩 방식이 다르기 때문이다.

IE를 같이 서비스해야 한다면 HttpServletRequest에 포함된 헤더 정보들을 이용해서 요청이 발생한 브라우저가 IE 계열인지 확인해서 다르게 처리하는 방식으로 처리한다. HTTP 헤더 메시지 중에서 디바이스의 정보를 알 수 있는 헤더는 'User-Agent'값을 이용한다.

 

기존의 downloadFile()은 'User-Agent' 정보를 파라미터로 수집하고 IE에 대한 처리를 추가한다. Edge 브라우저는 IE와 또 다르게 처리되므로 주의한다.

 

-UploadController.java

@GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
		@ResponseBody
		public ResponseEntity<Resource> downloadFile(@RequestHeader("User-Agent") String userAgent ,String fileName){
			log.info("download file : " + fileName);
			 Resource resource = new FileSystemResource(fileFolder + fileName);
			
			 if(resource.exists() == false ) {
				 return new ResponseEntity<>(HttpStatus.NOT_FOUND);
			 }
			 
			 String resourceName = resource.getFilename();
			 
			 HttpHeaders headers = new HttpHeaders();
			 
			 try {
				 String downloadName = null;
				 if(userAgent.contains("Trident")) {
					 log.info("IE browser");
					 
					 downloadName = URLEncoder.encode(resourceName, "UTF-8").replaceAll("/", "");
					 log.info("Edge name : " + downloadName);
				 } else { 
					 log.info("Chrome browser");
					 downloadName = new String(resourceName.getBytes("UTF-8"),"ISO-8859-1");
				 }
				headers.add("Content-Disposition", "attachment; filename="+ new String(resourceName.getBytes("UTF-8"), 
						 "ISO-8859-1"));
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			 return new ResponseEntity<Resource>(resource, headers, HttpStatus.OK);
			
		}

downloadFile()은 @RequestHeader를 이용해서 필요한 HTTP 헤더 메시지의 내용을 수집할 수 있다. 이를 이용해서 'User-Agent'의 정보를 파악하고, 값이 'MSIE' 혹은 'Trident'(IE 브라우저 엔진 이름)인 경우에는 다른 방식으로 처리하도록 한다.

위의 코드가 적용되면 우선은 Chrome에서 한글파일의 다운로드를 먼저 시도한 후에 인터넷 등을 이용해서 URL 주소로 인코딩하는 페이지를 이용해서 파일 이름을 변호나해 본다. IE에서 주소창에 한글을 적으면 아래와 같이 에러가 발생한다.

IE에서 테스트를 진행하고 싶다면 'URLEncode Website'를 검색해서 인코딩 결과를 알아내고 변환된 문자열로 호출하면 된다.

 

1-2. 업로드된 후 다운로드 처리

다운로드 자체에 대한 처리는 완료되었으므로, /uploadAjax 화면에서 업로드된 후 파일 이미지를 클릭한 경우에 다운로드가 될 수 있도록 처리한다. 이미지 파일이 아닌 경우는 아래와 같이 첨부파일 아이콘이 보이게 된다.

 

수정되어야 하는 부분은 'attach.png' 파일을 클릭하면 다운로드에 필요한 경로와 UUID가 붙은 파일 이름을 이용해서 다운로드가 가능하도록 <a> 태그를 이용해서 '/download?fileName=xxx' 부분을 추가한다.

-uploadAjax.jsp

function showUploadFile(uploadResults, tag){
          var str = "";
          $(uploadResults).each(function(i, obj){
            if(!obj.image){
              // 원본 파일
              var fileCallPath = encodeURIComponent(obj.uploadPath + "/" + obj.uuid + "_" + obj.fileName);
              str += "<li><div><a href='/upload/download?fileName?="+ fileCallPath + "'><img src='/upload/resources/img/attach.png'>"+ obj.fileName +"</a>";
              str += "<span data-file'"+ fileCallPath +"' data-type='file'>x</span></div></li>";
              /*
              data 속성은 Map 구조로 DOM 객체에서 사용할 수 있다.
              */
            } else {
              /* 
              encodeURIComponent("문자열값")
              get방식으로 전송 시 파라미터로 전달할 떄, 값에 인식할 수 없는 문자가 있을 경우 쿼리 스트링 문법에 맞게 변경해야 한다.
              이 때 사용하는 메소드이다. 
              (p.s MacOS에서는 encodeURIComponent를 하지 않아도 잘 되지만, 윈도우에서는 안되는것으로 추측.)
              */
              var fileCallPath = encodeURIComponent(obj.uploadPath + "/s" + obj.uuid + "_" + obj.fileName);

              var originPath = obj.uploadPath + "/" + obj.uuid + "_" + obj.fileName;
              originPath = originPath.replace(new RegExp(/\\/g), "/");

              str += "<li><div><a href=\"javascript:showImage(\'"+ originPath + "\')\">";
              str += "<img src='/upload/display?fileName="+ fileCallPath + "'>" + obj.fileName + "</a>";
              str += "<span data-file='" + fileCallPath + "' data-type='image'>x</span></div></li>";
            }
          })
          $(tag).append(str);
       }

브라우저에서는 <img> 태그를 클릭하게 되면 자동으로 다운로드가 되는 것을 확인할 수 있다.

 

 

반응형

댓글