68_[jsp servlet]cos 라이브러리 기반 파일 업로드
COS 라이브러리 기반 파일 업로드
🌺 WHAT IS COS?
- 자바에서 파일 업로드 시에 자주 사용되는 라이브러리
- 우리는 cos.jar 파일을 이용할 것!
- com.oreilly.servlet의 약자!
📌 COS 라이브러리의 cos.jar 파일 설정 방법
- http://www.servlets.com/cos 접속
- 스크롤을 내려서(왼쪽 네비게이션 바 부분이 아니라 본문 부분을 내리다 보면 존재!) download 카테고리의 cos-26Dec2008.zip 파일 다운로드(21.06.08 기준 현재 cos-20.08.zip으로 갱신되어있었다!)[http://www.servlets.com/ 로 접속 수 네비게이션 바에서 COS~로 시작하는 카테고리를 눌러도 된다!]
- 압축파일을 받아 압축을 해제하고, lib 폴더의 cos.jar 파일을 확인해둔다
- 이 cos.jar 파일을 WebContent의 WEB-INF 폴더의 하위 폴더인 lib 폴더에 import 해주면 서블릿에서 사용할 수 있게 된다!
🌟 파일을 서버에 전송하기 위해서 form 태그 및 input 태그에서 설정해주어야 할 몇 가지 사항들 🌟
- form 태그의 method는 post로!
- form 태그의 enctype은 multipart/form-data 로 설정하기!!
- input 태그의 type은 file로!
❓ what is enctype ❓
ref : http://tcpschool.com/html-tag-attrs/form-enctype
enctype은 서버로 양식을 전송하기 전 인코딩하는 방식을 다루는 속성으로, post method인 경우에만 사용이 가능하다
이 속성의 값으로는
- application/x-www-from-urlencoded : 기본값! 모든 문자들은 서버로 전송되기 전에 인코딩 됨을 명시
- multipart/form-data : 주로 파일을 다룰 때 사용되는 값으로, 모든 문자를 인코딩하지 않음을 나타냄
- text-plain : 공백문자만 + 기호로 인코딩하고, 나머지 문자는 모두 인코딩하지 않음을 명시
❓ enctype을 설정해주는 이유 ❓
- 이 속성이 설정되지 않으면 파일명만 텍스트로 서버로 전송되고, 파일 객체는 전달되지 않기 때문!
❓ post method를 사용하는 이유❓
-
get 방식을 이용하게 되면 쿼리스트링에 해당 정보를 노출하여 정보를 전송하게 되는데, 이는 파일과 같은 전송할 데이터의 용량이 큰 경우에 부적합하기 때문!(get 방식으로는 255자(=$2^8$ -1 =즉 8비트를 이용])이하의 적은 용량의 데이터만 전송할 수 있음]
-
post 방식은 255자 이상의 대용량 데이터를 전송할 수 있음!
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
- 참고로, cos.jar에서 제공되는 MultipartRequest 클래스(이름에서 풍겨지는.. enctype의 속성값의 향기..?)를 확인하기 위해서는 Build Path- Add to Build Path를 눌러준다!
▶️ 그러면 java resources의 Libraries 의 Referenced Libraries 아래에서 cos.jar 하위에 com.oreilly.servlet 밑에서 MultipartRequest.class 파일을 확인해볼 수 있다
(클래스 파일을 확인해보고 싶었는데 attach 문제가 발생하여 문서로 확인해보기로 하였다 😭)
http://www.servlets.com/cos/javadoc/com/oreilly/servlet/MultipartRequest.html
위의 문서 등을 확인해보면 MutlipartRequest클래스의 생성자는 아래와 같은 파라미터를 이용함을 알 수 있다
MultipartRequest 클래스에서 사용되는 메서드들
🌟 클라이언트가 jsp 페이지 요청 시, 컴파일된 파일들이 워크스페이스 디렉토리 내의 .metadata/.plugins/org.exlipse.wst.server.core/tmp/wtpwebapps 폴더에 저장된다!
(나의 경우 D:\ji_study\jsp servlet\eclipse_jsp_servlet.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\web-study-10 에 저장됨을 확인하였다)
아래의 예제와 함께 몇가지 주의할 사항을 확인하면서 연습해보자
먼저, 파일을 업로드 하는 페이지를 작성해보자
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일업로드 테스트</title>
</head>
<body>
<section>
<article id="fileUpload">
<form action="upload.do" method="post" enctype="multipart/form-data">
<label>글쓴이 :<input type="text" name="writer"></label><br>
<label>제목 :<input type="text" name="title"></label><br>
<label>파일 지정하기 : <input type="file" name="uploadFile"></label><br>
<input type="submit" value="파일 업로드하기">
</form>
</article>
</section>
</body>
</html>
이번에는 위의 페이지와 post method로 연결될 upload.do에 해당되는 서블릿이다
package com.saeyan.controller;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
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 com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;
/**
* Servlet implementation class UploadServlet
*/
@WebServlet("/upload.do")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public UploadServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//response.getWriter().append("Served at: ").append(request.getContextPath());
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//doGet(request, response);
//post 방식으로 업로드 처리
request.setCharacterEncoding("UTF-8");
//content-type 설정(header setting)
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//파일을 다운받는 경로
String savePath = "upload";
//최대 업로드 파일 크기를 5MB로 제한
int uploadFileSizeLimit = 5 * 1024 * 1024;//MB=1024^2 B
//인코딩
String encType="UTF-8";
//서버 상 실제 경로 찾기
ServletContext context = getServletContext();
String uploadFilePath = context.getRealPath(savePath);
System.out.println("서버 상 실제 디렉토리:"+uploadFilePath);
// https://coding-factory.tistory.com/283
// java.lang.IllegalArgumentException: Not a directory 는 디렉토리가 없어서 생긴 문제이므로
//이를 체킹 후 생성
File folder = new File(uploadFilePath);
if(!folder.exists()) {
//폴더 생성(폴더가 없으면)
folder.mkdir();
}
//HttpServletRequest 객체, 업로드할 경로, 업로드할 사이즈 제한값, 인코딩 방법, 중복된 파일명에 대한
//새로운 이름 부여를 매개변수로 하는 MultipartRequest객체 생성 -> 서버로 파일이 업로드됨
try {
MultipartRequest req = new MultipartRequest(
request,//multipart request와 연결될 request 객체
uploadFilePath,//서버 상 실제 디렉토리
uploadFileSizeLimit,//파일 용량 제한
encType,//인코딩 방법
new DefaultFileRenamePolicy()//동일 이름 존재시 새로운 이름 부여
);
//업로드된 파일의 서버상 이름 얻기
String fileName = req.getFilesystemName("uploadFile");
if(fileName == null) {
//파일이 업로드되지 않았을 때
System.out.print("파일이 업로드되지 않았음");
}else {
//파일이 업로드되었을 때
out.println("<br>글쓴이 : "+req.getParameter("writer"));//request가 연결된 req객체에서도 이제 파라미터를 마음껏 가져올 수 있음
out.println("<br>제 목: "+req.getParameter("title"));
out.println("<br>파일명: "+fileName);//서버에 저장된 파일이름 출력
}
}catch(Exception e1) {
e1.printStackTrace();
}
}
}
주의할 점이 있다면, 위에서 저장되는 위치에 폴더명이 존재하지 않으면
java.lang.IllegalArgumentException: Not a directory
에러가 발생하는데, 이는 당연하다! 왜냐하면 지금 서버에 폴더가 없으니까!
그래서 폴더를 생성하는 방식을 찾아보았는데,
https://coding-factory.tistory.com/283
위의 글을 참조하였다! 리눅스의 명령어인 mkdir이 떠오르는 메서드를 이용하여
File folder = new File(폴더명);
if(!folder.exists()){
folder.mkdir();
}
위와 같이 폴더가 존재하지 않으면 폴더를 생성하도록 해주는 것이다!
그러면 위에 페이지 등 파일이 저장되는 위치에 upload라는 이름의 폴더가 생성됨을 확인해볼 수 있다
서블릿 부분에서 몇 가지 짚고 넘어갈 사항이 있어서 이 부분을 확인하고 넘어가보자!
COS 라이브러리를 이용한 파일 업로드
먼저 위에서
new DefaultFileRenamePolicy()
로 파일 rename 여부에 대한 기준을 세워두었기 때문에, 아래처럼, 동일한 파일명을 가진 파일의 경우, 서버에서 파일명을 새로운 이름으로 변경하여 적용하게 된다!
COS 라이브러리- new DefaultFileRenamePolicy
그 다음은, multipart/form-data를 지원하기 위한 MultipartRequest 클래스 객체를 생성하기 위한
- 클라이언트가 업로드한 파일을 서버에서 다운받는 경로(String savePath)
- 파일 업로드 크기 제한(int uploadFileSizeLimit)
- 인코딩(String encType)
를 미리 선언, 정의해둔 후 객체를 생성한 부분이다
//파일을 다운받는 경로
String savePath = "upload";
//최대 업로드 파일 크기를 5MB로 제한
int uploadFileSizeLimit = 5 * 1024 * 1024;//MB=1024^2 B
//인코딩
String encType="UTF-8";
...
MultipartRequest req = new MultipartRequest(
request,//multipart request와 연결될 request 객체
uploadFilePath,//서버 상 실제 디렉토리
uploadFileSizeLimit,//파일 용량 제한
encType,//인코딩 방법
new DefaultFileRenamePolicy()//동일 이름 존재시 새로운 이름 부여
);
그리고 위의 필드 중 uploadFilePath와 관련된 부분이 지금 짚고 넘어갈 부분인데, 서버 상의 실제 경로를 찾아내기 위해 ServletContext 객체를 이용하여 getRealPath 메서드를 이용하였다
//서버 상 실제 경로 찾기
ServletContext context = getServletContext();
String uploadFilePath = context.getRealPath(savePath);
🌟 마지막으로 , 파일의 서버상 이름을 얻기 위한 부분인데, 이 부분에서 파라미터는 input type=file의 name 값이 들어가야 한다!
//업로드된 파일의 서버상 이름 얻기
String fileName = req.getFilesystemName("uploadFile");
/*
input type=file name=uploadFile에서의 name과 일치시켜야!!
*/
❓ 음.. 그런데 파일을 한개만 올리는 상황만 있는 것도, 아니고, 여러 개 파일을 받아올 수 있어야 하지 않을까요 ❓
그래서 존재하는 것이 바로
MultipartRequest 객체.getFileNames()
이다! 이 메서드는 Enumeration 형으로 업로드된 파일명들을 반환해준다!
이를 이용하여
먼저 화면단에 보여질 화면은 다음과 같이 변경해주고
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일업로드 테스트</title>
</head>
<body>
<section>
<article id="fileUpload">
<form action="multiUpload.do" method="post" enctype="multipart/form-data">
<label>글쓴이 :<input type="text" name="writer"></label><br>
<label>제목 :<input type="text" name="title"></label><br>
<label>1.파일 지정하기 : <input type="file" name="uploadFile01"></label><br>
<label>2.파일 지정하기 : <input type="file" name="uploadFile02"></label><br>
<label>3.파일 지정하기 : <input type="file" name="uploadFile03"></label><br>
<input type="submit" value="파일 업로드하기">
</form>
</article>
</section>
</body>
</html>
요청이 들어올 컨트롤러에서는 아래와 같이
package com.js.controller;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletContext;
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 com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;
/**
* Servlet implementation class MultiUploadServlet
*/
@WebServlet("/multiUpload.do")
public class MultiUploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public MultiUploadServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//encoding setting
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//클라이언트에서 업로드한 파일(들)을 서버에서 다운로드할 경로
String savePath = "upload";
File folder = new File(savePath);
if(!folder.exists()) {
folder.mkdir();
}
//최대 5MB로 제한
int maxFileSize = 5 * 1024 * 1024;
//multipart request객체의 인코딩
String encType = "UTF-8";
//서버에서의 파일의 실제 경로를 얻기 위함
ServletContext context = getServletContext();
String realPath = context.getRealPath(savePath);
try {
MultipartRequest mr = new MultipartRequest(
request,//request객체-서버와 주고받게될 request 객체
realPath,//서버에서 관리되는 파일의 실제 경로
maxFileSize,//파일 최대 용량 지정
encType,//인코딩
new DefaultFileRenamePolicy()
);
//연결된 페이지에서 Enumeration 형태로 여러개의 파일 받아오기
**Enumeration files = mr.getFileNames();**
int i = 0;
**while(files.hasMoreElements())** {
//파일이 존재한다면
//파일 이름들을 저장 후 그 파일명으로 서버에 저장된 파일 이름을 가져오고
String file = (String)files.nextElement();
String serverFileName = mr.getFilesystemName(file);
//클라이언트에서 업로드한 파일명도 가져오기
String originalFileName = mr.getOriginalFileName(file);
out.print("<p>#"+i+"업로드된 파일명: "+serverFileName+"</p>");
out.print("<p>업로드된 파일 원본명: "+originalFileName+"</p>");
out.print("<hr>");
i++;
}
}catch(Exception e1) {
System.out.println("[ERR]서블릿 multipart request 객체 생성 에러");
e1.printStackTrace();
}
}
}
Enumeration MultipartRequest.getFileNames()- 여러 개의 파일 업로드
위의 MultipartRequest객체.getFilesystemName(name속성명)으로는 하나의 파일만 접근하여 String으로 받아올 수 있었는데, 이 방식으로 접근하였을때는 보다 많은 파일을 처리할 수 있다는 점이 매력적이다!
Enumeration getFileName()
꺼내는 방법은 자바 컬렉션의 구 iterator인 Enumeration이므로, hasMoreElements()로 접근하여 한개씩 한개씩 커서를 옮기면서 수행하면 된다!