2014년 1월 24일 금요일

JSP/서블릿으로 Gmail을 이용하여 메일 보내기

사용자가 적은 내용을 내 메일계정으로 보내도록 만들려고 여기저기 찾아본 끝에 결국 완료했다.
필요한 라이브러리는 Javabeans Activation Framework (JAF)와 JavaMail 이 두가지이다.
두 라이브러리를 다운 받아 적당한 폴더에 넣는다. 내 경우에는 이클립스에서 프로젝트폴더/WebContent/WEB-INF/lib 에 압축을 푼 JAR 파일들을 복사해넣었다.
두 라이브러리를 설치했다면 코드를 작성한다. 내가 작성한 코드는 JSP에서 form을 이용해 이름, 이메일주소, 제목, 내용을 입력받고 이 내용을 서블릿으로 보내고 서블릿에서 파라미터로 받은 내용을 메일로 보낸 다음, 처리 결과에 따라 적절한 결과 페이지로 보내게 되어있는 구조이다.
JSP의 form 입력양식 페이지는 어려울게 없다. 문제는 서블릿 부분이었다.
인터넷을 찾아보니 소스가 많이 있긴한데 대부분은 로컬 메일서버를 이용하는 방법들이었고 내 서버 역시 SendMail이 있긴하지만 굳이 그걸 쓸 필요도 없고 지메일쪽의 SMTP를 쓰는게 더 나을 것 같아서 방법을 찾아보았다. 지메일쪽은 SSL을 이용한 인증을 하고 있어서 이것저것 옵션을 붙여야할 것들이 많았다.
SendMail.java 서블릿의 코드는 다음과 같다.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package mailer;
 
import java.io.IOException;
import java.util.Properties;
 
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
/**
 * Servlet implementation class SendMail
 */
public class SendMail extends HttpServlet {
    private static final long serialVersionUID = 1L;
 
    /**
     * @see HttpServlet#HttpServlet()
     */
    public SendMail() {
        super();
        // TODO Auto-generated constructor stub
    }
 
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
    }
 
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
 
        request.setCharacterEncoding("UTF-8");
 
        response.setContentType("text/html;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
 
        String m_name =     request.getParameter("name");
        String m_email =    request.getParameter("email");
        String m_title =    request.getParameter("title");
        String m_text =     request.getParameter("text");
 
        try {
            String mail_from =  m_name + "<" + m_email + ">";
            String mail_to =    "admin<admin@83rpm.com>";
            String title =      "hosting.83rpm.com 요청사항 :: " + m_title;
            String contents =   "보낸 사람 :: " + m_name + "&lt;" + m_email + "&gt;<br><br>" + m_title + "<br><br>" + m_text;
 
            mail_from = new String(mail_from.getBytes("UTF-8"), "UTF-8");
            mail_to = new String(mail_to.getBytes("UTF-8"), "UTF-8");
 
            Properties props = new Properties();
            props.put("mail.transport.protocol", "smtp");
            props.put("mail.smtp.host", "smtp.gmail.com");
            props.put("mail.smtp.port", "465");
            props.put("mail.smtp.starttls.enable", "true");
            props.put("mail.smtp.socketFactory.port", "465");
            props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            props.put("mail.smtp.socketFactory.fallback", "false");
            props.put("mail.smtp.auth", "true");
 
            Authenticator auth = new SMTPAuthenticator();
 
            Session sess = Session.getDefaultInstance(props, auth);
 
            MimeMessage msg = new MimeMessage(sess);
 
            msg.setFrom(new InternetAddress(mail_from));
            msg.setRecipient(Message.RecipientType.TO, new InternetAddress(mail_to));
            msg.setSubject(title, "UTF-8");
            msg.setContent(contents, "text/html; charset=UTF-8");
            msg.setHeader("Content-type", "text/html; charset=UTF-8");
 
            Transport.send(msg);
 
            response.sendRedirect("request_complete.jsp");
        } catch (Exception e) {
            response.sendRedirect("request_failed.jsp");
        } finally {
 
        }
    }
 
}
중요한건 인증에 대한 부분인데 Session 객체에 이 인증에 대한 객체를 넣어줘야한다. 이 인증 객체는 내가 참조한 다른 글이나 구글링을 해서 찾은 외국문서들에도 모두다 하나의 클래스로 구현하고 있었다. 굳이 왜 그래야하는지는 잘 모르겠지만 어쨌튼 나도 따로 클래스 하나로 구현했다.
SMTPAuthenticator.java의 코드이다.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 *
 */
