GET/POST방식의 차이에 대해 자세히 알아보고, 파일업로드 방법에 대해 알아보자.


◆ form의 POST방식


▶ form의 method

 request.getMethod()를 호출하게되면 form에서 보낸 메서드방식을 반환해준다.
form에서 method=”get” 또는 “post”의 형식으로 옵션을 설정할 수 있다.

  • 메서드의 종류는 GET과 POST 가 있다.


▶ GET방식의 요청 파라미터

form의 메서드가 get방식인 데이터들의 전달은 주소창에서 쿼리스트링이라고 불리는 ?뒤에 붙은 값들을 인식해서 뽑아내는 방식이다.
( request.getQueryString()으로 뽑아낸 데이터들을 반환 )


▶ POST방식의 요청 파라미터

form의 메서드가 post방식인 데이터들은 서버 측과 연결된 OutputStream을 이용하여 뽑아내는 방식이다.
즉, post방식에서 주소창 ?뒤에 파라미터들은 의미가 없다.
( 따라서, request.getInputStream() 등을 통해 InputStream으로 데이터를 읽어 들이는 형식이다. )

BufferedReader br = request.getReader();
while (true) {
    String str = br.readLine();
    if (str == null) {
        break;
    }
    System.out.println("=>" + str);
}

위의 코드를 치게 되면 post방식으로 보낼 때는 출력이 되지만, get 방식으로 보낼 때는 확인할 수 없다.
( get방식은 getQueryString()으로 뽑아내기 때문에.. )

단, POST방식이라고 하여 굳이 위의 방식으로 데이터를 뽑아낼 필요는 없다. WAS에서 request.getParameter()메서드로도 POST방식의 데이터를 뽑아낼 수 있게 처리해 주기 때문이다.


▶ post 방식으로 getParameter()할 때, 한글을 보내게 되면 깨지게 된다.

post방식은 데이터를 뽑아내는 방식이 다르기 때문에 response가 아닌 reqeust의 문자인코딩 방식을 바꿔주어야 한다. POST방식의 디폴트 request캐릭터인코딩은 ISO-8859-1이기 때문에
request.setCharacterEncoding("utf-8"); 으로 해주면 된다.

( 단, getParameter() 하기 전에 해주어야 인식한다. )


◆ POST 방식을 쓰는 이유

GET 방식으로 보낼 수 있는 데이터는 한계가 있기 때문에 (1024byte정도? )
일정 수치 이상의 데이터를 보내게 되면 깨지게 된다. (게시물 작성 등..)
따라서 넘기는 데이터가 많을 경우 POST를 쓰거나, 또는 로그인 폼 등의 보안상의 문제가 있는 경우에는 POST를 쓰게 된다.

보통의 경우 DB에서 데이터를 불러오는 등의 작업을 하는 JSP페이지로 보낼 때는 GET방식으로 처리하고, 그 외의 경우엔 POST로 처리한다.


◆ Multipart


▶ POST 방식의 getParameter()

POST 방식에서 request.getParameter()메서드를 WAS에서 알아서 처리할 수 있도록 되어있는 이유는 form에서 method가 POST방식일 때는 디폴트값으로 enctype="application/x-www-form-urlencoded" 옵션이 설정 되어있기 때문에 이를 WAS에서 인식하고 알아서 in/output방식으로 데이터를 처리하기 때문이다.

  • 따라서, POST방식에서는 enctype이 "application/x-www-form-urlencoded"방식이 아닌 경우, getParameter()로 데이터를 불러올 수 없다.

  • request.getHeader(“Content-Type”) 또는 request.getContentType() 를 통해서 enctype을 확인할 수 있다. 또한 이 값은 get방식의 경우는 null을 반환하게 된다. ( get방식에는 enctype이 없다. )


▶ 파일전송

form에서 파일 또는 이미지를 전송하기 위해서는 enctype이 “multipart/form-data”로 설정되어 있어야 한다. 따라서, request.getParameter()로 데이터를 불러올 수 없게 된다.

  • 또한 enctype은 post방식에서만 존재하기 때문에 GET방식에서는 파일전송이 불가능하다.

  • request.getContentType();
    enctype을 확인할 수 있다.
  • request.getContentLength();
    전송받은 모든 데이터의 길이를 byte크기(int형)로 반환한다
    .
  • 파일전송의 경우 getParameter()를 사용할 수 없기 때문에, request.getReader().readLine(); 등의 in/out풋 방식을 통해서 불러와야 한다.
    또한, form에서 파일등 여러 가지 데이터를 가져올 때, getParameter()로 하나씩 뽑아오지 않기 때문에 데이터의 구분이 어렵다. 따라서 multipart요청을 처리 할 때는 보통 라이브러리로 처리한다.


