2008-12-31

JSP 파일 업로드

[개요]
"최범균의 JSP 2.0 프로그래밍" 이라는 책의 내용중 파일업로드 하는 부분이 나오는데, 해당 라이브러리의 버전이 오래된 것이어서 테스트에 약간의 문제가 있었습니다. 이에 최신 버전으로 테스트할 수 있는 예제 소스를 정리해보았습니다. Java의 파일 업로드 모듈로 많이 사용되는 Apache commons fileUpload 의 최신 버전은 2008년 12월 31일 현재 1.2.1 입니다.또한, FileUpload 는 commons-io 1.3.2 에 의존적입니다.

[예제]
◎ fileUploadForm.jsp
<%@ page contentType="text/html; charset=utf-8" %>
<html>
<head><title>파일 업로드 폼</title></head>
<body>
<form action="processFileUpload.jsp" method="post" enctype="multipart/form-data">
파일1: <input type="file" name="file1" /><br/>
파일2: <input type="file" name="file2" /><br/>
파일3: <input type="file" name="file3" /><br/>
파라미터1: <input type="text" name="param1" /><br/>
파라미터2: <input type="text" name="param2" /><br/>
파라미터3: <input type="text" name="param3" /><br/>
<input type="submit" value="전송" />
</form>
</body>
</html>

◎ processFileUpload.jsp
<%@ page contentType="text/html; charset=utf-8" %>
<%@ page import="org.apache.commons.fileupload.FileItem" %>
<%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory" %>
<%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.BufferedInputStream" %>
<%@ page import="java.io.BufferedOutputStream" %>
<%@ page import="java.io.FileOutputStream" %>
<%@ page import="java.io.IOException" %>
<html>
<head><title>파일 업로드 처리</title></head>
<body>
<%
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);


if( isMultipart ) {
    File temporaryDir = new File("/tmp/");


    // Create a factory for disk-based file items
    DiskFileItemFactory factory = new DiskFileItemFactory();
    // Set factory constraints
    factory.setSizeThreshold(1024*100);
    factory.setRepository(temporaryDir);
    // Create a new file upload handler
    ServletFileUpload upload = new ServletFileUpload(factory);
    // Set overall request size constraint
    upload.setSizeMax(1024*1024);
    // Parse the request
    List /* FileItem */ items = upload.parseRequest(request);


    // Process the uploaded items
    Iterator iter = items.iterator();
    while (iter.hasNext()) {
        FileItem fileItem = (FileItem) iter.next();


        if (fileItem.isFormField()) {
            // processFormField(fileItem);
            out.println("폼 파라미터 : " + fileItem.getFieldName() + "=" + fileItem.getString("utf-8") + "<br/>");
        } else {
            // processUploadedFile(fileItem);
            out.println("파일 : " + fileItem.getFieldName() + "=<pre>" + fileItem.getString() + "</pre>(" + fileItem.getSize() + " bytes)<br/>");


            // 업로드한 파일이 존재하는 경우
            if( fileItem.getSize() > 0 ) {
                int idx = fileItem.getName().lastIndexOf("\\");
                if( idx == -1 ) {
                    idx = fileItem.getName().lastIndexOf("/");
                }
                String fileName = fileItem.getName().substring(idx + 1);


                try {
                    File uploadedFile = new File("/opt/project/jspstudy/web/chap16/data/", fileName);
                    fileItem.write(uploadedFile);
                } catch(IOException ex) {}
            }
        }
    }
} else {
    out.println("인코딩 타입이 multipart/form-data 가 아님.");
}
%>
</body>
</html>

저장하고자 하는 디렉토리는 바꾸어서 테스트해보시기 바랍니다.
이제 몇시간 후면 2009년이 되는군요. 내년도 건강하고 즐거운 한 해가 되시기 바랍니다.

2008-12-08

.NET을 다시 알다

실버라이트를 연구하려다 보니, WPF에 대해 알고 싶어지고, 크게는 .NET에 대해 알고 싶어지더군요. 지난 주말에는 C#을 다시 한번 공부해보았습니다. WPF 와 실버라이트2 책도 주문해 놓고요.

