본문 바로가기

java

jspSmartUpload 패키지 사용을 금합니다

jspSmart(http://www.jspsmart.com)의 jspSmartUpload 패키지를 이용하여 File Upload 를
구현한 사이트는 반드시 아래 글을 읽으십시요...

과거버전의 jspSmartUpload 를 현재 사용하고 있을 경우, 심각한 문제를 유발하게 됩니다.
즉, 다소 크다 싶은 문서를 upload 하고 있는 도중에 브라우져를 중지시키거나, 닫거나,
혹은 LAN선을 뽑는 것과 같은 행위를 할 경우, 특정 for loop 를 영원히 빠져나오지 
못하고 계속적으로 수행됨으로 인해 CPU 사용율을 100% 까지 점유하게 됩니다.
여하한의 서블렛엔진/어플리케이션서버 제품에서도 동일한 현상을 보입니다.

아래는 jad tool (http://www.geocities.com/SiliconValley/Bridge/8617/jad.html) 을
이용하여, com.jspsmart.upload.SmartUpload 클래스의 소스를 디컴파일 해 본 것입니다.


-------------------------------------------------------------------------------
package com.jspsmart.upload;
.....
public class SmartUpload
{
    ....

    public void upload()
        throws IOException, ServletException
    {
        int totalRead = 0;
        int readBytes = 0;
        .....

        m_totalBytes = m_request.getContentLength();
        m_binArray = new byte[m_totalBytes];
        for(; totalRead < m_totalBytes; totalRead += readBytes)
            try
            {
                readBytes = m_request.getInputStream().read(m_binArray, totalRead,
                                                        m_totalBytes - totalRead);
            }
            catch(Exception exception) { }

        .....
    }
    ....
}
-------------------------------------------------------------------------------

위에서, 데이타를 전송받고 있는 read() 메소드는 정상적일 경우는 일정량씩 날아오는
데이타의 길이를 return 하거나, 장애가 발생할 경우 IOException 이 throw 되거나,
비정상적으로 Stream 이 상대편에서 닫힐 경우 -1 값을 return 하게 됩니다.

그러나, 그러한 Exception 을 catch 하여 무시함으로 인해, for loop 를 빠져나오지
못하고 infinite loop 에 빠지게 되는 것이죠... 이러한 상황은 upload 중에 브라우져를
닫는 것으로 쉽게 재현됩니다.


위 부분은 다음과 같은 형식으로 변경되어야 합니다.

-------------------------------------------------------------------------------
package com.jspsmart.upload;
.....
public class SmartUpload
{
    ....

    public void upload()
        throws IOException, ServletException
    {
        int totalRead = 0;
        int readBytes = 0;
        .....

        m_totalBytes = m_request.getContentLength();
        m_binArray = new byte[m_totalBytes];
        //-----------------------------------------------------
        //for(; totalRead < m_totalBytes; totalRead += readBytes)
        //    try
        //    {
        //        readBytes = m_request.getInputStream().read(m_binArray, totalRead, 
        //                                                 m_totalBytes - totalRead);
        //    }
        //    catch(Exception exception) { }
        //-----------------------------------------------------
        InputStream in = null;
        try {
            in = m_request.getInputStream();
            for(; totalRead < m_totalBytes; totalRead += readBytes) {
                readBytes = in.read(m_binArray, totalRead, m_totalBytes - totalRead);
                if ( readBytes < 0 )  throw new IOException("read status code : -1" );
            }
        }
        catch(Exception e){
            throw new IOException("Unable to upload : " + e.toString() );
        }
        //-----------------------------------------------------
        .....
    }
    ....
}
-------------------------------------------------------------------------------



최근(2001.07.20일자)의 jspSmartUpload v2.1 을 다운 받아 decompile 해 본 결과 다음과
같이 해당 부분이 살짝 수정되어 있네요...

-------------------------------------------------------------------------------
package com.jspsmart.upload;
public class SmartUpload
{
    ....

    public void upload()
        throws SmartUploadException, IOException, ServletException
    {
        int totalRead = 0;
        int readBytes = 0;
        ....

        m_totalBytes = m_request.getContentLength();
        m_binArray = new byte[m_totalBytes];
        for(; totalRead < m_totalBytes; totalRead += readBytes)
            try
            {
                m_request.getInputStream();
                readBytes = m_request.getInputStream().read(m_binArray, totalRead, 
                                                        m_totalBytes - totalRead);
            }
            catch(Exception e)
            {
                throw new SmartUploadException("Unable to upload.");
            }
         ......
    }
    .....
}
-------------------------------------------------------------------------------

그러나 이것 역시 완벽하지 않습니다. Network Traffic 이 극심할 경우, read() 메소드에서
IOException 보다는 -1 값이 return 될 가능성이 더 많습니다. 또한, HttpServletRequest의
getInputStream() 메소드는 서블렛엔진(컨테이너) 혹은 어플리케이션 서버의 Vendor 가
어떻게 implement 하였냐에 따라, IOException 을 발생할 수도 있고 그렇지 않을 수도
있습니다. javax.servlet.http.HttpServletRequest interface 를 구현한 실제 클래스의
인스턴스는 벤더마다 다를 수 있는 것이지요. 따라서, m_request.getInputStream() 메소드는
InputStream 의 reference 만 return 해 줄 가능성이 높기 때문에 IOException 이 발생치
않을 수 있습니다.

따라서, jspSmartUpload 2.1 의 경우라면 다음과 같이 수정하여 재컴파일하시길 권합니다.

-------------------------------------------------------------------------------
package com.jspsmart.upload;
public class SmartUpload
{
    ....

    public void upload()
        throws SmartUploadException, IOException, ServletException
    {
        int totalRead = 0;
        int readBytes = 0;
        ....

        m_totalBytes = m_request.getContentLength();
        m_binArray = new byte[m_totalBytes];
        //for(; totalRead < m_totalBytes; totalRead += readBytes)
        //    try
        //    {
        //        m_request.getInputStream(); <-- 이건 쓸데 없이 왜 들어가 있을까..
        //        readBytes = m_request.getInputStream().read(m_binArray, totalRead, 
        //                                                m_totalBytes - totalRead);
        //    }
        //    catch(Exception e)
        //    {
        //        throw new SmartUploadException("Unable to upload.");
        //    }
        //-----------------------------------------------------
        InputStream in = null;
        try {
            in = m_request.getInputStream();
            for(; totalRead < m_totalBytes; totalRead += readBytes) {
                readBytes = in.read(m_binArray, totalRead, m_totalBytes - totalRead);
                if ( readBytes < 0 )  throw new IOException("read status code : -1" );
            }
        }
        catch(Exception e){
            throw new SmartUploadException("Unable to upload : " + e.toString() );
        }
        //-----------------------------------------------------
        .....

    }
    .....
}
-------------------------------------------------------------------------------



그러나,위와 같은 문제 외에도, 업로드할 파일의 사이즈 만큼 통채로 메모리에 할당하는
구조를 취하고 있습니다. 덩치가 큰 파일을 업로드 할 경우, 메모리 사용이 극심하게
되며, JVM 자체를 OutOfMemoryError 를 내며 crashing 시킬 수도 있습니다. 이것은
아래의 코드에 기인합니다.
        .....
        m_totalBytes = m_request.getContentLength();
        m_binArray = new byte[m_totalBytes]; <---- !!!
        ....

이러한 구조가 아니라 일정한 크기씩 InputStream 에서 read() 하여 곧바로 특정 파일에
기록하는 형식으로 가져갔었으야 했었습니다. 

메모리 문제는 이미 아래 글에서도 지적되었더군요.
http://www.javaservice.net/~java/bbs/read.cgi?m=resource&b=QandA&c=r_p&n=994152203


궁극적으로, jspSmartUpload 패키지는 사용치 않으실 것을 권합니다. 


PS: jad 로 디컴파일 후, 다시 컴파일하려고 할 때. File 클래스 참조에 대한 컴파일
에러가 발생할 것입니다. SmartUpload 클래스에서 에러를 유발하는 라인의 "File"을
"java.io.File" 로 변경한 후 다시 컴파일하면 됩니다. 이는 File 이란 클래스명이
com.jspsmart.upload 패키지와 JDK의 java.io 에도 동일한 이름으로 존재하여 Jad 가
똑똑하게 디컴파일하지 못한 것에 기인한 듯 합니다.

PS: 들어와서 빠져나가지 않고 계속적으로 돌고 있는 Servlet/JSP 서비스를 모니터링 
하실려면,아래 글을 참조하여 Request Monitor 를 적용하여 보실 수 있습니다.
279   현재실행중인 서비스 모니터링 - requestmon -  
http://www.javaservice.net/~java/bbs/read.cgi?m=appserver&b=was&c=r_p&n=982131370


--------------
2002.12.10 추가
jspSmartUpload 모듈을 사용하여 발생한 장애의 유형이 발견되었네요.
Re: Java Stack Trace 분석 예제
http://www.javaservice.net/~java/bbs/read.cgi?m=etc&b=jdk&c=r_p&n=1039563791

-------------------------------------------------  
  본 문서는 자유롭게 배포/복사 할 수 있으나 반드시
  이 문서의 저자에 대한 언급을 삭제하시면 안됩니다
================================================
  자바서비스넷 이원영
  E-mail: javaservice@hanmail.net
  PCS:011-898-7904
================================================

'java' 카테고리의 다른 글

[JAVA] 파일의 복사, 이동, 삭제, 생성, 존재여부 확인  (0) 2013.07.17