package mailer;
 
import javax.mail.PasswordAuthentication;
import javax.mail.Authenticator;
 
/**
 * @author viper9
 *
 */
public class SMTPAuthenticator extends Authenticator {
    public SMTPAuthenticator() {
        super();
    }
 
    public PasswordAuthentication getPasswordAuthentication() {
        String username = "지메일주소";
        String password = "지메일암호";
        return new PasswordAuthentication(username, password);
    }
}
Authenticator를 상속받는 이러한 인증객체를 따로 구현한 다음 위 서블릿 클래스와 같은 디렉토리에 넣는다.
이 서블릿 클래스에 파라미터로 name, email, title, text 파라미터를 주면 설정된 메일 주소로 메일을 보내게 된다. 받는 사람도 따로 받고 싶다면 역시 파라미터로 받으면 그만이다.
난 서버의 거의 모든 부분에서 UTF-8 인코딩만 쓰고 있고 MySQL도 UTF-8을 기준으로 구성되어있으며 자바 작업을 할 때도 UTF-8을 선호하는 편이라 입력되는 파라미터들을 모두 UTF-8로 처리해주며 작업했다.
작업하며 어려웠던건 인증객체를 생성할 때 사용하는 SMTPAuthenticator 클래스를 만들 때였는데 이게 이클립스의 자동완성을 믿고 하다가 스펠링이 몇개 틀려서 한참 애먹었다. 내가 참조하던 그 문서도 아마 이 스펠링이 틀렸던 것으로 기억한다.Authentication와 Authenticator 이 스펠링이 틀리면 자바가 계속 암호가 틀린거 아니냐고 에러를 출력한다. 굉장히 짜증나던 부분.
여튼 한참 디버깅 끝에 오류를 잡고 나서 작동해보니 메일은 잘 오는데 몇가지 문제점이 있었다.
  1. 메일을 보내는 속도가 빠르지 않다. 외국서버라 그런지 메일을 보내고 나서 2~3초 후에 결과가 수신되는 것 같다. form의 submit 버튼을 사용자가 연속으로 누를 가능성이 있어서 이 부분을 조치를 취해야할 것 같다.
  2. 메일이 바로바로 오지 않는다. 바로바로 올때도 있는데 안 그럴 때도 꽤 많다. 메일을 보내고 나서 몇초 후에 도착하는 일이 꽤 많았다.
  3. 이걸 이용해서 메일을 받아보면 보낸 사람이 SMTPAuthenticator 클래스에서 설정한 그 지메일주소로 설정된다. 내가 따로 지정해주고 싶은데 아무리 해도 되질 않았다. Properties에서 설정이 가능할 것 같기도한데 찾지 못했다. (혹시 아시는 분은 답글 좀 부탁드립니다.)
위 3번 문제 때문에 이걸 이용해서 메일을 보내면 보낸 사람 정보를 알수 없어서 아예 메일 본문에다가 보낸 사람의 이름과 메일주소까지 넣도록 서블릿 클래스에 추가해야했다.
메일러의 메일주소를 따로 부여하기 위해 noreply@83rpm.com으로 메일 주소를 하나 만들어줬다. (지메일은 만들고 한번 로그인해야 비로고 SMTP 서버를 사용가능하다.)
이로써 사용자요청사항을 받는 페이지는 적절히 구현되었다. 이걸 정리해서 아예 메일 송신 전용 클래스로 만들어놔야겠다.

Java Character Set의 이해

현재 일하는 업무 상 String의 Code page를 변환해야 하는 작업이 많다.

하지만 이에 관한 자료들이 매우 미흡하며 잘못된 지식을 전달하는 블로그나 웹도 많이 보아왔다.
(처음에 그것이 잘못된 것인지도 몰랐지만)

그리고 믿고 사용했지만 여전히 깨져버리는 한글을 보며 고민하기도 했다.

사실 DB모니터링 툴 개발 업무를 하다보니 Character Set을 직접 변환해야 하는 작업들이 꽤 많았다.

Java에선 과연 어떤 형태로 변환작업을 수행할 수 있으면 읽을 수 있을까 고민도 했다.

이 글이 조금은 어려울 수도 있지만 천천히 읽어본다면 충분히 이해할 수 있고 명확하게 java의

