web-dev-qa-db-ja.com

JavaMail IMAP overSSLが非常に遅い-複数のメッセージの一括フェッチ

現在、JavaMailを使用してIMAPサーバー(Gmailなど)からメールを取得しようとしています。基本的に、私のコードは機能します。実際、ヘッダーや本文の内容などを取得できます。私の問題は次のとおりです。IMAPサーバー(SSLなし)で作業している場合、メッセージの処理には基本的に1〜2ミリ秒かかります。 IMAPSサーバー(GmailなどのSSLを使用)にアクセスすると、メッセージあたり約250mに達します。私はメッセージを処理する時間だけを測定します(接続、ハンドシェイクなどは考慮されません)。

これはSSLなので、データは暗号化されていることを私は知っています。ただし、復号化の時間はそれほど重要ではありませんね。

より高いServerCacheSize値、より高いconnectionpoolsizeを設定しようとしましたが、真剣にアイデアが不足しています。この問題に直面した人はいますか?希望するかもしれないそれを解決しましたか?

私の恐れは、JavaMail APIがIMAPSサーバーからメールをフェッチするたびに異なる接続を使用することです(ハンドシェイクのオーバーヘッドが含まれます...)。もしそうなら、この動作をオーバーライドする方法はありますか?

Main()クラスから呼び出された私のコード(かなり標準的ですが)は次のとおりです。

 public static int connectTest(String SSL, String user, String pwd, String Host) throws IOException,
                                                                               ProtocolException,
                                                                               GeneralSecurityException {

    Properties props = System.getProperties();
    props.setProperty("mail.store.protocol", SSL);
    props.setProperty("mail.imaps.ssl.trust", Host);
    props.setProperty("mail.imaps.connectionpoolsize", "10");

    try {


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

        // session.setDebug(true);

        Store store = session.getStore(SSL);
        store.connect(Host, user, pwd);      
        Folder inbox = store.getFolder("INBOX");

        inbox.open(Folder.READ_ONLY);                
        int numMess = inbox.getMessageCount();
        Message[] messages = inbox.getMessages();

        for (Message m : messages) {

            m.getAllHeaders();
            m.getContent();
        }

        inbox.close(false);
        store.close();
        return numMess;
    } catch (MessagingException e) {
        e.printStackTrace();
        System.exit(2);
    }
    return 0;
}

前もって感謝します。

21
Justmaker

多くの作業とJavaMailの人々の支援の後、この「遅さ」の原因はAPIのFETCH動作にあります。実際、pjaolが言ったように、メッセージの情報(ヘッダーまたはメッセージコンテンツ)が必要になるたびにサーバーに戻ります。

FetchProfileで多くのメッセージのヘッダー情報またはフラグを一括フェッチできる場合、複数のメッセージのコンテンツを直接取得することはできません。

幸い、この「制限」を回避するために独自のIMAPコマンドを作成できます(メモリ不足エラーを回避するためにこの方法で実行されました。1つのコマンドでメモリ内のすべてのメールをフェッチすると非常に重い場合があります)。

これが私のコードです:

import com.Sun.mail.iap.Argument;
import com.Sun.mail.iap.ProtocolException;
import com.Sun.mail.iap.Response;
import com.Sun.mail.imap.IMAPFolder;
import com.Sun.mail.imap.protocol.BODY;
import com.Sun.mail.imap.protocol.FetchResponse;
import com.Sun.mail.imap.protocol.IMAPProtocol;
import com.Sun.mail.imap.protocol.UID;

public class CustomProtocolCommand implements IMAPFolder.ProtocolCommand {
    /** Index on server of first mail to fetch **/
    int start;

    /** Index on server of last mail to fetch **/
    int end;