◆ multipart 라이브러리 처리

multipart 파일처리를 하기 위해 사용되는 라이브러리는 크게 두 가지가 있다.

  1. apache에서 만든 commons file upload libary
  2. O’Reilly에서 만든 cos libary - www.servlets.com


◆ cos library

1. 라이브러리 추가

www.servlets.com 에서 다운로드 후 WEB-INF/lib 파일에 복사

2. MultipartRequest 객체를 생성

객체 생성 시 생성과 동시에 form을 통해 받아온 파일이 바로 세이브 디렉토리로 복사된다.(업로드)

MultipartRequest mr = new MultipartRequest(request, "d:\\upload");
▶ MultipartRequest객체의 생성자의 매개인자로는 2개짜리부터 5개짜리까지 있다.
  • 1번째 인자로는 무조건 request객체를 받는다.
  • 2번째 인자는 세이브디렉토리의 경로.
  • 3번째 인자는 제한용량설정(int 형 (1024*1024*100 =>100mb )
  • 4번째 인자는 문자인코딩 방식
  • 5번째 인자는 리네임정책

  • MultipartRequest객체의 기본 문자인코딩 방식으로는 한글파일명이 처리가 불가능하기 때문에 4번째 인코딩 방식을 “utf-8”로 설정해준다.
    => 5번째 인자인 리네임정책은 중복파일명을 어떻게 처리할 것인지에 대해 설정하는 것


◆ 업로드경로

파일의 업로드 경로를 지정해 줄 때 웹 경로가 아닌 실제 경로를 적어 주어야 한다.
실제 경로를 적는것이 번거롭기 때문에 request.getRealPath(“웹경로”)메서드를 이용하면 해당 웹 경로에 대한 실제 서버경로를 반환해 준다.

request.getRealPath("uploadFolder");
// => C:\Users\gunbin\Desktop\apache-tomcat-8.5.40\wtpwebapps\jsp_test\filefolder

파일은 실제 경로로 저장되기 때문에 이클립스에서 생성한 업로드폴더에 업로드된 폴더가 생성되지 않고, 실제 경로에 들어가야 해당 파일을 확인할 수 있다.
단, 업르드된 파일을 <img>태그등으로 불러올 때는 웹 경로를 사용하여 적어도 WAS가 알아서 인식해준다.


◆ MultipartRequest객체 메서드


▶ 파일이 아닌 데이터 처리 메서드

enctype이 multipart일 때 request.getParameter()로 데이터를 뽑아낼 수 없지만,
MultipartRequest객체를 이용하면 똑같이 getParameter()메서드로 일반 데이터를 뽑아낼 수 있다.
따라서, mr.getParameter(“네임”) 을 하게 되면 똑같이 데이터를 뽑아낼 수 있다.

MultipartRequest mr = new MultipartRequest(request, realPath, size, "UTF-8", new DefaultFileRenamePolicy());
String name = mr.getParameter("name");


▶ 파일 관련 메서드

  • mr.getContentType(“name”);
    보낸 파일의 형태가 무엇인지를 반환 ( request.getcontentType과 다른 메서드 )
    => 이미지파일만 허용하는 등의 기능에서 사용 ex ) image/jpeg (jpg일 경우)
  • mr.getOriginalFileName(“form에서 보낸 파일의 name”);
    form에서 선택한 원래 파일의 이름
  • mr.getFilesystemName(“form에서 보낸 파일의 name”);
    업로드된 파일의 이름( 리네임 되었을 시 리네임된 이름 )
  • mr.getFile(“form에서 보낸 파일의 name”);
    업로드한 파일의 객체를 반환 – File
  • mr.getFileNames()
    파일형태의 인풋데이터의 name속성을 Enumeration객체로 반환해준다.
    따라서 for-each문으로 name을 뽑아낼 수 있다.


▶ 에러가 터지는 경우

  • form의 enctype이 multipart/form-data가 아닐 때
  • 세이브 디렉토리경로가 잘못되거나 없을 때
  • 제한용량(10241024100byte - 1mb)보다 클 때
// WebContent아래에 filefolder디렉터리를 미리 생성해 놓은것을 가정
// C://등으로 시작하는 실제 경로반환
String realPath = request.getRealPath("filefolder"); 

int size = 1024 * 1024 * 10; // 최대 10mb사이즈

String fileName = "";
String fileReName = "";
try{
    // 생성과 동시에 업로드
	MultipartRequest mr = new MultipartRequest(request, realPath, size, "UTF-8", new DefaultFileRenamePolicy()); 
	fileName = mr.getOriginalFileName("f"); // 업로드한 원래 파일이름
	fileReName = mr.getFilesystemName("f"); // 업로드된 파일이름
}catch(Exception e){
	e.printStackTrace();
}
out.println("<img src='/filefolder/" + fileReName +"'"); // 이미지출력


