저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의
javax.activation.DataSource를 이용하여 파일 첨부 기능을 구현해본다.

메일의 파일첨부와 javax.activation.DataSource

2부에서는 웹을 기반으로 한 메일 전송에 대해서 알아보았다. 하지만, 지난번에 살펴본 내용에는 파일을 첨부하는 기능이 포함되어 있지 않았다. 이번 3부에서는 2부에서 예고한 것 처럼 파일 첨부 기능을 추가해보도록 하자.

파일 첨부 기능을 구현하기 위해서는 JAF(JavaBeans Activation Framework) API를 이용해야 한다. JAF API는 다양한 종류의 데이터 타입을 표현할 수 있도록 javax.activation.DataSource 인터페이스를 제공하고 있다. DataSource 인터페이는 다음과 같은 메소드를 선언하고 있다.

메소드 설명
String getContentType() 데이터의 MIME 타입을 리턴한다.
InputStream getInputStream() 데이터를 읽어올 수 있는 InputStream을 리턴한다.
String getName() 이 객체의 이름을 리턴한다.
OutputStream getOutputStream() 데이터를 쓸 수 있는 OutputStream을 리턴한다. 만약 그럴 수 없다면 예외를 발생한다.

DataSource 인터페이스를 implements 하여 알맞게 데이터 소스를 저장할 수 있는 클래스를 구현하면, javax.activation.DataHandler 클래스를 사용하여 DataSource 로부터 데이터를 읽어올 수 있게 된다. 예를 들어, 다음은 이번 예제에서 첨부 파일 데이터를 나타낼 때 사용되는 데이터 소스 클래스는 ByteArrayDataSource 이며, 이는 다음과 같다.

package javacan.web.mail;

import javax.activation.*;
import java.io.*;

class ByteArrayDataSource implements DataSource {
   byte[] bytes;
   String contentType;
   String name;

   ByteArrayDataSource(byte[] bytes, String contentType, String name) {
      this.bytes = bytes;
      if(contentType == null)
         this.contentType = "application/octet-stream";
      else
         this.contentType = contentType;
      this.name = name;
   }

   public String getContentType() {
      return contentType;
   }

   public InputStream getInputStream() {
      // 가장 마지막의 CR/LF를 없앤다.
      return new ByteArrayInputStream(bytes,0,bytes.length - 2);
   }
   
   public String getName() {
      return name;
   }
   
   public OutputStream getOutputStream() throws IOException {
      throw new FileNotFoundException();
   }
}

위 코드를 보면 ByteArrayDataSource 클래스는 DataSource 인터페이스를 implements 하고 있으며, DataSource 인터페이스에 선언되어 있는 모든 메소드를 알맞게 구현하고 있다. ByteArrayDataSource 클래스에서 눈여겨 볼 부분은 byte 배열을 데이터로 입력받는다는 점이다. 여기서 byte 배열은 첨부되는 파일을 나타낸다. 그리고 contentType 필드의 값이 "application/octet-stream" 인 것을 알 수 있는 데, 이는 첨부되는 파일의 MIME 타입을 지정하는 것이다.

위 코드에서 또하나 눈여겨 볼 부분은 getInputStream() 메소드이다. ByteArrayDataSource 클래스의 getInputStream() 메소드는 생성자에서 파라미터로 전달받은 byte 배열로부터 데이터를 읽어오는 것을 알 수 있다. 이때, 가장 마지막의 2 글자는 사용하지 않는데, 이는 웹 브라우저가 웹 서버에 "multipart/form-data" 인코딩 타입을 사용하여 데이터를 전송할 때에는 각 필드의 마지막에 "\r\n"을 추가하기 때문이다. (multipart/form-data 인코딩 타입으로 전송되는 데이터에 대한 내용을 알고자 하는 회원들은 시중에 나와 있는 서적에서 파일 업로드 부분을 보기 바란다.)

만약 로컬 파일시스템에 있는 파일로부터 데이터를 읽어오길 원한다면 javax.activation.FileDataSource 클래스를 사용하면 된다. 하지만, 이 글에서는 웹 브라우저를 통해서 입력받은 바이트 데이터를 이용해야 하기 때문에 FileDataSource 클래스를 사용하지 않고 앞에서 작성한 ByteArrayDataSource 클래스를 사용한다. FileDataSource 클래스를 사용하여 파일 첨부 기능을 구현하는 것에 대해서는 자바 서비스넷-http://www.javaservice.net-에 있는 글을 참고하기 바란다.