캐릭터 셋에 대해 알 수 있을 것이다.

 영문은 대부분의 캐릭터셋이 1바이트기 때문에 변환작업에서 깨질일이 거의 없다고 할 수 있지만

한글은 utf-8의 경우 1에서 4바이트까지의 가변형으로 저장되기 때문에 1글자의 바이트 길이가 달라

명시적인 변환이 요구된다.

여기저기 자료도 많이 찾아봤지만 역시나 테스트 하는 것이 가장 빠르게 이해할 수 있었다.

결론 부터 언급하면 String 객체내 바이트 배열이 어떤 캐릭터셋으로 저장될 것이라는 생각이 오해를 불러왔고

잘못된 사고를 하게 했다. String이 실제 메모리 상에 어떠한 캐릭터셋의 바이트 배열로 저장되어 있는지

사용자들은 고민할 필요도 없이 자바는 잘 되어 있었다.

Jdk 1.4를 기준으로 내 머리의 이해를 도와봤다.

내 이름 한민호라는 세자를 utf-8로 byte 배열에 저장해보았다.

막무가내로 시작했지만 getBytes라는 함수가 어떻게 이용되는지 알 수 있었다.

byte [] bytes = new String("한민호").getBytes("utf-8");

처음 이 코드를 작성하고 "한민호"라는 객체는 어떤 캐릭터 셋의 바이트 배열로 저장되어 있을까 생각했지만

그것을 생각하면서 이미 정상적인 사고를 하기가 어려웠다. 자바에선 String 객체로 생성되었다면 어떠한 종류의

캐릭터 셋의 바이트 배열이든 리턴이 가능하기 때문이다.

단 한글로 생성했는데 한글을 지원하지 않는 캐릭터셋이라면 리턴한 바이트 배열의 값이 깨지게 된다.

 getBytes라는 메소드에 대해 전혀 알지 못했을 땐 String을 생성할 때 지정한 즉 메모리 상에 저장된

"한민호"라는 객체의 바이트 배열의 캐릭터 셋을 지정해줘야 되는 줄 알았다.

그러나 이 메소드가 내가 상상했던거 보다 훨씬 대단하다는 것을 알게 되었다.

 이 메소드는 현재 저장된 String값이 어떠한 캐릭터 셋으로 저장되든 상관없이

바이트 배열과 바이트 배열에 맞는 캐릭터 셋으로 생성만 했다면

java에서 한글을 지원(영문만 지원하는 캐릭터 셋은 깨지게 된다.) 하는

어떠한 캐릭터 셋으로든 변환하여 변환된 바이트 배열로 리턴한다.

 여기서 핵심은 정상적인 String 객체의 생성이라는 것이다. 아래서 정상적인 String 객체의 생성에 대해 알아보겠다.

 위에서 생성한 바이트 배열을 다시 String 객체로 변환해 보겠다.

String name = new String(bytes, "utf-8");

이런 식으로 변환이 가능하다. 간혹 String의 두 번째 파라메터로 넣는 캐릭터 셋을 바이트 배열의 캐릭터 셋이 아닌 다른

캐릭터셋을 넣어 변환하겠다는 코드를 많이 봤다. 이건 잘못된 것이다. 이 자린 바이트 배열에 저장된 바이트들의 캐릭터 셋을

설정하는 곳이다.

잘못된 변환의 사용예를 한가지 들어보겠다.

잘못된 변환
String convert = new String(message.getBytes("euc-kr"), "utf-8");

소스를 보면 message String에 저장된 문자를 getBytes를 이용하여 euc-kr라는 캐릭터셋 바이트 배열로 얻고있다.

분명 이것을 작성한 사람은 위 코드에서 new String(스트링배열, "euc-kr")라고 String 개체를 생성했을 것이다.

간단히 설명하면 euck-kr라는 캐릭터 셋으로 바이트 배열을 읽어들인 

다음 utf-8이라는 새로운 캐릭터셋으로 변환(?)을 시도하겠다는 것이다.

이것은 두 번째 파라메터에 대해 잘못된 이해를 하고 있기에 이런 코드가 가능한 것이다.

때문에 이렇게 변환을 하게 되면 한글이 저장된 경우 같은 계열(변환가능한)의 캐릭터 셋이 아니라면 100프로 깨지게 된다.

이러한 변환은 자바에서 지원을 안하는 사항이다.

