반응형
V. 메일 수신 예제
=================

 이 장에서는 JavaMail API를 이용한 메시지 수신 예제인 MailReciever.java에 대하여 다루겠다.
MailReciever는 간단하게 POP3를 이용하여 수신함에서 메일 메시지를 가져다가 local disk에
저장하는 프로그램이다.  전체 코드는 MailReciever.java 화일로 첨부해놓았으며, 여기서는
주로 전체적인 기능 및 부분 코드로 설명하겠다.

  MailReciever를 실행 시키려면 우선 다음과 같이 메일 호스트, 사용자 아이디, 암호 그리고
메일을 저장할 디렉토리이름을 main 메소드 에서 변경하여 바꾼 후에 컴파일 하여 실행 시키면 된다.

MailReciever reciever = new MailReciever();
reciever.setHost("www.xxxx.yyyy");
reciever.setUser("aaaa");
reciever.setPassword("bbbb");
reciever.setHomeDir("c:/mail");
        
reciever.recieve();


1. constructor
-----------------------------------------

  MailReciever의 Constructor에서는 우선 다음과 같이 메일 처리를 위한 Session 객체를 
생성하고 있다. Session 객체는 member variable인 session에 대입하여 놓는다.

Properties props = System.getProperties();
session = Session.getDefaultInstance(props, null);


2. recieve()
-----------------------------------------
  실제 메일을 수신하는 메소드는 recieve() 메소드 이다.  recieve() 메소드는 session으로 부터
pop3 Store 객체를 생하고 INBOX folder를 생성하여 message를 수신한다.

......

// Get folder
Folder folder = store.getFolder("INBOX");
    
// Open read-write
folder.open(Folder.READ_WRITE);
    
// Get message
Message message[] = folder.getMessages();
......

메일을 목록을 저장할 index화일과 메시지들을 저장할 디렉토리를 생성한 후에 
각각의 메시지들을 processMessage() 메소드를 호출하여 처리한다.

// index open
File indexFile = File.createTempFile("list", ".idx", new File(getHomeDir()));
FileOutputStream fout = new FileOutputStream(indexFile.getPath(), true);
indexWriter = new BufferedWriter(new OutputStreamWriter(fout));
        
// make folder
mailFolderName = getHomeDir() + "/" + indexFile.getName() + ".dir";
File mailFolder = new File(mailFolderName);
mailFolder.mkdir();
        
// process message
for(int i=0; i < message.length; i++) {
    processMessage(message[i]);
}

.......


3. processMessage()
---------------------------------------
  processMessage() 메소드는 각각의 메시지에 대한 발신자, 제목, 발신일, 저장할 폴더 등
목록에 해당하는 내용을 만든다.

.............

 // from
 sarr = message.getHeader("From");
 if(sarr == null || sarr.length == 0) {
   from = "";
 }
 else {
  from = decodeString(sarr[0]);
 }
            
 // subject
 sarr = message.getHeader("Subject");
 if(sarr == null || sarr.length == 0) {
   subject = "";
 }
 else {
   subject = decodeString(sarr[0]);
  }
            
// send date
sendDate = message.getSentDate();

.................


  메시지의 내용을 저장할 message+msgIndex 디렉토리를 만든다.  메시지의 내용과 
첨부 화일을 저장해야 함으로 내용을 저장할 $content.message 화일은 미리 open해 놓는다.
message는 여러부분의 Part로 구성될 수 있으므로 processPart 메소드를 호출하여 처리한다.
메시지를 처리한 후에 index 부분을 write한다.

        
// open content writer
FileOutputStream fout = new FileOutputStream(msgFolderName + "/" + "$content.message");
contentWriter = new BufferedWriter(new OutputStreamWriter(fout));
        
//process part
System.out.println(message.getSubject());
processPart(message);        
        
// header white
indexWriter.write(from + "\t");
indexWriter.write(subject + "\t");
indexWriter.write(sendDate +"\t");
indexWriter.write(msgFolderName + "\t");
indexWriter.write(appendCount + "\n");

// close content writer
contentWriter.close();


4. processPart()
--------------------------------------
  processPart()는 우선 Part의 내용을 화일로 저장할지 아니면 메시지 내용으로 write할 지를
