Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

cos.jar로 파일 업로드 하기

파일업로드를 하려면 기본적인 POST방식으로는 안되고 파일업로드를 처리할 수 있는 무언가(?) 있어야 한다.

일단 jsp에서 파일 업로드를 선택하는 form에서 entype이 multipart/form-data로 보내야 한다. 그렇지 않으면  받는쪽에서 파일을 받을 수가 없다. 파일이 없으면 보통의 form은 entype을 바꾸어 주지 않아도 된다.


<form name="writeForm" method="post" action="writeProc.jsp" enctype="multipart/form-data" >
     <input type="file" name="attachFile" size="40" />
</form>

이제 받아야 하는데 이걸 받는 역할을 cos.jar가 한다. 폼전송을 multipart/form-data로 보냈기 때문에 기존에 폼을 받던 request.getParameter()로는 받을 수가 없다. 그래서 cos.jar가 파일도 받고 폼의 다른 값들도 다 받아주는 역할을 한다.

cos는 com.oreilly.servlet의 약자이다. 보면 알겠지만 보통 java에서 package를 정의할 때 쓰는 방식이고 이 팩키지를 jar로 묶어서 cos.jar라고 배포를 하는 것이다. cos.jar의 페이지에서 cos-05Nov2002.zip를 다운로드 가능하다.(파일명에서 보아 알겠지만 2002년 11월이 가장 최신판이다  ㅡ..ㅡ) 다운받은 파일안에 lib에 있는 cos.jar를 WAS쪽에 넣어도 되고 해당 프로젝트의 WEB-INF안에 lib안에 넣어도 된다. cos.jar를 사용한다고 했지만 정확히는 cos.jar의 MultipartRequest를 이용해서 파일을 받는다.


<%@ page import="com.oreilly.servlet.MultipartRequest" %> 
<%
     int maxPostSize = 10 * 1024 * 1024; // 10MB
     saveDirectory = config.getServletContext().getRealPath("/upload");
     MultipartRequest multi = new MultipartRequest(request, saveDirectory, maxPostSize, "utf-8");

     Enumeration formNames=multi.getFileNames();  // 폼의 이름 반환

     String fileInput = "";
     String fileName = "";
     String type = "";
     File fileObj = null;
     String originFileName = "";    
     String fileExtend = "";
     String fileSize = "";

     while(formNames.hasMoreElements()) {
          fileInput = (String)formNames.nextElement();                // 파일인풋 이름
          fileName = multi.getFilesystemName(fileInput);            // 파일명
          if (fileName != null) {
               type = multi.getContentType(fileInput);                   //콘텐트타입    
               fileObj = multi.getFile(fileInput);                             //파일객체
               originFileName = multi.getOriginalFileName(fileInput);           //초기 파일명
               fileExtend = fileName.substring(fileName.lastIndexOf(".")+1); //파일 확장자
               fileSize = String.valueOf(fileObj.length());                    // 파일크기
          }
     }
%>

위의 소스가 MultipartRequest를 이용한 파일 받기 예제이다. 소스가 그렇게 어렵지 않기 때문에 크게 어려운 부분은 없다고 생각한다. MultipartRequest에 파라미터를 넘기기 위해서 최대용량과 저장폴더의 실제경로를 미리 만들어 주고 MultipartRequest의 객체 생성을 할 때 request와 함께 넘겨준다. MultipartRequest는 아주 다양하게 오버라이딩되어 있기 때문에 전달해야하는 파라미터에 대해서는 원하는 대로 사용할 수 있다. MultipartRequest의 정의는 cos.jar쪽 페이지에 자세하기 나와 있다.

파일이 몇개가 넘어오든 간에 MultipartRequest에서는5번라인의 객체생성 부분에서 모두 파일이 업로드 되어 저장되고 그 뒤에 저장한 파일들의 정보를 가져오는 형태로 되어 있다. 이 말은 정보를 얻기 전에 파일 저장이 먼저 되기 때문에 파일의 어떤 정보를 검사해서 저장할지 말지를 결정하는 것이 안된다는 얘기고 저장한 다음에 검사를 해서 삭제해 주는 형태가 되어야 할 것이다.

