본문 바로가기
관리자

Programming-[Backend]/Spring

[스프링 웹MVC-2] 20. 파일 업로드

728x90
반응형

 

1. 파일 전송 시 HTTP 작동 방식

 

 

보통 파일 업로드를 위해서 HTML Form을 이용한다. 이때, 아래 그림과 같이 단순 text를 보낼때는 x-www-form-urlencoded로 보내지만, text와 binary 데이터를 같이 보내야 하는 경우처럼 여러 타입의 데이터를 함께 보내야 하는 경우, multipart/form-data로 넘어가고 boundary, filename 등 부가적인 정보가 붙어서 HTTP 요청이 생성된다. 뒤에서 다루겠지만, boundary로 나눠진 부분들을 parts로 표현하며, request.getParts()로 각 부분을 얻을 수 있다. 이런 기본적인 내용을 숙지하였으니, 파일 업로드를 실습해보자.

 

 

 

 


 

 

2. 서블릿의 multipart 요청 정보 처리

 

multipart 요청 보내보기

 

서블릿이 multipart 요청 정보를 어떻게 처리하는지 확인하기 위해서, 컨트롤러와 view 페이지를 작성한다. view 페이지는 강의에 나온 양식을 사용한다.

 

"/servlet/v1/upload"로 접속 시, "upload-form" 을 보여주고, post로 itemName과 parts 정보를 얻어온다.

 

java/hello/upload/controller/ServletUploadControllerV1.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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";
  }
}
cs

 

등록 페이지

 

상품명과 파일을 선택하여 업로드하면, log.info를 통해서 다음과 같은 결과가 콘솔에 출력된다.

 

기본 요청 정보를 확인하기 위해 application.properties에 아래와 같이 설정 코드를 추가해준다.

 

logging.level.org.apache.coyote.http11=debug

 


요청 결과 살펴보기

 

결과1. 기본 요청 정보

boundary로 구분된 body 정보와 알 수 없는 문자로 이루어진 바이너리 데이터 정보가 보인다.

 

 

결과2. log로 출력하는 request, itemName, parts 정보

 

순서대로 잘 출력되었다. parts의 경우 text로 입력한 itemName과 이미지 file의 2가지 part가 ApplicationPart 객체로 생성되었다.

 

HTTP request 정보는 StandardMultipartHttpServletRequest 객체가 출력되는 것을 볼 수 있다. 이것은 multipart request 요청이 오면, 스프링은 이를 감지하여 MultipartResolver를 실행하여 HttpServletRequest를 StandardMultipartHttpServletRequest 객체로 변환한다. 이 객체에서는 getFileName() 등 multipart로 들어오는 요청 정보를 더 쉽게 처리할 수 있도록 하는 여러가지 기능들이 존재한다. 그러나 실제로는 뒤에서 배울 MultipartFile 이라는 것을 사용하여 더 편리하게 처리한다.

 


스프링부트 설정

 

업로드 크기 제한

아래 명령어를 application.properties에 추가하여 업로드 파일의 크기를 제한할 수 있다. max-file-size는 하나의 파일의 크기를, max-request-size는 요청 자체의 크기를 제한한다.

 

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

 

시험을 위해 max-file-size=1KB 로 설정하고, 약 70kB의 이미지 파일을 업로드 해보니, FileSizeLimitExceededException이 발생하였다.

 

 

 

멀티파트 처리 가능여부 설정

서블릿 컨테이너가 멀티파트 요청을 처리하지 않도록 설정할 수 있다. 아래 코드를 추가하면, 콘솔에서 itemName과 parts 정보가 아예 출력되지 않는 것을 확인할 수 있다.

 

spring.servlet.multipart.enabled=false

 

 


 

3. 파일 업로드 후 저장하기 : part.write()

 

 

이번엔 단순히 업로드를 통해 서버단에 요청정보를 전달하는 것이 아니라, 전송된 정보를 바탕으로 파일을 저장해본다. 서블릿에서는 part.write(저장될 path 명)을 통해 저장이 가능하다. 다만, 나중에 스프링에서는 더 직관적인 방식을 이용하니, 서블릿에 이런 기능이 있다는 정도만 알아두면 될 것 같다. 그리고 이 과정 중에 더 자세한 요청 정보를 출력할 수 있는 메서드들을 살펴본다.

 


경로 지정: file.dir

우선 파일이 저장될 경로를 만들어준다. 폴더를 하나 따서 만들면 되고, application.properties에 file.dir로 지정해주면 된다. 마지막 부분에 슬래시(/)를 꼭 포함해줘야한다.

 

file.dir=C:/Users/.../save_test/

 


파일 저장 및 로깅용 컨트롤러

이전에 작성한 ControllerV1을 복사해서 V2로 바꿔주고, 파일 업로드 부분에 코드를 추가해준다. 살펴볼 부분이 많으므로 코드를 먼저 보고 하나씩 이해해보자.

 

다만, @Value 어노테이션으로 application.properties에 있는 값을 필드값으로 지정해줄 수 있다는 것을 기억하자. lombok의 @Value가 아니라 springframwork의 @Value를 이용한다. 괄호 안의 정보는 반드시 application.properties의 값일 필요는 없다. 그냥 괄호 안에 path 정보 등을 문자열 방식으로 작성해줘도 된다.

ex) @Value("C:/User/Desktop/file/image/")

 

 

java/hello/upload/controller/ServletUploadControllerV2.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@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());
 
      //데이터 읽기
      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";
  }
}
cs

 

part의 편의 메서드들

직관적으로 메서드명이 표현되어 바로 이해할 수 있다. 아래 업로드 결과화면을 살펴보면 각 메서드가 의미하는 바를 쉽게 이해할 수 있다.

 

데이터 읽기

part.getInputStream()으로 바이너리 데이터를 inputStream화 한 후, 예전에 배운 바와 같이 StreamUtils.copyToString으로 Stream을 String으로 변환해준다.

 

파일 저장

part.getSubmittedFileName() 메서드는 input의 type이 "file"인 경우, 즉 파일 업로드가 입력된 경우 그 파일의 이름값을 찾아와준다. 그리고 StringUtils.hasText() 메서드는 유용하므로 기억해두자. 이 정보들을 바탕으로 fullPath를 만들고, part.write(경로) 메서드로 최종적으로 파일을 저장하게 된다.

 

 

업로드 결과화면 : 로그

 

 

단점

서블릿이 제공하는 Part는 편하게 보이지만, HttpServletRequest를 사용해야하고, 파일 부분만 얻어오기 위해서 여러 코드를 넣어주어야 한다.

 

 


 

 

3. 스프링의 multipart 요청 정보 처리

 

 

서블릿에 비해 편한 스프링이 지원하는 파일 업로드 방식을 사용해보자. 메서드의 이름이 변경되고 좀 더 간단해졌다.

 

우선, HttpServletRequest가 삭제되어 서블릿에 대한 의존성이 줄어들었다.

 

@RequestParam MultipartFile

파일 정보들은 @RequestParam을 통해 쿼리파라미터 처럼 MultipartFile 타입으로 입력된다.

 

파일 이름, 파일 저장

file.getOriginalFilename(), file.transferTo, File(경로) 객체 구문을 통해, 파일의 이름값을 얻어오고, File 객체를 만들고, 파일을 저장하게 된다.

 

 

java/hello/upload/controller/SpringUploadController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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 filethrows IOException {
    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";
  }
}
cs

 

 


 

참조

 

1. 인프런_스프링 MVC 2편 - 백엔드 웹개발 핵심 기술_김영한 님 강의

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2

 

 

728x90
반응형