파일업로드 이해하기
웹에서는 이 클라이언트/서버 간 요청/응답을 HTTP 프로토콜로 진행한다.
HTTP에서는 파일도 지원해준다.
파일업로드란 클라이언트가 요청에 파일을 포함하고
서버가 요청받은 파일을 처리하는 과정의 일환이다.
클라이언트 : "서버야, 나 Request보낼 때 파일도 포함시켜 보낼게. 이거 서버에 저장해줘"
서버 : "OK. 어디보자. Request에 파일 있군. 알았어 잘 처리했어."
의 과정이다 . 물론 위의 대화를 HTTP프로토콜에서 처리해야 되는데 이게 생각보다 어렵다.
우선 파일업로드를 위해선 다음의 3가지 규칙을 꼭 지켜줘야 한다.
- <input type="file" > (파일을 선택해야되니까..)
- <form> 태그 method는 POST (밑에서 설명)
- <form> 태그 enctype=multipart/form-data (밑에서 설명)
Multipart란
HTTP에서 request는 header와 body부분으로 나누어져있다.
header의 content-type은 body에 대한 데이터를 정의한다.
서버는 content-type의 값을 보고 body를 알맞은 형태로 해석한다.
이 요청 헤더 content-type의 한 종류로서 웹 클라이언트가 요청을 보낼 때,
HTTP 요청의 바디부분을 여러부분으로 나눠서 보내는 방식이다.
Multipart가 생긴 이유
파일을 업로드 할 때, 사진 설명을 위한 input (type="text")과
사진을 위한 input(type="file") 2개가 들어간다고 가정합니다.
이 두 input 간에 Content-type은 사진 설명은 application/x-www-form-urlencoded 이 될 것이고,
사진 파일은 image/jpeg입니다. 즉 하나의 요청에 Content-type이 서로 다른것이 2개가 있습니다.
두 종류의 데이터가 하나의 HTTP Request Body에 들어가야 하는데,
하나의 Body에서 이 2 종류의 데이터를 구분에서 넣어주는 방법이 필요해졌습니다.
그래서 등장한 것이 multipart 타입입니다.
이렇게 Body에서 이 데이터를 구분해야하기 때문에
요청파라미터를 url뒤에 문자열로 추가하는 GET방식으로는 파일을 보낼 수 없습니다.
그래서 multipart타입은 POST방식에서만 사용가능합니다.
Multipart 사용이유 정리
- 다중 데이터 유형 처리: 파일을 업로드할 때 일반적으로 파일 자체와 함께 추가적인 데이터(예: 텍스트 입력)를 함께 전송해야 할 수 있습니다. Multipart를 사용하면 여러 유형의 데이터를 하나의 HTTP 요청 내에서 처리할 수 있습니다. 각 데이터 부분은 개별적으로 식별되어 서버가 이를 구분하여 처리할 수 있습니다.
- 효율적인 데이터 전송: 파일은 텍스트 데이터와는 달리 이진 데이터로 구성되어 있습니다. 이진 데이터를 전송하려면 데이터의 형식에 관계없이 직접 전송할 수 있어야 합니다. Multipart를 사용하면 이진 데이터를 효율적으로 전송할 수 있습니다.
- 유연성: Multipart는 여러 부분으로 구성되어 있으므로 각 부분의 Content-Type을 지정하여 서버가 데이터를 올바르게 해석하고 처리할 수 있습니다. 이는 파일의 형식이나 다른 데이터의 형식에 상관없이 다양한 유형의 데이터를 전송할 수 있는 유연성을 제공합니다.
- 표준화된 방법: Multipart는 HTTP의 표준 부분으로 정의되어 있으며, 대부분의 웹 서버 및 클라이언트에서 지원됩니다. 이는 파일 업로드를 위한 표준화된 방법을 제공하여 개발자들이 파일 업로드를 구현할 때 일관된 방법을 따를 수 있도록 합니다.
Multipart를 사용한 요청헤더
<form enctype="multipart/form-data" method="post">
<input id="desc" type="text" />
<input id="image" type="file" />
</form>
위의 form으로 데이터를 보냈을 때 request 요청은 다음과 같이 보내진다.
POST /someurl HTTP/1.1
...
...
Content-type: multipart/form-data; boundary=---------------------------7d81c536d04c8 <- Content-type
...
...
text데이터
-----------------------------7d81c536d04c8 <- input text의 데이터
Content-Disposition: form-data; name="desc"
사진
-----------------------------7d81c536d04c8 <- input file의 데이터
Content-Disposition: form-data; name="image"; filename="fileName.jpg"
Content-Type: image/jpeg
...(Binary 이미지 데이터)
Content-type: multipart/form-data; boundary=---------------------------7d81c536d04c8을 보자.
Content-type: multipart/form-data로 요청을 보내면
웹 클라이언트(브라우저)는 boundary라는 걸 생성한다.
이 boundary라는 걸 이용해서 요청 body를 여러 부분으로 나누게 된다.
서버는 맨 처음 Content-type이 multipart/form-data;라는 걸 보고
boundary값을 통해 데이터가 나뉘어져 온다는 걸 인식하고 그에 맞게 해석하게 될 것이다.
Spring으로 파일요청 처리하기
1.1 파일저장을 위한 FileUtils 관련 디펜던시 추가
pom.xml에 아래 코드를 추가
<!-- commons io -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
1.2 Spring에서 MultipartResolver 설정
Multipart 지원기능을 이용하려면 먼저 MultipartResolver를 스프링설정파일에 등록해주어야한다.
MultipartResolver는 Multipart형식으로 데이턱 전송된 경우, 해당 데이터를 스프링 MVC에서 사용할수있도록 변환해준다.
Spring이 기본으로 제공하는 MultipartResolver는 다음의 두 개가 있다.
- org.springframework.web.multipart.commons.CommonsMultipartResolver
- org.springframework.web.multipart.support.StandardServletMultipartResolver
위 두 MultipartResolver 구현체 중 하나를 스프링 빈으로 등록해주면 된다.
주의할 점은 빈의 id가 반드시 "multipartResolver"여야한다는 점이다.
DispatcherServlet은 내부적으로 이름이 "multipartResolver"인 빈을 사용하기 때문에
다른 이름으로 빈을 등록할 경우 MultipartResolver로 사용되지 않는다.
web.xml에 DispatcherServlet 의 Multipart 설정
<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/classes/spring/mvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<multipart-config>
<max-file-size>31457280</max-file-size> <!-- 30mb-->
<max-request-size>1004857600</max-request-size> <!-- 100mb -->
</multipart-config>
</servlet>
<multipart-config >의 설정 태그
태그 | 설명 |
<location> | 업로드 한 파일이 임시로 저장될 위치를 지정한다. |
<file-size-threshold> | 업로드 한 파일 크기가 이 태그의 값보다 크면 에서 지정한 디렉토리에 임시로 파일을 생성한다. 업로드 파일 크기가 이 태그의 값 이하면 메모리에 파일 데이터를 보관한다. 단위는 바이트이며, 기본 값은 0이다 |
<max-file-size> | 업로드 가능한 파일의 최대 크기를 바이트 단위로 지정한다. -1은 제한없음을 의미하며 기본값은 -1이다. |
<max-request-size> | 전체 Multipart 요청 데이터의 최대 제한 크기를 바이트 단위로 지정한다. -1은 제한없음을 의미하며 기본값은 01이다. |
mvc-servlet.xml에 설정 (DispatcherServlet 설정파일)
<!--빈 이름은 무조건 multipartResolver -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"></beans:bean>
2-1. @RequestParam어노테이션을 이용한 업로드 파일 접근
파일 업로드를 할 때는 form의 enctype = multipart/form-data로 작성해야하고, method = post여야 합니다.
그래야 MultipartResolver가 multipartFile객체를 컨트롤러에 전달할 수 있습니다.
업로드한 파일을 전달받는 첫 번째 방법은 @RequestParam어노테이션이 적용된 MultipartFile타입의 파라미터를 사용하는 것이다. 예를 들어, HTML 입력폼이 다음과 같이 작성되어 있다고 해보자
<form action="submitReport1" method="POST" enctype="multipart/form-data">
학번 : <input type="text" name="studentNumber" /><br/>
리포트 파일 : <input type="file" name="report" /><br/>
<input type="submit" />
</form>
위 HTML 코드에서 파일은 report 파라미터를 통해서 전달된다.
이 경우 다음 코드와 같이 @RequestParam 어노테이션과 MultipartFile 타입의 파라미터를 이용해서 업로드 파일 데이터를 전달받을 수 있다.
컨트롤러에서는 MultipartFile 객체를 통해 파일을 받습니다.
@RequestMapping(value="/report/submitReport1", method=RequestMethod.POST) //IOException - 파일이 없을 때 발생할 에러. 호출함수인 xml의 DispatcherServlet class로 예외처리 전가
public String submitReport1(@RequestParam("studentNumber") String studentNumber, @RequestParam("report") MultipartFile report) throws IOException { //command객체가 아닌 request로 submit한 값 받아오기 //studentNumber - submissionForm의 속성 name
//파일명
String originalFile = report.getOriginalFilename();
//파일명 중 확장자만 추출
String originalFileExtension = originalFile.substring(originalFile.lastIndexOf("."));
//UUID클래스 - (특수문자를 포함한)문자를 랜덤으로 생성 "-"라면 생략으로 대체
String storedFileName = UUID.randomUUID().toString().replaceAll("-", "") + originalFileExtension;
//파일을 저장하기 위한 파일 객체 생성
file = new File(filePath + storedFileName);
//파일 저장
report.transferTo(file);
System.out.println(studentNumber + "가 업로드한 파일은");
System.out.println(originalFile + "은 업로드한 파일이다.");
System.out.println(storedFileName + "라는 이름으로 업로드 됐다.");
System.out.println("파일사이즈는 " + report.getSize());
return "report/submissionComplete";
}
MultipartFile 인터페이스는 스프링에서 업로드 한 파일을 표현할 때 사용되는 인터페이스로서, MultipartFile 인터페이스를 이용해서 업로드 한 파일의 이름, 실제 데이터, 파일 크기 등을 구할 수 있다.
2-2. MultipartHttpServletRequest를 이용한 업로드 파일 접근
업로드한 파일을 전달받는 두 번째 방법은 MultipartHttpServletRequest 인터페이스를 이용하는 것이다.
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
@Controller
public class ReportSubmissionController {
@RequestMapping(value = "/report/submitReport2.do", method = RequestMethod.POST)
public String submitReport2(MultipartHttpServletRequest request) {
String studentNumber = request.getParameter("studentNumber");
MultipartFile report = request.getFile("report");
printInfo(studentNumber, report);
return "report/submissionComplete";
}
}
MultopartHttpServletRequest 인터페이스는 스프링이 제공하는 인터페이스로서, Multipart 요청이 들어올 때 내부적으로 원본 HttpServletRequest 대신 사용되는 Multipart 요청이 들어올 때
내부적으로 원본 HttpServletRequest 대신 사용되는 인터페이스이다. MultipartHttpServletRequest 인터페이스는 실제로는 어떤 메서드도 선언하고 있지 않으며, HttpServletRequest 인터페이스와 MultipartRequest 인터페이스를 상속받고 있다. MultipartHttpServletRequest 인터페이스는 javax.servlet.HttpServletRequest 인터페이스를 상속받기 때문에 웹 요청 정보를 구하기 위한 getParameter()나 getHeader()와 같은 메서드를 사용할 수 있으며, 추가로 MultipartRequest 인터페이스가 제공하는 Multipart 관련 메서드를 사용할 수 있다.
※ MultipartRequest 인터페이스의 파일 관련 주요 메서드
메소드 | 설명 |
Iterator<String> getFileNames() | 업로드 된 파일들의 이름 목록을 제공하는 Iterator를 구한다. |
MultipartFile getfile(String name) | 파라미터 이름이 name이 업로드 파일 정보를 구한다. |
List<MultipartFile> getFiles(String name) | 파라미터 이름이 name인 업로드 파일 정보 목록을 구한다. |
Map<String, MultipartFile> getFileMap() | 파라미터 이름을 키로 파라미터에 해당하는 파일 정보를 값으로 하는 Map을 구한다. |
2-3. 커맨드 객체를 통한 업로드 파일 접근
커맨드 객체를 이용해도 업로드 한 파일을 전달받을 수 있다. 단지 커맨드 클래스에 파라미터와 동일한 이름의 MultipartFile 타입 프로퍼티를 추가해주기만 하면 된다.
예를 들어, 업로드 파일의 파라미터 이름이 "report"인 경우, 다음과 같이 "report" 프로퍼티를 커맨드 클래스에 추가해 주면 된다.
public class ReportCommand {
//필드명이 jsp파일의 input type 속성들 name과 같아야한다.
private String studentNumber;
private MultipartFile report;
public String getStudentNumber() {
return studentNumber;
}
public void setStudentNumber(String studentNumber) {
this.studentNumber = studentNumber;
}
public MultipartFile getReport() {
return report;
}
public void setReport(MultipartFile report) {
this.report = report;
}
}
위 코드와 같이 MultipartFile 타입의 프로퍼티를 커맨드 클래스에 추가해주었다면, @RequestMapping 메서드의 커맨드 객체로 사용함으로써 업로드 파일 정보를 커맨드 객체를 통해서 전달받을 수 있게 된다.
@Controller
public class ReportSubmissionController1 {
private ReportService reportService;
public void setReportService(ReportService reportService) {
this.reportService = reportService;
}
@RequestMapping(value="/report/submission1", method=RequestMethod.GET)
public String form() {
return "report/submissionForm1";
}
@RequestMapping(value="/report/submitReport2")
public String submitReport2(ReportCommand reportCommand) throws IOException{
boolean result = reportService.fileupload(reportCommand);
if(result==false) {
System.out.println("파일이 저장 안 됐다.");
return "report/submissionForm1";
}
System.out.println("파일 저장 완료");
return "report/submissionComplete";
}
}
3. MultupartFile 인터페이스 사용
org.springframework.web.MutipartFile인터페이스는 업로드한 파일 및 파일데이터를 표현하기 위한 용도로 사용된다.
※ MulripartFile 인터페이스의 주요 메서드
메소드 | 설명 |
String getName() | 파라미터 이름을 구한다. |
String getOriginalFilename() | 업로드한 파일의 이름을 구한다. |
String isEmpty() | 업로드한 파일이 존재하지 않는 경우 true를 리턴한다. |
long getSize() | 업로드한 파일의 크기를 구한다. |
byte[ ] getBytes() throws IOExcetion | 업로드한 파일 데이터를 구한다. |
InputStream getInputStream() throws IOException | 업로드한 파일 데이터를 읽어오는 InputStream을 구한다. InputStream의 사용이 끝나면 알맞게 종료해주어야 한다. |
void transferTo(File dest) throws IOException | 업로드한 파일 데이터를 지정한 파일에 저장한다. |
업로드 한 파일 데이터를 구하는 가장 단순한 방법은 MultipartFile.getByte() 메서드를 이용하는 것이다. 바이트 배열을 구한 뒤에 파일이나 DB등에 저장하면된다.
if(mutipartFile.isEmpty()){
byte[ ] fileData = multipartFile.getBytes();
//byte 배열을 파일/DB/네트워크 등으로 전송
...
}
업로드한 파일데이터를 특정 파일로 저장하고 싶다면 MultipartFile.transferTo() 메서드를 사용하는 것이 편리하다.
if(mutipartFile.isEmpty()){
File file = new File(fileName);
multipartFile.transferTo(file);
...
}
private FileOutputStream fos = null;
public String uploadResult(@RequestParam("selFile") MultipartFile mFile) throws Exception, IOException {
String fileExtension = mFile.getOriginalFilename().substring(mFile.getOriginalFilename().lastIndexOf("."));
String storedFileName = UUID.randomUUID().toString().replaceAll("-", "") + fileExtension;
String filePath = "C:\\eclipse\\workspace\\memPid\\src\\main\\webapp\\resources\\";
File file = new File(filePath + storedFileName);
//MultipartFile.transferTo(File file) - Byte형태의 데이터를 File객체에 설정한 파일 경로에 전송한다.
mFile.transferTo(file);
//두 번째 방법, MultipatrFile클래스의 getBytes()로 multipartFile의 데이터를 바이트배열로 추출한 후,
//FileOutputStream클래스의 write()로 파일을 저장
try {
//MultipatrFile클래스의 getBytes()를 사용해서 multipartFile의 데이터를 바이트배열로 추출
byte fileData[] = mFile.getBytes();
fos = new FileOutputStream(filePath + mFile.getOriginalFilename());
//FileOutputStream클래스의 write()로 파일을 filePath에 저장
fos.write(fileData);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(fos != null) {
fos.close();
}
}
return "upload_result";
}
https://jihwan-study.tistory.com/83
'STUDY > SpringLegacy' 카테고리의 다른 글
[STS] STS버전 이클립스버전 확인하기 (0) | 2024.04.22 |
---|---|
[STS] 프로젝트 빨간 엑스 x 표시 해결방법 (maven) (0) | 2024.04.22 |
[Spring] jsonView 사용 방법 (json형태로 화면 반환) (0) | 2024.01.15 |
[Spring] 한글 깨짐 해결 방법 (0) | 2023.07.25 |
[Spring] egovMap (0) | 2023.05.24 |