01.JAVA/Java2009. 6. 16. 20:15
반응형
출처 : http://www.javanuri.net/devforum/boardView.jsp?pg=4&menuId=12&Id=305711&gb=qna

multipart form parser - http file upload 기능 java class 연재1

다음과 같은 기능의 file upload 프로그램을 java code를 소개하겠습니다.

1. 대용량 파일 업로그 기능 (1G 이상 파일 업로드에도 문제가 없는 기능)
2. 파일 업로드시 upload 진행 상황을 Internet Explorer에서 볼수 있을것

위의 기능 중에서 이번 글에서는 1번 기능에 대해서 설명을 하겠습니다.
2번 기능은 AJAX 기능을 이요하여 구현이 가능하며 이후 글에서 차차 설명하겠습니다.
첨부하는 소스코드는 실제 프로젝트에서 사용하는데 무리가 없는 소스입니다.
(많은 시스템에서 실제 운영중인 코드 입니다.)

ActiveX를 제외한 일반적인 파일 업로드 프로그램은 클라이언트에서 서버로 전달된
multipart form 형식의 input stream을 parsing하여 어떤 값이 파일이고 어떤 값이
form의 input 값인지를 결정하여 파일은 file system에 저장하는 방식입니다.

다음과 같은 html form을 submit하겠습니다.
선택한 파일은 c:\test.txt 파일입니다.

<form name="fParam" encType="multipart/form-data">
<input type="hidden" name="_cmd" value="file_data">
<input type="file" name="file">
</form>

서버에 전달되는 stream은 다음과 같습니다.

-----------------------------7d82efc203cc
Content-Disposition: form-data; name="_cmd"

uploadFile
-----------------------------7d82efc203cc
Content-Disposition: form-data; name="file"; filename="C:\test.txt"
Content-Type: text/plain

test
-----------------------------7d82efc203cc--

이와 같은 stream을 parsing하는 java class인
WCMultipartFormParser,WCMultipartFormElement를 소개합니다.
위와 같은 stream을 parsing하면 form element가 2개이고 하나는 
parameter(_cmd)이고 하나는 파일(c:\test.txt)입니다.

WCMultipartFormParser
  -> WCMultipartFormElement : _cmd=uploadFile
  -> WCMultipartFormElement : file c:\test.txt

WCMultipartFormParser(parser라고 간략히 부르겠습니다.)
WCMultipartFormElement(element라고 간략히 부르겠습니다.)
의 구조를 설명하겠습니다.

대용량 파일을 서버에 업로드 하려면 input stream으로 부터 전달된 file의 data를
memory에 모두 loading하면 안됩니다. 만약 5G file을 uploading한다면 이를 memory에 올릴 수가 
없기때문입니다. 따라서 upload된 내용을 parsing하면서 임시 directory에 저장을 합니다.

다음과 같은 순서로 설명을 하겠습니다.

1. input stream을 buffering하는 방법
2. parser에서 호출되는 순서
3. file을 임시파일로 저장(다음번 글에서 설명)
4. 임시로 저장된 파일을 사용자가 정의하는 directory로 복사(다음번 글에서 설명)

parser는 input stream으로부터 읽어들인 내용을 double buffering하여 관리합니다.
double buffering에 대해서 간단히 설명드리겠습니다.
하나의 buffer를 반으로 나누고 한쪽 부분에 input stream에서 읽은 내용을 임시로 저장합니다.
그리고 이 내용을 parsing을 합니다.
한쪽 부분 buffer를 모두 parsing을 하면 buffer의 나머지 반쪽에 다시 input stream에서 읽은
내용을 임시로 저장합니다.
이렇게 double buffering을 하는 이유는 한쪽의 buffer를 parsing하다가 parsing이 잘못되어
rollback을 하는 경우 한쪽 buffer에서만 rollback해서 안되는 경우가 있습니다.
예를 들어 보이겠습니다.
buffer size = 20이고 반으로 나눈 한쪽 버퍼는 10 size가 됩니다.
1234567890 1234567890
abcdefghij kl
아래와 같이 kl까지 parsing을 했는제 잘못 parsing을 해서 rollback 즉 gh부터 parsing을 다시
해야 하는 경우 double buffering 기법을 사용합니다.
그러면 buffer size를 크게 하면 되지않느냐 라고 질문 할 수 있습니다. 그렇지 않습니다.
buffer size가 아무리 커도 rollback에 대한 여분을 충분히 확보할 수가 없습니다.
즉 이전에 사용하던 buffer를 지우지 말고 보유하고 있어야 충분한 rollback을 확보할 수 있습니다.
첨부한 source 코드에서 getByte는 하나의 input stream에서 1 byte 씩 읽어들입니다.
getByte에서 호출하는 getByteEx에서 double buffering 처리를 합니다.

