참고글
파일 업로드
웹 환경에서
파일업로드
는 많은곳에서 사용됩니다. 예를 들어 게시글의 첨부파일이나 회원가입시 프로필 사진을 업로드 할때에도 쓰이며, 쇼핑몰에서 상품 이미지를 등록할 때에도 사용될것입니다.Servlet/Jsp
환경에서 파일업로드를 구현할때는 보통 파일 업로드와 관련된 라이브러리를 사용합니다. Servlet 3.0
이전에는 전송된 File
을 쉽게 처리할 수 있도록 지원해주는 API
가 없기 때문입니다. 업로드 관련 라이브러리 없이 직접 업로드 기능을 구현하여 사용해도 되지만 HTTP 기반의 파일 입출력 관련 기능을 구현해서 사용하기엔 번거로운 점이 많기때문에 거의 대부분의 경우 라이브러리를 사용하여 구현합니다.가장 많이 사용되는 라이브러리는
스프링 프레임워크
에서도 내부적으로 사용하는 라이브러리인 Apache Commons FileUpload
가 있으며, 최근에는 COS
라는 라이브러리도 많이 사용합니다. Servlet 3.0 이후 부터는 Part
라는 API를 제공하므로 Part를 사용해도 됩니다.파일 업로드 구현 예제 프로젝트
이 글에서는
Apache Commons FileUpload
라이브러리를 사용하여 파일 업로드를 구현해 보도록 할것입니다.-
uploadPage.jsp
- 사용자가 파일을 업로드할 때 사용할 업로드 페이지입니다. -
FileUploadServlet.java
-uploadPage.jsp
에서 파일을 전송했을때 실질적으로 파일을 서버에 저장해주는 역할을 수행하는 서블릿입니다. -
WEB-INF/lib
- 파일 업로드시 사용되는 라이브러리가 저장되는 위치입니다.
파일 업로드를 위한 라이브러리 세팅하기
사용자가 업로드한 파일 데이터는 일반적으로
input 태그
를 통해 입력한 텍스트와는 다른 형태로 전송됩니다. multipart/form-data
라는 형태로 인코딩되어 전송되는데, 이를 다루려면 많은 번거로움이 있습니다. 따라서 파일 업로드를 지원해주는 Apache
의 오픈소스 라이브러리를 사용해야 합니다. 설정 과정이 조금 길다보니 따로 포스팅을 하였으니 아래 링크를 참고해주시기 바랍니다.업로드 파일 저장 경로에 폴더 생성
클라이언트에서 업로드한 파일을 저장해둘 경로에 폴더를 생성해 주어야 합니다.
저의 경우에는 알기 쉽게 C 드라이브 하위에 C:\attaches 경로로 폴더를 생성해 두었습니다.
파일업로드 JSP 페이지 작업하기
파일 업로드 관련 서버쪽 작업에 앞서 사용자가 파일을 등록할 수 있는 업로드 페이지를 만들어 볼 것입니다. 간단히 게시판이라 생각하고 업로드되는 파일이 어떤 파일인지를 설명하는 INPUT TEXT 태그와
파일첨부 버튼
그리고 전송 버튼
을 만들어보겠습니다.HTML
에서는 기본적으로 파일을 첨부할 수 있는 API
를 제공하고 있는데, <INPUT>
태그의 type
속성값을 FILE
로 지정해주는 것입니다. 여기서 주의해야 할 점은 <form>
태그의 method
속성이 post
여야 하며, enctype
속성이 multipart/form-data
여야 한다는 점 입니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>첨부 업로드</title>
</head>
<body>
<form action="<%= application.getContextPath() %>/fileUpload" method="post" enctype="multipart/form-data">
파일 설명 : <input type="text" name="description"><br>
파일1 : <input type="file" name="file1"><br>
파일2 : <input type="file" name="file2"><br>
<input type="submit" value="전송">
</form>
</body>
</html> |
cs |
enctype
enctype
속성은 <form>
태그의 데이터들을 전송할때 데이터들을 어떤 형식으로 변환할것인지에 대한 값을 지정하는 속성입니다. 이를 지정해주지 않으면 기본적으로 application/x-www-form-urlencoded
라는 형식으로 데이터를 전송하게 되는데 이는 단순한 text
로 name=value&name=value...
형식을 갖는 구조입니다. 그러나 파일은 내부적으로 0과 1로만 이루어진 바이너리 형식이므로 단순한 텍스트 데이터와는 성질이 다릅니다. 추가적으로 JSP 소스상에서
<form>
태그안을 자세히 보면 input 태그가 세 개 있고 하나는 text이며, 두개는 file 타입인것을 알 수 있습니다.
파일 설명 : <input type="text" name="description"><br>
파일1 : <input type="file" name="file1"><br>
파일2 : <input type="file" name="file2"><br> |
cs |
form태그의
method
속성값이 POST
이니 데이터가 HTTP 요청 메시지의 body
에 들어간다는점은 알 수 있지만 기본적으로 HTTP는 한번의 요청과 한번의 응답으로 트랜잭션이 이루어집니다.그런데 지금과 같이 텍스트 데이터 하나와 파일 바이너리 데이터 두개인 경우를 어떤식으로 각각의 데이터를 전송해야 할지 애매합니다. 요청을 세번으로 나누어 해야 하는것일까요?
이럴때 사용하는 인코딩 타입이 여러가지 타입의 데이터를 한번에 모두 전송할 수 있는 multipart/form-data인것입니다.
multipart/form-data
에 대한 내용은 글 상단의 링크를 참고하도록 하고 enctype
을 multipart/form-data
로 지정해주어야 한다는 점만 기억하면 됩니다.enctype을 multipart로 변경하지 않으면?
만약
enctype
속성을 변경해주지 않는 경우 앞서 말했듯 여러가지 타입의 데이터를 동시에 전송할 수 없기 때문에 텍스트 데이터만 전송하게 됩니다. 따라서 파일의 실질적인 내용은 전송되지 않고 파일명 또는 사용자 컴퓨터상의 파일 경로만을 전송하게 됩니다. 파일명만 전송하는지 파일의 full path를 전송할지의 동작은 브라우저별로 차이가 있기때문에 서버에서는 이를 고려하여 업로드 처리 해주어야 합니다.파일 업로드를 처리할 서블릿 작성
uploadPage.jsp
에서 사용자가 파일을 전송 했을때 실질적으로 업로드 처리를 수행할 FileUploadServlet
클래스를 작성해 보도록 하겠습니다. 먼저 코드는 다음과 같습니다.
package servlet;
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("/fileUpload")
public class FileUploadServlet extends HttpServlet {
private static final String CHARSET = "utf-8";
private static final String ATTACHES_DIR = "C:\\attaches";
private static final int LIMIT_SIZE_BYTES = 1024 * 1024;
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html; charset=UTF-8");
request.setCharacterEncoding(CHARSET);
PrintWriter out = response.getWriter();
File attachesDir = new File(ATTACHES_DIR);
DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
fileItemFactory.setRepository(attachesDir);
fileItemFactory.setSizeThreshold(LIMIT_SIZE_BYTES);
ServletFileUpload fileUpload = new ServletFileUpload(fileItemFactory);
try {
List<FileItem> items = fileUpload.parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
System.out.printf("파라미터 명 : %s, 파라미터 값 : %s \n", item.getFieldName(), item.getString(CHARSET));
} else {
System.out.printf("파라미터 명 : %s, 파일 명 : %s, 파일 크기 : %s bytes \n", item.getFieldName(),
item.getName(), item.getSize());
if (item.getSize() > 0) {
String separator = File.separator;
int index = item.getName().lastIndexOf(separator);
String fileName = item.getName().substring(index + 1);
File uploadFile = new File(ATTACHES_DIR + separator + fileName);
item.write(uploadFile);
}
}
}
out.println("<h1>파일 업로드 완료</h1>");
} catch (Exception e) {
// 파일 업로드 처리 중 오류가 발생하는 경우
e.printStackTrace();
out.println("<h1>파일 업로드 중 오류가 발생하였습니다.</h1>");
}
}
} |
cs |
DiskFileItemFactory
DiskFileItemFactory
는 업로드된 파일을 저장할 저장소와 관련된 클래스입니다.
File attachesDir = new File(ATTACHES_DIR);
DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
fileItemFactory.setRepository(attachesDir);
fileItemFactory.setSizeThreshold(LIMIT_SIZE_BYTES); |
cs |
setRepository()
메서드는 업로드 된 파일을 저장할 위치를 File
객체로 지정합니다.setSizeThreshold()
메서드는 저장소에 임시파일을 생성할 한계 크기를 byte
단위로 지정합니다. 예를 들어 이 값을 1024
로 지정한 경우 1024byte
이상의 파일을 업로드 했을때 메모리에 있던 파일의 바이너리 데이터를 저장소에 임시파일로 잠시 저장합니다.이렇게 임시파일로 저장하는 이유는 대용량 파일을 업로드 했을때 그만한 크기의 데이터를 웹어플리케이션이 동작하는 JVM 메모리상에 모두 로드하는것은 부담이 되기 때문입니다. 임시파일로 저장되었던 파일은 이후에
FileItem#write()
메서드를 통해 실제 파일로 변경되거나 FileItem#delete()
메서드를 통해 제거됩니다.ServletFileUpload
ServletFileUpload
클래스는 HTTP 요청에 대한 HttpServletRequest
객체로부터 multipart/form-data
형식으로 넘어온 HTTP Body 부분을 다루기 쉽게 변환(parse
)해주는 역할을 수행합니다. parseRequest()
메서드를 수행하면 FileItem
이라는 형식으로 변환해줍니다.
ServletFileUpload fileUpload = new ServletFileUpload(fileItemFactory);
List<FileItem> items = fileUpload.parseRequest(request); |
cs |
FileItem
사용자가 업로드한
File
데이터나 사용자가 input text
에 입력한 일반 요청 데이터에 대한 객체입니다. FileItem#isFormField()
메서드의 리턴값이 true
이면 text
같은 일반 입력 데이터이며, false
이면 파일 데이터입니다. 즉 리턴값이 false
인 경우에만 업로드된 파일인것으로 인지하여 처리하면 됩니다.
메서드 |
설명 |
public String getFieldName() |
데이터의 name을 리턴합니다. input태그에 설정된 name 값입니다. |
public boolean isFormField() |
input태그에 입력된 데이터가 단순히 text나 checkbox등을 통해 입력된 값인지의 여부를 리턴합니다. 첨부파일같은 바이너리 데이터이면 false를 리턴합니다. |
public String getString(final String charset) |
첨부파일이 아닌 단순한 form데이터인 경우 입력된 값을 chareset 에 따라 인코딩하여 리턴합니다. |
public String getName() |
데이터가 첨부파일인 경우 파일명 또는 파일 경로를 리턴합니다. |
public long getSize() |
데이터의 크기(파일의 크기)를 byte 단위로 리턴합니다. |
public void write(File file) throws Exception |
현재 데이터가 첨부파일 일 때 매개변수로 넘겨준 File객체의 경로로 출력(저장)합니다. |
파일 저장코드 분석
File.separator
는 운영체제별로 다른 파일경로 구분자를 담고 있습니다. 예를 들어 Windows
환경에서는 각 디렉터리를 구분할 때 \
를 사용하며 linux
계열은 /
를 사용합니다. 따라서 업로드한 파일 경로의 마지막 separator
뒤에 오는 값이 실제 파일명이라 할 수 있습니다. (예: C:\app\test.txt 의 경우 마지막 \ 뒤인 text.txt가 실제 파일명)
String separator = File.separator;
int index = item.getName().lastIndexOf(separator);
String fileName = item.getName().substring(index + 1);
File uploadFile = new File(ATTACHES_DIR + separator + fileName);
item.write(uploadFile); |
cs |
크롬의 경우 POST로 파일전송시 실제 파일명만을 넘겨주는 반면 인터네 익스플로러의 경우 사용자의 드라이버경로부터 절대경로 전부를 리턴해주기 때문에 파일명을 추출할때 이를 고려해 주어야 합니다.
마지막으로
FileItem#write()
메서드를 통해 업로드된 파일을 저장소 디렉터리에 저장합니다. 임시파일로 저장된 경우 임시파일을 실제 파일명으로 변경합니다.이번 글에서는 단순히 파일을 서버측 드라이브에 저장하고 끝이 나는 프로세스였지만 실무에서는 Database에 첨부파일 관련 테이블을 생성해두고 첨부파일에 대한 정보들을 저장해두고 사용할 것입니다.
테스트
지금까지 구현한 내용을 테스트 해보도록 하겠습니다.
서버를 기동하고 http://localhost:8080/jspServletStudy/uploadPage.jsp 로 접근합니다.
앞서작성한 jsp에서
input
태그의 속성을 file
로 해두었기 때문에 파일 선택 버튼이 자동으로 생성되었습니다.파일1과 파일2에
파일선택
버튼을 눌러 파일을 첨부하고 전송
버튼을 눌러 업로드합니다.파일 업로드가 완료됩니다.
저장소로 지정한 경로에 업로드됩니다.
다음글