◆ Multipart의 리네임정책

리네임 정책은 같은 이름의 파일이 업로드 될 때 어떻게 처리할 지를 결정하는 MultipartRequest객체 생성자의 5번째 인자이다.


▶ 디폴트 리네임정책

디폴트 리네임정책은 파일이름에 증가하는 숫자를 계속 붙여나가는 형식으로,
“파일”이라는 이름의 똑같은 파일이 또 업로드 되었을경우 “파일2”로 업로드 시키는 형식이며
인자로 new DefaultFileRenamePolicy()를 적어주면 된다.

MultipartRequest mr = new MultipartRequest( ... , new DefaultFileRenamePolicy()); 


▶ 리네임정책 만들기

리네임 정책을 직접 만들기 위해서는 implements FileRenamePolicy를 구현하는 클래스를 만들어서 rename(File f)메서드를 오버라이드 하여 처리해야 한다.
=> rename(File f) 메서드에서 파일객체를 리턴하여 이 리턴된 파일객체로 실제 복사될 파일이 만들어진다.

public class TestPolicy implements FileRenamePolicy {
    String preFix;
    
    public TestPolicy(String preFix) {
        this.preFix = preFix;
    }
    
    @Override
    public File rename(File f) {  
        if (f.exists()) {
            return new File(f.getParent(), this.preFix);
        } else {
            return f;
        }
    }
}

이런 식으로 인터페이스를 구현하고,5번째 인자로 TestPolicy객체를 생성하여 보내면 된다.
ex ) new MultipartRequest(reuqest,saveDir, …… , new TestPolicy(“파일명”));


▶ 파일 DB처리

업로드된 파일을 DB에 저장할 때 실제 파일을 저장하는 것이 아닌, 파일이 저장된 경로와 파일의 이름을 저장한다. 따라서 파일을 DB에서 불러온다는 것은 경로와 파일명의 정보를 불러오는 것이다.


◆ 파일이 여러 개일 때, 멀티파트 처리

MultipartParser parser = new MultiParser(request, 1024*1024*100);
parser.setEncoding("utf-8");
while (true) {
    Part part = parser.readNextPart();
    if (part == null)
        break;
    if (part.isFile()) {
        FilePart filePart = (FilePart) part;
        String fileName = filePart.getFileName();
        if (fileName != null) {
            File saveDir = new File(application.getRealPath("/image/" + writer));
            if (!saveDir.exists())
                saveDir.mkdirs(); 
            // mkdirs  상위 폴더가 없어도 무조건 생성!
            File file = new File(saveDir, System.currentTimeMillis() + ".jpg");
            filePart.writeTo(file); // 인자경로에 파일을 생성
        }
    } else {
        // 파라미터
        ParamPart paramPart = (ParamPart) part;
        String param = paramPart.getName(); // 파라미터명
        String value = paramPart.getStringValue(); // 
        String[] words = value.split("\\s+");
    }
}

=> 파일이 여러 개 일 때는 MultipartRequest객체로 처리할 수 없고, MultipartParser객체를 이용하여 처리해야 한다.

  • 객체생성 : MultipartParser parser = new MultiParser(request, size);
  • 파일 인코딩 : parser.setEncoding(“utf-8”)
  • 폼으로 넘겨받은 인자를 하나씩 반환 : Part객체 = parser.readNextPart();
  • Part객체는 파일일 경우 FilePart객체로 캐스팅, 파일이 아닌 파라미터의 경우 ParamPart객체로 캐스팅
  • (FilePart)part.writeTo(file);


◆ 파일 미리보기

스크립트에서 input file태그의 객체의 onchange함수를 호출하여 페이지전환없이 파일을 미리볼 수 있도록 지원한다.

<input type="file" name="profile" onchange="miri(this);">
<img src="" id="img">
<script>
    function miri(tag){
        var reader = new FileReader();
        // files메서드는 배열로 반환되기 때문에 멀티파일도 처리가능하다.
        reader.readAsDataURL(tag.files[0]);
        reader.onload = function() {
            // 여기서의 this reader객체
            document.getElementById("img").src = this.result;
        } 
    }
</script>

FileReader()객체를 통해서 readAsDataURL(파일객체)를 통해 FileReader객체를 해당 파일객체로 물려두고
onload()시 읽어 들인 데이터의 result로 업로드 되기 전에 파일을 미리 읽어 들일 수 있다.
=> 여러개일 때는 반복문을 돌리면서 처리하면 된다.