또 문제는 다시 String 객체로 생성을 한다는 것이다. 이 때 String생성 시 잘못된 캐릭터 셋을 주었기 때문에 깨진

바이트 배열로 저장이 되게 된다. 때문에 이러한 경우 getBytes() 메소드를 통해 어떠한 캐릭터 셋으로 읽든

읽을 수가 없게 된다.

 그렇다면 이제까지의 사항들에 대해 테스트를 통해 명확히 알아보겠다.

아래는 테스트에 자주 사용될 byte 배열을 16진수로 보여주는 함수다.

public static String BinToHex(byte [] buf) {
  String res = "";
  String token = "";
  for (int ix=0; ix<buf.length; ix++) {
   token = Integer.toHexString(buf[ix]);
//   CommonUtil.println("[" + ix + "] token value : " + token + " len : " + token.length());
   if (token.length() >= 2)
    token = token.substring(token.length()-2);
   else {
    for(int jx=0; jx<2-token.length();jx++)
     token = "0" + token;
   }     
   res += " " + token;
  }
  
  return res.toUpperCase();
 }

< 테스트 소스 >
1   String name = new String("한민호");   
2   strs = name.getBytes();   
3   System.out.println("Length : " + strs.length);
4   System.out.println("Hex    : " + BinToHex(strs));
5   System.out.println("Value  : " + new String(strs));
6   System.out.println();   
7   strs = name.getBytes("utf-8");
8   System.out.println("Length : " + strs.length);
9   System.out.println("Hex    : " + BinToHex(strs));
10   System.out.println("Value  : " + new String(strs, "utf-8") );
11   System.out.println();   
12  name = new String(strs, "utf-8");
13   strs = name.getBytes();
14   System.out.println("Length : " + strs.length);
15   System.out.println("Hex    : " + BinToHex(strs));
16   System.out.println("Value  : " + name);   
17   System.out.println();   
18   String convert = new String(name.getBytes("euc-kr"), "utf-8");
19   System.out.println(convert);
20   strs = convert.getBytes();
21   System.out.println("Length : " + strs.length);
22   System.out.println("euc-kr Hex    : " + BinToHex(strs));
23   strs = convert.getBytes("utf-8");
24   System.out.println("Length : " + strs.length);
25   System.out.println("utf-8 Hex    : " + BinToHex(strs)); 
26   System.out.println();
27   System.out.println();

테스트 코드를 보며 이 결과들이 어떻게 나올것이라고 예측했는데 그것이 맞아떨어지지 않는다면

다시 글을 보면서 이해하면된다.

결과는 아래와 같다.

< 결과 >
Length : 6
Hex    :  C7 D1 B9 CE C8 A3
Value  : 한민호
Length : 9
Hex    :  ED 95 9C EB AF BC ED 98 B8
Value  : 한민호
Length : 6
Hex    :  C7 D1 B9 CE C8 A3
Value  : 한민호
????
Length : 4
euc-kr Hex    :  3F 3F 3F 3F
Length : 10
utf-8 Hex    :  EF BF BD D1 B9 EF BF BD C8 A3

1번 라인을 보면 한민호라는 String객체를 생성하고 있다

이 객체를 strs라는 바이트 배열에 getBytes()를 이용해서 받는다. getBytes에 아무 파라메터도 주지 않는다면

이것은 디폴트 캐릭터셋으로 바이트 배열이 리턴된다.

디폴트 캐릭터셋은 System.getProperty("file.encoding") 메소드를 통해 알수 있다.

이것을 utf-8로 저장하는 로직이 7번 라인이다. 단지 getBytes에 utf-8이란 파라메터를 주고 utf-8 캐릭터 셋의

바이트 배열을 받아 올 수 있다.

그리고 12번 라인은 이 utf-8로 저장된 바이트 배열을 다시 String객체로 파라메터 값으로 "utf-8"을 주고 생성한 것이다.

이때 파라메터를 주지 않거나(디폴트 파라메터가 지정됨) 다른 캐릭터셋을 준다면 깨지게 된다.

마지막으로 18번 라인은 잘못된 컨버팅 예이다. euc-kr을 utf-8로 변환하겠다는 건데

위에서 설명했듯 이러한 변환 때문에 바이트 배열이 깨져서 euc-kr이든 utf-8이든 어떠한 바이트 배열로 읽어오든

깨져있는 것을 확인할 수 있다. 이미 깨져서 생성된 String 객체의 바이트 배열은 어떻게든 복구가 불가능 하다.