결정한다.

String fileName = part.getFileName();
String disposition = part.getDisposition();
boolean isAttachement = false;
        
if (fileName != null || disposition != null) {
  isAttachement = true;
}

......

  mime type에 따라 Part의 내용을 처리하되 "text/*" 일 경우는 내용 저장, "multipart/*" 일 경우는
이를 다시 Part로 나누어 재귀적으로 processPart() 메소드를 호출, 그리고 "message/rfc822" 일 경우는
다시 processPart()를 다시 호출 한다.  나머지 경우는 내용을 저장한다.


if (part.isMimeType("text/*")) {
    
  if(isAttachement) {
    saveFile(fileName, part.getContent());
    appendCount++;
  }
  else {
    saveContent(part.getContent());
  }
                            
} else if (part.isMimeType("multipart/*")) {
            
  Multipart mp = (Multipart)part.getContent();        
  for (int i = 0; i < mp.getCount(); i++) {
    processPart(mp.getBodyPart(i));
  }
    
} else if (part.isMimeType("message/rfc822")) {
            
  processPart((Part)part.getContent());
            
} else {
            
  Object obj = part.getContent();
        
  if(isAttachement) {
    saveFile(fileName, obj);
    appendCount++;                
  }
  else {
    saveContent(obj);
  }

}


5. saveFile()
--------------------------------------
  화일이름과 내용을 담은 Object를 주면 중복되지않게 화일 이름에 일련번호를 붙여
내용을 저장한다.  내용 Object 가 InputStream일 경우는 stream을 읽어 내용을 저장하고
아닐 경우는 String으로 만들어 저장한다.


6. saveContent()
--------------------------------------
  내용을 담은 Object를 받아들여 이미 열려있는 contentWriter에 내용을 write한다.
내용 Object 가 InputStream일 경우는 stream을 읽어 내용을 저장하고
아닐 경우는 String으로 만들어 저장한다.


7. 기타 methods
--------------------------------------
  toHangle 메소드는 문자열을 한글체계로 바꾸는 메소드이고, decodeString은
메일 메시지의 헤데 문자열이 encoding되어 있을 때 MimeUtility를 이용하여
decode 하는 메소드이다.  searchPattern은 encoding되어 있는 문자열을 구분해내는
역할을 한다. 
반응형
IV. 메일 메시지 수신
======================

  메일 메시지의 수신은 우선 POP3나 IMAP은 지원하는 메일 서버가 있다는 전제로
될 수 있다.  그리고 메일서버에 메일을 수신할 수 있는 계정이 만들어져있고
수신함 즉 "INBOX"가 존재하여 타인이 메일 서버로 메시지를 보낼 때 이 수신함에
그 메시지가 저장이 되어야 한다. 

  이러한 환경이 설정되어 있어야 JavaMail API를 이용하여 메일 서버의 수신함에
저장된 메시지를 가져올 수 있다.  메일 서버와 통신을 하여 수신함에서 수신된 
메시지를 꺼내올 수 있는 실제 클레스들은 기본적으로 com.sun.mail.imap 과 
com.sun.mail.pop3 패키지로 제공되는데 만일 다른 제공자의 클레스를 사용하고자 
한다면 Session의 프로퍼티를 설정해야 한다.

  다음은 POP3 프로토콜을 이용하여 수신함에서 메시지를 가져와 발신자와
제목을 화면에 보여주는 예제이다.


String server = "www.xxx.zzz";
String user   = "aaaa";
String pass   = "bbbb";

// 프로퍼티 
Properties props = new Properties();

// 세션 객체 생성 
Session session = Session.getDefaultInstance(props, null);

// POP3 Store 연결
Store store = session.getStore("pop3");
store.connect(server, user, pass);

// INBOX Folder 가져옮
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);

// 수신된 메시지들을 Folder에서 가져옮
Message message[] = folder.getMessages();

for (int i=0, n=message.length; i<n; i++) {
   System.out.println(i + ": " + message[i].getFrom()[0] 
     + "\t" + message[i].getSubject());
}

// 연결을 끊는다.
folder.close(false);
store.close();
  
  위의 예에서 보듯이 POP3를 지원하는 메일 서버 주소, 아이디, 패스워드만 있으면
