파일 업로드 처리

김주언·2022년 10월 17일
0

Spring

목록 보기
12/15
post-thumbnail

파일 업로드 방식

브라우저 → 서버

  • <form> 태그 사용
    • 페이지 이동과 동시에 첨부파일 업로드
    • <iframe> 이용하여 화면 이동 없이 첨부파일 처리
  • AJAX 이용
    • 첨부파일을 별도로 처리하는 방식
    • <input type = 'file'> 과 AJAX 처리
    • HTML5의 Drop and Drop 기능 또는 JQuery

서버에서 첨부파일을 처리하는 방법

서버상에서 첨부파일의 처리는 컨트롤러에서 이루어진다.
폼 태그를 이용하여 파일을 보낼 때는 POST로 처리해줘야 한다.

  • commons-fileupload API 사용
  • 서블릿 3.0 이상에서 지원하는 자체적인 파일 업로드 방식 사용


스프링의 첨부파일을 위한 설정

web.xml 파일의 servlet 태그 내에 <multipart-config> 태그를 추가한다.

 <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>

        <multipart-config>
            <location>/Users/jueon/Desktop/study/spring/upload/temp</location>
            <max-file-size>20971520</max-file-size> <!--20MB -->
            <max-request-size>41943040</max-request-size> <!--40MB -->
            <file-size-threshold>20971520</file-size-threshold>
        </multipart-config>
    </servlet>
  • <location> : 파일 업로드 위치

  • <max-file-size> : 업로드 되는 파일의 최대 크기

  • <max-request-size> : 한번에 올릴 수 있는 최대 크기

  • <file-size-threshold> : 특정 사이즈의 메모리 사용 설정

web.xml은 톰캣(WAS) 자체의 설정이고, 스프링에서 업로드 처리를 하기 위해서는 MultipartResolver 타입의 객체를 빈으로 등록해야한다.
이는 웹과 관련된 설정이므로 servlet-context.xml을 이용하여 설정한다.

servlet-context.xml

<beans:bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>

<form> 방식의 파일 업로드

컨트롤러 작성

UploadController

package com.ze.controller;
// import 생략

@Controller
@Log4j2
public class UploadController {
    @GetMapping("/uploadForm")
    public void uploadForm() {
        log.info("get /uploadForm");
    }