<결론>
 이야기를 종합해보면 String 객체로 생성된 것을 다른 캐릭터 셋의 String 객체로 변환한다는 것은

어불성설인 것이다. 이러한 변환은 무지에서 나오는 것이며 String 객체에 이미 깨진 내용은 어떠한 변환이 있더라도

정상적인 출력이 불가능하다는 것이다. 캐릭터셋을 포함하여 관리하겠다면 철저하게 바이트 배열을 이용해야 한다.

그리고 String에 어떤 캐릭터 셋으로 저장되어 있는지에 대해 논하는 것은 애초부터 잘못된 것이다.

자바 new String() 시 초보들이 하기 쉬운 실수...

캐릭터셋 변환에 대해 인터넷 블로그 등에 잘못 떠돌고 있는 괴담(?) 은아니고 괴코드(?) 가 있다.

// 예상과는 다른 동작을 하는 코드
String convert = new String(message.getBytes("euc-kr"), "utf-8");

이건 잘못된 API의 이해가 부른 오동작 코드 이다.

String::getBytes 는 자바 내부에 관리되는 유니코드 문자열을 인자로 지정된 캐릭터셋의 바이트 배열로 반환하는 메서드이며,
new String(바이트배열, 캐릭터셋) 생성자는 해당 바이트 배열을 주어진 캐릭터 셋으로 간주 하여 스트링을 만드는 생성자이다.

다음 예제를 보자

String d = "안녕 親9"; // 자바는 내부 문자열을 모두 유니코드 처리한다
  
// 유니코드 문자열을 UTF-8 캐릭터 바이트배열로 변환하여 반환
byte [] utf8 = d.getBytes("UTF-8");

// 유니코드 문자열을 EUC-KR 캐릭터 바이트배열로 변환하여 반환
byte [] euckr = d.getBytes("EUC-KR");
  
// 당연히 다른 바이트 배열이므로 사이즈가 다르다.
System.out.println("byte length > " + utf8.length); // byte length > 11
System.out.println("byte length > " + euckr.length); // byte length > 8
  
// 실수 코드.
// UTF-8 캐릭터셋으로 배열된 바이트배열을 EUC-KR로 해석할 수 없다.
System.out.println(new String(utf8, "EUC-KR"));

절대 캐릭터 변환이랍시고 new String(바이트배열, 변환하고싶은 희망사항 캐릭터셋) 을 쓰는 오류는 범하지 말자.

자바 내부에서 처리하는 문자열은 일괄적으로 같은 유니코드 형식으로 처리되며, 
이기종 전송 등 필요한 경우에는 getBytes()로 해당 문자열 바이트 배열로 변환 뒤 전송하면 그만일 것이다.

다만 예전 구형 웹서버등을 사용한 프로젝트의 경우의 문자열을 원래 캐릭터로 복구하는 코드가 위의 new String 을 쓰는 경우가 있는데,
이건 웹 서버에서 캐릭터셋을 잘못 해석하여 주는 것을 바로잡는 코드이거나, 비슷한 캐릭터 코드에서 코드로 해석한 것이며, 캐릭터 셋 변환이 아님을 알아두자.


좋은 참고 글 하나 링크한다.

Java Character Set의 이해

Java Encoding / Decoding (자바 인코딩 / 디코딩)

문제:
얼마 전 디비 서버 변경 작업이 있었는데
서버 변경 이후로 인터페이스 되는 데이터들 중 한글이 깨지는 것들이 발견됐다.

각 데이터들은 여러 방법으로 인터페이스 된다.
  - 파일로 전송 받아 디비에 업데이트
  - 특정 모듈을 통해 대상 서버에 접속해 그 내용을 파일로 떨군 후에 업데이트
  - 대상 디비로 직접 접근해서 가져온 후 업데이트

unix 서버에서 crontab 에 등록된 쉘 프로그램이
인터페이스 역할을 하는 java 클래스를 실행시켜 가져온 데이터를 디비로 업로드한다.

헌데,.. 하나를 수정하면 하나가 깨지고, 다른 하나를 수정하면 또 기존 것이 깨지고 이런다....

뭐가 문제인 걸까...

해결책:
전체적으로 인코딩/디코딩에 대한 이해가 필요했다.
여러 자료들을 찾아보고 테스트해 본 결과, (무려 3일 동안이나... =_=;;)
인코딩/디코딩의 전체적인 맥락을 알게 됐다.
또한 그 흐름을 알면 개발자가 따로 코딩할 부분이 거의 없다는 것도...
좀 정리를 해볼까.


