Spring/Spring MVC

스프링 MVC - 파일 업로드

taey 2024. 10. 4. 01:29

파일 업로드 소개

HTML 폼 전송 방식

  • application/x-www-form-urlencoded
  • multipart/form-data

 


서블릿과 파일 업로드

package hello.upload.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import java.io.IOException;
import java.util.Collection;

@Slf4j
@Controller
@RequestMapping("/servlet/v1")
public class ServletUploadControllerV1 {
     @GetMapping("/upload")
     public String newFile() {
     	return "upload-form";
     }
     
     @PostMapping("/upload")
     public String saveFileV1(HttpServletRequest request) throws ServletException, IOException {
         log.info("request={}", request);
         String itemName = request.getParameter("itemName");
         
         log.info("itemName={}", itemName);
         Collection<Part> parts = request.getParts();
         
         log.info("parts={}", parts);
         return "upload-form";
 	}
}

request.getParts() : multipart/form-data 전송 방식에서 각각 나누어진 부분을 받아서 확인할 수 있다

 

멀티파트 사용 옵션

업로드 사이즈 제한

spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB

큰 파일을 무제한 업로드하게 둘 수는 없으므로 업로드 사이즈를 제한할 수 있다.

사이즈를 넘으면 예외(SizeLimitExceededException)가 발생한다.

max-file-size : 파일 하나의 최대 사이즈, 기본 1MB

max-request-size : 멀티파트 요청 하나에 여러 파일을 업로드할 수 있는데, 그 전체 합이다. 기본 10MB  

 

spring.servlet.multipart.enabled 끄기

spring.servlet.multipart.enabled=false // 기본 true

 

 

참고
spring.servlet.multipart.enabled 옵션을 켜면 스프링의 DispatcherServlet 에서 멀티파트 리 졸버( MultipartResolver )를 실행한다.
멀티파트 리졸버는 멀티파트 요청인 경우 서블릿 컨테이너가 전달하는 일반적인 HttpServletRequest를 MultipartHttpServletRequest 로 변환해서 반환한다.
MultipartHttpServletRequest 는 HttpServletRequest 의 자식 인터페이스이고, 멀티파트와 관련된 추가 기능을 제공한다

스프링이 제공하는 기본 멀티파트 리졸버는 MultipartHttpServletRequest 인터페이스를 구현한 StandardMultipartHttpServletRequest 를 반환한다.
이제 컨트롤러에서 HttpServletRequest 대신에 MultipartHttpServletRequest 를 주입받을 수 있 는데, 이것을 사용하면 멀티파트와 관련된 여러가지 처리를 편리하게 할 수 있다.
그런데 이후 강의에서 설명할 MultipartFile 이라는 것을 사용하는 것이 더 편하기 때문에 MultipartHttpServletRequest 를 잘 사용하지는 않는다.

 

 

파일을 업로드 하려면 실제 파일이 저장되는 경로가 필요하다.

application.properties

file.dir=파일 업로드 경로
예) /Users/taeyoung/study/file/

 

주의

  1.  꼭 해당 경로에 실제 폴더를 미리 만들어두자.
  2. application.properties에서 설정할 때 마지막에 /(슬래시)가 포함된 것에 주의

예시 코드

@Slf4j
@Controller
@RequestMapping("/servlet/v2")
public class ServletUploadControllerV2 {
	@Value("${file.dir}")
	private String fileDir;
     
	@GetMapping("/upload")
	public String newFile() {
		return "upload-form";
	}
     
	@PostMapping("/upload")
	public String saveFileV1(HttpServletRequest request) throws ServletException, IOException {
		log.info("request={}", request);
		String itemName = request.getParameter("itemName");
         
		log.info("itemName={}", itemName);
		Collection<Part> parts = request.getParts();
         
		log.info("parts={}", parts);
		for (Part part : parts) {
			log.info("==== PART ====");
			log.info("name={}", part.getName());
			Collection<String> headerNames = part.getHeaderNames();
            for (String headerName : headerNames) {
                log.info("header {}: {}", headerName, part.getHeader(headerName));
            }
     
			//편의 메서드
			//content-disposition; filename
			log.info("submittedFileName={}", part.getSubmittedFileName());
			log.info("size={}", part.getSize()); //part body size
     
			//데이터 읽기
			InputStream inputStream = part.getInputStream();
			String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
			log.info("body={}", body);
     
			//파일에 저장하기
			if (StringUtils.hasText(part.getSubmittedFileName())) {
				String fullPath = fileDir + part.getSubmittedFileName();
				log.info("파일 저장 fullPath={}", fullPath);
				part.write(fullPath);
			}
		}
		
        return "upload-form";
	}
}

 

Part 주요 메서드

part.getSubmittedFileName() : 클라이언트가 전달한 파일명
part.getInputStream(): Part의 전송 데이터를 읽을 수 있다.
part.write(...): Part를 통해 전송된 데이터를 저장할 수 있다.

 

참고
큰 용량의 파일을 업로드를 테스트 할 때는 로그가 너무 많이 남아서 다음 옵션을 끄는 것이 좋다.
logging.level.org.apache.coyote.http11=trace

 

서블릿이 제공하는 Part는 편하기는 하지만, HttpServletRequest를 사용해야 하고, 추가로 파일 부분만 구분하려면 여러가지 코드를 넣어야 한다.

 

 


스프링과 파일 업로드

스프링은 MultipartFile이라는 인터페이스로 멀티파트 파일을 매우 편리하게 지원한다. 

 

예시 코드

package hello.upload.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;

@Slf4j
@Controller
@RequestMapping("/spring")
public class SpringUploadController {
     @Value("${file.dir}")
     private String fileDir;
     
     @GetMapping("/upload")
     public String newFile() {
     	return "upload-form";
     }
     
     @PostMapping("/upload")
     public String saveFile(@RequestParam String itemName, @RequestParam MultipartFile file, 
			HttpServletRequest request) throws IOException {
        
		log.info("request={}", request);
		log.info("itemName={}", itemName);
		log.info("multipartFile={}", file);
		if (!file.isEmpty()) {
			String fullPath = fileDir + file.getOriginalFilename();
			log.info("파일 저장 fullPath={}", fullPath);
			file.transferTo(new File(fullPath));
     	}
    	
        return "upload-form";
     }
}

 

 MultipartFile 주요 메서드

file.getOriginalFilename() : 업로드 파일 명
file.transferTo(...) : 파일 저장

,