public byte getByte() throws Exception

input stream을 parsing 하는 부분에서는 이제 byte단위로 parsing하다가 문제가 있으면
rollback을 할 수 있습니다.
위에서 sample로 보여드린 form 즉 _cmd,file을 parsing하는 방법에 대해 설명하겠습니다.

WCMultipartFormParser.init()
  -> parseContent(oRequest)
    -> parseContentEx(oRequest)
      -> findBoundary() : -----------------------------7d82efc203cc 이 부분을 parsing합니다.
      -> findHeader() : Content-Disposition: form-data; name="_cmd" 이 부분을 parsing 합니다.
        -> WCMultipartFormElement.setHeader : header 정보를 element에 저장합니다.
      -> procContent() : content body를 parsing 합니다.
        -> 임시파일에 파일 저장 : content type이 file인경우
        -> parameter인경우 WCProperties m_rParam 에 parameter 이름과 value를 저장

위와 같은 구조로 form을 parsing합니다.

<form name="fParam" encType="multipart/form-data">
<input type="hidden" name="_cmd" value="file_data">
<input type="file" name="file">
</form>

이 form을 submit할때 request parameter 값은 WCMultipartFormParser.getParam()을 호출하면
parsing되어 저장된 request parameter값을 얻어올 수 있습니다.
그리고 아래의 소스에서 WCMultipartFormParser.saveFile(sDir)을 호출하여 임시 저장된
file들을 개발자가 원하는 directory로 이동을 합니다.

다음번 글에서는 file을 저장하는 구조에 대해서 설명하겠습니다.

아래의 소스 코드는 JSP 에서 WCPage의 object를 생성하고 WCMultipartFormParser의 기능을 호출하여
multipart form을  parsing하고 파일을 저장하는 sample code입니다.

WCPage.initCtrl
  -> WCPage.initCtrl
    -> WCPage.isMultipart : file을 submit한 겨우인지를 검사
      -> WCMultipartFormParser.init()

if (isMultipart())
{
    WCMultipartFormParser oFormParser = new WCMultipartFormParser(this);
    oFormParser.setDebug(true);
    oFormParser.init();
    byte[] btDebugBuf = oFormParser.getDebugBuf();
    String sDebug = new String(btDebugBuf);
    this.printOut("<pre>");
    this.printOut(sDebug);
    this.printOut("</pre>");
    
    WCProperties rParam = oFormParser.getParam();
    this.printOut(rParam.serializeOut());

    String sDir = "c:/tmp3";
    oFormParser.saveFile(sDir);
    return 1;
}
      
여기서 잠깐 WCPage에 대해서 설명하겠습니다.
WCPage는 JSP 혹은 Servlet에 대한 정보를 저장하고 있는 Object입니다.
WCMultipartFormParser 혹은 WCMultipartFormElement에서 request 혹은 response object를 access할때
WCPage를 통하여 access할 수 있습니다.
WCPage.initCtrl에서 pageContext를 넘김으로 WCPage에서 JSP 관련 context를 모두 저장하고
또한 page로 전달된 parameter를 WCPage가 모두 저장하고 있습니다.
여기서 sample로 제공하는 WCPage java class는 WDL(Web Development Library)에서 제공하는 WCPage.java에서
필요한 부분만 추출하여 새로 구성한 sample입니다.
(개발자들이 이해를 쉽게 하기 위해서 일부러 추출을 하였습니다. http://www.webdevlib.net 에서 
모든 소스코드는 무료로 다운로드 받을 수 있습니다.)

첨부한 소스만으로도 어느정도 동작이 가능하지만 WCProperties,WCVector,WCLog,WCSystem,WCEnv
등의 파일은 http://www.webdevlib.net에서 다운받아 사용하십시요
소스의 일부분은 고의로 누락한 것은 아닙니다. 모든 소스를 설명하려니 너무 많기때문에
중요한 부분을 추출하여 sample로 구성하면서 최대한 이해가 쉽도록 재구성을 하였습니다.

첨부파일에는 3개의 파일이 zip으로 묶여있습니다.

WCMultipartFormParser.java
WCMultipartFormElement.java
file_upload_progress1.jsp

