Spring 파일 업로드

김용현·2024년 1월 20일
0

Spring

목록 보기
10/13

본 포스트는 김영한 님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의를 토대로 작성하였습니다.

파일 업로드 방식

파일을 HTML 폼 전송 방식으로 하는 방법은 크게 2가지가 있다.

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

하나씩 알아보자.

application/x-www-form-urlencoded

html 폼 데이터를 서버로 전송하는 가장 기본적인 방식이다.
form 태그에 별도의 enctype 옵션이 없으면 브라우저는 해당 요청의 Content-Type을
application/x-www-form-urlencoded로 설정한다.

그러나 파일을 전송하기 위해선 바이너리 데이터를 전송해야 한다. 그리고 문자를 전송하는 이 방식으로는 바이너리 데이터를 전송하기 어렵다. 또한 파일 전송 외에 이름, 나이 등 다른 데이터들도 함께 보내는 경우 내용을 구분하기 위한 방법이 필요하다.

multipart/form-data

form 태그에서 사용하려면 enctype="multipart/form-data" 를 지정해야 한다.
위 그림처럼 요청을 multipart로 보낼 시 하이픈과 랜덤한 문자열을 기준으로 데이터들이 나누어 전송하게 된다.

Spring에서 multipart 요청을 처리하는 방법

그럼 이제 Spring에서 어떻게 multipart 요청을 받아들이고 처리할 수 있는지 알아보자.


먼저 DispatcherServlet에서 들어온 요청이 multipart 요청인지 확인한다.

이후 MultipartResolver를 호출하여 해당 request를 MultipartHttpServletRequest로 변환하여 반환한다.

이 MultipartHttpServletRequest는 Multipart 요청을 처리하기 위한 좀 더 다양한 기능을 제공한다.

그 다음은 매핑된 핸들러 찾기, 핸들러 어댑터 호출 등 기존의 요청 처리 방식과 동일하다.

파일 처리하기

컨트롤러 단에서는 어떻게 요청을 처리하는지 알아보자.

//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";
	} 
}

가장 기본적인 HttpServletRequest를 사용하는 방법이다.
request.getParts() 를 호출하면 Part라는 객체를 담은 컬렉션이 반환되고 이 Part들 안에 나뉜 데이터들이 들어오게 된다.

파일 저장하기

@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);
        } 
}

Part의 값을 가져오는 방법은 다음과 같다.

  • part.getName() -> form에 지정한 name속성의 값
  • part.getHeaderNames -> 하나의 body안에 있는 각 header 이름들
    (header에는 content-disposition, content-type이 들어간다.)
  • part.getInputStream() -> part의 inputStream
  • part.getSubmittedFileName() -> 전송한 파일명
  • part.write() -> Part를 통해 전송된 데이터 저장

간단히 보면 다음과 같은 기능들을 사용할 수 있다.
part.write()의 경우 인자로 파일 저장 디렉토리를 넘겨주면 된다.

Spring MultipartFile 이용하기

@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을 사용하면 상당히 코드가 짧아진다. 먼저 인자에 MultipartFile이라는 객체로 파일을 받아올 수 있으며 이를 이용하면 된다.

  • multipartfile.getOriginalFilename() -> 전송한 파일 이름
  • multipartfile.transferTo() -> 파일 저장
profile
평생 여행 다니는게 꿈 💭 👊 😁 🏋️‍♀️ 🦦 🔥

0개의 댓글

관련 채용 정보