간단한 결론.
input 으로 가져오려는 데이터의 캐릭터셋과 동일한 캐릭터셋으로 데이터를 가져와야 한다.
또한 출력해서 보여주려는 환경의 캐릭터셋과 동일하게 인코딩 해줘야 한다.

또한, Java 는 내부적으로 데이터를 Unicode 로 변환하여 처리하기 때문에,
처음 내용을 가져올 때 제대로 된 캐릭터셋으로 가져온다면 어떠한 캐릭터셋으로든 변환하여 출력이 가능하다.
'인코딩 변환'을 키워드로 검색으로 했을 때 쉽게 검색되는 new String(str.getBytes(), "UFT-8"); 과 같은 것은
이와 같이 정상적으로 데이터를 읽어온 이후에 사용되어야 한다.

자 그럼, 일단 한글이 깨진다면 '어떻게 하지?' 라고 생각하기 이전에 아래 목록에 대해 스스로에게 질문해보자.


1. 콘솔에서 문자가 깨질 때
  - 자바 소스 파일의 인코딩이 무엇인가?
  - 자바를 실행하는 환경의 인코딩은 무엇인가? (언어 설정)

  [도움말]
     자바 소스 내에 한글이 있고 이 내용을 콘솔에서 출력하려고 할 때에는,
     자바 소스 파일의 인코딩과 컴파일된 파일이 실행되는 환경의 인코딩이 동일해야 한다.
     기본적으로 자바는 OS 환경에 설정되어 있는 기본 인코딩으로 동작하며,
     실행 환경의 인코딩을 따로 설정해주기 위해서는 file.encoding 옵션을 사용하면 된다.

  [도움이 될 만한 포스트]     
     자바서비스넷: 자바 컴파일 환경의 인코딩과 실행 환경의 인코딩 테스트
     유닉스에서 언어 설정 방법
     자바 실행 환경 인코딩 설정 방법: -Dfile.encoding 의 역할
     자바에서 한글 처리 문제 - 아직도냐...

2. 읽어온 파일의 문자가 깨질 때
  - 읽어오려는 파일은 어떤 캐릭터셋으로 인코딩되어 있는가?
  - 자바를 실행하는 환경의 인코딩은 무엇인가?

  [도움말]
     일단 파일을 읽어오기 전에 읽어오려는 파일이 어떤 캐릭터셋으로 인코딩 되어 있는 지 알아야 한다.
     읽어오려는 파일의 인코딩과 동일한 캐릭터셋으로 파일을 읽어와야 하며,
     자바의 FileReader 와 같은 input 객체들은 특별한 설정이 없으면 실행 환경(file.encoding)의 캐릭터셋으로 읽어온다.
     실행 환경의 캐릭터셋과 읽어오려는 파일의 인코딩이 달라 특정 캐릭터셋으로 읽어오려면
     캐릭터셋 설정이 가능한 InputStreamReader 객체를 사용하도록 한다.
     (만약 설정한 캐릭터셋으로 파일을 읽어올 수 없을 경우 MalformedInputException 이 발생한다.)
     출력할 때도 마찬가지로 OutputStreamReader를 통해 원하는 캐릭터셋으로 출력할 수 있다.

  [도움이 될 만한 포스트]
     읽어올 파일의 인코딩 확인 방법(영문)
         또는, EditPlus, UltraEdit 같은 에디터를 통해 인코딩을 바꿔가며 확인해보는 것도 좋겠다.
     InputStreamReader API  /  OutputStreamWriter API
     sun.io.MalformedInputException 이 발생할 경우
     특정 캐릭터셋으로 읽어오는 방법 (InputStreamReader)
     UTF-8 환경에서도 인코딩이 깨졌다는 이야기
  

3. 디비에 문자가 깨질 때
  - DB 에 넣으려는 문자의 캐릭터셋은 무엇인가?
  - DB 의 인코딩은 어떤 캐릭터셋으로 설정되어 있는가?

  [도움말]
     다른 경우와 마찬가지다. Java에서 DB 에 보내는 캐릭터셋과 DB 에 설정되어 있는 캐릭터셋이 동일해야 한다.
     만약 Java 와 DB 의 캐릭터셋이 다르다면, 서버의 설정을 변경하거나 new String() 을 쓰는 방법으로 해결한다.
   
  [도움이 될 만한 포스트]
     DB 인코딩 설정으로 구글링
     PreparedStatement 설정 시 new String() 으로 인코딩을 변경하여 설정


