첨부파일은 단순한 문자열이 아니고 0과 1로 이루어진 바이너리 데이터이기에
enctype속성이 multipart/form-data라는 형식을 이용해 전송
이때
------WebKitFormBoundaryg3IbmadDo87Bmh2R
이런식의 마디로 끊어지는 part가 생긴다.
@MultipartConfig -> part를 얻을 수 있도록 도와줌/ 조건을 지정
part 인터페이스 -> 여러개로 쪼개진 파트를 하나씩 객체로 보고 만든 인터페이스
디스크에 저장되기 전에 데이터를 임시파일로 잠시 저장해두는 곳
임시 파일은 다른 목적으로 메모리를 늘리기 위해 정보를 임시로 저장하거나 프로그램이 특정 기능을 수행할 때 데이터 손실을 방지하기 위한 안전망의 역할을 하기 위해 만들어지는 파일이다. 임시 파일은 프로그램이 작업에 충분한 메모리를 할당 할 수 없을 때 특히 유용함.
-알아본 점
(1) 요청으로부터 part 객체를 얻어온다
(2) part의 헤더의 정보를 파악한다.
(3) part 인터페이스에 대한 메서드 종류
(4) uuid class와 Type 4 UUID를 얻는 메소드를 이용해서 고유하게 파일을 유지
쿼리스트링에 UUID도 주는 이유 = 실제 저장된 파일
-유니크한 이름을 주기위해 ( 동일한 파일 이름이 업로드 되는 경우를 피하자!)
-어떻게 사용하려나?
실제 파일은 DB에 경로를 저장해놓고 해당하는 UUID를 연결해준다.
(참고) https://small-dbtalk.blogspot.com/2014/12/
https://okky.kr/article/489173?note=1486286
(5) URLEncoder 를 해주는 이유
!URL 인코딩 필요성
-프로그램 전송파라미터의 값을 만들어 낼때는 무조건 인코딩하라
-> 빈문자/특수문자 등 비문자는 인식을 못해서 정확한 전달이 어렵기 때문이다.
@WebServlet("/Upload")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.trace("service(request,response) invoked");
request.setCharacterEncoding("utf8");
response.setContentType("text/html; charset=utf8");
@Cleanup
PrintWriter out = response.getWriter();
// --------------------------
// Common Step:
// --------------------------
// multiple 속성이 있는 <input type = file multiple> 태그로 업로드된 복수개의 파일을 저장하는 로직
// 이게 전체라면
Collection<Part> parts = request.getParts();
parts.forEach(p -> log.info(p.getName()));
//멀티파트를 구성하는 각 Part의 이름이란? => 각 파트를 구성하는 전송파라미터의 이름
// 이건 각각
// Part uploadFilePart =request.getPart("uplaodFile");
// log.info("uploadFilePart:{} , uploadFilePart");
// 각 파트의 구성 헤더정보를 접근해보자!
Iterator<Part> iter = parts.iterator();
while(iter.hasNext()) { // 그 다음 요소가 있느냐 ?
Part part =iter.next(); // 있다면 그 다음 요소를 달라.
// 이 Part에 포함된,
log.info("================================================");
log.info("\t + 1. part.getName: {}", part.getName());// 전송파라미터 이름(=파트 이름)
log.info("\t + 2. part.getContentType(): {}" , part.getContentType()); // Content-Type 헤더의 값
log.info("\t + 3. part.getSize(): {}" , part.getSize()); // Body의 Content Length
log.info("\t + 4. part.getSubmittedFileName(): {}" , part.getSubmittedFileName()); // 파일의 원본파일명
log.info("\t + 5. part.getHeaderNames(): {}" , part.getHeaderNames()); // 헤더명의 목록
// 첨부파일만 포함하고 있는 Part를 필터링 해서, 첨부파일 저장
if( part.getSubmittedFileName() != null) { // 첨부파일의 조건에 부합할때만 저장
// part.write(part.getSubmittedFileName()); // 원본파일명으로 기 지정된 경로에 저장
// // ->이렇게 저장하면 안된다. 유니크한 파일 저장명을 사용하라
// UUID 파일로 저장
try {
// 파일을 저장시, 오늘날짜 폴더가 있으면 재사용하고, 없으면 생성하여 사용(날짜바뀜)
String uuid = "UUIDGenerator.generateUniqueKeysWithUUIDAndMessageDigest()";
part.write(uuid);
part.delete();// 다운로드시 임시파일 삭제
// 응답으로 각 파일의 다운로드 링크 생성
String encodedFilename = URLEncoder.encode(part.getSubmittedFileName(),"utf8");
String link = String.format("<a href='/FileDown?file_name=%s&uuid=%s'>파일다운로드</a><br>",encodedFilename,uuid);
out.println(link);
}catch (Exception e) {;;}
}// if
}//while
out.flush();
} // service
}// end class
MIME타입: ASCII가 아닌 문자 인코딩을 이용해 영어가 아닌 다른 언어로 된 전자 우편을 보낼 수 있는 방식을 정의
텍스트기반 프로토콜에 바이너리 파일을 전송하기위해 고안한다.
=> 헤더에 content type 사용 (타입/ 서브타입)
=>ex) (video/webm) , (image/bmp)
Base64:바이너리 데이터를 텍스트 데이터로 변환할 때 사용
64진법으로 표현 /모두 64개의 문자로 구성(6 bit)
=>주고 받는쪽에 서로 문자셋이 다를 수 있기 때문에 공통적인것들로만 뽑아놓은 것
=> 단점: 6bit => 8bit 33%사이즈 커짐 / 통쨰로 HTML에 넣었을 때 링크깨지는것은 걱정안해도 된다.
-바이너리를 어떤 형식으로 보여줄지 둘중에 결정하면 된다.
-알아본점
(1) Mime Type을 설정해주는 것 (파일은 Mime타입으로 전송)
기본값: application/octet-stream
(2)헤더 영역을 추가해주는것 (파일명은 utf8 아스키문자집합으로 생성)
왜? filename속성에 본래 파일명으로 나올 수 있게 하기위하여
(3)ServletOutputStream
파일데이터 출력 시 사용
@WebServlet("/FileDown")
public class FileDownServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
log.trace("service");
req.setCharacterEncoding("utf8");
// Step.1 다운로드에 필요한 전송 파라미터 획득
String file_name = req.getParameter("file_name");
String uuid = req.getParameter("uuid");
log.info("\t + file_name: {}",file_name); // 원본파일명
log.info("\t + uuid: {}",uuid); // 저장파일명
// Step.2 저장파일명을 이용해서, 파일데이터를 응답메시지의 Body에 Write.
String path = "C:/temp/upload/" +uuid; // 저장파일에 대한 경로
@Cleanup
FileInputStream fis = new FileInputStream(path);
//Step.3 다운로드 대상 파일의 Mime Type 획득
ServletContext sc = req.getServletContext();
String mimeType = sc.getMimeType(file_name);
if(mimeType == null) {
mimeType = "application/octet-stream";
}// if
//Step.4 응답문서의 Content Type 헤더의 값을 을 Mime Type으로 지정 (핵심)
resp.setContentType(mimeType);
//Step.5 응답문서의 헤더영역에 새로운 아래의 헤더 추가 (핵심)
//String.getBytes("문자집합") ==> 문자열 > byte[] 배열로 인코딩
// 주의할점: 원본파일명은 아스키 문자집합으로 인코딩해서 넣어야한다.
String encodedFN = new String(file_name.getBytes("utf8"),"ISO-8859-1");
// 전송의 바디 부분에 오는 컨텐츠의 기질/성향을 나타냄
// 컨텐츠의 성질,배치에 부착해준다, filename= encodedFN 을 <<이런 뜻
resp.addHeader("Content-Disposition", "attachment; filename="+encodedFN );
//Step.6 저장파일명(UUID)를 이용하여, 파일데이터를 읽어, 응답메시지의 body에
// write & send
byte[] bagagi = new byte[100];
int readBytes = 0; // 실제 한 번 바가지로 읽어낸 바이트 수를 저장
// 응답메시지의 body에 바이트 기반 출력스트림을 획득
ServletOutputStream sos = resp.getOutputStream();
while((readBytes =fis.read(bagagi)) != -1) { // -1 = EOF
sos.write(bagagi, 0 , readBytes);
} //while
sos.flush();
} // service
} //end class