문 득, 다른 OS에서 돌아가는 .NET 을 찾아보게 되었습니다. 원래 .NET이라는것이 Java 처럼 모든 OS에서 돌아가는 VM 으로 발전하려고 했더니까요. 더 나아가서 여러 언어도 통합이 되어 있지요. 이런 개념으로 Perl 측에서도 Parrot 이라는 것을 만들고 있는 중이죠.

아무튼, 예전에 한번 보고 말았던 Mono 프로젝트 사이트를 다시 방문해봤습니다. 그다지 발전이 없어보였던 Mono 가 .NET 2.0 까지 지원되고 있더라구요. 거기에 monodevelop 이라고 하는 훌륭한 IDE까지 제공하고 있습니다. 제 Debian Linux 에서도 패키지를 찾아보니 모두 있더군요. 당장 설치해보고, 초간단 Hello World 프로그램까지 만들어 실행해보니, 완벽하게 실행이 됩니다. Mono 에서는 C#.NET, VB.NET, ADO.NET 이 지원되는 듯 했습니다. 여기에 추후 .NET 3.X 까지 지원된다면 더욱더 훌륭하리라 봅니다.

이로써 .NET 에 대해서 다시 보게 되었습니다. 잘 하면 Java 를 능가하는 멋진 시스템이 될 것 같습니다. 점점 .NET의 매력에 빠져버리는군요. 책이 빨리 도착하면 좋겠습니다. *^^*

2008-11-22

Canon IXUS 500 이미지 센서 수리기

내가 가지고 있는 카메라는 Canon IXUS 500 과 Samsung GX-1S 이다. 이중 IXUS 500 이 얼마전에 이미지 센서가 고장이 나서 가로로 줄이 생기거나 일그러지는 현상이 발생하였다. 구입한지 4,5년이 되어서 AS를 맡기면 돈을 많이 달라고 하지 않을까 걱정을 했었다. 만약 10만원 정도 비용이 들게 된다면 새로운 똑딱이를 사려고까지 마음 먹었다.

회사에서 가장 가까운 센터가 석촌호수 옆에 있어서 카메라를 맡기러 갔다. 센서에 문제가 있다고 얘기하니 담당 직원이 무상수리가 가능하다고 한다. 혹시 유상으로 처리가 되면 미리 연락을 주겠다고 하였다. 일주일정도 소요가 된다고 하였으나 3일만에 수리가 되었다고 연락이 왔다. 이미지 센서는 디지털 카메라에서 중요한 부품인데 무상보증 기간이 지나도 무상으로 교체를 해주니, 다시 카메라를 사지 않아도 되어 다행이었다.

생각외로 Canon의 A/S가 괜찮은 듯 하다. 그리고, Nikon 과는 다르게 내수 제품도 A/S가 된다는 것도 장점이다. DSLR은 가격대비 성능이 좋고, A/S가 좋은 삼성 제품인 GX-1S 를 쓰고 있는데, 나중에 DSLR을 바꾸게 된다면 다시 Canon 제품도 고려해봐야겠다.

2008-10-29

메일박스 이동...

PHP에서 메일을 IMAP의 다른 메일박스로 이동을 위해서 imap_mail_move() 함수를 이용합니다. 그리 어려워 보이지 않습니다. 그러나, 실제 이 함수를 사용해보면 메일이 다른 메일박스에 옮겨진 것을 확인할 수 있으나, 원래 메일박스에도 그대로 남아있는것을 보게 됩니다.

어찌된 영문인지 연구를 해보니 원래 메일박스에 그대로 남겨진 메일은 Delete 플래그가 설정이 된 채로 남겨진 것입니다. 이를 알아내기 위해서 이틀이나 소요했습니다. 이것을 확실하게 없애기 위해서는 imap_expunge() 함수를 호출하여 Delete 플래그가 설정된 메일을 삭제해주어야만 합니다.

다른 내용이긴 하지만, IMAP을 다룰때 또 한가지 주의할 점은 메일박스이름은 반드시 UTF-7 로 변환하고서 이용해야 합니다.

2008-10-10

imap_open 실행시 느리게 연결되는 현상 해결

대부분의 데몬에 원격에서 연결하는 것은 상당한 부하와 시간을 소비하게 됩니다. DB 의 경우에도 보통 Connection Pool 을 만들어서 커넥션 시간을 줄이고 있습니다.