4. 웹 페이지의 문자가 깨질 때
  - 요청된 (request로 들어오는) 내용은 어떤 캐릭터셋으로 인코딩 되어 있는가?
  - 응답으로 주는 페이지의 인코딩은 무엇인가?

  [도움말]
      역시 마찬가지!
      request 로 들어오는 요청의 캐릭터셋을 파악하고 그에 맞게 처리해줘야 한다.
      응답으로 뿌려줄 때도 마찬가지이다.
      이 내용은 검색하면 많이 나오므로 쉽게 찾을 수 있을 듯.
      잘 모를 경우 아래 키워드로 검색해보세요.
         request.setCharacterEncoding
         jsp 헤더, html 헤더에서 contentType 설정
         javascript encodeURI, encodeURL

  [도움이 될 만한 포스트]
      서블릿 엔진에서의 인코딩(자바서비스넷)



그 외 기타 도움이 될 만한 포스트
    Java 와 Charset (추천)
    유니코드(Unicode)에 대한 오해
    Character Set 과 Encodingn 이해하기
    Java Characterset 의 이해 (지식인)
    다국적 인코딩 문제의 위치 및 까발리기 (자바서비스넷) : 길지만 슥~ 읽어보면 좋음



이제, 한글 깨짐 문제에 봉착해도 문제 없이 해결하기를!!!

JSP 메일 보내기.

먼저 메일 서버가 구축되어야 합니다. 
다음 포스팅을 참조 하세요.

 James 메일 서버 구축하기.

i
f 모두 다 햇는데 다음과 같은 에러 화면이 뜬다면 100% 메일 서버가 구축이 않되잇는겁니다. -_-
else if 방화벽 포트를 25번 포트를 열어주세요. 그래도 않된다면..A/S 신청을;;;
(javax.servlet.ServletException: Could not connect to SMTP host: localhost, port: 25)
 

메일 서버가 구축이 되었다면 이제 메일 라이블러리를 통해서 간편하게 메일을 전송할수 있는 JSP파일을 만들어봅니다.

 
1. 라이블러리 다운~
   JAF(JavaBeans Activation Framework) 와  JavaMail 이 필요합니다.
   JAF 다운로드 :  http://www.oracle.com/technetwork/java/jaf11-139815.html
 
  javaMail 다운로드 : http://www.oracle.com/technetwork/java/index-138643.html
2. 다운로드 받은 파일들의 압축을 풀면 mail.jar 파일과 activation.jar 파일이 있습니다.
이 파일들을 Tomcat 의  common\lib 폴더로 복사합니다.(사실 어디에 있든 상관은 없지만 Tomcat 의 lib폴더는 서버 시작시 모든 라이블러리를 읽어오기 때문에 이곳에 두는게 가장 간편합니다.)
이클립스에서 해당 파일들을 Build Path를 잡아둡니다.
해당 프로젝트 폴더를 클릭하고 오른쪽 마우스 -> Properties 를 클릭합니다.

3. Java Build Path -> Libaraies -> Add External JARs.. 를 클릭합니다.

4. 복사해둔 폴더에서의 가져올 activation.jar, mail.jar 파일을 클릭하고열기!
 
5. 혹시 모르니 Order and Export 탭 를 선택하고 가져온 라이블러리를 위로 살짝 땡겨 줍니다.(up 버튼을 눌러주면 됩니다.) 참고로 상위단에 있을수록 가장 먼저 참고하게 됩니다.

기본 설정은 끝. 이제 JSP 파일만 작성해 주면 됩니다.
6. SendEmail.jsp

 <%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>

<%@ page import="java.util.*,java.io.*,javax.mail.*,javax.mail.internet.*,javax.activation.*" %>

