• Home
  • About
    • Young's Github Pages photo

      一日不作一日不食

    • About
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Java EE 정리 15

19 Mar 2019

Reading time ~7 minutes

Java EE 정리 15 - File Upload(1), File Download


File Upload

  • RFC 1867 Form Based File Upload in HTML
    • form태그로 웹에서 파일을 업로드하기 위한 규약
  • Web Browser를 사용하여 File을 업로드
    • 대용량 파일(600MB 이상) 업로드 시 효율이 많이 떨어진다.
  • 전송시 에러가 발생하면 처음부터 재전송해야 한다.
  • 별도의 프로그램을 개발하지 않고도 편하게 파일을 전송할 수 있다.
  • File Upload Component(외부 jar 파일, 외부 library)는 Apache Commons File Upload, Multipart Request(cos.jar) 등이 존재
  • 주의사항
    • encType을 변경해야 한다.
      • parameter 전송방식에서 file upload 방식으로 변경해야 함
      • parameter 전송방식 - “application/x-www-form-urlencoded”
        • 기본전송방식
        • submit 발생 시 request.getParameter로 값을 받을 수 있음
      • file upload 전송방식- “multipart/form-data”
        • request.getParameter로 받을 수 없다.
    • 파일은 GET방식(문자열 데이터 255자)으로 절대 전달할 수 없다.
      • 파일 전송방식은 POST!
<!-- 변경 전, 보통 생략 -->
<form ... enctype="application/x-www-form-urlencoded">
    ...
<!-- 변경 후 -->
<form ... enctype="multipart/form-data">
    <input type="file" ... />
    ...
</form>

파일 크기 계산법

  • 파일의 저장 단위
    • byte -> kb -> mb -> gb -> tb -> …

05

  • 5 MB의 크기는?
    • 1024(byte)*1024(kb)*5(mb)
  • 10 MB의 크기는?
    • 1024(byte)*1024(kb)*10(mb)

Apache Commons File Upload 설치

01

  • Apache 재단 사이트 - Commons - FileUpload 선택
  • Commons IO에 의존성 존재, Commons IO도 같이 설치해야 함
    • Apache Commons IO 2.6(JDK 1.7이상 필요), Apache Commons FileUpload 1.4(JDK 1.6이상 필요) Binaries 집 다운로드 후 사용할 jar파일들을 작업하는 프로젝트 WEB-INF/lib에 설치
    • commons-fileupload-1.4.jar, commons-io-2.6.jar

02

  • Apache Commons FileUpload 사용하기 위한 사전준비 페이지
<!-- upload_form.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
...
<script type="text/javascript">
      $(function() {
            $('#btnUpload').click(function() {
                  
                  var uploader = $("#uploader").val();
                  
                  if(uploader == "") {
                        $("#uploader").focus();
                        return;
                  }
                  
                  var file = $("#upfile").val();
                  
                  if (file == "") {
                        alert("업로드할 파일을 선택해주세요.");
                        return;
                  }
                  
                  $('#uploadFrm').submit();
            });
      });
</script>
...
<!-- HTML 폼 기반 파일 업로드
      1. enctype="multipart/form-data"
      2. method="post"
-->
<form id="uploadFrm" action="upload_process.jsp" method="post"  enctype="multipart/form-data">
      <label>이름</label>
      <input type="text" name="uploader" id="uploader"  class="inputBox"/><br/>
      <label>나이</label>
      <select name="age" id="age">
            <c:forEach var="i" begin="10" end="80" step="10">
            <option value="${ i }"><c:out value="${ i }"/></option>
            </c:forEach>
      </select>
      <br/>
      <label>파일</label>
      <input type="file" name="upfile" id="upfile" class="inputBox"  style="width:200px;"/><br/>
      <input type="button" value="upload" class="btn" id="btnUpload"  style="width:200px;"/>
</form>

03

  • 업로드 버튼을 누르면 이동하는 페이지
<!-- upload_process.jsp -->
<!--  enctype="multipart/form-data"인 경우 parameter를 받을 수 없다 -->
<% request.setCharacterEncoding("UTF-8"); // 한글처리 %>
이름 : <%= request.getParameter("uploader") %><br/>
나이 : <%= request.getParameter("age") %><br/>

