Java - 19. 파일 업로드 & 다운로드

갓김치·2020년 10월 27일
4

고급자바

목록 보기
46/47

들어가기 전

참고사항

  • 모든 jsp, html은 webcontent에
  • web-inf는 그 아무도 접근불가
  • 인코딩 filter 떼와서 여기에 복붙 + web.xml에 filter설정

중요사항

post!!!

  • 업로드는 무조건 http method 가 POST!
    • get 전송: url 전송 = 보안 및 용량 제한
    • post 전송: body에 함께 전송 = 용량 제한 없음

multipart!!!

  • form 속성 enctype="multipart/form-data"
    • 디폴트: enctype="application/x-www-form-urlencoded"
      • name=홍길동&나이=20 이런식으로 보내는거
      • 이형식으로 보내면 파일을 전송할 수 없기 때문에 multipart로 지정해줘야함
      • 이런식으로 가짐
    • 멀티파트로보내면

jsp vs html 절대경로처리시

  • jsp : form method="post" action="<%=request.getContextPath() %>/UploadServlet2" enctype="multipart/form-data"
    • 동적으로처리가능
  • html : action="/P17_ServletExam/UploadServlet2"
    • 절대경로 정적처리밖에안됨

이미지 정보 response

  • ServletOutputStream을 이용한 이미지 출력 예제
    • 이미지와 같은 바이너리 데이터를 웹브라우저에게 전달
    • HttpServletResponse 혹은 ServletOutputStream의 getOutputStream()이용
  • 이미지파일을 스트림으로 브라우저에게 write하기
  • 파일을 읽어올 때는 FileInputStream으로 읽어오고 브라우저에 출력할 때 ServletOutputStream을 사용
  • 게시판에 파일 올릴 때 사용

ImageServlet.java

public class ImageServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {

    resp.setContentType("image/jpeg"); // 컨텐츠타입 설정
    // -> body에 binary data 들어갈 것임

    ServletOutputStream out = resp.getOutputStream();

    FileInputStream fis = new FileInputStream("d:/D_Other/Tulips.jpg");

    // 입출력 속도 향상을 위한 버퍼드 스트림 사용
    BufferedInputStream bis = new BufferedInputStream(fis);
    BufferedOutputStream bos = new BufferedOuputStream(out);

    int readBytes = 0; // 읽은 ㅏ이트수
    while((readBytes = bis.read()) != -1) {
      bos.write(readBytes);
    }

    bis.close();
    bos.close();
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    doGet(req, resp);
  }
}

web.xml

<servlet>
  <servlet-name>ImageServlet</servlet-name>
  <servlet-class>kr.or.ddit.img.ImageServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>ImageServlet</servlet-name>
  <url-pattern>/ImageServlet</url-pattern>
</servlet-mapping>

결과

  • 잘되는군요...

파일 업로드

1. COS.JAR (옛날방법)

upload1.html

<body>
  <form action="UploadServlet1" method="post" enctype="multipart/form-data" > 
    선택한 파일 : <input type="file" name="file01" /><br>
    타이틀 정보 : <input type="text" name="title"/><br>
    <input type="submit" value="upload" />
    <br /> 
    <br /> 
  </form>
</body>

UploadServlet1.java

  • MulitpartRequest(request, 저장경로[, 최대허용크기, 인코딩캐릭터셋, 동일한 파일명 보호여부]);
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
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;

public class UploadServlet1 extends HttpServlet{

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
	throws ServletException, IOException {

  resp.setContentType("text/html"); // 인코딩은 필터로 적용

  PrintWriter out = resp.getWriter(); // 브라우저에 출력하기 위해

  // 인코딩필터 = body에 들어가는 인코딩이라 파일업로드시 인코딩과 개념 다름
  // 파일인코딩시 encType은 MultipartRequest의 관점 => 파일 제목등 파일과 관련이 있음
  String encType = "UTF-8"; // cos.jar파일의 객체 생성 시점에 넣어줌
  int maxFileSize = 5 * 1024 *1024; // 5MB

  // MulitpartRequest(request, 저장경로[, 최대허용크기, 인코딩캐릭터셋, 동일한 파일명 보호여부]);
  // 파일명보호: DefaultFileRenamePolicy => name.zip, name1.zip, ...

  // 업로드에 해당하는 부분
  MultipartRequest mr = new MultipartRequest(req, "d:/D_Other/", maxFileSize, encType, new DefaultFileRenamePolicy());

  // 파일 관련 정보 추출
  File file01 = mr.getFile("file01") // upload1.html의 폼태그 값
  System.out.println(file01); // 첨부된 파일의 전체 경로 출력

  // 파라미터값 읽어오기
  System.out.println(mr.getParameter("title"));

  out.println("업로드 완료됨");
  out.println("파일 경로 => " + file01.toString());
  out.println("타이틀명 => " + mr.getParameter("title");

  }
}// class

web.xml

<servlet>
  <servlet-name>UploadServlet1</servlet-name>
  <servlet-class>kr.or.ddit.upload.UploadServlet1</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>UploadServlet1</servlet-name>
  <url-pattern>/UploadServlet1</url-pattern>
</servlet-mapping>

결과

2. APACHE JAKARTA의 COMMONS

