브라우저 → 서버
<form>
태그 사용<iframe>
이용하여 화면 이동 없이 첨부파일 처리<input type = 'file'>
과 AJAX 처리서버에서 첨부파일을 처리하는 방법
서버상에서 첨부파일의 처리는 컨트롤러에서 이루어진다.
폼 태그를 이용하여 파일을 보낼 때는 POST로 처리해줘야 한다.
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>
방식의 파일 업로드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();
}
}
}
}
메서드 | 설명 |
---|---|
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
코드를 작성한 후 파일을 전송해보면 컨트롤러에서 이를 받고 로그를 출력하고 파일을 지정된 경로에 저장한다
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에 속하는 필드는 아래와 같은 메서드로 수정이 가능하다.
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();
}
}
}
<%@ 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){
)