Stroe 객체를 통하여 서버와 연결하여 Folder객에체서 Message 객체들을 얻어와 메일을 수신할 
수 있다.  Message 객체는 메일을 구성하는 수신자, 발신자, 제목, 내용등이 모두들어
있다.

  Message객체에서 getFrom 메소드를 호출하면 발신자 경로를 추적할 수 있는 Address
Array를 받을 수 있다.  최종 발신자는 0 번째에 있다.  메일 메시지의 제목을 얻으려면
Message의 getSubject 메소드를 호출하면 된다.  이러한 수신자, 발신자 또는 제목 등은
메일은 헤더에 해당하는데, 이는 getHeader 메소드를 통하면 원본 String을 얻을 수 있다.

  String from = message.getHeader("From");
  String subject = message.getHeader("Subject");

위의 예제에서 원본 String은 MIME 규칙에 의해 Encoding되어 있는 경우가 있는데 이럴때는
java.mail.internet.MimeUtility의 decodeWord 메소드를 이용하여 원본을 복구해내야 한다.

  메시지의 내용을 받는 방법은 기본적으로 Message 클레스의 getContent 메소드를 이용하도록
되어 있다.  getContent 메소드는 Part 인터페이드에 정의 되어 있다.  하지만 메시지의
내용이 어떤형태로 구성되어 있는냐를 고려해야 한다.  

  메시지 내용의 MIME type이 "text/plain" 또는 "text/html"일 경우는 비교적 간단하게
처리할 수 있다.  getContent로 얻은 객체를 toString 메소드를 이용하여 문자열로 받을 수도
있고 또다를 방법은 getText 메소드를 이용하여 내용을 문자열로 받을 수 있다.  
내용이 어떠한 MIME TYPE을 하고 있는지는 isMimeType 메소드로 비교할 수 있다. 다음은
text로 구성된 메시지의 내용을 받은 예제이다.

  Message[] message = folder.getMessages();

  for(int i=0; i < message.length; i++) {

    if(message[i].isMimetype("text/*")) {
      String content = message[i].getContent().toString();
      System.out.println(content);
    }
    else if(message[i].isMimeType("multipart/*)) {
      ...
    }
    else {
      ...
    }

  }

 메시지의 내용이 첨부화일을 갖는 경우는 보통 MIME type이 "multipart" 로 되어 있다.
이러한 경우는 Message 객체 의 content를  Multipart 객체로 casting해야 처리할 수 있다.
Multipart는 여러가지 Part가 모여있는 형태임으로 각각의 BodyPart를 추출하여 기본 내용 
텍스트로 쓸 것인지 첨부화일로 쓸 것인지를 판단한다.

Part의 내용을 처리하는 기준은 getDisposition 함수를 이용하여 얻을 수 있는데 비교할 수 있는
Constants는 Part에 있는 Part.ATTACHMENT 와 Part.INLINE이다.  Part.ATTACHMENT는 첨부
화일이라는 의미이고 Part.INLINE은 내용을 화면에 표시한다는 의미이다. 다음은 본문은
화면에 표시하고 첨부는 화일에 저장하는 예제이다.

// 메시지가 multipart인 경우
if(message.isMimeType("multipart/*")) {

  // 내용을 Multipart로 만듬
  Multipart mp = (Multipart)message.getContent();

  // 각각의 part를 순서데로 처리
  for (int i=0, n=multipart.getCount(); i<n; i++) {

    // 처리될 body part
    Part part = multipart.getBodyPart(i));

    // 처리 기준을 가져옴
    String disposition = part.getDisposition();
    String filename    = part.getFileName();
    
    // disposition이 Attachment이거나 file name이 있으면 저장
    if((disposition != null && disposition.equals(Part.ATTACHMENT)) ||
       (filename != null)) {
      saveFile(filename, part.getInputStream());
    }
    // 본문 처리
    else {
      .......
    }

  }
}

else {
  ....
}

위의 예제는 Message의 MIME type이 "multipart/*" 일 때 처리하는 로직이며
saveFile 메소드는 이미 있다고 가정한다.  그리고 본문처리 로직은
적절히 화면이 보여주거나 다른 곳이 저장한다고 가정한다.