-- file_upload_progress1.jsp 소스 시작
<%@ page language="java" import="wdl.*,java.util.*,java.sql.*,java.lang.*,java.io.*,java.io.File " contentType="text/html; charset=EUC-KR"%>
<%
    WCPage oPage = new WCPage();
    if (oPage.initCtrl(pageContext) > 0)
    {
        return;
    }
%>
<%@ include file="/wdl/src/java/WCMultipartFormParser.java"%>
<%@ include file="/wdl/src/java/WCMultipartFormElement.java"%>
<%!
public class WCPage
{
    public int initCtrl(javax.servlet.jsp.PageContext oPageContext)
    {
        return initCtrl(
            (javax.servlet.http.HttpServletRequest)oPageContext.getRequest()
            ,(javax.servlet.http.HttpServletResponse)oPageContext.getResponse()
            ,(javax.servlet.jsp.JspWriter)oPageContext.getOut()
            ,(javax.servlet.http.HttpSession)oPageContext.getSession()
            ,(javax.servlet.ServletContext)oPageContext.getServletContext()
            ,oPageContext
            ,(javax.servlet.ServletConfig)oPageContext.getServletConfig()
            );
    }
    public int initCtrl(
        javax.servlet.http.HttpServletRequest oRequest
        ,javax.servlet.http.HttpServletResponse oResponse
        ,javax.servlet.jsp.JspWriter oOut
        ,javax.servlet.http.HttpSession oSession
        ,javax.servlet.ServletContext oApplication
        ,javax.servlet.jsp.PageContext oPageContext
        ,javax.servlet.ServletConfig oConfig)
    {
        try
        {
            m_request = oRequest;
            m_response = oResponse;
            m_out = oOut;
            m_session = oSession;
            m_application = oApplication;
            m_pageContext = oPageContext;
            m_config = oConfig;
            
            if (isMultipart())
            {
                WCMultipartFormParser oFormParser = new WCMultipartFormParser(this);
                oFormParser.setDebug(true);
                oFormParser.init();
                byte[] btDebugBuf = oFormParser.getDebugBuf();
                String sDebug = new String(btDebugBuf);
                this.printOut("<pre>");
                this.printOut(sDebug);
                this.printOut("</pre>");
                
                WCProperties rParam = oFormParser.getParam();
                this.printOut(rParam.serializeOut());

                String sDir = "c:/tmp3";
                oFormParser.saveFile(sDir);
                return 1;
            }
        }
        catch (Exception ex)
        {
        }
        return 0;
    }
    public javax.servlet.http.HttpServletRequest m_request = null; 
    public javax.servlet.http.HttpServletResponse m_response = null; 
    public javax.servlet.jsp.JspWriter m_out = null; 
    public javax.servlet.http.HttpSession m_session = null; 
    public javax.servlet.ServletContext m_application = null; 
    public javax.servlet.jsp.PageContext m_pageContext = null; 
    public javax.servlet.ServletConfig m_config = null; 
    
    public javax.servlet.http.HttpSession getSession()
    {
        return m_session;
    }
    public javax.servlet.http.HttpServletRequest getRequest()
    {
        return m_request;
    }
    public javax.servlet.jsp.PageContext getPageContext()
    {
        return m_pageContext;
    }

    public boolean isMultipart()
    {
        HttpServletRequest oRequest = (javax.servlet.http.HttpServletRequest)m_request;
        String content_type = oRequest.getHeader("content-type");
        if (WCString.indexOf(WCString.toUpper(content_type),"MULTIPART") == 0)
        {
            return true;
        }
        return false;
    }
}
%>

<form name="fParam" encType="multipart/form-data">
<input type="hidden" name="_cmd" value="file_data">
<input type="file" name="file">
</form>

<a href="javascript:uploadFile();">upload the file</a>

<iframe id="ifrmAction" name="ifrmAction" width="0" height="0"></iframe> 

<script>
function uploadFile()
{
    var fParam = window.document.fParam;
    fParam._cmd.value = "uploadFile";
    fParam.method = "post";
    fParam.target = "_blank";
    fParam.submit();
}
</script>
-- file_upload_progress1.jsp 소스 끝

출처 : 고급 웹 UI 개발 라이브러리 Web Development Library 소스공개 : http://www.webdevlib.net

첨부파일 : multipart_form_parser1.zip
첨부파일 :  
multipart_form_parser1.zip   [7 KB]   다운로드 횟수 72 회
Posted by 1010