83일차 Servlet File전송

쿠우·2022년 7월 25일
0

첨부파일과 일반 form 데이터의 차이점

첨부파일은 단순한 문자열이 아니고 0과 1로 이루어진 바이너리 데이터이기에
enctype속성이 multipart/form-data라는 형식을 이용해 전송
이때

------WebKitFormBoundaryg3IbmadDo87Bmh2R
이런식의 마디로 끊어지는 part가 생긴다.

@MultipartConfig -> part를 얻을 수 있도록 도와줌/ 조건을 지정
part 인터페이스 -> 여러개로 쪼개진 파트를 하나씩 객체로 보고 만든 인터페이스

업로드를 하게되면 임시파일이 생김 임시파일에 대해서

디스크에 저장되기 전에 데이터를 임시파일로 잠시 저장해두는 곳
임시 파일은 다른 목적으로 메모리를 늘리기 위해 정보를 임시로 저장하거나 프로그램이 특정 기능을 수행할 때 데이터 손실을 방지하기 위한 안전망의 역할을 하기 위해 만들어지는 파일이다. 임시 파일은 프로그램이 작업에 충분한 메모리를 할당 할 수 없을 때 특히 유용함.

upload 서블릿 예제

-알아본 점
(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에 넣었을 때 링크깨지는것은 걱정안해도 된다.

-바이너리를 어떤 형식으로 보여줄지 둘중에 결정하면 된다.

FileDown 서블릿 예제

-알아본점
(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

profile
일단 흐자

0개의 댓글