    public CustomProtocolCommand(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public Object doCommand(IMAPProtocol protocol) throws ProtocolException {
        Argument args = new Argument();
        args.writeString(Integer.toString(start) + ":" + Integer.toString(end));
        args.writeString("BODY[]");
        Response[] r = protocol.command("FETCH", args);
        Response response = r[r.length - 1];
        if (response.isOK()) {
            Properties props = new Properties();
            props.setProperty("mail.store.protocol", "imap");
            props.setProperty("mail.mime.base64.ignoreerrors", "true");
            props.setProperty("mail.imap.partialfetch", "false");
            props.setProperty("mail.imaps.partialfetch", "false");
            Session session = Session.getInstance(props, null);

            FetchResponse fetch;
            BODY body;
            MimeMessage mm;
            ByteArrayInputStream is = null;

            // last response is only result summary: not contents
            for (int i = 0; i < r.length - 1; i++) {
                if (r[i] instanceof IMAPResponse) {
                    fetch = (FetchResponse) r[i];
                    body = (BODY) fetch.getItem(0);
                    is = body.getByteArrayInputStream();
                    try {
                        mm = new MimeMessage(session, is);
                        Contents.getContents(mm, i);
                    } catch (MessagingException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        // dispatch remaining untagged responses
        protocol.notifyResponseHandlers(r);
        protocol.handleResult(response);

        return "" + (r.length - 1);
    }
}

getContents(MimeMessage mm、int i)関数は、メッセージの内容をファイルに再帰的に出力する古典的な関数です(ネット上で入手可能な多くの例)。

メモリ不足エラーを回避するために、次のように使用するmaxDocsおよびmaxSize制限を設定するだけです(これは任意に行われ、おそらく改善できます!)。

public int efficientGetContents(IMAPFolder inbox, Message[] messages)
        throws MessagingException {
    FetchProfile fp = new FetchProfile();
    fp.add(FetchProfile.Item.FLAGS);
    fp.add(FetchProfile.Item.ENVELOPE);
    inbox.fetch(messages, fp);
    int index = 0;
    int nbMessages = messages.length;
    final int maxDoc = 5000;
    final long maxSize = 100000000; // 100Mo

    // Message numbers limit to fetch
    int start;
    int end;

    while (index < nbMessages) {
        start = messages[index].getMessageNumber();
        int docs = 0;
        int totalSize = 0;
        boolean noskip = true; // There are no jumps in the message numbers
                                           // list
        boolean notend = true;
        // Until we reach one of the limits
        while (docs < maxDoc && totalSize < maxSize && noskip && notend) {
            docs++;
            totalSize += messages[index].getSize();
            index++;
            if (notend = (index < nbMessages)) {
                noskip = (messages[index - 1].getMessageNumber() + 1 == messages[index]
                        .getMessageNumber());
            }
        }

        end = messages[index - 1].getMessageNumber();
        inbox.doCommand(new CustomProtocolCommand(start, end));

        System.out.println("Fetching contents for " + start + ":" + end);
        System.out.println("Size fetched = " + (totalSize / 1000000)
                + " Mo");

    }

    return nbMessages;
}

ここでは不安定なメッセージ番号を使用していることに注意してください(これらはサーバーからメッセージが消去されると変更されます)。より良い方法は、UIDを使用することです!次に、コマンドをFETCHからUIDFETCHに変更します。

これがお役に立てば幸いです。

24
Justmaker

メッセージを繰り返す前に、FetchProfileを受信ボックスに追加する必要があります。メッセージは遅延読み込みオブジェクトであり、メッセージごと、およびデフォルトのプロファイルで提供されないフィールドごとにサーバーに返されます。例えば.

for (Message message: messages) {
  message.getSubject(); //-> goes to the imap server to fetch the subject line
}

From、Subject、Sent、Attachementなどの受信トレイリストのように表示したい場合は、次のようなものを使用します

    inbox.open(Folder.READ_ONLY);
    Message[] messages = inbox.getMessages(start + 1, total);

    FetchProfile fp = new FetchProfile();
    fp.add(FetchProfile.Item.ENVELOPE);
    fp.add(FetchProfileItem.FLAGS);
    fp.add(FetchProfileItem.CONTENT_INFO);

    fp.add("X-mailer");
    inbox.fetch(messages, fp); // Load the profile of the messages in 1 fetch.
    for (Message message: messages) {
       message.getSubject(); //Subject is already local, no additional fetch required
    }

お役に立てば幸いです。

17
pjaol

合計時間には、暗号化操作に必要な時間が含まれます。暗号化操作にはランダムシーダーが必要です。暗号化で使用するためのランダムビットを提供するさまざまなランダムシードの実装があります。デフォルトでは、Javaは/ dev/urandomを使用します。これはJava.securityで次のように指定されます。

securerandom.source=file:/dev/urandom

Windowsでは、Javaは通常問題のないMicrosoftCryptoAPIシード機能を使用します。ただし、UNIXおよびLinuxでは、Javaはデフォルトでランダムシードに/ dev/randomを使用します。 。そして/ dev/randomでの読み取り操作はブロックされ、完了するまでに長い時間がかかることがあります。* nixプラットフォームを使用している場合、これに費やされた時間は全体の時間にカウントされます。

使用しているプラ​​ットフォームがわからないので、これが問題になる可能性があるとは言えません。しかし、そうであれば、これが操作に時間がかかる理由の1つである可能性があります。これに対する解決策の1つは、ブロックしないランダムシーダーとして/ dev/randomの代わりに/ dev/urandomを使用することです。これは、システムプロパティ「Java.security.egd」で指定できます。例えば、

  -Djava.security.egd=file:/dev/urandom

このシステムプロパティを指定すると、Java.securityファイルのsecurerandom.source設定が上書きされます。あなたはそれを試してみることができます。それが役に立てば幸い。

1
Drona