<%
String subject = request.getParameter("subject")==null?"":request.getParameter("subject");
String content = request.getParameter("content")==null?"":request.getParameter("content");
String from = request.getParameter("from")==null?"":request.getParameter("from"); //보내는 사람
String to = request.getParameter("to")==null?"":request.getParameter("to"); //받는 사람 


 if(from.equals("") || to.equals("") || content.equals("") || subject.equals("")){
     System.out.println("메일 전송 실패");
 }else{
 // 프로퍼티 값 인스턴스 생성과 기본세션(SMTP 서버 호스트 지정)
 Properties props = new Properties();
 Session sess= Session.getDefaultInstance(props, null);
 Message msg = new MimeMessage(sess);
 msg.setFrom(new InternetAddress(from));//보내는 사람 설정
 InternetAddress address = new InternetAddress(to);
 msg.setRecipient(Message.RecipientType.TO, address);//받는 사람설정
 msg.setSubject(subject);//제목 설정
 msg.setSentDate(new java.util.Date());//보내는 날짜 설정
 msg.setContent(content,"text/html;charset=euc-kr"); // 내용 설정 (HTML 형식)
 Transport.send(msg);//메일 보내기
 }
%>


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
<title>메일 보내기 TEST</title>
</head>
<body>
<form action="sendEmail.jsp">
제목 :        <input type="text" width="20px" name="subject"/><br>
보내는 사람 : <input type="text" width="20px" name="from"/><br>
받는   사람 : <input type="text" width="20px" name="to"/><br>
내용<br>
 <textarea rows="5" cols="30" name="content"></textarea>
  <input type="submit" name="button" value="전송">
</form>
</body>
</html>
 
7. 설치한 mail 서버를 실행한 상태에서 전송 버튼을 눌르셔야 합니다. 아니면 에러가 납니다.
8. 메일이 잘 왔는지 확인~!
 

오호! 잘왔습니다! ㅋㅋ 이제 메일 주소를 잘 넣었는지. 스크립트는 않들어갔는지 등등 파일 전송도 있겠군요. 
여러명에게 전송하는 것도 필요하고요. 메일의 기본적인 걸 만들려면 아직도 멀었네요.
가장 기초적인 전송부분만 제공되는 라이블러리를 통해서 만들어보았습니다.

James 메일 서버 구축하기.

Java Apache Mail Enterprise Server 의 약자인 James !! (미국 남자의 아주 흔한 이름과 같다)
간단한 설치 및 구성으로 메일 서버를 구축 할수 있는 장점이 있다.

홈페이지를 둘러보니 재미있는 프로젝트 들이 많이있다. 흠.. 영어가 딸려서 정확히는 모르겠지만 뭐 나름 눈여겨 볼만한 것들이 있다. 

James 홈페이지 방문하기

1. 일단 서버 구성을 필요한 파일을 다운 받아야 한다.

파일 다운로드  

경로가 한글이 들어가면 않됩니다
. 그래서 전 C:에 압축을 풀었습니다. 



현재 상태에서는 기본적인 구성이 이루어지지 않았습니다.
기본적인 설정 파일을 생성하기 위해선 실행을 한번 해줘야 합니다. 
(james-2.2.0\apps 폴더 아래에 여러 환경설정 파일이 있어야 합니다. )

2. 실행 -> cmd ->  cd c:\james-2.2.0 ->  cd bin 

run.bat 파일을 실행 합니다.



3.  컴파일이 되면서 설정 폴더와 파일들이 생성됩니다.
 아래와 같이 나오면 성공!

4. 기본 설정을 하기 위해서 config.xml 을 편집해야 합니다.
 파일의 위치는 C:\james-2.2.0\apps\james\SAR-INF 의 폴더에 있습니다. 


5.먼저 관리자의 암호를 바꿉니다. (ID도 바꿀수 있습니다.)
찾기 힘드시니 password 키워드로 찾기하시기바랍니다. (저의 경우 498라인에 있었습니다.)
간단히 ID 와 password 를 바꿀수 있습니다. 콘솔에서 사용자 편집을 할때 사용하지만 저는 필요가 없어서 패스.

6. 자신의 IP와 DNS정보를 넣어줍니다.
   <dnsserver> <servers> 안에 자신의 IP 와 DNS 정보를 넣어줍니다.
    태그는 <server>로 해주시면 됩니다.
 (당연히 저의 IP는 가렸습니다;; )

 7. 모든 설정은 완료 하였습니다. 이제 다시 run.bat을 통해서 서버를 실행합니다.

8. 서버의 종료는 ctrl+c 키를 눌러주면 종료 됩니다. 종료 할꺼냐는 물음이 나오면 Y로 하시면 종료 됩니다.

서버의 구성은 모두완료 하였습니다. 이제 JSP로 정상적으로 메일이 가는지 확인해봐야겠쬬?