MimeBodyPart의 데이터로 DataSource 사용하기

MimeBodyPart는 DataSource를 데이터로 나타낼 수 있는 방법을 제공하고 있다. 이는 MimeBodyPart 클래스의 setDataHandler() 메소드를 통해서 이루어진다. setDataHandler() 메소드는 DataHandler 객체를 파라미터로 전달받으며, DataHalder 클래스는 DataSource로부터 데이터를 읽어올 수 있도록 해 주는 핸들러 역할을 한다. 예를 들어, 앞에서 작성한 ByteArrayDataSource 객체에 저장된 데이터를 내용으로 사용하는 MimeBodyPart 객체를 작성하고 싶다면 다음과 같이 하면 된다.

MimeBodyPart bodyPart = new MimeBodyPart();
DataSource ds = new ByteArrayDataSource(
    byteArray,
    contentType, filename);
bodyPart.setDataHandler( new DataHandler(ds));
bodyPart.setDisposition("attachment; filename=\"" + filename + "\"");
bodyPart.setFileName(filename);

MimeMultipart multipart = new MimeMultipart();
multipart.addBodyPart(bodyPart);

이 코드는 첨부할 파일을 MimeMultipart에 추가하는 것으로서 DataHandler와 DataSource를 사용하여 BodyPart의 내용을 지정하는 방법을 보여주고 있다. 위 코드는 다음과 같은 순서로 DataSource 데이터를 MimeBodyPart의 내용으로 지정하고 있다.

  1. DataSource 생성
  2. 생성된 DataSource를 사용하여 DataHandler 생성
  3. MimeBodyPart.setDataHandler() 메소드를 사용하여 DataSource로부터 데이터를 읽어오도록 지정.
그 외 MimeBodyPart.setDisposition() 메소드를 사용하여 현재 BodyPart가 첨부된 파일이라는 것을 지정하고 있으며, setFileName() 메소드를 사용하여 첨부된 파일의 이름을 지정하고 있다.

웹 기반의 메일 전송 2

이제 실제로 파일 첨부가 가능한 메일을 전송해주는 서블릿을 작성해보자. 먼저 서블릿에 메일 데이터를 전송해주는 폼부터 작성할 것이다. 폼은 mailform.jsp를 통해서 보여지며, mailform.jsp는 다음과 같다.

<html>
<head><title>메일 전송 폼</title>
</head>
<body>

<form action="http://localhost:8080/servlet/javacan.web.mail.WebSendMail"
      method="post"      enctype="multipart/form-data">
<table border="1" cellpadding="0" cellspacing="0">
<tr>
   <td>보내는사람</td>
   <td><input type="text" name="from" size="20"></td>
</tr>
<tr>
   <td>반는사람</td>
   <td><input type="text" name="to" size="20"></td>
</tr>
<tr>
   <td>참조</td>
   <td><input type="text" name="cc" size="20"></td>
</tr>
<tr>
   <td>제목</td>
   <td><input type="text" name="subject" size="40"></td>
</tr>
<tr>
   <td>내용</td>
   <td><textarea name="body" rows="10" cols="40"></textarea></td>
</tr>
<tr>
   <td>첨부파일</td>
   <td><input type="file" name="attachment"></td>
</tr>
<tr>
   <td colspan="2"><input type="submit" value="전송"></td>
</tr>
</table>
</form>

</body>
</html>

mailform.jsp를 보면 <form> 태그의 enctype 속성의 값이 "multipart/form-data" 인 것을 알 수 있는데, 이렇게 함으로써 파일 데이터를 전송할 수 있게 된다. mailform.jsp를 실행해보면 다음 그림과 같은결과가 출력된다.


위 그림에서 [전송] 버튼을 누르면 실제로 데이터를 javacan.web.mail.WebSendMail 서블릿에 전송하며, WebSendMail 서블릿은 입력받은 데이터를 이용하여 메일을 전송한다. POSTE 방식을 사용하여 데이터를 전송하기 때문에 WebSendMail 서블릿의 doPost() 메소드가 실행된다. WebSendMail 서블릿의 doPost() 메소드는 다음과 같다.

