POP3クライアントをSocketとJavaMailで作る その1

はじめに

表題の通り,POP3クライアントをSocketとJavaMailを使って作り比べてみよう.

まず,動作環境は以下の通り.

  • Microsoft Windows XP Professional SP1a
  • Java 2 SDK Standard Edition 1.4.2_04
  • JavaMail 1.3.1
  • JAF(JavaBeans Activation Framework) 1.0.2

こんな要件としよう.プログラムを単純にするために,GUIは使わずコマンドラインだ.

  1. プログラムを起動すると,コマンドラインにread:を表示してコマンドの入力を待つ
  2. 入力可能なコマンドはlist/read/quitの3種類
  3. listでメールサーバにあるメールの件数を表示
  4. read NでN番目のメールの内容を表示.例えば,2通目のメールを表示する場合はread 2と入力
  5. quitでプログラムを終了
  6. 受信したメールはそのままサーバに残す

Socket版

まずはSocket版のプログラムである.いきなりだが,こんなソースになった.ポート番号110でメールサーバとのSocketを作成し,BufferedReader/BufferedWriterでコマンドと結果をやり取りしている.

import java.net.Socket;
import java.io.*;

public class Pop3{
  private Socket mailserver;
  private BufferedReader reader;
  private BufferedWriter writer;

  public Pop3() throws Exception{
    final String servername="mailserver"; //適当なメールサーバに書換える
    final String username="username"; //適当なユーザに書換える
    final String password="password"; //適当なパスワードに書換える

    mailserver=new Socket(servername,110);
    reader=new BufferedReader(new InputStreamReader(mailserver.getInputStream()));
    writer=new BufferedWriter(new OutputStreamWriter(mailserver.getOutputStream()));

    if(isReady()&&isReady("user "+username)&&isReady("pass "+password)){
      //メールサーバにログインできれば何もしない
    }
    else{
      mailserver.close();
    }
  }

  private boolean isReady() throws IOException{
    final boolean OK=true;
    final boolean NG=!OK;

    return reader.readLine().matches("^\\+OK.*$")? OK: NG;
  }

  private boolean isReady(String message) throws IOException{
    final String end="\r\n";

    writer.write(message+end);
    writer.flush(); //コマンドをメールサーバに送って
    return isReady(); //結果を判定
  }

  public void transaction() throws Exception{
    BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
    while(!mailserver.isClosed()){
      System.out.print("ready: ");
      String command=reader.readLine();
      if(command.equalsIgnoreCase("quit")){
        if(isReady(command)){
          mailserver.close();
        }
      }
      else if(command.equalsIgnoreCase("list")){
        if(isReady(command)){
          System.out.println(getMessage().split(" ")[0]); //件数とバイト数を受取る.件数のみ表示
        }
      }
      else if(command.matches("(?i)^read \\d+$")){
        if(isReady(command.replaceFirst("read","retr"))){
          System.out.println(getMessage());
        }
      }
      else{
        System.out.println("ignore: "+command);
      }
    }
  }
  
  private String getMessage() throws IOException{
    StringBuffer message=new StringBuffer();
    while(true){
      String s=reader.readLine();
      if(s.equals(".")){
        if(message.length()>0){
          message.deleteCharAt(message.length()-1); //つけすぎた改行を削除
        }
        break;
      }
      else{
        message.append(s+"\n");
      }
    }
    return message.toString();
  }

  public static void main(String[] args){
    try{
      Pop3 pop3=new Pop3();
      pop3.transaction();
     }
    catch(Exception e){
      e.printStackTrace();
    }
  }
}
        

プログラムとメールサーバとのやり取りを図にするとこのようになる.

JavaMailをダウンロード&インストール

Socket版の次はJavaMail版を作成する.その前に,JavaMailのダウンロードとインストールを行う.

JavaMail APIからJavaMailを,JavaBeans Activation FrameworkからJAFダウンロードする.ダウンロードしたアーカイブからそれぞれmail.jarとactivation.jarを取り出し,CLASSPATHに設定したディレクトリに置く.今回は${JAVA_HOME}\jre\lib\extに配置した.${JAVA_HOME}はJDKをインストールしたディレクトリを表す.

JavaMail版

Socketを使用した場合は,メールサーバとサーバとコマンドや結果をやり取りするストリームが必要だった.JavaMailを使用すると,Session/Store/Folderを使用してメールの受信ができる.以下のような手順である.

  1. メールサーバとの間にSessionを作成
  2. SessionからStoreを取得
  3. StoreからFolderを取得
  4. Folderのメソッドを使ってメールを受信
import java.io.*;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;

public class Pop3{
  private Store store;
  private Folder folder;
  
  public Pop3() throws Exception{
    Properties prop=new Properties();
    prop.put("mail.host","mailserver"); //適当なメールサーバに書換える
    prop.put("mail.store.protocol","pop3"); //"pop3"固定
    Session session=Session.getDefaultInstance(prop,new Authenticator(){ //メールサーバとの間にSessionを作成
      protected PasswordAuthentication getPasswordAuthentication(){
        return new PasswordAuthentication("username","password"); //適当なユーザ名とパスワードに書換える
      }
    });
    store=session.getStore(); //SessionからStoreを取得
    store.connect();
    folder = store.getFolder("INBOX"); //SessionからFolderを取得.名称は"INBOX"固定
    folder.open(Folder.READ_ONLY);
  }

  private void transaction() throws Exception{
    BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
    while(store.isConnected()){
      System.out.print("ready: ");
      String command=reader.readLine();
      if(command.equalsIgnoreCase("quit")){
        folder.close(false);
        store.close();
      }
      else if(command.equalsIgnoreCase("list")){
        System.out.println(folder.getMessageCount());
      }
      else if(command.matches("(?i)^read \\d+$")){
        folder.getMessage(Integer.parseInt(command.replaceAll("read\\s",""))).writeTo(System.out);
      }
      else{
        System.out.println("ignore: "+command);
      }
    }
  }

  public static void main(String[] args){
    try{
      Pop3 pop3=new Pop3();
      pop3.transaction();
    }
    catch(Exception e){
      e.printStackTrace();
    }
  }
}
        

プログラムとSession/Store/Folderとのやり取りを図にするとこのようになる.

参考文献・サイト