7번 라인에서는 Enumeration으로 multi객체로 부터 전달받은 폼의 이름을 구해온다. 9~14번에서 while루프 안에서 사용한 변수들을 초기화 하고 16번라인부터 7번에서 구해온 폼이름을 통해서 파일객체를 가져오면서 다 가져올때까지 루프를 돈다. while형태로 되어 있기 때문에 단일 파일이든 여러개의 파일이 올라오든 모두 처리가 가능하다. while문 안에서는 각 파일의 정보를 구해온다. 이렇게 구해온 정보는 보통은 Database에 넣을 목적으로 구해 올 것이며 필요한 것만 가져와서 사용하면 되겠다.

여기서 input=file외에 다른 input에 대한 값들을 가져오려면 request.getParameter()대신에 multi.getParameter()를 사용해주면 된다. (여기서 multi는 위에 예제소스에서 만든 MultipartRequest의 객체이다.) 사용법은 동일하다.





각자 다르겠지만 일반적으로 서버에 파일을 저장할 때는 사용자가 올린 파일명을 그대로 올리지 않는다. 이는 여러가지 이유가 있는데 jsp같은 경우는 올리지 못하게 하는게 일반적이고 올리더라도 실행되지 않도록 확장자를 날리는 등의 조치를 취함으로써 보안처리를 해준다. 또한 같은 파일명이 있을 경우에 이전파일을 덮어쓰면 안되기 때문에 같은 파일명이 있을 경우에 대한 처리를 해야하고 우리같은 경우는 한글로 된 파일명은 서버에 따라서 문제가 생길수도 읽고 띄어쓰기나 특수기호등 파일명에 대한 안정성을 보장할 수 없기 때문에 나같은 경우는 대게 영문이나 숫자의 조합으로 특정파일명을 만들어서 저장한 뒤에 디비에는 서버쪽 파일명과 초기파일명을 둘다 넣어좋고 서버쪽 파일명을 이용해서 찾고 다운할때나 보여줄때는 초기파일명을 가지고 처리한다.


이렇게하려면 업로드할때 파일명을 바꾸어 주어야 한다. MultipartRequest객체를 생성할 때 파일명을 수정하도록 파라미터를 하나 더 주면 된다.

MultipartRequest multi = new MultipartRequest(request, saveDirectory, maxPostSize, "utf-8", new DefaultFileRenamePolicy());

4번째 파라미터로 DefaultFileRenamePolicy를 넘겨준다. DefaultFileRenamePolicy는 cos.jar는 안에 존재하는 클래스이다. 파일명을 어떻게 바꾼다라는 규칙이 정해져 있는 클래스를 파라미터로 넘겨주고 파일을 업로드 할때 그 규칙에 따라 파일명이 바뀌어서 올라간다. 여기서 DefaultFileRenamePolicy는 같은 파일명이 있는지를 검사하고 있을 경우에는 파일명뒤에 숫자를 붙혀준다. ex: aaa.zip, aaa1.zip, aaa2.zip 이런 식으로...

이 규칙에 따르면 중복에 대한처리는 할 수 있지만 그 외의 경우에는 대응할 수가 없다. 그래서 DefaultFileRenamePolicy의 규칙을 필요하다면 수정해 주어야 한다. cos.jar안에 src파일도 있으니 직접 수정하고 다시 jar로 만들어도 되고 cos.jar는 그대로 두고 따로 클래스를 만들어서 MultipartRequest객체를 생성할 때 내가 만든 클랙스를 넘겨주어도 된다.

MultipartRequest multi = new MultipartRequest(request, saveDirectory, maxPostSize, "utf-8", new MyFileRenamePolicy());

MyFileRenamePolicy 클래스는 FileRenamePolicy로 부터 상속도 받아야 하고 혹시모를 다른 충돌도 막기 위해서 DefaultFileRenamePolicy와 동일하게 com.oreilly.servlet.multipart라는 팩키지를 만들어서 넣는다.