POP3 프로토콜을 수신함에 있는 메시지를 삭제하는 기능도 정의하고 있다.
따라서 다음과 같이 수신함에 있는 메시지를 받아온 다음 수신함에서 해당 메시지를 
삭제할 수도 있다.

메시지 삭제는 다음과 같은 메시지 상태 flag를 바꾸어줌으로서 가능한데 각가의 flag는
Flags 클레스에 정의되어 있다.

- Flags.Flag.ANSWERED 
- Flags.Flag.DELETED 
- Flags.Flag.DRAFT 
- Flags.Flag.FLAGGED 
- Flags.Flag.RECENT 
- Flags.Flag.SEEN 
- Flags.Flag.USER 

위의 flag들은 모든 메일 서버가 지원하는 것은 아니다.  각각의 메일 서버마다 지원하는
flag가 있고 지원하지 않는 flag가 있다.  그리고 POP3 프로토콜에서는 DELETED flag만을
지원하도록 되어 있다. 어떠한 flag들이 지원되는지 알려면 Folder 객체의 getPermanentFlags()
메소드를 이용하면 된다.

메시지의 삭제 방법은 우선 Folder 객체를 READ/WRITE 형으로 연 다음  Message 객체의 flag을 
DELETED 상태로 바꾼다.

folder.open(Folder.READ_WRITE);
.......
message.setFlag(Flags.Flag.DELETED, true);

실제 메시지가 삭제되는 시점은 folder를 close하는 시점인데 다음과 같이 파라미터에 true를
준다.

folder.close(true);

메시지를 수신하는 전반적인 방법은 다음 장에 Reciever.java 예제로 다시한번 설명하겠다. 
반응형
III. 메일 메시지 발신
======================

  메일 메시지를 보내려면 우선 보낼 서버, 수신자, 발신자, 제목, 내용 등이 필요한다.
우선 서버의 지정은 Session을 생성할 때 넘겨주는 Properties에 "mail.smtp.host" 라는
키로 서버 주소를 지정함으로서 SMTP 전송을 처리하는 메일 서버를 지정한다.

  수신자, 발신자, 제목, 내용 등을 지정하려면 우선 이들을 하나의 메시지에 묶는
Message 객체가 필요한데, Message는 추상 클레스이므로 javax.mail.internet 패키지에
있는 MimeMessage를 이용한다.

  발신자는 Message 객체의 setFrom 메소드로 지정하되 InternetAddress 객체를 생성하여
지정한다.  수신자는 Message 객체의 addRecipient 메소드로 지정하되 Message.RecipientType
을 참고하여 지정한다.

  최종적 전송은 Transport 클레스의 static 메소드인 send로 한다.  다음은 일반 텍스트를 
SMTP를 이용하여 간단히 전송하는 예제이다.


String host = "xxx.zzz.com";
String from = "hong@xxx.zzz";
String to = "chun@aaa.bbb";

// 서버 프로퍼티 설정 
Properties props = System.getProperties();
props.put("mail.smtp.host", host);

// Session 객체 생성
Session session = Session.getDefaultInstance(props, null);

// 메시지 생성
MimeMessage message = new MimeMessage(session);
InternetAddress addr = new InternetAddress(from, "홍길동", "euc-kr");

message.setFrom(addr);
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("Test Mail");
message.setText("This is a Test Mail");

// 메시지 전송 
Transport.send(message);


  메일 내용을 HTML로 보내려면 Message 객체의 setContent 메소드를 사용하면 된다. setContent
메소드는 Part인터페이스에 정의되어 있는데 Message 클레스는 이를 Implement하도록 되어 있다.
다음은 message 객체에 "text/html" 형식으로 내용을 주는 예제이다.


String content = "<FONT COLOR=BLUE SIZE=20> 안녕하십니까 ? " +
                 "<A HREF=WWW.JAVANURI.COM> 자바누리입니다. </A> </FONT>";
message.setContent(content, "text/html; charset=\"euc-kr\"");


  첨부 화일을 보내는 경우는 Message의 내용을 MultiPart 객체로 설정해야 한다.  MultiPart 클레스는