지 금 만들고 있는 웹메일에서 IMAP 서버에 접근할때에도 같은 문제가 발생하고 있습니다. 한번 연결할때마다 약 2초의 시간이 걸립니다. 그래서 페이지를 이동하거나, 메일 내용을 보려고 할때마다 2초 이상의 시간을 기다려야만 했습니다. PHP에서 개발하고 있어서 다른 페이지에서 IMAP 의 연결을 유지할 수 있는 방법이 없었습니다. SESSION 변수를 이용하면 될 듯 하였으나, 테스트 실패했습니다.혹시나 하고 계속 찾아보았지만 PHP에서 해결할 수 있는 방법은 없었습니다.

그렇지만, 방법이 아주 없으면 글을 적지도 않았겠지요. IMAP 연결을 유지하려면 IMAP Proxy 를 사용하면 됩니다. 데비안 리눅스의 경우, apt-get install imapproxy 라고 입력하면 바로 설치되어 실행까지 됩니다. 기본적으로 로컬에 IMAP 이 있다고 가정하고 있으며, 1143 포트를 열고 있습니다. 개발자는 imap_open 함수를 사용할때 localhost:1143 으로 접근하면 됩니다. PHP 뿐만 아니라 다른 환경에서도 적용할 수 있어서 괜찮은 방법인 것 같습니다.

이렇게 구현을 하고 보니, 페이지 이동시 2~3 초 걸리던 것이, 거의 즉시 화면이 전환되었습니다.
상용 웹메일에서는 이것을 어떻게 해결하고 있는지 궁금하군요.

2008-09-30

IMAP 에서 메일목록 가져오기

웹메일서버를 만들고 있습니다. OS는 Debian Linux 기반에 Postfix, Dovecot, MySQL 등을 사용하고 있습니다. 웹은 PHP로 개발중입니다. MySQL 로 도메인과 계정 연동도 끝났고, 스팸 및 바이러스도 걸러집니다. 남은 부분은 PHP로 웹UI 를 개발하는 것입니다.

간단하게 메일 목록을 가져오는 부분을 구현하였고, 메시지를 보여주는 부분을 구현중입니다. 메일목록을 가져오기 위해서 imap_fetch_overview 함수를 사용하고 있는데, 당장은 목록을 보여주는데 문제는 없지만, 더 많은 정보를 보여주고자 할때, 정보 부족으로 보여줄수 없게 됩니다. 예를 들어서 보낸사람의 이메일 주소를 표시하고 싶어도 그렇게 할 수가 없습니다. 그래서, 더 많은 정보를 가져올 수 있는 함수를 찾아보니, imap_headerinfo 가 있더군요.

일단, 메시지 보여주는 부분을 구현하고, 목록 부분도 imap_headerinfo 를 이용해서 다시 구현하려고 합니다. 메시지를 보여주는 부분에서도 처리해야 할 일들이 많군요. 처음에 개발하려고 할때는 만만하게 봤었는데, 뒤져봐야할 문서들이 많습니다. ^^ 한큐에 설치할 수 있는 설치프로그램까지 만들었습니다. 어느정도 완성되려면 1,2달 정도 걸릴 듯 합니다.

2008-09-22

받은 메일에서 보낸이,제목에 대한 디코딩

PHP에서 imap_fetch_overview 함수를 통해서 헤더를 가져와서 제목과 보낸이를 출력해보면 =?EUC-KR?B?udrB2Lq5?= 와 비슷한 형태로 출력되버린다. 이것을 정상적으로 출력하려면 2번의 디코딩 과정이 필요하다. 화면은 UTF-8로 되어 있다고 가정하자.

우선 이 문나열을 분석해보자. ?를 기준으로 해서 단어를 나누어 보면,

  1. =
  2. EUC-KR
  3. B
  4. udrB2Lq5
  5. =

로 나누어지는 것을 확인 할 수 있다.
여기에서 두번째 항목인 EUC-KR캐릭터셋을 의미한다.
또한, 세번째 항목인 BBase64 로 인코딩 되어 있음을 의미한다.
그리고, 네번째 항목인 udrB2Lq5실제내용 이다.

즉, 이것을 화면에 표시하려면, 우선 Base64로 디코딩을 한 후에, UTF-8로 캐릭터셋을 변경하면 된다.
base64로 디코딩할 수 있는 함수는 imap_base64() 이며,
캐릭터셋을 변경할수 있는 함수는 mb_convert_encodingiconv 가 있다.