DefaultFileRenamePolicy.java

package com.oreilly.servlet.multipart;
import java.io.*;

public class DefaultFileRenamePolicy implements FileRenamePolicy {
     public File rename(File f) {
          if (createNewFile(f)) {
               return f;
          }
          String name = f.getName();
          String body = null;
          String ext = null;

          int dot = name.lastIndexOf(".");
          if (dot != -1) {
               body = name.substring(0, dot);
               ext = name.substring(dot);  // includes "."
          }
          else {
               body = name;
               ext = "";
          }

          int count = 0;
          while (!createNewFile(f) && count < 9999) {
               count++;
               String newName = body + count + ext;
               f = new File(f.getParent(), newName);
          }
          return f;
     }

     private boolean createNewFile(File f) {
          try {
                return f.createNewFile();
          }
          catch (IOException ignored) {
               return false;
          }
     }
}

이게 기존에 있는 DefaultFileRenamePolicy.java의 소스이다. 소스는 처음에는 좀 어색하지만 그리 어렵지 않다. rename()이 처음 호출되는데 createNewFile()를 실행해서(5라인) 파일생성이 가능하면 그대로 끝을 내고 생성을 못했으면 파일명의 점(.)을 기준으로 파일명과 확장자를 나누어서 저장한다.(13라인) 그리고 파일생성이 가능할 때까지(중복파일이 없을때까지) 루프를 돌아서 파일명+숫자+확장자형태로 다시 만들어서 리턴한다.(23라인)



MyFileRenamePolicy.java

package com.oreilly.servlet.multipart;
import java.io.*;
import java.util.Date;
import java.text.SimpleDateFormat;

public class AlmapFileRenamePolicy  implements FileRenamePolicy {
     public File rename(File f) {
          long currentTime = System.currentTimeMillis();
          SimpleDateFormat simDf = new SimpleDateFormat("yyyyMMddHHmmss");
          int randomNumber = (int)(Math.random()*100000);
    
          String uniqueFileName = "" + randomNumber + simDf.format(new Date(currentTime));

          String name = f.getName();
          String body = null;
          String ext = null;

          int dot = name.lastIndexOf(".");
          if (dot != -1) {
               body = name.substring(0, dot);
               ext = name.substring(dot);  // includes "."
          }
          else {
               body = name;
               ext = "";
          }
    
          String tempName = uniqueFileName + ext;
          f = new File(f.getParent(), tempName);
          if (createNewFile(f)) {
               return f;
          }

          int count = 0;
          while (!createNewFile(f) && count < 9999) {
               count++;
               String newName = uniqueFileName + "_" + count + ext;
               f = new File(f.getParent(), newName);
          }

          return f;
     }

     private boolean createNewFile(File f) {
          try {
               return f.createNewFile();
          }
          catch (IOException ignored) {
               return false;
          }
     }
}

새로 만든 파일 명명규칙인 MyFileRenamePolicy이다. 기존에 있던 것에서 파일명명하는 방식만 약간 바꾼 것이다. 앞에것보다는 약간 길어졌지만 간단하다. 파일중복을 줄이기 위해서 5자리짜리 랜덤수와 현재날짜시간의 조합으로 유니크한파일명을 만든다(12라인) 앞에것과 동일하게 점(.)을 기준으로 파일명과 확장자를 분리하고(18라인) 분리한파일명대신 유니크한 파일명과 확장자를 조합해서 임시로 파일명을 만든 다음에 파일생성을 시도한다.(28라인) 성공하면 그대로 끝내고 실패하면 루프를 돌면서 파일생성가능할때까지 카운트를 올려서 유니크한 파일명_카운트.확장자형태가 되도록 만든다.


이렇게 모든 파일은 랜덤수+현재시간.확장자의 형태로 만든다.여기서는 파일명으로 수자만 사용했지만 원하는 규칙대로 만들어서 클래스를 구성해 주면 그에 맞게 리네임을 할 수 있다.
2008/10/02 02:05 2008/10/02 02:05