첨부파일을 서버에 전송하는 방식은 크게 <form> 태그를 이용해서 업로드하는 방식과 Ajax를 이용하는 방식으로 나눠볼 수 있다. 주로 다음과 같은 방식을 이용하는 경우가 대부분이다.
- <form> 태그를 이용하는 방식 : 브라우저의 제한이 없어야 하는 경우에 사용
- 일반적으로 페이지 이동과 동시에 첨부파일을 업로드 하는 방식
- <iframe>을 이용해서 화면의 이동 없이 첨부파일을 처리하는 방식
- Ajax를 이용하는 방식 : 첨부파일을 별도로 처리하는 방식
- <input type = 'file'>을 이용하고 Ajax로 처리하는 방식
- HTML5의 Drag And Drop 기능이나 jQuery 라이브러리를 이용해서 처리하는 방식
브라우저 상에서의 방식은 다양하지만 서버쪽에서의 처리는 거의 대부분 비슷하다. 응답을 HTML 코드로 하는지 아니면 JSON 등으로 처리하는지 정도의 구분만 하면 된다. 이 책은 Ajax를 위주로 처리한다고 한다!
서버에서 주의해야 하는 점은 첨부파일의 처리를 위해서 어떤 종류의 라이브러리나 API 등을 활용할 것인지에 대한 부분이다. 서버에서 첨부파일을 처리하는 방식은 크게 다음과 같은 API 들을 활용한다.
- cos.jar : 2002년도 이후에 개발이 종료되었으므로, 더 이상 사용하는 것을 권장하지 않는다.
- commons-fileupload : 가장 일반적으로 많이 활용되고, 서블릿 스펙 3.0 이전에도 사용 가능
- 서블릿 3.0 이상 - 3.0 이상부터는 자체적인 파일 업로드 처리가 API 상에서 지원
위의 방식에서 가장 일반적인 형태는 commons-fileupload를 이용한 설정이지만, Tomcat 7 버전 이후에는 서블릿 3.0 이상을 지원하므로, 예제에서는 이를 활용하는 방식으로 설정해서 사용하겠다. 첨부파일은 실제 서버가 동작하는 머신 내에 있는 폴더에 업로드 시켜야 하므로 C 드라이브 밑에 upload 폴더와 임시 업로드 파일을 저장할 temp 폴더를 아래와 같은 구조로 생성한다.
1. 스프링의 첨부 파일을 위한 설정
1) 예제를 위해 'ex05' 프로젝트를 Spring Legacy Project로 생성하고, 생성된 프로젝트의 pom.xml 일부는 버전을 변경한다. (아래 그림에서 깜빡하고 spring version을 안바꿨는데 5.0.7.RELEASE로 바꾸도록 하자!)
2) 서블릿 3.0 이상을 활용하기 위해서 pom.xml에 설정된 서블릿의 버전을 수정하고, Lombok 등을 추가한다.
1-1. web.xml을 이용하는 경우의 첨부파일 설정
프로젝트가 web.xml을 이용하는 경우라면 첨부파일의 처리에 대한 설정 역시 web.xml을 이용해서 처리한다. Spring Legacy Project로 생성된 경우에는 서블릿 버전은 2.5 버전이므로 이를 수정하는 작업이 필요하다.
1) web.xml 상단에 XML 네임스페이스가 2.5 버전으로 된 설정을 찾아서 수정한다.
2) web.xml의 <servlet> 태그 내에는 <multipart-config> 태그를 추가한다.
<multipart-config> 의 설정은 특정 사이즈의 메모리 사용, 업로드 되는 파일을 저장할 공간과 업로드 되는 파일의 최대크기와 한번에 올릴 수 있는 최대 크기를 지정할 수 있다.
web.xml의 설정은 WAS(Tomcat) 자체의 설정일 뿐이고, 스프링에서 업로드 처리는 MultipartResolver라는 타입의 객체를 빈으로 등록해야만 가능하다. web과 관련된 설정이므로 servlet-context.xml을 이용해서 설정한다.
3) serlvet-context.xml의 일부에 아래와 같은 코드를 추가한다.
첨부파일을 처리하는 빈을 설정할 때 id는 'multipartResolver'라는 이름으로 지정된 이름을 사용한다.
2. <form> 방식의 파일 업로드
1) 서버상에서 첨부파일의 처리는 컨트롤러에서 이루어지므로, 실습을 위해 UploadController를 작성한다.
package org.zerock.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class UploadController {
@GetMapping("/uploadForm")
public void uploadForm() {
log.info("upload form");
}
}
2) UploadController에는 클래스 선언부에 @RequestMapping이 적용되지 않았으므로, WEB-INF/views 폴더에 uploadForm.jsp 파일을 추가한다.
3) upload.jsp에 아래와 같은 코드를 추가한다.
uploadForm.jsp는 간단하게 <form> 태그만을 생성하고 <input type = 'file'>을 추가한다. 실제 전송은 uploadFormAction 경로를 이용해서 처리한다. 파일 업로드에서 가장 신경 써야 하는 부분은 enctype의 속성값을 'multipart/form-data'로 지정하는 것이다. <input type='file'>의 경우 최근 브라우저에서는 'multiple'이라는 속성을 지원하는데 이를 이용하면 하나의 <input> 태그로 한꺼번에 여러 개의 파일을 업로드할 수 있다.
multiple 속성은 브라우저의 버전에 따라 지원 여부가 달라지므로 IE의 경우 10이상에서만 사용할 수 있다.
4) 작성된 프로젝트는 '/'경로를 이용해서 서비스하도록 지정하고, Tomcat을 통해 확인한다.
2-1. MultipartFile 타입
스프링 MVC에는 MultipartFile 타입을 제공해서 업로드 되는 파일 데이터를 쉽게 처리할 수 있다. 위의 <input type = 'file' name = 'uploadFile'>의 name 속성으로 변수를 지정해서 처리한다.
1) UploaController.java에 아래와 같은 코드를 추가한다.
@PostMapping("/uploadFormAction")
public void uploadFormPost(MultipartFile[] uploadFile, Model model) {
for(MultipartFile multipartFile : uploadFile) {
log.info("--------------------------------------");
log.info("Upload File Name : " + multipartFile.getOriginalFilename());
log.info("Upload File Size : " + multipartFile.getSize());
}
}
파일처리는 스프링에서 제공하는 MultipartFile이라는 타입을 이용한다. 화면에서 첨부파일을 여러 개 선택할 수 있으므로 배열 타입으로 설정한 후 파일을 업로드 해본다. (아직은 파일 이름이 한글인 경우에 대한 설정이 없는 상태이므로 영문 이름의 파일만을 업로드 해서 테스트 하도록 한다.)
아직 결과 페이지를 작성하지 않았기 때문에 아래와 같은 화면이 뜨지만
UploadController에는 정상적으로 파일 데이터가 감지되는 것을 확인할 수 있다.
IE 의경우는 모양새도 조금 다르고 getOriginalFilename()의 결과도 파일이름이 나오지 않고, 전체 경로가 출력된다.
IE 까지 같이 처리하려면 마지막에 경로까지 잘라낸 문자만을 취해야 하는데, 이에 대한 처리는 Ajax 처리 시 알아보도록 한다.
MultipartFile은 다음과 같은 메서드들을 가지고 있다.
String getName() | 파라미터의 이름 <input> 태그의 이름 |
String getOriginalFileName() | 업로드 되는 파일의 이름 |
boolean isEmpty() | 파일이 존재하지 않는 경우 true |
long getSize() | 업로드 되는 파일의 크기 |
byte[] getBytes() | byte[]로 파일 데이터 반환 |
InputStream getInputStream() | 파일데이터와 연결된 InputStream을 반환 |
transferTo(File file) | 파일의 저장 |
[ 파일 저장 ]
업로드 되는 파일을 저장하는 방법은 간단히 transferTo()를 이용해서 처리할 수 있다.
1) UploadCotroller에 아래와 같이 uploadFormPost 메소드를 수정한다.
@PostMapping("/uploadFormAction")
public void uploadFormPost(MultipartFile[] uploadFile, Model model) {
String uploadFolder = "C:\\upload";
for(MultipartFile multipartFile : uploadFile) {
log.info("--------------------------------------");
log.info("Upload File Name : " + multipartFile.getOriginalFilename());
log.info("Upload File Size : " + multipartFile.getSize());
File saveFile = new File(uploadFolder, multipartFile.getOriginalFilename());
try {
multipartFile.transferTo(saveFile);
}catch (Exception e) {
log.error(e.getMessage());
}
}
}
transferTo()의 파라미터로는 java.io.File의 객체를 지정하면 되기 때문에 업로드되는 원래 파일의 이름으로 C 드라이브 upload 폴더에 원래 이름으로 저장한다.
3. Ajax를 이용하는 파일 업로드
첨부파일을 업로드하는 또 다른 방식은 Ajax를 이용해서 파일 데이터만을 전송하는 방식이다. Ajax를 이용하는 첨부파일 처리는 FormData라는 객체를 이용하는데 IE의 경우 10이후의 버전부터 지원되므로 브라우저에 제약이 있을 수 있다.
1) UploadController에 GET 방식으로 첨부파일을 업로드하는 페이지를 제작한다.
@GetMapping("/uploadAjax")
public void uploadAjax() {
log.info("upload ajax");
}
2) WEB-INF/views 폴더에는 uploadAjax.jsp 페이지를 작성한다.
uploadAjax.jsp는 순수한 JavaScript를 이용해서 처리할 수도 있지만, jQuery를 이용해서 처리하는 것이 편리하다. uploadAjax.jsp 내에 jQuery 라이브러리의 경로를 추가(jQuery cnd으로 검색하면 쉽게 링크를 찾을 수 있다.)하고 <script>를 이용해서 첨부파일을 처리한다.
<script
src = "http://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script>
$(document).ready(function(){
$("#uploadBtn").on("click", function(e){
var formData = new FormData();
var inputFile = $("input[name='uploadFile']");
var files = inputFile[0].files;
console.log(files);
});
});
</script>
jQuery를 이용하는 경우에 파일 업로드는 FormData라는 객체를 잉요하게 된다. (브라우저의 제약이 있으므로 주의한다. ) FormData는 쉽게 말해서 가상의 <form> 태그와 같다고 생각하면 된다. Ajax를 이용하는 파일 업로드는 FormData를 이용해서 필요한 파라미터를 담아서 전송하는 방식이다.
본격적으로 첨부파일 데이터를 전송하기 전에 여러 개의 파일을 선택했을 때 jQuery에서 파일 데이터를 처리가 가능한지 브라우저에서 먼저 확인해야 한다.
3-1. jQuery를 이용한 첨부파일 전송
Ajax를 이용해서 첨부파일을 전송하는 경우 가장 중요한 객체는 FormData 타입의 객체에 각 파일 데이터를 추가하는 것과 이를 Ajax로 전송할 때 약간의 옵션이 붙어야 한다는 점이다.
첨부파일 데이터는 formData에 추가한 뒤에 Ajax를 통해서 formData 자체를 전송한다. 이때 processData와 contentType은 반드시 'false'로 지정해야만 전송되므로 주의해야 한다. UploadController 에서는 기존과 동일하게 MultipartFile 타입을 이용해서 첨부파일 데이터를 처리한다.
- UploadController.java
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile) {
log.info("update ajax post.................");
String uploadFolder = "C:\\upload";
for(MultipartFile multipartFile : uploadFile) {
log.info("---------------------------------");
log.info("Upload File Name :"+multipartFile.getOriginalFilename());
log.info("Upload File Size : " + multipartFile.getSize());
String uploadFileName = multipartFile.getOriginalFilename();
//IE has file path
uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
log.info("only file name : " + uploadFileName);
File saveFile = new File(uploadFolder, uploadFileName);
try {
multipartFile.transferTo(saveFile);
}catch (Exception e) {
log.error(e.getMessage());
}//end catch
}//end for
}
uploadAjaxPost()는 기존의 <form> 태그를 이용하던 방식과 아무런 차이가 없지만, 조금 뒤에 Ajax 방식으로 결과 데이터를 전달하면서 리턴 타입이 달라지도록 한다. 파라미터에선 Ajax 방식을 이용하기 때문에 Model을 사용할 일이 없으므로 사용하지 않는다. IE 의 경우에는 전체 파일 경로가 정송되므로, 마지막 '\'를 기준으로 잘라낸 문자열이 실제 파일 이름이 된다.
브라우저를 이용해서 '/uploadAjax'에서 정상적으로 첨부파일이 업로드 되는지 확인한다.
3-2. 파일 업로드에서 고려해야 하는 점들
첨부파일을 서버에 전송하고 저장하는 일은 그다지 복잡한 일은 아니지만, 생각해야 하는 일들이 많다.
- 동일 이름으로 파일이 업로드 되었을 때 기존 파일이 사라지는 문제
- 이미지 파일의 경우에는 원본 파일의 용량이 큰 경우 섬네일 이미지를 생성해야 하는 문제
- 이미지 파일과 일반 파일을 구분해서 다운로드 혹은 페이지에서 조회하도록 처리하는 문제
- 첨부파일 공격에 대비하기 위한 업로드 파일의 확장자 제한
'Spring' 카테고리의 다른 글
[31] 파일 업로드 처리 - 파일 업로드 상세 처리 (1) | 2020.01.06 |
---|---|
@GetMapping, @RequestMapping, @DeleteMapping 등이 import 안되는 에러 (0) | 2020.01.06 |
[29] AOP와 트랜잭션 - 댓글과 댓글 수에 대한 처리 (0) | 2020.01.05 |
ERROR: org.springframework.test.context.TestContextManager - Caught exception while allowing (3) | 2020.01.05 |
[28] AOP와 트랜잭션 - 스프링에서 트랜잭션 관리 (0) | 2020.01.05 |