개발/[Spring] 블로그 만들기

[코드로 배우는 스프링 웹 프로젝트] 21강. 파일 업로드 방식 (form태그, Ajax방식, MultipartFile)

ee2ee2 2022. 6. 6. 00:01
728x90
반응형

해당 프로젝트는 코드로 배우는 스프링 웹 프로젝트(개정판)을 기반으로 진행됩니다.


첨부파일을 서버에 전송하는 방식은 크게 <form> 태그를 이용해서 업로드하는 방식Ajax를 이용하는 방식으로 나눌 수 있다.

  • <form> 태그를 이용하는 방식 : 브라우저 제한이 없어야 하는 경우에 사용
    • 일반적으로 페이지 이동과 동시에 첨부파일을 업로드하는 방식
    • <iframe>을 이용해서 화면의 이동없이 첨부파일을 처리하는 방식
  • Ajax를 이용하는 방식 : 첨부파일을 별도로 처리하는 방식
    • <input tyoe='file'>을 이용하고 Ajax로 처리하는 방식
    • HTML5의 Drag And Drop 기능이나 JQuery 라이브러리를 이용해서 처리하는 방식

서버 쪽에서의 처리는 대부분 비슷하다. 응답을 HTML 코드로 하는지, JSON 등으로 처리하는지 정도만 구분하면 된다.

시작에 앞서, 첨부파일을 업로드할 폴더 생성 C:\upload\tmp


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

pom.xml 일부

..생략

	<properties>
		<java-version>1.8</java-version>
		<org.springframework-version>5.0.7.RELEASE</org.springframework-version>
		<org.aspectj-version>1.9.0</org.aspectj-version>
		<org.slf4j-version>1.7.25</org.slf4j-version>
	</properties>
    
		//서블릿 3.0 이상을 활용하기 위해서 서블릿 버전 수정
    	<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
        
       <dependency>
      		<groupId>org.projectlombok</groupId>
      		<artifactId>lombok</artifactId>
      		<version>1.18.22</version>
     	 	<scope>provided</scope>
		</dependency>
        
..생략

21.1.1 web.xml을 이용하는 경우의 첨부파일 설정

web.xml

: xml 네임스페이스 버전 정보 수정 및 <multipart-config> 태그 추가

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">

	...생략

	<!-- Processes application requests -->
	<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>C:\\upload\\tmp</location>				<!-- 업로드된 파일을 저장할 공간 -->
			<max-file-size>20971520</max-file-size> 			<!-- 1MB * 20 : 업로드되는 파일의 최대 크기 -->
			<max-request-size>41943040</max-request-size>		<!-- 40MB : 한 번에 올릴 수 있는 최대 크기 -->
			<file-size-threshold>20971520</file-size-threshold> <!-- 20MB : 특정 사이즈의 메모리 사용 -->
		</multipart-config>
	</servlet>

web.xml이 설정은 WAS(Tomcat) 자체의 설정일 뿐이고,  스프링에서 업로드 처리는 MultipartResolver라는 타입의 객체를 빈으로 등록해야만 가한다. Web과 관련된 설정으로 servlet-context.xml을 이용하여 설정한다.

servlet-context.xml

	<context:component-scan base-package="org.zerock.controller" />
	
	<beans:bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
	</beans:bean>

21.2 <form> 방식의 파일 업로드

서버상에서 첨부파일의 처리는 컨트롤러에서 이뤄지므로 UploadController 작성이 필요하다. UploadController는 GET 방식으로 첨부파일을 업로드할 수 있는 화면을 처리하는 메소드와 POST 방식으로 첨부파일을 업도드 처리하는 메소드를 추가한다.

컨드롤러 처리에 앞서, @RequsestMapping이 매핑될 uploadForm.jsp 먼저 추가해보자.

uploadForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<form action="uploadFormAction" method="post" enctype="multipart/form-data">
		<input type='file' name='uploadFile' multiple>
		<button>Submit</button>
	</form>
	
</body>
</html>

실행 결과

http://localhost:8080/uploadForm


21.2.1 UploadController에 MultipartFile 타입 적용하여 구현하기

스프링 MVC에서는 MultipartFile 타입을 제공하여 업로드되는 파일 데이터를 쉽게 처리 가능하다. 위 uploadForm.jsp의 <input type='file' name='uploadFile' multiple> 의 name 속성으로 변수를 지정해서 처리한다.

UploadController.java 일부

package org.zerock.controller;

import javax.swing.plaf.multi.MultiFileChooserUI;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;

import lombok.extern.log4j.Log4j;

@Controller
@Log4j
public class UploadController {

	@GetMapping("uploadForm")
	public void uploadForm() {
		log.info("upload form");
	}
	
	@PostMapping("/uploadFormAction")
	public void uploadFormPost(MultipartFile[] uploadFile, Model model) {
		
		for(MultipartFile multipartFile : uploadFile) {
			
			log.info("--------------------------------------");
			log.info("Upload File Name: " + multipartFile.getOriginalFilename());
			log.info("Upload File Size: " + multipartFile.getSize());
		}
	}
}

실행 결과

아직 파일 업로드&nbsp; 액션 이후의 처리 화면(uploadFormAction.jsp)을 구현하기 전이어서 404 에러 발생


<MultipartFile이 가진 메소드들>


반응형