  • 자카르타 프로젝트의 fileupload 모듈을 이용한 파일업로드 예제
  • 필요한 jar
    • commons-fileupload-1.3.3.jar
    • commons-io-2.6.jar
    • ServletFileUpload 쓰기 위해 commons.jar 사용하는 것

upload2.html

<body>
  <h3>아파치 자카르타 프로젝트의 fileupload 모듈을 이용한 파일업로드</h3>
  <form method="post" action="UploadServlet2" enctype="multipart/form-data">
  <!-- action="/P17_ServletExam/UploadServlet2" 이건 절대경로 -->
    파일선택: <input type="file" name="uploadFile" multiple="multiple"/>
    전송자: <input type="text" name="sender">
    <input type="submit" value="Upload"/>
  </form>
</body>

upload2.jsp (upload2.html을 jsp로 변환)

  • html->jsp 변환시 동적으로 경로 처리가능
<body>
   <h3>아파치 자카르타 프로젝트의 fileupload 모듈을 이용한 파일업로드</h3>
    <form method="post" action="<%=request.getContextPath() %>/UploadServlet2" enctype="multipart/form-data">
      <!-- action="/P17_ServletExam/UploadServlet2" html에서 쓰는 방식(하드코딩) -->
        파일선택: <input type="file" name="uploadFile" multiple="multiple"/>
        전송자: <input type="text" name="sender">
        <input type="submit" value="Upload"/>
    </form>
</body>

UploadServlet2.java

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadServlet2 extends HttpServlet {

  // 업로드 디렉토리(폴더명) 정의: 어디에 업로드 될 것인지
  private static final String UPLOAD_DIR = "upload_files";

  // 메모리 임계크기
  // 이보다 파일크기가 작으면 메모리 내에서 처리
  // 초과시 임시파일을 만들어 디스크(레파지토리)에 저장하여 처리
  private static final int MEMORY_THRESHOLD = 1024 * 1024 * 3; // 3MB
  // 크게 잡을수록 서버 메모리를 많이 잡아먹음. (임계크기내에서는 메모리에서 처리하니까)
  // 3MB : 3MB까지는 디스크에 저장하지 않아도되지만 넘어가면 파일로 떨구세여

  // 파일 1개당 최대 크기
  private static final long MAX_FILE_SIZE = 1024 * 1024 * 40; // 40MB

  // 요청 파일 최대 크기
  // ex) 리퀘스트 1회에 파일 5개 전송 -> 그 때 5개파일의 전체 크기 제한
  private static final long MAX_REQUEST_SIZE = 1024 * 1024 * 40; // 40MB

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {

    // 인코딩 타이이 multipart/form-data인 경우
    if(ServletFileUpload.isMultipartContent(req)) { // ServletFileUpload 쓰려고 commons.jar 쓰는것
    // 들어오는 데이터: (파일 + 폼필드데이터)

    // 폼필드 데이터 저장용 (편의를 위해 만드는 것)
    Map<String, String> formMap = new HashMap<String, String>();

    // 들어온 데이터가 파일인 경우,
    // 파일 아이템으로 처리하기 위해 DiskFileItemFactory 클래스 사용 (commons.jar에 내장)
    DiskFileItemFactory factory = new DiskFileItemFactory();

    factory.setSizeThreshold(MEMORY_THRESHOLD); // 메모리 임계크기 지정
    facotry.setRepository(new File(System.getProperty("java.io.tmpdir");
    // java.io.tmpdir = C:\Users\PC-02\AppData\Local\Temp\
    // default로 java가 가지고있는 시스템정보 = 임시디렉토리정보 


    // ServletFileUpload 객체 생성 시, 위에서 설정 마친 facotry 객체를 매개변수로 넣어줌
    ServletFileUpload upload = new ServletFileUpload(factory);
    upload.setFileSizeMax(MAX_FILE_SIZE);
    upload.setSizeMax(MAX_REQUEST_SIZE);
    // --------------위의 과정: 파일 업로드시 필요한 환경설정 (cos.jar에 없는 기능)

    // 웹애플리케이션 루트 디렉토리 기준 업로드 경로 설정하기
    String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIR;
    // getServletContext() : 웹어플리케이션 설정정보의 RealPath에 원하는 패스 정보 삽입
    // getServletContext().getRealPath("/WEB/")
    // -> /WEB/ 웹어플리케이션경로를 절대경로로 잡고 PATH를 잡아놓은것
    //    =/= d:/~ 이런식으로 경로 설정하는 것과 다름

    // ★ RealPath를 가져오는 이유
    // => 사용자가 Multipart로 보내준 파일 데이터를 디스크에 저장하기위해 디스크 실제적 위치를 파악하려고
    // ex) localhost:9090/ServletExam/index.jsp => root밑의 index.jsp 접근해

    // 웹애플리케이션 리소스 관점) WebContent가 기준
    // 실제 디스크 저장 관점) D:\A_TeachingMaterial\3.HighJava\workspace\P17_ServletExam\WebContent

    // File.separator : 리눅스, 유닉스(/), 윈도우(\) 자동으로 인식 => 시스템 의존적으로 경로 설정가능

    // UPLOAD_DIR : 우리가 선언한 상수, Context root 밑에 폴더 만들어져있을 것임.

    File uploadDir = new File(uploadPath); // 디렉토리 생성
    if(!uploadDir.exists()) { // 한번 더 체크후 없으면 디렉토리 생성
      uploadDir.mkdir(); 
    }

    // ★★★ 실제 업로드가 처리되는 과정
    try {
    // Multipart로 날아온 request를 parse 시작
      List<FileItem> formItems = upload.parseRequest(req);

      if(formItems != null && formItems.size() > 0) { // parse된 아이템이 있으면
        for(FileItem item : formItems) {
          if(!item.isFormField()) { // 폼필드가 아닌 경우 = 파일 --> 저장 시작

            // 전체 경로를 제외한 파일명만 추출하기
            String fileName = new File(item.getName()).getName();
            // 1. item.getname() : 파일 전체 경로가 잡힘
            // 2. new File(파일전체경로) : 그 경로의 파일을 다루는 객체 생성
            // 3. new File(파일전체경로).getName() : 파일명만 쏙 빼오기 (substring 안해도됨)
            String filePath = uploadPath + File.separator + fileName; 
            // 업로드 폴더에 세퍼레이터로 구분해서 파일 경로 만듬
            File storeFile = new File(filePath) // 파일 객체 생성
            item.write(storeFile); // item의 write 메서드 이용해서 사용자가 업로드한 파일 저장 완료
            req.setAtrribute("message", "업로드 완료됨. => " + fileName);

          } else { // 폼필드인 경우 => 폼필드를 따로 관리할 Map에 저장
            formMap.put(item.getFieldName(), item.getString("UTF-8");
          }

        } // for문 완료 => Multipart로 날아온 request parsing 완료

      } // if

    } catch (Exception e) {
      req.setAttribute("message", "예외발생 : " + e.getMessage());
    }

    // 폼데이터 꺼내서 확인 (Map의 내부클래스 Entry 이용)
    for(Entry<String, String> entry : formMap.entrySet()) {
      System.out.println("파라미터명 : " + entry.getKey());
      System.out.println("파라미터값 : " + entry.getValue());
    }
    // URLencoded 방식으로 되어있는게 아니라 Multipart로 되어있어서 
    // request.getParameter로 꺼낼수 없기때문에 Map에 넣어놓고 쓰는 것
    System.out.println("꺼내짐 : " + formMap.get("sender");
    System.out.println("안꺼내짐 : " +  req.getParameter("sender");
    resp.setContentType("text/html");
    resp.getWriter().print("업로드 완료됨");

    } // if
  } // doPost()
}

web.xml

<servlet>
  <servlet-name>UploadServlet2</servlet-name>
  <servlet-class>kr.or.ddit.upload.UploadServlet2</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>UploadServlet2</servlet-name>
  <url-pattern>/UploadServlet2</url-pattern>
</servlet-mapping>

결과

이클립스 위에서 톰캣이 돌고있어서 저장이 되어도 진짜 워크스페이스에 되는게아니고 여기되고있었음

  • D:\A_TeachingMaterial\3.HighJava\workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0
  • 사실 여기에 저장이되는것이었따.......

3. 서블릿3.0부터 가능한 PART INTERFACE

upload2.jsp

<body>
    <h3>서블릿3부터 지원하는 Part 인터페이스를 이용한 파일업로드</h3>
    <form method="post" action="UploadServlet3" enctype="multipart/form-data">
        파일선택: <input type="file" name="multiPartServlet" multiple="multiple"/>
        전송자: <input type="text" name="sender">
        <input type="submit" value="Upload"/>
    </form>
</body>

UploadServlet3.java

  • web.xml에 설정하지않고 서블릿3부터 지원하는 어노테이션 사용
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@WebServlet(name="UploadServlet3", urlPatterns = {"/UploadServlet3"})
@MultipartConfig(fileSizeThreshold = 1024*1024, maxFileSize = 1024*1024*5, maxRequestSize = 1024*1024*5*5)
public class UploadServlet3 extends HttpServlet {
private static final String UPLOAD_DIR = "upload_files";

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {

    // 업로드 경로
    String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIR;
    // getServlet().getRealPath("") 단점
    // - 애플리케이션 배포하다가 기존 서버를 clean하면 업로드폴더가 싹 날라감
    // - 절대경로로 지정해서 업로드하는게 안전함
    // - 수정, 배포가 잦은 경우에는 절대경로를 지정해야함.

    File uploadDir = new File(uploadPath);

    // 경로 없으면 mkdir로 생성
    if(!uploadDir.exists()) {
      uploadDir.mkdir();
    }

    try{
      String fileName = "";
      for(Part part : req.getParts()) { // req.getParts() : Multipart데이터들을 Part객체로 리턴
        System.out.println(part.getHeader("content-disposition"); // part마다 header 있음
        if(fileName != null && !"".equals(fileName)) { // !폼필드경우 && !파일아예업로드 안했을 때
          part.write(uploadPath + File.separator + fileName); // 파일 저장
          System.out.println("파일명 : " + fileName + "저장완료!!!");
        }
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    }
    System.out.println("파라미터 값 : " + req.getparameter("sender"));
    resp.setContentType("text/html");
    resp.getWriter().println("업로드 완료...!!");
  } // doPost()

  /**
   * Param객체 파싱하여 파일이름 추출하기
   * @param part
   * @return 파일명 : 파일명이 존재하지 않으면 null (폼필드 데이터)
   */
  // 헤더 예시
  // form-data; name="multiPartServlet"; filename="git command.txt"
  private String getFileName(Part part) {
    for (String content : part.getHeader("content-disposition").split(";")) {
      if(content.trim().startsWith("filename")) {
        return content.substring(content.indexOf("=")+2, content.longth()-1);
      }
    }
    return null; // filename이 없는 경우 (폼필드 데이터인 경우):
  }
} // class

결과

파일 다운로드

Content-Disposition 헤더

response 헤더에 사용되는 경우

URLEncoded ex) 파일 다운로드

  • Content-Disposition: inline(default)
    • 브라우저 내에 보이도록 이미지 전송
  • Content-Disposition: attachment;
    • 로컬에 저장되는 파일 다운로드 ★
  • Content-Disposition: attachment; filename="filename.jpg"
    • 저장할 파일명 ★

Multipart body를 위한 헤더 정보로 사용되는 경우

ex) 파일 업로드

  • Content-Disposition: form-data
  • Content-Disposition: form-data; name="fieldName";
    • 폼필드 데이터
  • Content-Disposition: form-data; name="fieldName"; filename="filename.jpg"
    • input type="file"인 경우 filename이 추가됨

DownloadServlet.java

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/DownloadServlet")
public class DownloadServlet extends HttpServlet{
private static final String DOWNLOAD_DIR = "d:/D_Other/";

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {

    String fileName = req.getParameter("fileName");
    // 파일 다운로드 처리를 위한 응답헤더 정보 설정하기
    resp.setContentType("application/octet-stream"); 
    // 이미지 = binary data -> html이 아닌 octet stream으로 설정
    resp.setHeader("Content-disposition", "attachment; filename=\"" + fileName + "\"");
    // 다운로드 -> attachment, filename="" -> 저장할 파일명

    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(DOWNLOAD_DIR + fileName));
    BufferedOutputStream bos = new BufferedOutputSteram(resp.getOutputStream());

    int readBytes = 0;
    while ( (readBytes = bis.read()) != -1 ) {
      bos.write(readBytes);
    }

      bis.close();
      bos.close();
  }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
    }
}

출처

profile
갈 길이 멀다

1개의 댓글

comment-user-thumbnail
2023년 11월 6일

UploadServlet3.java 파일에서 파일 이름을 가져오는 부분이 어딨나요?

답글 달기