public void doPost(HttpServletRequest request,
                   HttpServletResponse response)
                   throws IOException, ServletException {
    if(request.getContentType().startsWith("multipart/form-data")) {
        try {
            HashMap data = getMailData(request, response);
            sendMail(data);
            
            ServletContext sc = getServletContext();
            RequestDispatcher rd = sc.getRequestDispatcher("/sendedMail.jsp");
            rd.forward(request, response);
        } catch(MessagingException ex) {
            throw new ServletException(ex);
        }
    } else {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

doPost() 메소드에서는 먼저 사용자가 전송한 데이터 타입이 "multipart/form-data" 인지를 검사한다. 만약 그렇다면, getMailData() 메소드를 사용하여 사용자가 폼을 통해서 입력한 메일 데이터를 읽어오고 이어서 sendMail() 메소드를 사용하여 메일을 전송한다. getMailData()는 "multipart/form-data" 타입으로 전송된 데이터로부터 사용자가 입력한 데이터를 추출해서 HashMap에 저장한다. HashMap에 저장할 때 사용되는 키 값은 폼의 name 속성의 값과 같다. 다음은 getMailData() 메소드이다.

private HashMap getMailData(HttpServletRequest request,
                            HttpServletResponse response)
        throws IOException, ServletException, MessagingException {
   String boundary = request.getHeader("Content-Type");
   int pos = boundary.indexOf('=');
   boundary = boundary.substring(pos + 1);
   boundary = "--" + boundary;
   ServletInputStream in = request.getInputStream();
   byte[] bytes = new byte[512];
   int state = 0;
   ByteArrayOutputStream buffer = new ByteArrayOutputStream();
   String name = null, value = null, 
          filename = null, contentType = null;
   HashMap mailData = new HashMap();
   
   int i = in.readLine(bytes,0,512);
   while(-1 != i) {
      String st = new String(bytes, 0, i);
      if(st.startsWith(boundary)) {
         state = 0;
         if(null != name) {
            if(value != null)
               // -2 to remove CR/LF
               mailData.put(name, value.substring(0, value.length() - 2));
            else if(buffer.size() > 2) {
               MimeBodyPart bodyPart = new MimeBodyPart();
               DataSource ds = new ByteArrayDataSource(
                  buffer.toByteArray(),
                  contentType, filename);
               bodyPart.setDataHandler(new DataHandler(ds));
               bodyPart.setDisposition(
                    "attachment; filename=\"" + filename + "\"");
               bodyPart.setFileName(filename);
               mailData.put(name, bodyPart);
            }
            name = null;
            value = null;
            filename = null;
            contentType = null;
            buffer = new ByteArrayOutputStream();
         }
      } else if(st.startsWith("Content-Disposition: form-data") && state == 0) {
         StringTokenizer tokenizer = new StringTokenizer(st,";=\"");
         while(tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            if(token.startsWith(" name")) {
               name = tokenizer.nextToken();
               state = 2;
            } else if(token.startsWith(" filename")) {
               filename = tokenizer.nextToken();
               StringTokenizer ftokenizer = new StringTokenizer(filename,"\\/:");
               filename = ftokenizer.nextToken();
               while(ftokenizer.hasMoreTokens())
                  filename = ftokenizer.nextToken();
               state = 1;
               break;
            }
         }
      } else if(st.startsWith("Content-Type") && state == 1) {
         pos = st.indexOf(":");
         // + 2 to remove the space
         // - 2 to remove CR/LF
         contentType = st.substring(pos + 2,st.length() - 2);
      } else if(st.equals("\r\n") && state == 1)
         state = 3;
      else if(st.equals("\r\n") && state == 2)
         state = 4;
      else if(state == 4)
         value = value == null ? st : value + st;
      else if(state == 3)
         buffer.write(bytes,0,i);
      i = in.readLine(bytes,0,512);
   }
   return mailData;
}

getMailData() 메소드는 사용자가 폼에 입력한 값을 HashMap인 mailData에 저장한다. 굵게 표시한 부분이 사용자가 전송한 파일 데이터를 읽어와 MimeBodyPart 객체에 저장하는 부분이다.

getMailData() 메소드를 사용하여 메일 데이터를 HashMap에 저장하면 이제 sendMail() 메소드가 실행된다. sendMail() 메소드는 실제로 MimeMessage 클래스를 이용하여 인터넷 이메일을 작성한 후 메일을 전송해주는 역할을 한다. sendMail() 메소드는 다음과 같다.

private void sendMail(HashMap mailData) throws MessagingException {
   System.setProperty("mail.smtp.host", "smpthost");
   Message msg = new MimeMessage(
                     Session.getDefaultInstance(
                     System.getProperties(),null));
   msg.setFrom(new InternetAddress((String)mailData.get("from")));
   InternetAddress[] tos = InternetAddress.parse((String)mailData.get("to"));
   msg.setRecipients(Message.RecipientType.TO,tos);
   if(mailData.get("cc") != null) {
      InternetAddress[] ccs = InternetAddress.parse((String)mailData.get("cc"));
      msg.setRecipients(Message.RecipientType.CC,ccs);
   }
   msg.setSubject((String)mailData.get("subject"));
   msg.setSentDate(new Date());
   if(null == mailData.get("attachment"))
      msg.setText((String)mailData.get("body"));
   else {
      BodyPart body = new MimeBodyPart();
      BodyPart attachment = (BodyPart)mailData.get("attachment");
      body.setText((String)mailData.get("body"));
      MimeMultipart multipart = new MimeMultipart();
      multipart.addBodyPart(body);
      multipart.addBodyPart(attachment);
      msg.setContent(multipart);
   }
   Transport.send(msg);
}

sendMail() 메소드는 2부에서 살펴본 내용과 크게 다르지 않다. 다른 점이 있다면 첨부 파일을 처리할 수 있다는 점이다. 위 코드에서 굵게 표시한 부분이 파일을 첨부하는 부분이다. MimeMultipart 클래스는 다중의 BodyPart를 가질 수 있으며, sendMail() 메소드에서는 getMailData()에서 읽어온 첨부 파일을 저장하고 있는 BodyPart를 addBodyPart() 메소드를 사용하여 MimeMultipart에 추가하고 있다. 위 코드에서 주의할 점은 System.setProperty("mail.smtp.host", "smtphost") 부분에서 "smtphost"의 값을 SMTP 서버에 알맞게 변경해주어야 한다는 점이다.

이제 mailform.jsp를 통해서 화면에 출력된 폼을 사용하여 첨부할 파일을 선택한 후 [전송] 버튼을 클릭해보자. 그러면 파일이 첨부된 이메일이 전송될 것이다.

결론

이번 글에서는 파일 첨부 기능을 구현해보았다. JAF API의 DataSource 인터페이스는 다양한 데이터를 표현할 수 있는 방법을 제공하고 있으며, 이를 이용하여 매우 손쉽게 파일 첨부 기능을 구현할 수 있었다.

지금까지 3부에 걸쳐서 Java Mail API의 기본적인 내용에서부터 응용까지의 내용을 살펴보았다. 이제 여러분은 어렵지 않게 웹 기반의 메일 전송 프로그램을 작성할 수 있을 것이며, 또한 여러분이 구현한 웹 사이트에 메일 전송 기능을 쉽게 추가할 수 있을 것이다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 혼자서도 잘해야되요 2015.09.10 16:36 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 여쭤볼게 있어서 글남깁니다.

    아래 DataSource 멀 import해야 하나요 그리고 ByteArrayDataSource 변수가

    byte[] int string 인데 위 소스에서는 byte string string 인데 잘안되네요 답변좀 부탁드릴게요.ㅠ

    DataSource ds = new ByteArrayDataSource(
    buffer.toByteArray(),
    contentType, filename);

Java Mail API를 이용하여 웹 기반의 메일 전송 어플리케이션을 작성해보자.

JavaMail API와 SMTP

SMTP는 Simple Mail Transfer Protocol의 약자로서 메일을 전송할 때 사용되는 프로토콜이다. 현재 우리가 흔히 사용하고 있는 이메일은 모두 SMTP 프로토콜을 사용하여 메일을 전송하고 메일을 전달받는다. JavaMail API는 기본적으로 STMP 프로토콜을 지원해준다. 따라서, JavaMail API를 사용하면 어렵지 않게 SMTP를 사용하여 메일을 전송할 수 있게 된다.

JavaMail API를 이용하여 메일을 전송하는 과정은 다음과 같다.

  1. javax.mail.Session 클래스의 인스턴스를 구한다.
  2. javax.mail.Message 클래스를 사용하여 전송하고자 하는 메일 메시지를 작성한다.
  3. javax.mail.Transport 클래스를 사용하여 작성한 메일을 전송한다.
세션 생성

JavaMail API는 메일과 관련된 모든 작업을 처리하기 위해서는 먼저 세션을 생성해야 한다. STMP와 관련된 세션을 생성하기 위해서는 STMP 서버와 관련된 정보를 지정해주어야 한다. 일반적으로, java.util.Properties 클래스를 사용하여 세션을 생성할 때 필요한 값을 설정한 후, javax.mail.Session을 사용하여 세션을 생성한다. 다음은 세션을 생성하는 코드이다.

Properties prop = new Properties();
prop.put("mail.smtp.host", "mail.myHost.com");
prop.put("mail.smtp.user", "webmaster");

Session session = Session.getDefaultInstance(prop, null);

위 코드에서 "mail.smtp.host" 속성은 이메일 발송을 처리해줄 STMP 서버를 나타낸다. "mail.smtp.user" 속성은 SMTP 서버에 있는 사용자의 계정(또는 아이디)를 나타낸다. Session 클래스의 getDefaultInstance() 메소드는 파라미터로 전달받은 Properties에 저장되어 있는 속성값을 사용하여 세션을 생성한다.

메시지 작성

세션을 생성했다면, 그 다음으로 해야 할 일은 메시지를 작성하는 것이다. 메시지 작성은 javax.mail.Message 클래스를 통해서 이루어진다. 오늘날 사용되는 대부분의 이메일은 다양한 Mime 타입을 지원하기 때문에, MimeMessage 클래스를 사용해야 할 것이다. MimeMessage 클래스는 Message 클래스를 상속받았으며, Message 메시지는(즉, MimeMessage 클래스는) 보내는 사람, 받는 사람, 제목, 내용과 같이 메일과 관련된 내용을 지정할 수 있도록 해 준다.

다음 코드는 MimeMessage 클래스를 사용하여 전송할 메시지를 작성하는 코드를 보여주고 있다.

MimeMessage message = new MimeMessage(session);
InternetAddress from = new InternetAddress("webmaster@myHost.com");
message.setFrom(from);

InternetAddress to = new InternetAddress("era13@hanmail.net", "beom-kyun");
InternetAddress[] toList = { to };
message.setRecipients(Message.RecipientType.TO, toList);
message.setSubject("mail subject");
message.setContent("메일의 내용", "text/plain");

위 코드에서 InternetAddress 클래스는 이메일 주소를 나타낼 때 사용되는 클래스로서, Message 클래스의 수취인과 발신인을 지정할 때 사용된다. Message 클래스는 setRecipients() 메소드와 setFrom() 메소드를 사용하여 수취인과 발신인을 설정하며 setSubject() 메소드와 setContent() 메소드를 사용하여 메시지의 주제(또는 제목)과 내용을 설정할 수 있다.

단순한 텍스트 뿐만 아니라 HTML 형식을 비롯한 다양한 MIME 타입을 메일의 내용을 지정할 수 있다. 예를 들어, HTML 형식의 메일을 전송하고자 할 경우 다음과 같이 Message의 setContent() 메소드를 호출해주면 된다.

message.setContent("메일 내용..", "text/html");

동시에 여러 개의 MIME 타입을 전송할 수도 있다. 이를 위해서는 MultiPart 클래스를 사용하면 된다. 이 뿐만 아니라 다양한 형태의 데이터를 전송하기 위해서 JavaBean Activation Framework API를 사용할 수도 있다. JAF API를 사용하면 파일 첨부와 같이 바이너리 데이터를 비롯한 모든 데이터를 원하는 형태로 전송할 수 있게 된다.

메시지 전송

메시지 전송은 단순히 Transport 클래스의 send() 메소드를 호출함으로써 이루어진다. 예를 들어, 앞에서 생성한 메시지 객체인 message를 전송하고자 할 경우 다음과 같이 Transport.send() 메소드를 호출하면 된다.

Transport.send(message);

웹 기반의 메일 전송

다음을 비롯한 많은 사이트에서 기본적으로 이메일 서비스를 제공해주고 있다. 이러한 사이트들은 대부분 이메일 전송을 위해 SMTP를 사용하고 있다. 이 뿐만 아니라 대부분의 메일링 리스트 역시 SMTP를 이용하여 메일을 발송한다. 그 외에도 몇몇 사이트는 게시판에 답변글이 올라올 경우 그 답변글을 메일로 전송해주기도 한다.

이런 기능들을 구현하기 위해서는 웹 어플리케이션이 메일을 전송할 수 있어야 한다. 웹 기반의 메일 전송은 이 글의 앞에서 살펴본 내용만으로도 충분히 구현할 수 있다. 이 글에서는 마지막으로 간단하게 웹 기반의 메일 전송 어플리케이션을 작성해보도록 하자.

최대한 간단하게 구현하기 위해서 메일의 내용을 입력할 수 있는 폼을 보여주는 MailInputForm.html과 메일을 전송해주는 sendMail.jsp의 두 페이지만 사용해서 구현해볼 것이다. 먼저, MailInputForm.html은 다음과 같다.

<html><head><title>메일 작성 폼</title></head>
<body>
<form action="sendMail.jsp" method="post">
<table border="1" cellpadding="0" cellspacing="0">
<tr>
   <td>SMTP서버</td>
   <td><input type="text" name="smtpServer" size="20"></td>
</tr>
<tr>
   <td>STMP 서버 계정</td>
   <td><input type="text" name="serverAccount" size="10"></td>
</tr>
<tr>
   <td>보내는 이메일</td>
   <td><input type="text" name="senderEmail" size="30"></td>
</tr>
<tr>
   <td>받는 이메일</td>
   <td><input type="text" name="receiverEmail" size="30"></td>
</tr>
<tr>
   <td>제목</td>
   <td><input type="text" name="subject" size="50"></td>
</tr>
<tr>
   <td>내용</td>
   <td><textarea name="content" rows="8" cols="50"></textarea></td>
</tr>
<tr>
   <td colspan="2"><input type="submit" value="전송"></td>
</tr>
</table>
</form>
</body></html>

위 HTML의 각 입력란에 알맞게 정보를 입력한 후, [전송] 버튼을 클릭하면 sendMail.jsp가 실행된다. sendMail.jsp는 다음과 같다.

<%@ page import = "javax.mail.*" %>
<%@ page import = "javax.mail.internet.*" %>
<%@ page import = "java.util.*" %>
<%   
   Properties prop = new Properties();
   prop.put("mail.smtp.host", request.getParameter("smtpServer"));
   prop.put("mail.smtp.user", request.getParameter("serverAccount"));   
   javax.mail.Session mailSession = 
             javax.mail.Session.getDefaultInstance(prop, null);
   
   MimeMessage message = new MimeMessage(mailSession);
   InternetAddress from = new InternetAddress(request.getParameter("senderEmail"));
   message.setFrom(from);   
   InternetAddress to = new InternetAddress(request.getParameter("receiverEmail"));
   InternetAddress[] toList = {to};
   message.setRecipients(Message.RecipientType.TO, toList);
   message.setSubject(request.getParameter("subject"), "iso8859-1");
   message.setContent(request.getParameter("content"), "text/plain");   
   Transport.send(message);
%>
<html>
<head><title>메일전송완료</title></head>
<body>
메일을 전송하였습니다.
</body>
</html>

sendMail.jsp를 보면 특별히 어려운 코드가 없다는 것을 알 수 있다. 모든 코드가 이미 앞에서 설명한 코드를 모아 놓은 것에 불과하다. sendMail.jsp에서 이메일 메시지의 제목을 설정하는 코드를 살펴보면 제목의 인코딩 타입을 "iso8859-1"로 설정해주는 부분이 있다. 이는 서블릿/JSP 엔진이 기본적으로 파라미터를 "iso8859-1" 인코딩으로 읽어오기 때문이다.

결론

2부에서는 SMTP 서버를 사용하여 메일을 전송하는 것에 대해서 알아보았다. 이번 글을 통해서 어렵지 않게 메일링 리스트와 같은 것을 구현할 수 있게 되었을 것이다. 이제 남은 것이 있다면, 메일에 파일 첨부를 하는 방법과 POP3를 통해 메일을 읽어오는 것이다. POP3를 사용하여 메일을 읽어오는 것은 이미 이 시리즈의 1부에서 간단하게 구현해보았으므로, 이 시리즈의 마지막인 3부에서는 파일첨부 기능을 구현해보도록 하자

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

Java Mail API에 대해서 알아본다.

Java Mail API

기존에 자바에서 메일과 관련된 프레임워크를 개발하기 위해서 개발자들은 스스로 전체적인 메일 시스템을 설계하고 구현해야만 했다. 하지만, Java Mail API가 발표되면서, 개발자들은 메일 시스템의 프레임워크를 설계하는 데 고민할 필요가 없어졌으며, 또한 Java Mail API와 함께 제공되는 클래스들(SMTP, POP3, IMAP을 구현하였다)을 사용하여 매우 손쉽게 메일을 주고 받는 시스템을 구축할 수 있게 되었다.

이 글에서는 Java Mail API가 무엇이며, 어떻게 구성되어 있는 지 살펴볼 것이며, 다음 기사에서는 Java Mail API를 이용하여 간단한 웹 기반의 메일 시스템을 구현해볼 것이다.

Java Mail API의 주요 목적은 개발자들이 손쉽게 어플리케이션의 메일 기능을 추가하고 또한 정교한 사용자 인터페이스를 생성할 수 있도록 하는 것이다. Java Mail API는 Message, Transport, Session과 같이 메일의 공통적인 기능과 프로토콜을 추상화한 클래스를 포함하고 있으며, 또한 자바 기반이기 때문에 자바 플랫폼에서 제공하는 다양한 API를 사용할 수 있다. 다음 그림은 Java Mail API의 전체적인 구조를 보여주고 있다.


위 그림을 보면 가장 상위에 메일 시스템을 사용하는 어플리케이션 위치하는 것을 알 수 있다. 이 어플리케이션은 Java Mail의 추상 클래스를 통해서 실제 구현 클래스 계층에 접근하게 된다. 구현 클래스 계층은 하부에 있는 실제 이메일 시스템 구현 계층과 상호작용한다.메일의 컨텐츠와 관련된 데이터는 JAF(JavaBean Activation Framework)를 사용하여 표현할 수 있다. 일반적으로 Java Mail API를 사용하는 어플리케이션은 다양한 포맷을 제공하기 위해 JAF를 통해 메일 메시지의 컨텐츠를 생성한다.

JavaMail 프레임워크

Java Mail API는 다음과 같은 기능을 수행하는 것을 기본 목적으로 한다.

  • 헤더와 데이터로 구성된 메일 메시지를 생성한다. Java Mail은 메일 메시지를 정의하기 위해 Message 클래스를 사용한다.
  • Session 객체를 생성한다. 이 객체는 사용자를 인증하고 메시지 저장과 전송에 대한 접근을 제어한다.
  • 메시지를 전송한다.
  • 메시지 저장소로부터 메시지를 읽어온다.
  • 읽은 메시지에 명령어를 수행한다. '보기'나 '출력' 같은 명령어는 JAF를 인식하는 자바빈에서 구현하도록 하고 있다.
일반적으로 이러한 기능은 메일 클라이언트가 기본적으로 제공하는 것들이다. 전체 프레임워크를 그림으로 나타내면 다음과 같다.



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를 이용하면 간단한 텍스트 데이터에서부터 이미지, 비디오 등의 매우 복잡한 멀티 미디어 데이터로 구성된 문서까지 다양한 데이터를 제공할 수 있다.

예제 어플리케이션

여기서 사용할 예제는 일정 시간 동안 메일 박스를 검사하는 어플리케이션이다. 예제 어플리케이션은 POP3를 사용하기 때문에, 이 예제를 실행하기 위해서는 POP3를 지원하는 메일 시스템에 이메일 계정을 갖고 있어야 한다.

예제 어플리케이션을 실행하기 위해서는 Java Mail API와 JAF API가 필요하다. 이 두 API는 각각 http://java.sun.com/products/javamail/index.htmlhttp://java.sun.com/products/javabeans/glasgow/jaf.html에서 다운로드 받을 수 있다. Java Mail API 1.2를 다운로드 받은 후 압축을 풀면 mailapi.jar, mail.jar, pop.jar, smtp.jar, imap.jar 파일이 생성된다. 여기서 mail.jar는 mailapi.jar, pop3.jar, smtp.jar, imap.jar에 있는 모든 클래스 파일을 포함하고 있는 JAR 파일이다. 따라서 간단하게 mail.jar만 CLASSPATH에 추가하면 Java Mail 1.2에서 제공하는 추상 클래스 계층과 POP3, SMTP, IMAP 구현 클래스 계층을 모두 사용할 수 있다.

예제로 사용되는 POP3Checker.java는 일정한 시간 간격으로 POP3 호스트를 검사해서 새로 수신된 메일이 있을 경우 그 메일을 읽어온다. 즉, 간단한 메일 클라이언트라고 생각하면 된다.

POP3Checker의 핵심 부분은 다음과 같다.

   public void process() throws Exception {
      // Session 객체를 구한다.
      java.util.Properties sysProperties = System.getProperties();
      Session session = Session.getDefaultInstance(sysProperties, null);
      session.setDebug(true); // 디버깅 정보를 화면에 보여준다.
      
      // POP3 호스트에 접속한다.
      Store store = session.getStore("pop3");
      store.connect(pop3Host, -1, user, password);
      
      // 기본 폴더를 구한다.
      Folder folder = store.getDefaultFolder();
      if (folder == null)
         throw new NullPointerException("디폴트 메일 폴더 없음");
      
      // "INBOX" 폴더를 구한다.
      folder = folder.getFolder("INBOX");
      if (folder == null)
         throw new NullPointerException("INBOX 폴더를 구할 수 없음");
      
      // 메시지 개수를 구한다.
      folder.open(Folder.READ_WRITE);
      int totalMessages = folder.getMessageCount();
      if (totalMessages == 0)
      {
         System.out.println(folder + "가 비었습니다.");
         folder.close(false);
         store.close();
         return;
      }
      
      // 각 메시지의 속성과 플래그를 구한다.
      Message[] messages = folder.getMessages();
      FetchProfile fp = new FetchProfile();
      fp.add(FetchProfile.Item.ENVELOPE);
      fp.add(FetchProfile.Item.FLAGS);
      fp.add("X-Mailer");
      folder.fetch(messages, fp);
      
      for (int i = 0; i < messages.length; i++)
      {
         if (!messages[i].isSet(Flags.Flag.SEEN)) {
            printMsssage(messages[i]);
         }
         // 읽어온 메시지를 호스트에서 삭제한다.
         messages[i].setFlag(Flags.Flag.DELETED, true); 
      }
      
      folder.close(true);
      store.close();
   }

POP3Checker 클래스는 process() 메소드를 일정 주기 동안 반복적으로 호출한다. POP3Checker.process() 메소드를 살펴보면 다음과 같은 순서로 작업을 진행하는 것을 알 수 있다.

  1. Session 객체 구함
  2. 알맞은 프로토콜을 사용하여 호스트에 접속(Store 객체 사용)
  3. 메일이 저장된 폴더 구함(Folder 객체 사용)
  4. Folder로부터 Message 객체 구함
  5. Message 출력
  6. 읽어온 메시지를 호스트에서 삭제한다.
아웃룩 익스프레스와 같이 일반적으로 널리 사용되는 메일 프로그램들은 모두 1-6번 과정을 거쳐서 메일을 호스트로부터 읽어온다.

위 코드에서 printMessage() 메소드는 파라미터로 전달받은 Message 객체를 화면에 출력해주는 부분으로서 다음과 같다.

   private void printMsssage(Message message) throws Exception {
      String replyTo = null, subject;
      Object content;
      java.util.Date sentDate;
      Address[] a=null;
      
      if ((a = message.getFrom()) != null)
         replyTo = a[0].toString();
      
      subject  = message.getSubject();
      sentDate = message.getSentDate();
      DataHandler dataHandler = message.getDataHandler();
      String contentType = dataHandler.getContentType();
      
      System.out.println("=====================================================");
      System.out.println("보낸사람:"+replyTo);
      System.out.println("보낸시각:"+sentDate);
      System.out.println("주제:"+subject);
      System.out.println("-----------------------------------------------------");
      
      if ( (contentType.indexOf("text/html") != -1) || (contentType.indexOf("text/plain") != -1) ) {
         BufferedReader br = new BufferedReader(new InputStreamReader(dataHandler.getInputStream()));
         char[] buff = new char[512];
         int len;
         while ( (len = br.read(buff)) != -1) {
            System.out.print(new String(buff, 0, len));
         }
         br.close();
      }
      System.out.println("\n=====================================================\n");
   }

printMessage() 메시지에서 실제 메일의 내용은 ContentHandler 객체로부터 추출된다. ContentHandler 클래스는 JAF에 정의되어 있는 클래스로서 다양한 포맷의 데이터를 취급하기 위해서 사용된다. 위 예제에서는 ContentHandler 객체가 처리하고 있는 데이터의 타입이 "text/html"이거나 "text/plain"인 경우에 화면에 출력하도록 하고 있다. POP3Checker.java의 완전한 소스 코드는 관련 링크 부분을 참조하기 바란다.

결론

이 글에서는 Java Mail API가 무엇인지에 대해 개괄적으로 알아보았다. Java Mail API는 모든 메일 시스템에 공통적으로 적용될 수 있는 프레임워크를 제공하고 있기 때문에 별도의 프레임워크 개발없이 손쉽게 새로운 메일 시스템을 구현할 수 있도록 하고 있으며, 뿐만 아니라 널리 사용되고 있는 SMTP나 POP3와 같은 프로토콜에 기반한 메일 시스템 프레임워크를 구현해 놓고 있다. 따라서 개발자들은 특별한 어려움없이 Java Mail API 만을 사용해서 간단하면서도 견고한 메일 시스템을 구축할 수 있게 되었다.

다음 글에서는 웹에서 SMTP를 사용하여 이메일을 전송할 수 있는 간단한 웹기반의 메일 전송 시스템을 구축해 볼 것이다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요