top / index / prev / next / target / source
日記形式でつづる いがぴょんコラム ウェブページです。
シンプルなメール受信サンプルについて 試作しました。
シンプルなメール受信サンプルについて 試作しました。メール受信は 実装が結構大変なように感じました。※このメモは blancoMail の仕様検討に該当します。
最初にメール一覧取得処理を作成しました。(このサンプルではメール本文は読んでいません)
このAPI利用範囲では POP3の RETRコマンドは送出されません。これは デバッグトレースにて確認しました。RETRコマンドは どうしても必要になるまでは呼ばれない実装になっているようです。これは嬉しいことです。
※注意: このサンプルを実行すると、メールサーバの種類および設定によってはメールサーバ上のメールが削除されてしまう場合があります! gmailにおいて 特定の設定のもと このサンプルをベースにしたものを動作させると DELETEフラグを立てていないのにもかかわらずサーバ上のメールが削除される現象を確認しました。おそらく POP3 のRETRコマンドを呼び出すと メールが削除される、という現象だとは思います。(このサンプルでは RETRは呼び出さないようなのですが、このサンプルをベースに変更して動作させると RETRが呼び出される場合があります) 同様の事象は 他のメールサーバ環境でも発生しうると考え、この注意書きを示しておきます。ただし 正しいメールサーバの実装としては、DELETEによってのみメールが削除されるというのが正しい動きだと私は考えています。 SimpleRecvMail.java
import java.util.Properties;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.Authenticator;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
/**
* シンプルなメール受信サンプル。
*/
public class SimpleRecvMail {
public static void main(final String[] args) {
System.out.println("メール受信: 開始");
new SimpleRecvMail().process();
System.out.println("メール受信: 終了");
}
public void process() {
final Properties props = new Properties();
// 基本情報。ここでは gmailへの接続例を示します。
props.setProperty("mail.pop3.host", "pop.gmail.com");
props.setProperty("mail.pop3.port", "995");
// タイムアウト設定
props.setProperty("mail.pop3.connectiontimeout", "60000");
props.setProperty("mail.pop3.timeout", "60000");
// SSL関連設定
props.setProperty("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.setProperty("mail.pop3.socketFactory.fallback", "false");
props.setProperty("mail.pop3.socketFactory.port", "995");
final Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("受信者○○○@gmail.com", "password");
}
});
// デバッグを行います。標準出力にトレースが出ます。
session.setDebug(true);
Store store = null;
try {
try {
store = session.getStore("pop3");
} catch (NoSuchProviderException e) {
System.out.println("メール受信: 指定プロバイダ[pop3]の取得に失敗しました。"
+ e.toString());
return;
}
try {
store.connect();
} catch (AuthenticationFailedException e) {
System.out.println("メール受信: サーバ接続時に認証に失敗しました。" + e.toString());
return;
} catch (MessagingException e) {
System.out.println("メール受信: サーバ接続に失敗しました。" + e.toString());
return;
}
Folder folder = null;
try {
try {
// INBOXは予約語です。
folder = store.getFolder("INBOX");
} catch (MessagingException e) {
System.out.println("メール受信: INBOXフォルダ取得に失敗しました。"
+ e.toString());
return;
}
try {
folder.open(Folder.READ_ONLY);
} catch (MessagingException e) {
System.out
.println("メール受信: フォルダオープンに失敗しました。" + e.toString());
return;
}
// メッセージ一覧を取得
try {
final Message messages[] = folder.getMessages();
for (int index = 0; index < messages.length; index++) {
final Message message = messages[index];
// このAPI利用範囲であれば TOPコマンド止まりで、RETRコマンドは送出されない。
System.out.println("Subject: " + message.getSubject());
System.out.println(" Date: " + message.getSentDate().toString());
// TODO 0番目の配列アクセスをおこなっている点に注意。
final InternetAddress addrFrom = (InternetAddress) message.getFrom()[0];
System.out.println(" From: " + addrFrom.getAddress());
// MimeUtility.decodeText(addrFrom.getPersonal())
// To: を表示。
final Address[] addrsTo = message
.getRecipients(RecipientType.TO);
for (int loop = 0; loop < addrsTo.length; loop++) {
final InternetAddress addrTo = (InternetAddress) addrsTo[loop];
System.out.println(" To: " + addrTo.getAddress());
}
// Cc:は割愛
// なお、例えば message.getContentを呼び出すと RETRコマンドが送出される。
}
} catch (MessagingException e) {
System.out.println("メール受信: メッセージ取得に失敗しました。" + e.toString());
return;
}
} finally {
if (folder != null) {
try {
folder.close(false);
} catch (MessagingException e) {
System.out.println("メール受信: フォルダクローズに失敗しました。"
+ e.toString());
}
}
}
} finally {
if (store != null) {
try {
store.close();
} catch (MessagingException e) {
System.out.println("メール受信: サーバ切断に失敗しました。" + e.toString());
}
}
}
}
}
アイデアメモ
メールの Subjectをもとに、自動処理すべきメールかどうかを判断する方法が考えられます。
例: [blanco] という文字が Subjectに入っていれば処理を行う。
関連する日記
次に メールの内容を読み込む+読んだメールを削除するサンプルを作成しました。なお、このサンプルは、メール削除の処理が含まれています。サンプル実行に際しては、メールが削除されるという点を予め理解してください。意味が分からない場合には、このサンプルは実行しないでください。
※注意: このサンプルを実行すると、メールサーバ上のメールが削除されます!明示的に DELETEフラグをセットしています。 SimpleRecvMail.java
import java.io.IOException;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.Authenticator;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
/**
* 本文も読むメール受信サンプル。
*
* 注意: このサンプルを実行すると、メールサーバ上のメールが削除されます!
*/
public class SimpleRecvMail {
public static void main(final String[] args) {
System.out.println("メール受信: 開始");
new SimpleRecvMail().process();
System.out.println("メール受信: 終了");
}
public void process() {
final Properties props = new Properties();
// 基本情報。ここでは gmailへの接続例を示します。
props.setProperty("mail.pop3.host", "pop.gmail.com");
props.setProperty("mail.pop3.port", "995");
// タイムアウト設定
props.setProperty("mail.pop3.connectiontimeout", "60000");
props.setProperty("mail.pop3.timeout", "60000");
// SSL関連設定
props.setProperty("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.setProperty("mail.pop3.socketFactory.fallback", "false");
props.setProperty("mail.pop3.socketFactory.port", "995");
final Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("受信者○○○@gmail.com", "password");
}
});
// デバッグを行います。標準出力にトレースが出ます。
session.setDebug(true);
Store store = null;
try {
try {
store = session.getStore("pop3");
} catch (NoSuchProviderException e) {
System.out.println("メール受信: 指定プロバイダ[pop3]の取得に失敗しました。"
+ e.toString());
return;
}
try {
store.connect();
} catch (AuthenticationFailedException e) {
System.out.println("メール受信: サーバ接続時に認証に失敗しました。" + e.toString());
return;
} catch (MessagingException e) {
System.out.println("メール受信: サーバ接続に失敗しました。" + e.toString());
return;
}
Folder folder = null;
try {
try {
// INBOXは予約語です。
folder = store.getFolder("INBOX");
} catch (MessagingException e) {
System.out.println("メール受信: INBOXフォルダ取得に失敗しました。"
+ e.toString());
return;
}
try {
// 読み書きモードでオープン
folder.open(Folder.READ_WRITE);
} catch (MessagingException e) {
System.out
.println("メール受信: フォルダオープンに失敗しました。" + e.toString());
return;
}
// メッセージ一覧を取得
try {
final Message messages[] = folder.getMessages();
for (int index = 0; index < messages.length; index++) {
final Message message = messages[index];
// このAPI利用範囲であれば TOPコマンド止まりで、RETRコマンドは送出されない。
System.out.println("Subject: " + message.getSubject());
System.out.println(" Date: "
+ message.getSentDate().toString());
// TODO 0番目の配列アクセスをおこなっている点に注意。
final InternetAddress addrFrom = (InternetAddress) message.getFrom()[0];
System.out.println(" From: " + addrFrom.getAddress());
// MimeUtility.decodeText(addrFrom.getPersonal())
// To: を表示。
final Address[] addrsTo = message.getRecipients(RecipientType.TO);
for (int loop = 0; loop < addrsTo.length; loop++) {
final InternetAddress addrTo = (InternetAddress) addrsTo[loop];
System.out.println(" To: " + addrTo.getAddress());
}
// Cc:は割愛
try {
// RETRコマンドを送出
System.out.println(message.getContent());
} catch (IOException e) {
e.printStackTrace();
}
// 読み込んだメールについては削除マークします。
// ※これで読み込み済みメールが削除されます。
message.setFlag(Flags.Flag.DELETED, true);
}
} catch (MessagingException e) {
System.out.println("メール受信: メッセージ取得に失敗しました。" + e.toString());
return;
}
} finally {
if (folder != null) {
try {
// 削除マークされたメールを実際に削除
folder.close(true);
} catch (MessagingException e) {
System.out.println("メール受信: フォルダクローズに失敗しました。"
+ e.toString());
}
}
}
} finally {
if (store != null) {
try {
store.close();
} catch (MessagingException e) {
System.out.println("メール受信: サーバ切断に失敗しました。" + e.toString());
}
}
}
}
}
添付ファイル付きのメール受信サンプルを作成しました。 変更点のみ
import java.io.IOException;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.Authenticator;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.NoSuchProviderException;
import javax.mail.Part;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeUtility;…中略…
// 変更点: 開始
// なお、message.getContentを呼び出すので、結果的に RETRコマンドが送出される。
try {
final Object objContent = message.getContent();
if (objContent instanceof Multipart) {
final Multipart multiPart = (Multipart) objContent;
for (int indexPart = 0; indexPart < multiPart.getCount(); indexPart++) {
final Part part = multiPart.getBodyPart(indexPart);
final String disposition = part.getDisposition();
if (Part.ATTACHMENT.equals(disposition)
|| Part.INLINE.equals(disposition)) {
System.out.println("添付ファイル: ファイル名["
+ MimeUtility.decodeText(part
.getFileName()) + "]");
// 本当はここでストリーム読み込み処理を行う。
// part.getInputStream();
} else {
System.out.println("メール本文(添付ファイル付) ["
+ part.getContent().toString()
+ "]");
}
}
} else {
System.out.println("メール本文[" + objContent.toString() + "]");
}
} catch (IOException e) {
e.printStackTrace();
}
// 変更点: 終了
※このソースコードの差分は、受信後削除をおこなわない版 (最初に掲載した方) のソースコードに組み込んで 簡単な動作確認を行いました。 blancoMail的に考えると、添付ファイルの処理まで含めた最適解としては、一度の処理では ひとつのメールしか受信できないように仕様のしばりを加えておいたほうが安全かなぁ、などと考えています。(トランザクション粒度)
ふう、、、それにしても メール受信は 結構手間がかかり 面倒ですね…。様々な環境から送られてくるメールをもって試験をしないと 妥当性が確認取れないと考えます。
気になる点 (TODO)