최종적으로 간단하게 아래와 같은 코드로 정리할 수 있겠다.
mb_convert_encoding(imap_base64("udrB2Lq5"), "UTF-8", "EUC-KR");

관련글 : http://forums.mozilla.or.kr/viewtopic.php?f=3&t=1462

추가 :
PHP에는 아주 유용한 함수가 있군요.
imap_mime_header_decode 라는 함수가 알아서 변경해주는군요.
이제, 삽질할 필요 없겠습니다. 그래도, 캐릭터셋은 여전히 변경해주셔야 합니다. ^^

2008-09-04

window.open() 에서 두번째 인자...

며칠동안 여러 자바스크립트 라이브러리를 이용해서 다이얼로그 박스를 만들어서 처리하려고 하였으나, 만족으러운 결과를 얻지 못해서, 결국, 새로운 윈도우를 띄우기로 결정했다.

브라우저에서 새로운 창을 띄우려면 window.open(URL, Name, Options) 함수를 이용해야 하는데, 여기에서 두번째 인자인 Name 을 쓸 때 주의할 것이 있다. 주의해야 할 점은 Name 을 쓸 때 절대로 공백문자를 넣어서는 안된다는 것이다. 파이어폭스, 크롬 등에서는 문제가 없으나 IE에서는 Name 에 공백문자가 들어가면 윈도우가 나타나지 않기 때문이다. 이것은 IE의 버그는 아니고, 표준을 잘 지키는 것뿐이다.

http://developer.mozilla.org/index.php?title=En/DOM/Window.open&highlight=window.open 에 가서 확인해보면 알 수 있다.


위에서 보이듯이 This string parameter should not contain any blank space. 라는 구문이 보이는데, 해석을 해보면 "이 문자열 파라미터는 절대로 공백을 포함해서는 안된다." 라고 명시되어 있다.

그러므로, 앞으로는 반드시 window.open() 함수의 두번째 인자에 공백이 들어가지 않도록 주의해서 사용해야 한다. 이것이 오늘의 핵심 내용이다. 아마도 많은 사람들이 알고 있을거라 생각하지만, 혹시라도 모르는 사람이 나처럼 헛고생하지 않기를 바랄뿐이다.

2008-05-15

FreeBSD 7.0 에 Java 설치하기


오랜만의 포스팅이군요~ 그동안 이래저래 바빴답니다. 이번에는 FreeBSD 에서 Java 를 설치하는 방법을 정리하였습니다.

JDK는 라이센스 문제로 재배포를 할 수가 없다고 합니다. 그래서 사용자가 직접 사이트에서 다운로드하여 설치해야만 합니다.
FreeBSD에 설치할 수 있는 JDK는 여러가지가 있습니다. 우선, /usr/ports/java 로 이동해보고 ls 를 해보면 여러 디렉토리가 나올 것입니다.
jikes,diablo-jdk15, diablo-jre15, jdk11 ~ jdk16, linux-blackdown-jdk14,linux-sun-jdk12 ~ linux-sun-jdk16 과 같이 다양한 JDK를 볼 수 있습니다.
이중에서 diablo-jdk15 와 jdk15 를 설치하는 방법을 살펴보겠습니다. 그 전에 포트를 최신으로 업데이트 합니다.

# portsnap fetch update

포트를 처음 업데이트 하는 것이라면 # portsnap fetch extract update 로 업데이트해주셔야 합니다.
포트 업데이트를 하지 않으면 아래의 tzupdater 의 해당 버전을 구할 수 없는 문제가 발생할 수 있으니, 꼭 업데이트 해야 합니다.
아래 설치방법은 현재(2008-05-15)를 기준으로 FreeBSD 7.0 에서 작성되었습니다.

[ diablo-jdk15 설치 ]
/usr/ports/java/diablo-jdk15 로 이동하여 바로 make 를 하면 에러를 보게 될 것이다.
우선, http://www.FreeBSDFoundation.org 에서 diablo-caffe-freebsd6-i386-1.5.0_07-b01.tar.bz2 를 다운로드 받고,
http://java.sun.com/javase/downloads/index.jsp 에서 tzupdater-1_3_5-2008b.zip 을 다운로드 받는다.
그리고, 다운로드 받은 파일을 /usr/ports/distfiles 에 복사한다.
이제 아래와 같이 수행하면 잘 설치될 것입니다.
# cd /usr/ports/java/diablo-jdk15
# make; make install

