파일 업로드

Lilac-_-P·2023년 4월 25일
0

스프링 MVC

목록 보기
15/15

HTTP를 이용하여 통신을 할 때, 보통의 경우 문자 데이터를 보내는 경우가 많다.

만약, 문자 데이터가 아니라 파일 형태의 데이터를 보내고자한다면 어떻게 해야할까? 일반적으로 문자 데이터를 HTTP를 통해 보내는 방법으로는 쿼리 파라미터 형식의 GET 요청으로 요청 URL에 데이터를 싣거나 HTML 폼을 이용해 application/x-www-form-urlencoded 방식으로 메시지 바디에 데이터를 보내는 POST 요청이 있었다.

하지만 파일 형태의 데이터는 문자가 아니라 바이너리 데이터를 전송해야한다. 문자를 전송하는 위의 방식들로는 파일을 전송하기가 어렵다. 또, 보통 HTML 폼을 이용해서 데이터를 전송할 때 파일만 전송하는 것이 아니라 부가적으로 문자 데이터도 함께 보내는 경우가 대부분이다. 즉, 문자와 바이너리 데이터를 동시에 전송하는 경우가 많다.

이 문제를 해결하기 위해 HTTP는 multipart/form-data라는 또다른 HTML 폼을 이용한 전송 방식을 제공한다. multipart/form-data 방식은 다른 종류의 여러 파일과 폼의 내용(문자 데이터)를 함께 전송할 수 있다.

HTML 폼에서 multipart/form-data 방식을 사용하려면, Form 태그에 별도의 'enctype="multipart/form-data"'를 지정해야한다. 지정하지 않을 경우, 'application/x-www-form-urlencoded' 방식이 사용된다.

multipart/form-data 방식의 HTTP 요청은 어떤식으로 구성되어있는지 아래의 HTTP 메시지 예시를 봐보자.

위의 HTTP 요청에서 눈여겨봐야할 곳은 2군데이다.

  1. Content Type: multipart/form-data 와 boundary 값
  2. Content-Disposition : 항목별 헤더와 부가정보

Content-Type이 multipart/form-data로 지정되어있고, 각각의 전송 항목은 boundary의 값으로 구분되어진다. Content-Disposition은 각 항목별의 헤더이고, 여기에 항목에 대한 부가 정보가 있다.
multipart/form-data는 이렇게 각각의 항목을 구분해서, 한번에 여러 항목을 전송하는 것이다.

참고.
HTML 폼에서 파일을 여러 개 업로드하려면, input 태그에 'multiple="multiple"' 옵션을 주면 된다.

이렇게 보내진 HTTP 요청을 서버측에서는 어떻게 사용할 수 있을까?
자바의 서블릿은 Part라는 인터페이스로 multipart/form-data 방식으로 전달되는 데이터를 편리하게 사용할 수 있는 기능을 제공한다. 아래의 코드를 보자.

public void ex(HttpServletRequest request){
	
    Collection<Part> parts = request.getParts();
    Part ex = request.getPart("ex");
    
    part.getName();
    part.getHeader();
    part.getHeaders();
    part.getHeaderNames()  
	part.getInputStream(); // 전송된 바이너리 데이터를 읽을 때 사용
    part.getSize();
    part.getSubmittedFileName();
    part.getContentType();

    part.write(); // 원하는 경로에 part에 들어있는 데이터를 쓰고 싶을 때 사용
    part.delete();
}

getPart() 메서드는 통해 특정 항목을 받아서 확인할 수 있고, getParts() 메서드는 모든 항목을 전부 받아서 확인할 수 있다. 각 Part는 제공되는 편의성 함수를 통해 실제 HTTP 요청에 담긴 정보를 얻을 수 있다.

스프링은 자바의 서블릿 인터페이스를 이용해 구현한 멀티파트용 서블릿(MultipartHttpServletRequest)을 통해 이를 편리하게 사용할 수 있는 기능을 제공한다. 스프링이 위대한 이유

DispatcherServlet은 멀티파트 리졸버를 이용하여 서블릿 컨테이너(WAS)로부터 전달된 요청이 멀티파트 요청인 경우 HttpServletRequest를 MultipartHttpServletRequest로 변환한다. 이후의 모든 과정에서 MultipartHttpServletRequest이 사용된다.

참고.
멀티파트 요청을 사용할 때 파일 업로드와 관련해서 옵션을 설정할 수 있다.
spring.servlet.multipart.max-file-size 나 spring.servlet.multipart.max-request-size를 사용하면 된다.
또, 서블릿 컨테이너에서 멀티파트와 관련된 처리를 하지 않도록 하려면 spring.servlet.multipart.enable를 사용하면 된다.

스프링은 여기서 멈추지않는다.

서블릿이 제공하는 Part가 편하기는 하지만, HttpServletRequest를 필수적으로 사용해야하고, 추가로 파일부분만 구분하려면 여러가지 처리용 코드들이 필요하다. 그래서 스프링은 MultipartFile이라는 인터페이스를 제공하여 더 쉽게 멀티파트 파일을 다룰 수 있게 해준다.

public void ex(@RequestParam MultipartFile file, HttpServletRequest request)
            throws IOException {
            
        log.info("multipartFile = {}", file);

        if (!file.isEmpty()) {
            String fullPath = fileDir + file.getOriginalFilename(); // 업로드된 원본 파일명
            file.transferTo(new File(원하는 경로)); // 원하는 경로에 파일 저장
        }
}

@RequestParam과 MultipartFile 인터페이스를 사용하면 HTML 폼에서 업로드한 파일을 전송할 때 사용한 name을 맞춰주면, 해당 파일이 스프링 컨트롤러 메서드의 파라미터인 MultipartFile의 값으로 들어온다. @RequestParam에서 사용할 수 있기 때문에, @ModelAttribute에서도 동일하게 사용할 수있다.

파일 저장과 관련된 실무적인 요소

  1. 사용자가 업로드한 파일명으로 서버 내부에 파일을 저장하면 안된다. 서로 다른 사용자가 같은 파일이름을 업로드하는 경우 기존 파일 이름과 충돌이 날 수도 있기 때문이다. 그러므로 사용자가 업로드한 파일명은 따로 저장해놓고, 서버에서는 저장할 파일명이 겹치지 않도록 내부에서 관리하는 별도의 파일명이 필요하다.

  2. 웹 애플리케이션 서버 자체에서 파일의 바이너리 데이터를 저장하지 않는다. 보통의 경우 웹 애플리케이션에서는 파일의 이름이나 경로만을 문자열 형태로 사용하고, 실제 파일의 바이너리 데이터는 파일 저장용 서버(ex. AWS S3)에 저장한다.

  3. 사용자가 업로드한 파일을 추후에 다시 사용자에게 제공할 때는 서버 내부적으로 다시 바이너리 파일을 불러와서 바이너리 형태의 데이터를 사용자에게 돌려주거나, 파일이 다른 외부 서버에 있다면 외부 서버에 있는 파일을 서버 내로 불러와서 사용자에게 제공하거나 사용자에게 외부 서버에 접근할 수 있는 방법을 제공하면 된다.

참고.
스프링의 Resource 인터페이스를 이용하면, 사용자에게 바이너리 파일을 제공할 때, 편하게 제공할 수 있다.

profile
열심히 하자

0개의 댓글