Bigfat

[모델2] Multipart를 이용한 단일 파일 업로드 구현하기 (게시판 구현) 본문

Java/web

[모델2] Multipart를 이용한 단일 파일 업로드 구현하기 (게시판 구현)

kyou 2017. 5. 1. 14:14

Multipart를 이용해 단일 파일 업로드(Single File Upload)를 구현해보자

  서블릿 3.0 스펙부터 파일 업로드(upload) 기능을 지원한다.


  web.xml을 열어 파일을 업로드할 디렉토리의 경로를 컨텍스트 파라미터로 설정해준다. D드라이브 아래에 upload 폴더와 temp 폴더를 미리 만들어두자.

1
2
3
4
<context-param>
    <param-name>saveDirectory</param-name>
    <param-value>D:\upload\</param-value><!-- 파일명을 바로 붙이기 위해 \까지 넣어주자 -->
</context-param>
cs


  서블릿이 multipart를 인식하기 위해서 multipart-config를 설정해준다. 서블릿 3.0부터는 web.xml 없는 개발 및 배포가 가능하므로 @MultipartConfig 어노테이션으로 fileSizeThreshold, location, maxFileSize, maxRequestSize 속성을 설정할 수 있다(참고1).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<servlet>
  <servlet-name>bbsServlet</servlet-name>
  <servlet-class>com.edu.bbs.BBSServlet</servlet-class>
  <init-param>
    <param-name>bbsProperties</param-name>
    <param-value>D:\Dev\education\bbs\WebContent\bbs.properties</param-value>
  </init-param>
  <!-- multipart 설정 추가 -->
  <multipart-config>
      <location>D:\temp</location>
      <max-file-size>-1</max-file-size>
      <max-request-size>-1</max-request-size>
      <file-size-threshold>1024</file-size-threshold>
  </multipart-config>
</servlet>
cs


  파일 업로드가 필요한 화면(*.jsp)의 form 태그에 enctype 속성을 multipart/form-data로, form 태그 내에 input 태그의 type을 file로 지정해준다. multipart는 HTTP POST로만 전송해야 한다(참고2).

1
2
3
4
5
<form action="/bbs/write.bbs" method="post" enctype="multipart/form-data">
    ...
    <input type="file" class="btn btn-default" name="file">
    ...
</form>
cs


  톰캣을 실행시키고 글쓰기 버튼을 누르면, 즉 write.bbs를 요청했을 때 Content-Type이 multipart/form-data로 되어있고, 데이터가 Request Payload로 넘어가는 것을 확인할 수 있다.


  form 태그에 enctype 속성을 작성하지 않으면 default는 enctype="application/x-www-form-urlencoded"다. 아직 수정하지 않은 글 수정 기능으로 update.bbs 요청의 Request Headers와 Form Data를 확인할 수 있다.


  write.bbs 요청에 대한 WriteImpl 클래스를 아래와 같이 작성한다. multipart 데이터는 request.getPart 메서드나 getParts 메서드를 이용해 받는다. multipart로 넘겨줬을 때 서블릿 3.0 규약에 따르면 Stream으로 데이터를 받아와야 하지만 파일을 제외한 form-data는 request.getParameter 메서드로 받아와도 문제는 없다.

  getFileName 메서드는 Request Payload에서 filename의 값인 파일명을 뽑아내기 위한 메서드다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.edu.bbs;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
 
public class WriteImpl implements BBSService {
 
    @Override
    public String bbsService(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        BBSDto article = new BBSDto();
        String saveDir = req.getServletContext().getInitParameter("saveDirectory");
        
        article.setId(req.getSession().getAttribute("id").toString());
        article.setTitle(req.getParameter("title"));
        article.setContent(req.getParameter("content"));
        
        if(req.getPart("file").getSize() != 0) {
            Part filePart = req.getPart("file");
            String originFileName = getFileName(filePart);
            article.setFileName(originFileName);
            
            File file = new File(saveDir + originFileName);
            InputStream is = filePart.getInputStream();
            FileOutputStream fos = null;
            
            fos = new FileOutputStream(file);
            
            int temp = -1;
            while((temp = is.read()) != -1) {
                fos.write(temp);
            }
            
            is.close();
            fos.close();
        }
        
        try {
            if(BBSOracleDao.getInstance().insertArticle(article) == 1) {
                System.out.println("게시글이 삽입되었습니다.");
            } else {
                System.out.println("게시글 삽입이 실패하였습니다.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        resp.sendRedirect("/bbs/list.bbs?pageNum=1");
        return null;
    }
 
    public String getFileName(Part filePart) {
        for(String filePartData : filePart.getHeader("Content-Disposition").split(";")) {
            System.out.println(filePartData);
            if(filePartData.trim().startsWith("filename")) {
                return filePartData.substring(filePartData.indexOf("="+ 1).trim().replace("\"""");
            }
        }
        
        return null;
    }
 
}
cs


  다중 파일 업로드의 경우 HTML5가 지원하는 input 태그의 multiple 속성을 이용하면 되는데, 이는 스프링 프로젝트에서 진행하도록 한다.



[서블릿 3.0에서 파일 업로드 참고]

[MultipartConfig annotation examples 참고1]

[multipart는 HTTP POST로만 전송해야 한다, Outsider's Dev Story 참고2]