학원/JSP

12/01 55-1 [JSP] Up/Download

도원결의 2022. 12. 1. 12:22

혼돈의 파일 업/다운로드

정리가 너무 필요하다...

파일 업로드/다운로드도 다른 요소들과 비슷한데

우리가 자바에서 배운 io 개념을 더한거라고 생각 하면 된다

io ... 아이오... 기억 안나는데 .. 다시 공부해야겠네... 아이오....

 

크게보면 이동경로는 이렇다

** 파일 업로드하면 저장할 폴더인 upload도 생성해 놔야함

이건 탐색기로 직접 들어가야 업로드된 파일들을 볼 수 있다.

 

Upload.jsp 파일에서 사용자가 입력  -> FileuploadServlet 업로드 처리과정 거쳐서  -> uploadComplete.jsp 업로드시키기-> UploadList.jsp 업로드된 목록 뿌려주고 -> Download 다운로드도 받을 수 있게 하기

 

- form의 Method속성는 반드시  post방식으로 post방식일 경우 2가지 형태의 인코딩 방식이 있다.
 
    1)application/x-www-form-urlencode: 파일 이름만 전송됨(디폴트값)
    2)multipart/form-data: 파일이름과 함께 파일 데이타가 전송된다

- form의 enctype은 multipart/form-data로 지정해야 한다.
- form의 하위 요소로는  <input type=file> 사용

- form에서 encytype을 "multipart/form-data"로 설정하는 경우,
   사용자가 입력하는 내용과 업로드되는 파일 내용은 한 번에 서버에 전달된다.  이 때 사용자가 입력한 내용과 파일 내용을 구분하기 위해 특수 문자를 사용하는데 이것을 boundary라고 한다.
바운더리 문자는 "--"로 시작한다.

 

1.upload.jsp 

폼을 먼저 받아서 봐!

<div class="d-flex justify-content-end">
        <a href="UploadList.jsp" class="btn btn-success">파일목록</a>
    </div>
    <% if(request.getAttribute("ERROR") !=null){ %>
    <div class="alert alert-danger alert-dismissible">
      <button class="close" data-dismiss="alert">&times;</button>
      <strong>${ERROR }</strong> 
    </div>
    <%} %>
    <form action="${pageContext.request.contextPath}/Upload.kosmo" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label><kbd class="lead">올린이</kbd></label> 
            <input type="text" class="form-control" name="name" placeholder="올린이를 입력하세요">
        </div>
        <div class="form-group">
            <label><kbd class="lead">제목</kbd></label> <input type="text"
                class="form-control" name="title" placeholder="제목을 입력하세요">
        </div>			
        <div class="form-group">
            <label><kbd class="lead">관심사항</kbd></label>
            <div class="d-flex">
                <div class="custom-control custom-checkbox">
                    <input type="checkbox" class="custom-control-input" name="inter"
                        value="정치" id="POL"> <label class="custom-control-label"
                        for="POL">정치</label>
                </div>
                <div class="custom-control custom-checkbox mx-2">
                    <input type="checkbox" class="custom-control-input" name="inter"
                        value="경제" id="ECO"> <label class="custom-control-label"
                        for="ECO">경제</label>
                </div>
                <div class="custom-control custom-checkbox">
                    <input type="checkbox" class="custom-control-input" name="inter"
                        value="연예" id="ENT"> <label class="custom-control-label"
                        for="ENT">연예</label>
                </div>
                <div class="custom-control custom-checkbox ml-2">
                    <input type="checkbox" class="custom-control-input" name="inter"
                        value="스포츠" id="SPO"> <label class="custom-control-label"
                        for="SPO">스포츠</label>
                </div>
            </div>
        </div>
        <div class="form-group">
            <label><kbd class="lead">첨부파일</kbd> </kbd></label>
            <input type="file" multiple name="attachfile" class="form-control-file border">
        </div>
    </div>
        <button type="submit" class="btn btn-primary">파일 업로드</button>
    </form>				
 </div>

파일 업로드를 누르면 예전처럼 바로 다음 페이지로 가는게 아니고 

서블릿으로 보내서 요청로직을 받은 다음 완료 페이지로 넘어오게 될거임

그래서 저 기 폼테그에 이동경로 보면 서블릿경로가 나와있다

"${pageContext.request.contextPath }/Upload.kosmo"  

이거 절대경로로 적어야 한다 !

