framework/spring

스프링 mvc2 - 9. 파일 업로드

wooweee 2023. 4. 5. 23:31
728x90

1. 파일 업로드 소개

  • form 전송 방식 2가지
    1. application/x-www-form-urlencoded
    2. multipart/form-data


  • application/x-www-form-urlencoded
    • 가장 기본적인 방법
    • Form tag에 별도의 enctype 옵션이 없는 경우 web browser 역할
    • content-type: application/x-www-form-urlencoded를 추가
    • form에 입력한 전송할 항목을 HTTP Body에 문자로 & 로 구분해서 전송

  • multipart/form-data
    • file 업로드시  application/x-www-form-urlencoded의 방식의 문제점
      1. binary 데이터 전송이 필요한데 문자 전송 방식이기 때문에 부적합
      2. 실제로 파일만 전송하는 경우가 드물고 문자와 바이너리 파일을 복합적으로 보내는 경우가 대다수
    • 문자와 binary file을 동시에 전송하기 위해서 multipart/form-data 전송 방식을 제공

 

  • multipart/form-data 방식
    • form tag에 enctype="multipart/form-data" 작성
    • 실제 전송되는 http 메시지 생김새
      • boundary=------random값 으로 구분
      • 각 boundary마다 해당 값에 맞는 헤더 타입과 바디값을 가진다.
    • 전송 HTTP message의 핵심은 Part 라는 부분으로 나누어져서 해당 타입에 맞게 전송한다는 것

스프링 mvc2편 파일 업로드 pdf

 

 

2. 서블릿과 파일 업로드1 - 서버로 파일 보내기

 

  • 파일 보낼 form과 controller 생성
@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();// 중요. parts가 httpMessage에 각각의 부분을 의미하는 것
        log.info("parts={}", parts);

        return "upload-form";
    }

}

 

  • form 형식을 encype = "multipart/form-data"로 만들었다.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>상품 등록 폼</h2>
    </div>
    <h4 class="mb-3">상품 입력</h4>
    <form th:action method="post" enctype="multipart/form-data">
        <ul>
            <li>상품명 <input type="text" name="itemName"></li>
            <li>파일<input type="file" name="file" ></li>
        </ul>
        <input type="submit"/>
    </form>
</div> <!-- /container -->
</body>
</html>

 

  • console 창에 request 찍어주기
# applications.properties
# http message를 console 창에서 볼 수 있다.
logging.level.org.apache.coyote.http11=debug

 

  • 결과
    • 2개의 parts가 나온 것을 확인 할 수 있다.
    • request를 받는 class가 StandardMultipartHttpServletRequest로 바뀐 것을 확인 할 수 있다.
    • 원래는 RequestFacade 가 기본으로 나왔었다.
    • itemName과 같은 단순 text는 직접 가져올 수 있다.
    • file은 binary 파일이기에 못가져오지만 parts=[]에 binary file이 들어온 것을 확인 할 수 있다.

 

# applications.properties
spring.servlet.multipart.enabled=false  # default가 true
  • 멀티파트와 관련된 처리를 못하게 막아서 StandardMultipartHttpServletRequest가 사용되지 못하고 기본 request 받는 RequestFacade를 사용한다.
  • 하지만 itemName=null 과 parts=[] 는 이렇게 값 자체를 받지를 못하게 된다.

 

  • 작동 방식 * 깊이 알 필요가 없다. 더 좋은 MultipartFile가 존재
    1. 스프링이 DispatcherServlet에서 멀티파트 리졸버(Multipartresolver)를 실행
    2. 멀티파트 요청인 경우 일반적인 HttpServletRequest(RequestFacade)에서 MultipartHttpServletRequest로 변환해서 반환
      * MultipartHttpServletRequest은 HttpServletRequest의 자식 인터페이스이고 멀티파트와 관련된 추가 기능을 제공한다.
    3. MultipartHttpServletRequest를 주입받는데 이를 구현한 StandardMultipartHttpServletRequest를 반환한다.

 

3. 서블릿과 파일 업로드2 - 서버로 파일 받기

 

파일 업로드를 위해 실제 파일이 저장되는 경로 필요

directory 생성 후 application.properties 저장하기

file.dir=/Users/유저명/~~~/

 

  • 주요 메서드
    • part.getSubmittedFileName() : client가 전달한 파일명
    • part.getInputStream() : Part의 전송 데이터를 읽을 수 있다.
    • part.write(directory경로 + 파일명) : Part를 통해 전송된 데이터를 저장할 수 있다.
package hello.upload.controller;

@Slf4j
@Controller
@RequestMapping("/servlet/v2")
public class ServletUploadControllerV2 {

    // application.properties에 있는 속성 그대로 들고 올 수 있다.
    @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();// 중요. parts가 httpMessage에 각각의 부분을 의미하는 것
        log.info("parts={}", parts);

        for (Part part : parts) {

            // 헤더와 헤더 값 찍기
            log.info("==== PART ====");
            log.info("name={}", part.getName()); // form의 name칸의 name
            Collection<String> headerNames = part.getHeaderNames(); // request header name, string 같은 경우는 1가지지만, file 같은 것은 2가지가 나온다.
            for (String headerName : headerNames) {
                log.info("header {} : {}", headerName, part.getHeader(headerName)); // headerName: 헤더 tag 명, part.getHeader(headerName) : 해당 tag명의 실제 값
            }

            // 편의 메서드
            // content-disposition; filename
            // part body size

            log.info("submittedFileName={}", part.getSubmittedFileName()); // 실제 파일 이름
            log.info("size={}", part.getSize()); // 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";
    }

}

 

 

4. 스프링과 파일 업로드

  • MultipartFile 이라는 인터페이스로 멀티파트 파일을 편리하게 지원
    • 제공 기능
      1. file.getOriginalFilename() : 업로드 파일 명
      2. file.tramsferTo(...) : 파일 저장
@Slf4j
@Controller
@RequestMapping("/spring")
public class SpringUploadController {
    @Value("${file.dir}") // application.properties에 있는 속성 그대로 들고 올 수 있다.
    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(); // spring 제공하는 기능
            log.info("파일 저장 fullPath={}", fullPath);
            file.transferTo(new File(fullPath));
        }
        return "upload-form";
    }
}

 

5. 예제로 구현하는 파일 업로드, 다운로드

 

  • 지금 해봤자 까먹을꺼 같애서 나중에 필요할 때 강의 듣기