    @PostMapping("/upload")
    public void upload(MultipartFile[] uploadFile, Model model) {
        String uploadPath = "/Users/jueon/Desktop/study/spring/upload/temp";

        for (MultipartFile file : uploadFile) {
            log.info("post /upload");
            log.info("upload file name : " + file.getOriginalFilename());
            log.info("upload file size : " + file.getSize());
            
            File saveFile = new File(uploadPath, file.getOriginalFilename());

            try {
                file.transferTo(saveFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  • MultipartFile 타입
    Spring MVC 에서 제공하는 파일 데이터를 처리하기 위한 타입
    • 제공하는 메서드
      메서드설명
      String getName()파라미터의 이름을 반환한다. (input 태그에 지정된 name 속성)
      String getOriginalFileName업로드되는 파일의 이름을 반환한다.
      boolean isEmpty()파일이 존재하지 않는 경우 true
      long getSize()업로드되는 파일의 크기를 반환한다.
      byte[] getBytes()바이트 배열로 파일 데이터를 반환한다.
      inputStream getInputStream()파일 데이터와 연결된 InputStream을 반환한다.
      transferTo(File f)파일 저장

      InputStream : 바이트 기반 입력 스트림의 최상위 추상클래스
      파일 데이터를 읽거나 네트워크 소켓을 통해 데이터를 읽거나 키보드에서 입력한 데이터를 읽을 때 사용

뷰 작성


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFile" multiple>
    <input type="submit">
</form>
</body>
</html>
  • enctype="multipart/form-data
    파일을 받기 위한 속성
  • multiple
    해당 속성으로 한번에 여러개의 파일을 업로드 할 수 있다.

코드를 작성한 후 파일을 전송해보면 컨트롤러에서 이를 받고 로그를 출력하고 파일을 지정된 경로에 저장한다



AJAX 이용 파일 업로드

FormData

Ajax를 이용하여 파일을 업로드 할 때는 FormData 객체를 이용한다

FormData는 폼을 쉽게 보내도록 도와주는 객체이며 HTML 폼 데이터를 나타낸다. ( 키 - 값 쌍으로 폼 값을 저장한다 )

RESTful API 엔드포인트에 폼 데이터를 전송해야하는 경우 유용하게 사용이 가능하다. 예를 들어 fetch 등의 네트워크 메서드를 사용할 때가 있다. 이 때 FormData 객체는 요청의 바디에 담겨져서 전송된다. 서버 관점에선 FormData를 사용한 방식과 일반 폼 전송 방식에 차이가 없다

let formData = new FormData();		 // 빈 폼 데이터 객체 생성
let formData = new FormData([form]); // 폼을 받아서 폼데이터 객체를 생성

HTML에 form 요소가 있는 경우, 위와 같은 코드를 작성하면 해당 폼 요소의 필드 전체가 자동 반영된다

파일이 첨부된 폼을 전송할 때 HTTP 메시지의 Content-Type 속성은 항상 multipart/form-data이고 메시지는 인코딩되어 전송된다. 파일이 있는 폼도 당연히 이 규칙을 따르기 때문에 <input type="file">로 지정한 필드 역시 일반 폼을 전송할 때와 유사하게 전송된다.

FormData methods

FormData에 속하는 필드는 아래와 같은 메서드로 수정이 가능하다.

  • formData.append(name, value) – name과 value를 가진 폼 필드를 추가. 폼은 이름(name)이 같은 필드 여러 개를 허용하기 때문에 append 메서드를 여러 번 호출해 이름이 같은 필드를 계속 추가해도 문제가 없다.

  • formData.append(name, blob, fileName)<input type="file">형태의 필드를 추가. 세 번째 인수 fileName은 (필드 이름이 아니고) 사용자가 해당 이름을 가진 파일을 폼에 추가한 것처럼 설정해줌

  • formData.delete(name) – name에 해당하는 필드를 삭제

  • formData.get(name) – name에 해당하는 필드의 값을 가져옴

  • formData.has(name) – name에 해당하는 필드가 있으면 true를, 그렇지 않으면 false를 반환

  • formData.set(name, value) - append 메서드 이외에 필드 추가 시 사용할 수 있는 메서드로, append메서드와 다른 점은 set은 name과 동일한 이름을 가진 필드를 모두 제거하고 새로운 필드 하나를 추가한다. 즉 set은 특정 name을 가진 필드가 단 하나만 존재하도록 보장한다.

  • formData.set(name, blob, fileName)

컨트롤러 작성

    @GetMapping("/ajaxUpload")
    public void ajaxUpload(){
        log.info("get /ajaxUpload");
    }


    @PostMapping("/ajaxUpload")
    public void uploadAjax(MultipartFile[] uploadFile){
        log.info("post /ajaxUpload");
        String uploadPath = "/Users/jueon/Desktop/study/spring/upload/temp";

        for (MultipartFile file : uploadFile) {
            log.info("upload file name : " + file.getOriginalFilename());
            log.info("upload file size : " + file.getSize());

            File saveFile = new File(uploadPath, file.getOriginalFilename());

            try {
                file.transferTo(saveFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

뷰 작성

ajaxUpload.jsp


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-3.6.1.min.js"
            integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>

</head>
<body>
<div class="uploadDiv">
  <input type="file" name="uploadFiles" multiple>
</div>
<button id="uploadBtn" >Upload</button>

<script>
    $(function () {
        $("#uploadBtn").on("click", function (e){
            let formData = new FormData()
            let inputFile = $("input[name='uploadFiles']");
            let files = inputFile[0].files;
            console.log(files)

            // formData 필드에 파일 추가하기
            for (let i = 0; i < files.length; i++) {
                formData.append("uploadFile", files[i]);
            }

            $.ajax({
                url : '/ajaxUpload',
                processData : false,
                contentType : false,
                method: 'post',
                data: formData,
                success : function(result) {
                    console.log(result);
                }
            })


        })
    })
</script>
</body>
</html>
  • processData: false
    서버로 보내지는 data는 "application/x-www-from-urlencoded"에 맞는 쿼리 문자열로 처리 및 변환된 형태이다.
    ex) http://example.com/test/there?title=Main&page=1
    ex) {key:value, key:value} -> key=value&key=value
    DOMDocument 또는 기타 처리되지 않은 데이터(파일)을 보낼때는 이 옵션값을 false로 지정해야함.

  • contentType: false
    default 값 : "application/x-www-form-urlencoded; charset=UTF-8"
    "multipart/form-data" 로 전송이 되게 옵션값을 false 지정

  • formData.append("uploadFile", files[i]);
    append 시 설정해 준 name과 컨트롤러에서 받는 파라미터 name이 일치해야함!! ( 컨트롤러의 public void uploadAjax(MultipartFile[] uploadFile){ )


파일 업로드 시 고려할 점

  • 동일 파일명이 업로드 되었을 경우 기존의 파일이 사라진다.
  • 이미지 파일의 경우 원본 파일의 용량이 큰 경우 썸네일 이미지를 생성해야한다.
  • 이미지 파일과 일반 파일을 구분해서 다운로드 혹은 페이지에서 조회하도록 처리하는 문제
  • 첨부파일 공격에 대비하기 위한 업로드 파일의 확장자 제한
profile
학생 점심을 좀 차리시길 바랍니다

0개의 댓글