여러 개의 파일을 업로드 하기 위해 input 타입에 multiple속성으로 주고 

 

 

 <div class="container">
        <div class="jumbotron bg-info">
           <h1>파일 업로드/다운로드</h1>
        </div>
		<fieldset class=" form-group border p-3">
			<legend class="w-auto px-3">파일 업로드 결과</legend>
			<h3>type="file"요소를 제외한 폼 요소들</h3>
			<ul class="list-unstyled">
			   <li>올린이 :<%=request.getParameter("name") %></li>
			   <li>제목 :<%=request.getParameter("title") %></li>
			   <li>관심사항 :<%=Arrays.toString(request.getParameterValues("inter")) %> </li>		
			</ul>
			
			<h3>업로드한 파일 정보</h3>
	<% 
			List<Map<String,String>> files=(List<Map<String,String>>)request.getAttribute("fileInfos");	
			int index=1;
			for(Map<String,String> file: files){ //꺼내오면 map !
	%>
			<kbd class="lead"><%=index++ %>번째 파일</kbd>
    		<ul class="list-unstyled">
    			<li>원본 파일명 :<%=file.get("OriginalFileName") %> </li>
    			<li>실제 파일시스템에 저장된 파일명 :<%=file.get("systemFileName")%> </li>
    			<li>컨텐츠 타입 :<%=file.get("ContentType") %> </li>
    			<li>파일 크기 :<%=file.get("FileSize") %> KB</li>     			    			  			
    		</ul> 		
    		<%} %>
		</fieldset>
	 </div>

 

 

src/main/java안에

model패키지 안에 서블릿 하나 생성!

 

2.FileUploadServlet.java  

서블릿 3.0부터 @MultipartConfig 어노테이션과 jakarta.servlet.http.Part 인터페이스
사용하야 쉽게 파일업로드 구현

 

maxFileSize: 파일 하나의 업로드 최대 크기(바이트).기본 값은 -1(크기 제한 없음)  파일 하나,하나
maxRequestSize : 하나의 요청의 데이타 최대 크기(바이트).여러 파일 및 기타 전송값 등을 합한 최대 크기(기본값 -1)  파일 총 합한거 
fileSizeThreshold : 디스크에 임시적으로 저장될 파일의 크기.(기본값 0)

 

※자바에서는 파일 쓰기 시에 먼저 임시 디렉터리에 파일을 저장한다.

기본 임시 디렉터리는 자바의 시스템 프로퍼티로 java.io.tmpdir로 지정되어 있고 
실제 저장 위치는 System.getProperty("java.io.tmpdir"); 로 확인 할수 있다.
[참고 사이트 : https://docs.oracle.com/javaee/6/tutorial/doc/gmhal.html ]

 

@MultipartConfig(maxFileSize = 1024*500, maxRequestSize = 1024*500*5)

    최대파일 사이즈 지정

@WebServlet("/Upload.kosmo")

     " " 여기 경로에 있는 파일을 작업 할 것이라는 의미!!

 

part가 뭐나 했더니 넘어오는 파라미터들 임!

파라미터명으로 묶여서 한 파트, 한 파트씩 넘어온다 

 

File.separator 운영 체제에 따라서 경로 사용할 때 분리자(/ 혹은 \ )들을 다르게 써 줌! ()

package model;

@MultipartConfig(maxFileSize = 1024*500, maxRequestSize = 1024*500*5)
@WebServlet("/Upload.kosmo")
public class FileUploadServlet extends HttpServlet {
	
    @Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			String saveDirectory=req.getServletContext().getRealPath("/upload"); // 어디다가 저장할거?(저장경로)
			Collection<Part> parts= req.getParts();  //
			System.out.println("올린이:"+req.getParameter("name"));
			System.out.println("임시 저장디렉토리:"+System.getProperty("java.io.tmpdir")); 
			
			List<Map<String,String>> fileInfos = new Vector<>(); 
			for(Part part:parts) {
				if(part.getContentType() != null) { // 타입이 파일 일때만 getcontentType() 메소드이 있다. 
					System.out.println("컨텐츠 타입:"+part.getContentType());
					System.out.println("파라미터명:"+part.getName());
					System.out.println("파일크기:"+part.getSize());
					System.out.println("원본 파일명:"+part.getSubmittedFileName());
			
            //이름이 중복일 때 원본파일을 구분해주기 위한 메소드		
			String systemFileName=FileUtils.getNewFileName(saveDirectory,part.getSubmittedFileName());
					
					Map<String,String> map = new HashMap<>();  맵으로 저장해서
					map.put("ContentType",part.getContentType());
					map.put("FileSize",String.valueOf(Math.ceil(part.getSize()/1024.0)));
					map.put("OriginalFileName",part.getSubmittedFileName());
					map.put("systemFileName", systemFileName);
					fileInfos.add(map);   //리스트컬렉션에 담아
					
			//파일 업로드(진짜 업로드 되는 코드, File.separator는 운영체제따라 경로 사용할 떄 \혹은/ 를 다르게 적용시켜줌 )
					part.write(saveDirectory+File.separator+systemFileName);
				 }			
			  }
			//필요정보를 리퀘스트 영역에 저장 (저장된 리스트컬렉션을 담는다)
			req.setAttribute("fileInfos", fileInfos);
		 }
		catch(Exception e) {
			req.setAttribute("ERROR","업로드 최대 파일용량을(500KB) 초과 했습니다.");
			req.getRequestDispatcher("/fileupdown12/Upload.jsp").forward(req, resp);
		}	
             //포워딩시키기(정보넘겨줘)
	  req.getRequestDispatcher("/fileupdown12/UploadComplete.jsp").forward(req, resp);  
	}
	
    //파일 이름 넘기는 메소드(이거 안쓰는 듯)
	private List<String> getFileName(Part part) { 
		String[] contents=part.getHeader("Content-Disposition").split(";");
		for(int i=0;i < contents.length;i++) {
			System.out.println(String.format("contents[%s]:%s",i,contents[i]));
		}
        
		List<String> filenames = new Vector<>();
		for(String content : contents) {	
			if(content.trim().startsWith("filename")) {
				filenames.add(content.split("=")[1]);
			}		
		}
		return filenames;
	}
}

