JavaMail IMAP через SSL довольно медленный - массовая загрузка нескольких сообщений - PullRequest
21 голосов
/ 30 ноября 2011

В настоящее время я пытаюсь использовать JavaMail для получения электронной почты с серверов IMAP (Gmail и других). По сути, мой код работает: я действительно могу получить заголовки, содержимое тела и так далее. Моя проблема заключается в следующем: при работе на сервере IMAP (без SSL) обработка сообщения в основном занимает 1-2 мс. Когда я захожу на сервер IMAPS (следовательно, с SSL, например, Gmail), я достигаю около 250 м / сообщение. Я ТОЛЬКО измеряю время при обработке сообщений (соединение, рукопожатие и т.п. НЕ учитываются).

Я знаю, что поскольку это SSL, данные зашифрованы. Однако время для расшифровки не должно быть таким важным, не так ли?

Я попытался установить более высокое значение ServerCacheSize, более высокий размер соединения, но у меня серьезно заканчиваются идеи. Кто-нибудь сталкивался с этой проблемой? Решил, можно ли надеяться?

Я боюсь, что API JavaMail использует разные соединения каждый раз, когда он получает почту с сервера 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;
}

Заранее спасибо.

Ответы [ 3 ]

24 голосов
/ 08 марта 2012

после большой работы и помощи людей из JavaMail, источником этой "медлительности" является поведение FETCH в API.Действительно, как сказал pjaol, мы возвращаемся на сервер каждый раз, когда нам нужна информация (заголовок или содержимое сообщения) для сообщения.

Если FetchProfile позволяет нам массово извлекать информацию заголовка или флаги для многих сообщений, получение содержимого нескольких сообщений НЕ возможно напрямую.

К счастью, мы можем написать нашу собственную команду IMAP, чтобы избежать этого «ограничения» (это было сделано таким образом, чтобы избежать ошибок нехватки памяти: загрузка каждой почты в памяти одной командой может быть довольно тяжелой).

Вот мой код:

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 на UID FETCH.

Надеюсь, это поможет!

16 голосов
/ 03 февраля 2012

Вам необходимо добавить FetchProfile в папку «Входящие», прежде чем выполнять итерацию по сообщениям. Сообщение является отложенным загрузочным объектом, оно будет возвращаться на сервер для каждого сообщения и для каждого поле, которое не предоставляется с профилем по умолчанию. например,

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

Если вы хотите отобразить список входящих сообщений, например, «От», «Тема», «Отправлено», «Вложение» и т. Д., Вы должны использовать что-то вроде следующего

    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
    }

Надеюсь, это поможет.

1 голос
/ 30 ноября 2011

Общее время включает время, необходимое для криптографических операций.Криптографические операции нуждаются в случайной сеялке.Существуют различные реализации случайного заполнения, которые предоставляют случайные биты для использования в криптографии.По умолчанию Java использует / dev / urandom , и это указано в вашем java.security , как показано ниже:

securerandom.source=file:/dev/urandom

В Windows, java использует Microsoft CryptoAPI seedфункциональность, которая обычно не имеет проблем.Однако в unix и linux Java по умолчанию использует / dev / random для случайного заполнения.И операции чтения в / dev / random иногда блокируются и для их завершения требуется много времени.Если вы используете платформы * nix, то время, потраченное на это, будет учитываться в общем времени.

Поскольку я не знаю, какую платформу вы используете, я точно не могу сказать, что это может бытьтвоя проблема.Но если это так, то это может быть одной из причин, почему ваши операции занимают много времени.Одним из решений этой проблемы может быть использование / dev / urandom вместо / dev / random в качестве случайной сеялки, которая не блокируется.Это можно указать с помощью системного свойства "java.security.egd".Например,

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

Указание этого системного свойства переопределит параметр securerandom.source в вашем файле java.security.Вы можете попробовать.Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...