UploadController 클래스 일부 - 파일 저장

	@PostMapping("/uploadFormAction")
	public void uploadFormPost(MultipartFile[] uploadFile, Model model) {
		
		String uploadFolder = "C:\\upload";
		
		for(MultipartFile multipartFile : uploadFile) {
			
			log.info("--------------------------------------");
			log.info("Upload File Name: " + multipartFile.getOriginalFilename());
			log.info("Upload File Size: " + multipartFile.getSize());
			
			File saveFile = new File(uploadFolder, multipartFile.getOriginalFilename());
			
			try {
				multipartFile.transferTo(saveFile); //파일 저장
			} catch (Exception e) {
				// TODO: handle exception
				log.error(e.getMessage());
			}
		}
	}

실행 결과 - 설정한 경로에 파일 저장 확인


21.3 Ajax를 이용하는 파일 업로드

첨부파일을 업로드하는 다른 방식으로는 Ajax를 이용해서 파일 데이터만을 전송하는 방식이 있다. Ajax를 이용하는 첨부파일 처리는 FormData라는 객체를 이용하는데 IE 브라우저의 경우는 버전 10 이후부터 지원된다는 제약이 있다는 점을 알아두자!

UploadController에 GET방식으로 첨부파일을 업로드하는 페이지를 만든다.

UploadController의 일부

	@GetMapping("/uploadAjax")
	public void uploadAjax() {
		log.info("Upload Ajax!!");
	}

 

WEB-INF/views 폴더의 uploadAjax.jsp 파일 생성

 

JQuery를 이용하는 편이 더욱 편리하여, JQuery 라이브러리의 경로를 추가하고 <script>를 이용해서 첨부파일을 처리한다. 라이브러리 경로 찾는 법은 게시글 최하단에 추가해놓았다.

JQuery를 이용한 파일 업로드는 FormData라는 객체를 이용하게 된다. 쉽게말해 가상의 <form> 태그와 같다고 생각하면 된다. Ajax를 이용하는 파일 업로드는 FormData를 이용해서 필요한 파라미터를 담아 전송하는 방식이다.

uploadAjax.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<h1>Upload with Ajax</h1>
	
	<div class='uploadDiv'>
		<input type='file' name='uploadFile' multiple>
	</div>
	
	<button id='uploadBtn'>Upload</button>
	
	<script src="https://code.jquery.com/jquery-3.3.1.min.js"
	  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
	  crossorigin="anonymous">
	</script>
	  
	<script>
	$(document).ready(function(){
		
		$("#uploadBtn").on("click", function(e){
			var formData = new FormData();
			var inputFile = $("input[name='uploadFile']");
			var files = inputFile[0].files;
			
			console.log(files);
			
			//add filedate to formdata
			for(var i=0; i<files.length; i++){
				formData.append("uplaodFile", files[i]);
			}
			
			$.ajax({
				url: '/uploadAjaxAction',
				processData: false, // 반드시 'false'로 지정해야만 전송됨
				contentType: false, // 반드시 'false'로 지정해야만 전송됨
				data: formData,
				type: 'POST',
				success: function(result){
					alert("Uploaed");
				}
			}); //$.ajax
			
		});
	});
	</script>
	  
</body>
</html>

이렇게 보낸 첨부파일 데이터를 UploadController에서는 MultipartFile 타일을 이용해서 첨부파일 데이터를 처리한다.

UploadController 클래스 일부

	@GetMapping("/uploadAjax")
	public void uploadAjax() {
		log.info("Upload Ajax!!");
	}
	
	@PostMapping("/uploadAjaxAction")
	public void uploadAjaxAction(MultipartFile[] uploadFile) {
		
		log.info("Update Ajax POST...............");
		
		String uploadFolder = "C:\\upload";
		
		for(MultipartFile multipartFile : uploadFile) {
			
			log.info("--------------------------------------");
			log.info("Upload File Name: " + multipartFile.getOriginalFilename());
			log.info("Upload File Size: " + multipartFile.getSize());
			
			String uploadFileName = multipartFile.getOriginalFilename();
			
			//IE has file path
			uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\")+1);
			
			File saveFile = new File(uploadFolder, uploadFileName);
			
			try {
				multipartFile.transferTo(saveFile);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				log.error(e.getMessage());
			}
		}

실행결과

uploadAjax 기본 페이지
파일 2개 업로드 후 Upload 버튼 클릭
파일 전송 처리 로그 확인
폴더에 정상적으로 저장됨을 확인


21.3.1 파일 업로드에서 고려해야하는 점들

  • 동일한 이름으로 파일이 업로드되었을 때 기존 파일이 사라지는 문제
  • 이미지 파일의 경우, 원본 파일의 용량이 큰 경우 썸네일 이미지를 생성해야하는 문제
  • 이미지 파일과 일반 파일을 구분해서 다운로드 및 페이지에서 조회하도록 처리하는 문제
  • 첨부파일 공격에 대비하기 위한 업로드 파일의 확장자 제한

위 고려해야하는 점들은 이어서 차차 풀어가 볼 것 이다.

다음 시간에는 '동일한 이름의 파일 업로드시 처리법'에 대해 구현해본다.

 


<추가> JQuery 라이브러리 경로 및 script 찾는 방법

https://releases.jquery.com/jquery/

 

jQuery Core – All Versions | jQuery CDN

jQuery Core – All Versions jQuery Core & Migrate - Git Builds UNSTABLE, NOT FOR PRODUCTION jQuery Core - All 3.x Versions jQuery Core 3.6.0 - uncompressed, minified, slim, slim minified jQuery Core 3.5.1 - uncompressed, minified, slim, slim minified jQue

releases.jquery.com