본문 바로가기
교재 실습/자바 웹 개발 워크북

11. 파일 업로드

by Jint 2022. 1. 6.

웹 서버에 바이너리 데이터를 보내도록 고안된 멀티파트 인코딩 방법을 알아본다. 톰캣 서버를 실행한 뒤 http://localhost:9999/web02/MultipartTest.html 링크를 실행한다.

MultipartTest.html 링크 실행 화면

MultipartTest.html의 소스를 살펴보면

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
	"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>파일 업로드</title>
</head>
<body>

<h3>파일 업로드 POST 요청: Multipart 인코딩</h3>
<p>
입력폼의 method를 POST으로 지정하고,<br>
enctype 속성의 값을 multipart/form-data로 설정합니다.
</p>
<!-- enctype 속성의 기본값은 "application/x-www-form-urlencoded" -->
<form action="FileUploadServlet" method="post" enctype="multipart/form-data">
사진: <input type="file" name="photo"><br> 
설명: <textarea name="description" cols="50" rows="3"></textarea><br>
<input type="submit" value="추가"><br>
</form> 
</body>
</html>

<form> 태그의 method 속성을 'post' 이고 enctype 속성이 'multipart/form-data' 인 것을 확인할 수 있다. <input> 태그의 type 속성은 'file' 이다.

 

입력폼에서 이미지 파일을 추가하고 입력폼에 설명을 적은뒤 추가 버튼을 클릭한다.

에러 발생

 

이 때 파일 경로를 찾을 수 없는 에러가 발생했다.

package lesson02.file;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

@WebServlet("/FileUploadServlet")
public class FileUploadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.println("<HTML><HEAD><TITLE>Multipart Test</TITLE></HEAD><BODY>");
		
		try {
			String contextRootPath = this.getServletContext().getRealPath("/");

			DiskFileItemFactory diskFactory = new DiskFileItemFactory();
			//diskFactory.setRepository(new File(contextRootPath + "/WEB-INF/temp")); //이 경로에선 보안으로 인해 사진이 안나온다.
			diskFactory.setRepository(new File(contextRootPath + "/upload"));
			ServletFileUpload upload = new ServletFileUpload(diskFactory);
			@SuppressWarnings("unchecked")
			List<FileItem> items = upload.parseRequest(request);
			
			FileItem item = null;
			for (int i = 0; i < items.size(); i++) {
				item = items.get(i);
				
				if (item.isFormField()) {
					processFormField(out, item);
				} else {
					processUploadFile(out, item, contextRootPath);
				}
			}
			
		} catch(Exception e) {
			out.println("<PRE>");
			e.printStackTrace(out);
			out.println("</PRE>");
		}
		
		out.println("</BODY></HTML>");
	}
	
	//파일 업로드
	private void processUploadFile(PrintWriter out, FileItem item, String contextRootPath) throws Exception {
		String name = item.getFieldName();
		String fileName = item.getName();
		String contentType = item.getContentType();
		long fileSize = item.getSize();
		
		String uploadedFileName = System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf("."));
		File uploadedFile = new File(contextRootPath + "/upload/" + uploadedFileName);
		item.write(uploadedFile);
		
		out.println("<P>");
		out.println("파라미터 이름:" + name + "<BR>");
		out.println("파일 이름:" + fileName + "<BR>");
		out.println("콘텐츠 타입:" + contentType + "<BR>");
		out.println("파일 크기:" + fileSize + "<BR>");
		out.println("<IMG SRC='upload/" + uploadedFileName + "' width='300'><BR>");
		out.println("</P>");
	}
	
	private void processFormField(PrintWriter out, FileItem item) throws Exception{
		String name = item.getFieldName();
		String value = item.getString("UTF-8");
		
		out.println(name + ":" + value + "<BR>");
	}
	
}

파일 저장 경로를 설정하는 부분이 diskFactory.setRepository(new File(contextRootPath + "/WEB-INF/temp")); 에서 폴더가 WEB-INF로 되었기 때문이다. 업로드 파일을 불러오는 경로는 File uploadedFile = new File(contextRootPath + "/upload/" + uploadedFileName); 로 되어 있었다. 파일 저장 경로와 업로드 파일을 불러오는 경로가 일치하지 않기에 위와 같은 오류가 발생한 것이다. 파일 저장 경로를 diskFactory.setRepository(new File(contextRootPath + "/upload")); 로 수정하고 해당 경로에 upload 파일을 생성한다.

upload 폴더 생성

이후 다시 이미지 파일을 업로드하고 설명을 적고 출력하면

파일 업로드 입력폼 실행 결과

이렇게 이미지 파일을 성공적으로 읽어서 출력하게 된다.

위에서 WEB-INF 폴더 밖으로 경로를 수정한 이유는 WEB-INF 폴더는 보안으로 인해 파일을 저장하더라도 파일 위치를 찾을 수 없는 오류가 발생하기 때문이다.

 

크롬의 DevTools를 실행하여 Network 탭에서 HTTP 요청 프로토콜을 확인한다.

멀티파트 방식의 POST 요청의 요청정보

POST 요청에서 일반 전송과 멀티파트 전송의 가장 큰 차이점은 Content-Type 헤더와 메시지 본문의 형식이다. 일반 전송 방식의 Content-Type 헤더는 'application/x-www-form-urlencoded' 이지만 멀티파트 전송 방식의 Content-Type 헤더는 'multipart/form-data; boundary=----WebKitFormBoundaryhulKrLlY66Ba1pnC' 이다. 'multipart/form-data'가 미디어 타입이면 파트 구분자는 'boundary=----WebKitFormBoundaryhulKrLlY66Ba1pnC'이다. GET이나 POST 요청의 웹 서버에 보내는 문자 매개변수들은 '&'를 사용하여 구분하지만 바이너리 데이터는 '&'를 사용하여 매개변수를 구분할 수 없기 때문에 Content-Type 헤더의 boundary 값이 구분자가 된다. 이 구분자는 웹 브라우저에서 임의로 생성한다.

 

자바 웹 프로그래밍에서도 일반적인 형식과 멀티 파트 형식을 구분하여 데이터를 처리한다.

 

참고도서 : https://freelec.co.kr/book/1674/

 

[열혈강의] 자바 웹 개발 워크북

[열혈강의] 자바 웹 개발 워크북

freelec.co.kr

'교재 실습 > 자바 웹 개발 워크북' 카테고리의 다른 글

13. 서블릿, JSP vs. Java EE vs. WAS  (0) 2022.01.08
12. CGI프로그램과 서블릿  (0) 2022.01.07
10. POST 요청  (0) 2022.01.05
9. GET 요청  (0) 2022.01.04
8. HTTP 프로토콜의 이해 (2)  (0) 2022.01.03

댓글