JavaMail IMAP przez SSL dość wolno-zbiorcze pobieranie wielu wiadomości

Obecnie próbuję używać JavaMail do otrzymywania wiadomości e-mail z serwerów IMAP (Gmail i inne). Zasadniczo mój kod działa: rzeczywiście mogę uzyskać nagłówki, zawartość ciała i tak dalej. Mój problem jest następujący: podczas pracy na serwerze IMAP (bez SSL) przetwarzanie wiadomości zajmuje w zasadzie 1-2ms. Gdy wchodzę na serwer IMAPS (stąd Z SSL, np. Gmail) osiągam około 250m / wiadomość. Mierzę tylko czas przetwarzania wiadomości (połączenie, uścisk dłoni i takie nie są brane pod uwagę konto).

Wiem, że ponieważ jest to SSL, dane są szyfrowane. Jednak czas na odszyfrowanie nie powinien być aż tak ważny, prawda?

Próbowałem ustawić wyższą wartość ServerCacheSize, wyższą connectionpoolsize, ale poważnie kończą mi się pomysły. Ktoś miał do czynienia z tym problemem? Można mieć nadzieję, że rozwiązał?

Obawiam się, że API JavaMail używa innego połączenia za każdym razem, gdy pobiera pocztę z serwera IMAPS (włączając w to nagłówek dla uścisk dłoni...). Jeśli tak, to czy istnieje sposób na nadpisanie tego zachowania?

Oto Mój kod (choć dość standardowy) wywoływany z klasy 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;
}
Z góry dzięki.
Author: Stan, 2011-11-30

3 answers

Po wielu pracach i pomocy ze strony ludzi w JavaMail, źródłem tej "powolności" jest zachowanie pobierania w API. Rzeczywiście, jak powiedział pjaol, wracamy do serwera za każdym razem, gdy potrzebujemy informacji (nagłówka lub treści wiadomości) dla wiadomości.

Jeśli fetchprofile pozwala na zbiorcze pobieranie informacji nagłówkowych lub FLAG dla wielu wiadomości, pobieranie zawartości wielu wiadomości nie jest bezpośrednio możliwe.

Na szczęście możemy napisać własne polecenie IMAP, aby tego uniknąć "ograniczenie" (zostało to zrobione w ten sposób, aby uniknąć błędów z pamięci: pobieranie każdej poczty w pamięci jednym poleceniem może być dość ciężkie).

Oto Mój kod:

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);
    }
}

Funkcja getContents (mimemessage mm, int i) jest klasyczną funkcją, która rekurencyjnie drukuje zawartość wiadomości do pliku(wiele przykładów dostępnych w sieci).

Aby uniknąć błędów z pamięci, po prostu ustawiłem limit maxDocs i maxSize (zostało to zrobione arbitralnie i prawdopodobnie można je poprawić!) używane w następujący sposób:

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;
}

Nie, że tutaj używam numerów wiadomości, co jest niestabilne (te zmiany, jeśli wiadomości są usuwane z serwera). Lepszą metodą byłoby użycie UIDs! Następnie zmienisz polecenie z FETCH na UID FETCH.

Mam nadzieję, że to pomoże!
 23
Author: Justmaker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-23 13:05:37

Musisz dodać FetchProfile do skrzynki odbiorczej, zanim przejdziesz przez wiadomości. Wiadomość jest leniwym obiektem ładowania, dla każdej wiadomości i dla każdej pole, które nie jest dostarczane z profilem domyślnym. np.

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

Jeśli chcesz wyświetlić listę w skrzynce odbiorczej powiedz tylko From, Subject, Sent, Attachement itp.. możesz użyć czegoś takiego jak następujące

    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
    }
Mam nadzieję, że to pomoże.
 14
Author: pjaol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-03 07:07:48

Całkowity czas obejmuje czas wymagany w operacjach kryptograficznych. Operacje kryptograficzne wymagają losowego siewnika. Istnieją różne implementacje losowego zaszeregowania, które zapewniają losowe bity do wykorzystania w kryptografii. Domyślnie Java używa /dev / urandomi jest to określone w Twoim java.Bezpieczeństwo Jak poniżej:

securerandom.source=file:/dev/urandom

W systemie Windows java wykorzystuje funkcjonalność Microsoft CryptoAPI seed, która zwykle nie ma problemów. Jednak na unix i linux, Java, domyślnie używa / dev / random do losowego siewu. I odczyt operacji na /dev / random czasami blokuje i trwa długo. Jeśli korzystasz z platform *nix, czas spędzony na tym będzie liczony w całym czasie.

Ponieważ Nie wiem, jakiej platformy używasz, nie mogę na pewno powiedzieć, że to może być twój problem. Ale jeśli tak, to może to być jeden z powodów, dla których twoje operacje trwają długo. Jednym z rozwiązań może być użycie /dev/urandom zamiast /dev/random jako losowy siewnik, który nie blokuje. Można to określić za pomocą właściwości systemowej " java.Ochrona.egd". Na przykład,

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

Podanie tej właściwości systemowej nadpisze securerandom.ustawienie źródła w Javie.akta bezpieczeństwa. Możesz spróbować. Mam nadzieję, że to pomoże.

 1
Author: Drona,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-30 09:38:23