04

  • Apache Commons FileUpload는 Doc을 참고해서 사용하면 됨
    • 가이드 Doc
  • Apache Commons FileUpload 사용
    • 매뉴얼대로 진행
    • 우선 파일 업로드 요청인지 확인하는 작업이 필요
      • 파일업로드가 아닌 부적합한 요청은 리다이렉트
      • 그리고 업로드 되는 파일의 위치, 크기제한 등 설정
<%
      request.setCharacterEncoding("UTF-8"); // POST방식 한글처리
      // 파일업로드에 적합한 요청인지 확인
      boolean isMultipart =  ServletFileUpload.isMultipartContent(request);

      i f(isMultipart) { // 파일 업로드에 적합한 요청
            File repository = new  File("C:/Users/owner/youngRepositories/SSangYoung/dev/workspace/jsp_prj/WebContent/upload");
            
            // Create a factory for disk-based file items
            DiskFileItemFactory factory = new DiskFileItemFactory();

            // 업로드 파일이 메모리에 저장되는 최대 크기
            factory.setSizeThreshold(1024*1024*1); // 1mb씩 끊어 저장

            // 저장될 위치
            factory.setRepository(repository); 

            // Create a new file upload handler
            ServletFileUpload upload = new ServletFileUpload(factory);

            // 업로드 파일의 최대 크기 설정
            upload.setSizeMax(1024*1024*5); // 5mb까지 저장

            // Parse the request, 전송된 파일 요청저장
            List<FileItem> items = upload.parseRequest(request);
            
            // Process the uploaded items, for보다 Iterator가 빠름
            Iterator<FileItem> iter = items.iterator();
            while (iter.hasNext()) {
                FileItem item = iter.next();
                if (item.isFormField()) { 
                      // 일반 HTML Form 컨트롤인지?  <input type="file"이 아닌 것
                      out.println("일반 폼 컨트롤<br/>");
                } else { 
                      // File Upload Control인지? <input type="file"
                      out.println("파일 폼 컨트롤<br/>");
                }
            }
                  
      } else {
            // 파일 업로드에 부적합한 요청(GET방식 요청 시)
            response.sendRedirect("upload_form.jsp");
      }
%>

06

  • 파일 요청이 맞고 전송된 파일이 있을 때 파일필드명과 값을 가져올 수 있다. * request.getParameter로 가져올 순 없음 * 파일명은 직접 한글처리클래스를 이용해서 한글처리
// Process the uploaded items
Iterator<FileItem> iter = items.iterator();
while (iter.hasNext()) {
    FileItem item = iter.next();
    if (item.isFormField()) { // 일반 HTML Form 컨트롤인지? <input  type="file"이 아닌 것
          String controlName = item.getFieldName();
          String controlValue = item.getString();
          out.println("컨트롤 명 : " + controlName);
          // 선택된 파일이 한글일 때 한글처리를 해줘야 안깨진다.
          out.println(" 컨트롤 값 : " +  HangulConv.toUTF(controlValue) + "<br/>");
    } else { // File Upload Control인지? <input type="file"
          out.println("파일 폼 컨트롤<br/>");
    }
}

07

  • 전송된 파일이 있는 요청일 때 파일필드명과 값을 가져올 수 있다.
    • request.getParameter를 사용할 수 없다.
// Process the uploaded items
Iterator<FileItem> iter = items.iterator();
String uploader = "", age="";
while (iter.hasNext()) {
    FileItem item = iter.next();
    if (item.isFormField()) { // 일반 HTML Form 컨트롤인지? <input  type="file"이 아닌 것
          String controlName = item.getFieldName();
          String controlValue = item.getString();

          // request.getParameter보다 처리가 까다롭다
          if ("uploader".equals(controlName)) { 
            uploader = HangulConv.toUTF(controlValue);
          }
          if ("age".equals(controlName)) {
            age = controlValue;
          }
    } else { // File Upload Control인지? <input type="file"
          out.println("파일 폼 컨트롤<br/>");
    }
}
%>
업로더명 : <%=uploader %><br/>
연령대 : <%=age %><br/>
<%

