웹 서버에 바이너리 데이터를 보내도록 고안된 멀티파트 인코딩 방법을 알아본다. 톰캣 서버를 실행한 뒤 http://localhost:9999/web02/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 파일을 생성한다.
이후 다시 이미지 파일을 업로드하고 설명을 적고 출력하면
이렇게 이미지 파일을 성공적으로 읽어서 출력하게 된다.
위에서 WEB-INF 폴더 밖으로 경로를 수정한 이유는 WEB-INF 폴더는 보안으로 인해 파일을 저장하더라도 파일 위치를 찾을 수 없는 오류가 발생하기 때문이다.
크롬의 DevTools를 실행하여 Network 탭에서 HTTP 요청 프로토콜을 확인한다.
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 |
댓글