메시지 내용을 여러가지 부분(BodyPart)으로 구성할 수 있게 해준다.  각각의 내용부분의 BodyPart와
화일부분의 BodyPart를 생성한 후에 이들을 MultiPart에 추가하여 하나의 메시지가되도록 병합한다.
이 MultiPart를 Message의 content로 설정한 후 전송하면 여러개의 첨부화일을 간단히 처리할 수 있다.

  JavaMail API에서 화일을 처리할 경우 FileDataSource, DataHandler 등의 JAF 패키지 클레스들을
사용하게되는데 이는 JAF(JAVABEANS ACTIVATION FRAMEWORK)의 표준 자료 처리방식을 지원하는 클레스들
이다. 

  다음은 MultiPart 및 BodyPart를 구현하는 구체적 클레스 MimeMultiPart, MimeBodyPart를 이용하여
첨부 화일을 전송하는 예제이다.

SendAttachement.java
==============================================================================================
import java.io.*;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;

public class SendAttachement {

  //-------------------------------------------
  // main
  //-------------------------------------------
  public static void main (String args[]) throws Exception {

    // 메일서버, 수신자, 발신자, 화일이름
    String host = "www.xxx.com";
    String from = "aaa@javanuri.com";
    String to = "zzz@javanuri.com";      
    String fileName = "mydoc.txt";
      
    // 프로퍼티 설정
    Properties props = new Properties();
    props.put("mail.smtp.host", host); 
  
    // Session 생성
    Session session = Session.getDefaultInstance(props, null);

    // MimeMessage 메시지 생성
    MimeMessage message = new MimeMessage(session);
    InternetAddress addr = new InternetAddress(from, "홍길동", "euc-kr");        
    message.setFrom(addr);
    message.setSubject("첨부 테스트 메일");        
    message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));

    // Multi Part 생성
    Multipart multipart = new MimeMultipart();        
        
    // 메시지 BodyPart 생성 및 Mutil Part에 추가 
    BodyPart messageBodyPart = new MimeBodyPart();
    messageBodyPart.setText("테스트 첨부화일 입니다.");    
    multipart.addBodyPart(messageBodyPart);

    // 첨부 화일 BodyPart 생성 및 Multi Part에 추가 
    BodyPart fileBodyPart = new MimeBodyPart();
    DataSource source = new FileDataSource(fileName);
    fileBodyPart.setDataHandler(new DataHandler(source));
    fileBodyPart.setFileName(fileName);
    multipart.addBodyPart(fileBodyPart);
        
    // Multi Part 메시지에 content로 설정
    message.setContent(multipart);

    // 메일 메시지 전송
    Transport.send(message);
    

  }
   
}
반응형

참고

http://java.sun.com/products/javamail/index.jsp
http://java.sun.com/javase/technologies/desktop/javabeans/glasgow/jaf.html
http://java.sun.com/developer/onlineTraining/JavaMail/contents.html

http://java.sun.com/products/javamail/javadocs/index.html
관련 시리즈
http://javacan.madvirus.net/main/search/search.tle?type=all&keyword=mail

주로 작성된 메일을 송신하고 수신하는 API를 제공하는 것이다. 이러한 기능은

메일 서버의 역할과는 구별되는 것이다.


메일 서버는 송신된 메일을 받아들이고 메일 내용을 저장하거나 재전송을 하는  Agent 역할을 한다.


JavaMail Package는 메일 서버에게 메일을 전송하고 메일 서버에 저장된 메일을 수신하는데 유용한 클레스 및 API를 제공한다.


ex) MicroSoft사의 OutLook


 JavaMail API를 구조적으로 살펴보면 우선 메시지 구성, 저장, 수신 구조를 추상화해놓은absctruct layer가 있다. 이러한 추상 계층의 목적은  특정 프로토콜과는 상관없이 메일 구조 체계를 제공하겠다는 목적이다. 즉 인터넷 메일 뿐만 아니라 모바일이나 또 다른 메일 체계에도 적용되는 구조를 제공하는 것이다.


 이러한 추상 계층 이외에 JavaMail API는 가장 흔히 사용하는 인터넷 메일을 위한 클레스 및 API를 제공한다. 이 클레스들은 기본적인 abstruct layer를 인터넷 메일이하는 측면에서 구현 즉 implementation한 클레스들이다. JavaMail API를 가지고 직접적으로 응용프로그램을 작성할 수 있는 방법은 이 인터넷 메일 구현 계층을 사용하는 것이다.