08

  • 업로드요청한 파일 정보 가져오기
...
   } else { // File Upload Control인지? <input type="file"
      // fieldName == File Control명, 여러 type="file" 존재 시 구분하기 위해 사용
      fieldName = item.getFieldName(); // upfile
      fileName = item.getName(); // File Control 값
      contentType = item.getContentType(); // 업로드 파일의 종류
      sizeInBytes = item.getSize(); // 크기
   }
}
%>
업로더명 : <%=uploader %><br/>
연령대 : <%=age %><br/>
file control명 : <%=fieldName %><br/>
file control값 : <%=fileName %><br/>
업로드 형식 : <%=contentType %><br/>
크기 : <%=sizeInBytes %><br/>
<%

09

  • 업로드요청한 파일 저장 하기
...

          } else { // File Upload Control인지? <input type="file"
            // File Control명, 여러 type="file" 존재 시 구분하기 위해  사용
                fieldName = item.getFieldName(); // upfile
                fileName = item.getName(); // File Control 값
                contentType = item.getContentType(); // 업로드 파일의  종류
                sizeInBytes = item.getSize(); // 크기
                
                // 업로드파일의  저장경로와 파일명 설정
                File uploadedFile = new  File(repository.getAbsoluteFile()+"/"+fileName);
                item.write(uploadedFile); // 설정된 파일을 업로드
          }
       }
            %>
            파일업로드 성공<br/>
            업로더명 : <%=uploader %><br/>
            연령대 : <%=age %><br/>
            업로드 파일명 : <%=fileName %><br/>
            크기 : <%=sizeInBytes %><br/>
            <a href="upload_form.jsp">업로드</a><!-- 업로드 처리 jsp -->
            <%
            
      } else {
            // 파일 업로드에 부적합한 요청(GET방식 요청 시)
            response.sendRedirect("upload_form.jsp");
      }
%>

10

  • 제한한 사이즈보다 큰 파일을 업로드하면 SizeLimitExceededException 예외발생
    • SizeLimitExceededException을 try~catch로 잡아 처리했을 때 콘솔에 출력은 되지만 브라우저에 출력은 안된다!

11

  • 파일업로드 기능에선 특정 확장자 파일의 업로드를 막는다
    • 서버에서 실행가능한 파일을 업로드 후 uri를 예측해서 악의적인 목적으로 실행가능기 때문!
<%@ page contentType="text/html;charset=UTF-8"
  pageEncoding="UTF-8"%>
  악의적인 목적을 심어둔 코드들
<!-- 서버 정보 탈취 -->
<%= System.getenv() %>
<%= System.getProperties() %>

<!-- 서버를 종료시킬 수도 있다 -->
<% System.exit(0); %>

12

13

  • 이런 이유로 특정 확장자 파일의 업로드를 막는다. * 프론트앤드에서 막음
...
            // 확장자가 .jsp, .java, .class, .xml은 업로드 하지 못하도록 막는다
            var bannedExt = [ "jsp", "java", "class", "xml" ];
            var extFlag = false;
            var fileExt = file.substring(file.lastIndexOf(".")+1).toLowerCase();
            
            for(var i=0; i<bannedExt.length; i++) {
                  if(bannedExt[i] == fileExt) {
                        extFlag = true;
                  }
            }
            
            if (extFlag) {
                  alert("확장자가 jsp, java, class, xml인 파일은  업로드하실 수 없습니다.");
                  return;
            }
            
            $('#uploadFrm').submit();
      });
});

14

  • 저장된 파일들을 보는 페이지
<!-- upload_form.jsp, upload_process.jsp -->
...
<a href="file_list.jsp">파일리스트</a><br/>
...
<%
      File uploadDir = new  File("C:/Users/owner/youngRepositories/SSangYoung/dev/workspace/jsp_prj/WebContent/upload");
      File[] uploadedFiles = uploadDir.listFiles(); // 업로드한 파일들의 정보를 가져옴
%>