[jdk15 설치]
역시, /usr/ports/java/jdk15 로 이동하여 make 를 하면 에러를 보게 됩니다.
그러니, http://download.java.net/tiger/archive/tiger_u14/ 에서 jdk-1_5_0_14-fcs-src-b03-jrl-05_oct_2007.jarjdk-1_5_0_14-fcs-bin-b03-jrl-05_oct_2007.jar 다운로드 받고,
http://java.sun.com/javase/downloads/index.jsp 에서 tzupdater-1_3_5-2008b.zip 을 다운로드 받는다.
http://www.eyesbeyond.com/freebsddom/java/jdk15.html 에서 bsd-jdk15-patches-8.tar.bz2 를 다운로드 받습니다.
그리고, 다운로드 받은 파일을 /usr/ports/distfiles 에 복사한다.
이제 아래와 같이 수행하면 잘 설치될 것입니다.
# cd /usr/ports/java/jdk15
# make; make install
jdk16 도 같은 방법으로 다운로드/설치하면 될 것 같습니다. diablo-sdk 는 1.6 버전이 없고, 꾸준이 업데이트되는 것 같지 않습니다. 따라서, java.net 에서 제공되jdk15 또는 jdk16을 설치해서 쓰는것이 좋을 듯 합니다.

2008-04-19

코드 조각 자동 포함 기능

JSP 를 다시 공부하면서 새로운(?) 기능을 알게 되었다.
JSP 2.0 이상부터 추가되었으며, 자동으로 JSP 의 앞뒤에 지정한 파일을 삽입시킬 수 있는 기능이다.
web.xml 에 파일에 다음과 같이 설정을 추가해 주면,


<jsp-config>
  <jsp-property-group>
    <url-pattern>/view/*</url-pattern>
    <include-prelude>/common/variable.jspf</include-prelude>
    <include-coda>/common/footer.jspf</include-coda>
  </jsp-property-group>
</jsp-config>


/view/ 밑에 있는 모든 JSP 파일에 /common/variable.jspf/common/footer.jspf 가 자동으로 앞과 뒤에 자동으로 포함된다.

이런 편리한 기능이 있었다니, 이번 프로젝트에 적용해보아야 겠다. ^^
혹시 Struts 2 에도 비슷한 기능이 있는 살펴보아야겠습니다.

2008-04-12

The value for the useBean class attribute ~~~ is invalid 문제 해결

Tomcat 5.5 에서 자바빈즈를 사용하는데 문제가 발생해서 해결하는데 3일이나 걸렸습니다. 문제의 원인은 server.xml 설정의 문제였는데요, Tomcat 4.1 까지는 정상적으로 동작한다는 것이 해결의 발목을 잡았습니다. Host 태그의 appBase 내용을 Context 태그의 docBase 로 옮기고 나니 정상적으로 동작하는 것을 확인하였습니다. server.xml 의 설정값을 어떻게 해야되는지 내용을 알기가 어렵더군요. 아무튼 이렇게 설정하고나니 모든 것이 잘 돌아가고 있습니다. server.xml 에 대해서 아주 쉽고 상냥하게 설명되어 있는 문서가 없을까요? ^^;

* 문제 발생시의 server.xml 내용
<Host name="test.com" debug="0" appBase="/opt/project/test.com/web" unpackWARs="true" autoDeploy="true">
 <Logger className="org.apache.catalina.logger.FileLogger" directory="logs" prefix="test.com_" suffix=".log" timestamp="true"/>   
  <Context path="" docBase="" debug="0">
    <Resources className="org.apache.naming.resources.FileDirContext" allowLinking="true" />
  </Context>
</Host>

* 문제 해결후의 server.xml 내용
<Host name="test.com" debug="0" appBase="" unpackWARs="true" autoDeploy="true">
  <Logger className="org.apache.catalina.logger.FileLogger" directory="logs" prefix="test.com_" suffix=".log" timestamp="true"/>   
  <Context path="" docBase="/opt/project/test.com/web" debug="0">
    <Resources className="org.apache.naming.resources.FileDirContext" allowLinking="true" />
  </Context>
</Host>