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
================================================