아 upload받을  폴더도 생성해  놔야 하고 그건

경로가 상당히 복잡하지만 저기로 가봐야 있는 지 알 수 있다

 


3. model/fileUtils  생성한 파일을 가져다가 쓰자....

이건 동일한 파일을 중복으로 다운받았을 때 파일을 덮어쓰는게 아닌 자동을 1,2, 순서대로 이름을 변경해주는 메소드를 정의한 파일임! 이건 선생님한테 받은거 우선 먼저 쓰고 이해는 나중으로...

model 패키지에 넣어놓고

파일 업로드로직 짜는 곳에서 파일 업로드 시 이름 지정할 때 사용한다!

 

4.UploadComplete.jsp

받은 업로드 정보들을 뿌려줄 곳임!

리퀘스트영역에 저장되어있어서 리퀘스트객체로 꺼내온다!

 <div class="container">
        <div class="jumbotron bg-info">
           <h1>파일 업로드/다운로드</h1>
        </div>
		<fieldset class=" form-group border p-3">
			<legend class="w-auto px-3">파일 업로드 결과</legend>
			<h3>type="file"요소를 제외한 폼 요소들</h3>
			<ul class="list-unstyled">
			   <li>올린이 :<%=request.getParameter("name") %></li>
			   <li>제목 :<%=request.getParameter("title") %></li>
			   <li>관심사항 :<%=Arrays.toString(request.getParameterValues("inter")) %> </li>		
			</ul>
			
			<h3>업로드한 파일 정보</h3>
			<% 
			List<Map<String,String>> files=(List<Map<String,String>>)request.getAttribute("fileInfos");	
			int index=1;
			for(Map<String,String> file: files){
			%>
			<kbd class="lead"><%=index++ %>번째 파일</kbd>
    		<ul class="list-unstyled">
    			<li>원본 파일명 :<%=file.get("OriginalFileName") %> </li>
    			<li>실제 파일시스템에 저장된 파일명 :<%=file.get("systemFileName")%> </li>
    			<li>컨텐츠 타입 :<%=file.get("ContentType") %> </li>
    			<li>파일 크기 :<%=file.get("FileSize") %> KB</li>     			    			  			
    		</ul> 		
    		<%} %>
		</fieldset>
	 </div>
</body>
</html>

 

 

5.UploadList.jsp

업로드한 파일명 뿌려주기

(자바 io 24file 에서 배움.... )

File f = new file('절대경로')  객체생성

f.getName : 파일명.. 요렇게 사용

<fieldset class=" form-group border p-3">
	<legend class="w-auto px-3">파일목록</legend>
	<ul class="list-unstyled">
<%
	String saveDirectory=application.getRealPath("/upload");  //어플리케이션영역에서 목
	File file = new File(saveDirectory);
	File[] files = file.listFiles();
	for(File f:files) {
%>				    		
		<li><a href="Download.jsp?filename=<%=f.getName() %>">파일명:<%=f.getName() %>, 파일크기 :<%=(int)Math.ceil(f.length()/1024.0)%> KB </a>></li> 			
			
	</ul>	
<%		}%>			
</fieldset>

 

6.Download  : 업로드 리스트에서 클릭하면 넘어오는 파라미터명 잘 봐둬! 