인터넷 메일에 대하여 잘 이해하려면 우선 인터넷 메일 시스템에서 사용되는 통신 프로토콜 표준에 대하여 이해할 필요가 있다. 이러한 프로토콜들은 IETF(Internet Engineering Task Force)에 의하여

소정의 절차를 거친 후 RFC라는 형태로 발표되는데 인터넷 메일과 관련된 주요 표준 프로토콜은

SMTP, POP3, IMAP, MIME 등이 있다. (http://www.ietf.org/)


- SMTP(Simple Mail Transfer Protocol)

RFC 0788에서 정의 되었는데 이후에도 많은 수정이 있다.

SMTP는 기본적으로 인터넷 메일을 서버로 송신하고 서버가 이 메일을 수신하는데 필요한 커멘트 및 표준 절차등을 정의하고 있다.

Java Mail API의 주요 구성 요소

이 글에서는 Java Mail API의 모든 클래스에 대해서 알아보지는 않을 것이며, 가장 중심적인 역할을 하는 클래스인 javax.mail.Session, javax.mail.Store, javax.mail.Transport, javax.mail.Folder, javax.mail.Message 클래스에 대해서 알아볼 것이다. 실제로 이 다섯개의 클래스만 알맞게 사용하면 매우 손쉽게 메일 시스템을 구축할 수 있다.

javax.mail.Session

javax.mail.Session은 Java Mail API를 사용하는 출발점이 되는 클래스로서 다양한 메일 프로토콜을 위한 서비스 프로바이더 구현(Service Provider Implementation; SPI)을 나타내는 클래스를 로딩하고 제어할 수 있는 메소드를 제공하고 있다. 예를 들어, javax.mail.Store 클래스의 인스턴스는 javax.mail.Session 클래스를 통해서 구할 수 있다. (여기서 서비스 프로바이더는 Java Mail API를 이용하여 구현 클래스 계층을 제공하는 개발자는 벤더를 의미한다. 현재 Java Mail API는 IMAP, SMTP, POP3 프로토콜에 대한 구현 계층을 제공하고 있다.)

javax.mail.Store

javax.mail.Store 클래스는 특정한 메일 프로토콜을 이용하여 메일의 읽기, 쓰기, 감시, 검색 등을 할 수 있도록 해 준다. Session 클래스를 사용하여 구할 수 있으며, 메일 저장소를 추상화한 javax.mail.Folder에 접근할 수 있도록 해 준다. 서비스 프로바이더는 Store 클래스를 알맞게 구현해야 한다.

javax.mail.Folder

javax.mail.Folder 클래스는 메일 메세지에 계층적 구조를 제공하며 메일 메세지에 접근할 수 있도록 해 준다. 메일 메세지는 javax.mail.Message 클래스의 객체로 표현된다. 서비스 프로바디어는 Folder 클래스를 알맞게 구현해야 한다.

javax.mail.Transport

javax.mail.Transport 클래스는 특정한 프로토콜을 사용하여 메세지를 전송할 때 사용된다. 서비스 프로바이더는 이 클래스를 알맞게 구현해야 한다.

javax.mail.Message

javax.mail.Message 클래스는 주제, 수신자의 이메일주소, 발송자의 이메일 주소, 보낸 날짜와 같은 실제 이메일 메세지의 세부 사항을 나타낸다. 서비스 프로바이더는 자신이 사용하는 프로토콜에 알맞게 Message를 구현해야 한다.

Java Mail API와 JAF

Java Mail API는 메시지에 다양한 포맷을 사용할 수 있도록 하기 위해 JAF를 사용한다. JAF는 별도의 확장 API로서 다양한 데이터 형식을 이용하여 작업하는 방법을 통합하기 위해 작성되었다. 실제로 JAF를 이용하면 간단한 텍스트 데이터에서부터 이미지, 비디오 등의 매우 복잡한 멀티 미디어 데이터로 구성된 문서까지 다양한 데이터를 제공할 수 있다.

+ Recent posts