<a href="upload_form.jsp">업로드</a><br/>
<table border='1'>
      <tr>
            <th width="60">번호</th>
            <th width="350">파일명</th>
            <th width="150">업로드 일시</th>
            <th width="120">크기</th>
      </tr>
      <%
      if (uploadedFiles.length != 0) {
            File temp = null;
            for(int i=0; i<uploadedFiles.length; i++) {
                  temp = uploadedFiles[i];
      %>
      <tr>
            <td><%=i+1 %></td>
            <td><%=temp.getName() %></td>
            <td><%=new  SimpleDateFormat("yyyy-MM-dd a HH:mm").format(temp.lastModified())%></td>
            <td><%=new  DecimalFormat("#,###,###,###,###").format(temp.length())%> byte</td>
      </tr>
      <% }
      } else {
            out.println("<tr><td colspan='4' align='center'>업로드된  파일이 존재하지 않습니다.</td></tr>");
      }
%>
</table>

15

  • 파일명 클릭해서 다운로드 하기 * 브라우저가 해석할 수 없는 확장자는 다운로드처리 * 브라우저가 해석하는 확장자는 브라우저에서 연다
<td><a href="../upload/<%=temp.getName()%>"><%=temp.getName()  %></a></td>

16

File Download

  1. 응답 방식을 변경(html -> 8bit Stream)
  2. 응답 헤더를 설정 - 다운로드하게 될 파일명 설정
  3. FileStream을 사용하여 읽어들일 파일과 연결
  4. 파일 내용을 읽어들임
  5. 출력 스트림 초기화
  6. 현재페이지의 내용으로 출력스트림 변경
  7. 출력 스트림을 8bit Stream으로 재설정
  8. 출력 스트림에 파일 내용을 기록
  9. 분출처리
<!-- file_list.jsp -->
...
<!-- 파일명을 param으로 전달-->
<td><a  href="download.jsp?file_name=<%=temp.getName()%>"><%=temp.getName()  %></a></td>
...
  • param으로 전달받은 파일명의 파일을 다운로드
<!-- download.jsp -->
<%@page import="java.io.OutputStream"%>
<%@page import="java.io.File"%>
<%@page import="java.io.FileInputStream"%>
<%@page import="java.net.URLEncoder"%>
<%@page import="java.net.URLDecoder"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
      // 1. 응답 방식 설정
      response.setContentType("application/octet-stream"); // 8bit  Stream
      
      String data = request.getParameter("file_name");
      
      if (data == null || "".equals(data)) { // 전달된 file_name이 없으면
            response.sendRedirect("file_list.jsp"); // 파일 리스트로 리다이렉트
            return;
      }

      // 2. 응답 헤더 설정, 저장할 파일명 설정
      response.setHeader("Content-Disposition", "attachment;fileName="+
            URLEncoder.encode(data,"UTF-8")); // 한글파일명 처리
      
      // 3. FileStream을 연결
      File file = new  File("C:/Users/owner/youngRepositories/SSangYoung/dev/workspace/jsp_prj/WebContent/upload/"+data);

      FileInputStream fis = null;
      OutputStream os = null;

      try {
            if (file.exists()) { // 파일이 존재하는지
                  fis = new FileInputStream(file);
            
                  // 4. 파일 내용을 읽어들여 byte[]에 저장
                  //  파일의 내용을 저장하기 위한 배열
                  byte[] fileArr = new byte[(int)file.length()];
                  
                  int read = 0, idx = 0;
                  while((read = fis.read()) != -1) { // 읽어들일 내용이 있을 때까지
                        fileArr[idx] = (byte)read; // 읽은 바이트를 배열에 기록
                        idx++;
                  }
                  
                  // 5. 출력 스트림 초기화
                  out.clear();      
                  
                  // 6. 현재 페이지의 내용으로 출력스트림을 변경
                  out = pageContext.pushBody();
                  
                  // 7. 출력 스트림을 8bit Stream으로 재설정
                  os = response.getOutputStream();
                  
                  // 8. 출력 스트림에 파일 내용을 기록
                  os.write(fileArr);
                  
                  // 9. 분출
                  os.flush();
            }
      } finally { // 연결 끊기
            if (fis != null) { fis.close(); }
            if (os != null) { os.close(); }
      }
%>


Java EEJSP