[다운로드 원리]
-웹 브라우저가 인식하지 못하는 컨텐트 타입을 응답헤더에 설정 해 주면 웹브라우저는 자체 다운로드 창을 띄움
-서버에 저장된 파일을 출력스트림을 통해 웹브라우저에 출력한다.

1.파라미터로 넘어오는 파일명 받기
  String filename = request.getParameter("filename");

 

2.파일이 저장된 서버의 물리적 경로 얻기
String saveDirectory=application.getRealPath("/upload");

3.파일크기를 얻기 위한 파일 객체 생성(옵션)
  다운로드시 프로그래스바를 표시하기 위함.
File file = new File(saveDirectory+File.separator+filename);
long length=file.length();

  <다운로드를 위한 응답헤더 설정>
4-1 웹브라우저가 인식하지 못하는 컨텐츠타입 설정
response.setContentType("application/octect-stream");
4-2 다운로드시 프로그레스바 표시하기 위한 컨텐츠 길이 설정
response.setContentLengthLong(length);

4-3 응답헤더명: Content-Disposition
      응답헤더값: attachment; filenam=파일명
        setHeader(응답헤더명,헤더값)으로 추가
브라우저 종류에 따라 한글파일이 깨지는 경우가 있으므로  브라우저별 인코딩 방식을 달리하자(파일명을 인코딩)  
boolean isIe = request.getHeader("user-agent").toUpperCase().indexOf("MSIE") != -1 ||
   request.getHeader("user-agent").toUpperCase().indexOf("11.0") != -1 ||
   request.getHeader("user-agent").toUpperCase().indexOf("EDGE") != -1;
if(isIe){  //인터넷 익스프롤러 혹은 엣지
      filename=URLEncoder.encode(filename,"UTF-8");
}
else{  //기타 브라우저
   new String(byte[ ] bytes, String charset)사용
     1.파일명을 byte형 배열로 변환
     2.String 클래스의 생성자에 변환한 배열과 charset는 8859_1을 지정 
   filename = new String(filename.getBytes("UTF-8"),"8859_1");
}
response.setHeader("Content-Disposition","attachment;filename="+filename);
IO작업을 통해서 서버에 있는 파일을 웹브라우저에 바로 출력
데이터 소스 : 파일-노드스트림 : FIleInputStream 
                      필터스트림 : BufferedInputStream
데이터 목적지 : 웹브라우저 - 노드스트림: response.getOutputStream()
                                               필터 스트림 :BufferedOutPutStream 

5.서버에 있는 파일에 연결 할 입력 스트림 생성
BufferedInputStream bis=
                   new BufferedInputStream(new FileInputStream(file));
   jsp에서 servlet으로 변환될때 내부적으로 out 객체가 지역변수로 선언되어 생성된다.
  out 내장객체 생성 후에 response내장객체로 출력스트림을 얻으면 이미 생성된 out내장객체와
  충돌이 나타나서 이미 호출되었다는 메세지가 나타난다  (getOutputStream() has already been called for this response)
 단,서 블릿에서는 상관없다. response객체로 출력스트림을 생성하기 전에
   다음과 같이 기존의 out 객체를 초기화하는 코드를 넣어주면 된다.
out.clear();
out=pageContext.pushBody();

6.웹 브라우저에 연결할 출력스트림 생성
BufferedOutputStream bos=
                     new BufferedOutputStream(response.getOutputStream());

7.bis로 읽고 bos로 웹브라우저 출력
int data;
while((data=bis.read())!= -1){
      bos.write(data);
      bos.flush();
}
8.스트림닫기
bis.close();
bos.close();

 

=====

 

++ 현업에서 필요할 수도 있어서 예전 방식도 적어 놓음

MultipartRequest API를 이용한 업로드]

-API다운로드 및 환경 설정
1)http://www.servlets.com/cos/->cos-22.05.zip 다운로드
2)압축을 푼다.
3)lib폴더에 있는 cos.jar파일을 복사 하여 WEB-INF/lib폴더에 붙여 넣는다.
(배포 목적)


 이클립스나 직접 javac컴파일에서 사용하려면
  [jdk폴더]\jre\lib\ext와 [톰캣이설치된 디렉토리]\lib에도 붙여 넣는다.
※ jsp페이지에서 com.oreilly.servlet 패키지 와 
  com.oreilly.servlet.multipart패키지를
                            import하여 API를 사용한다.
   com.oreilly.servlet.multipart패키지에는 
   파일중복시 자동으로 인덱스를 부여하여 생성하는 클래스를 제공해준다.
   (FileRenamePolicy 와 DefaultFileRenamePolicy )