JavaMail IMAP sobre SSL es bastante lento: la obtención masiva de varios mensajes

Actualmente estoy tratando de usar JavaMail para obtener correos electrónicos de servidores IMAP (Gmail y otros). Básicamente, mi código funciona: de hecho puedo obtener los encabezados, el contenido del cuerpo, etc. Mi problema es el siguiente: cuando se trabaja en un servidor IMAP (sin SSL), básicamente se necesitan entre 1 y 2 meses para procesar un mensaje. Cuando uso un servidor IMAPS (por lo tanto, con SSL, como Gmail) alcanzo unos 250 m / mensaje. ÚNICAMENTE mido el tiempo al procesar los mensajes (la conexión, el saludo de mano y otros NO se tienen en cuenta).

Sé que, dado que esto es SSL, los datos están encriptados. Sin embargo, el momento de descifrado no debería ser tan importante, ¿o sí?

Intenté establecer un valor de ServerCacheSize más alto, un tamaño de conexión de conexión mayor, pero me estoy quedando sin ideas. ¿Alguien confrontado con este problema? Lo resolvió uno podría esperar?

Mi temor es que la API de JavaMail use una conexión diferente cada vez que recupera un correo del servidor IMAPS (que implica la sobrecarga para el intercambio de información …). Si es así, ¿hay alguna manera de anular este comportamiento?

Aquí está mi código (aunque bastante estándar) llamado desde la clase 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; } 

Gracias por adelantado.

después de mucho trabajo y la asistencia de la gente de JavaMail, el origen de esta “lentitud” proviene del comportamiento FETCH en la API. De hecho, como dijo pjaol, volvemos al servidor cada vez que necesitamos información (un encabezado o contenido del mensaje) para un mensaje.

Si FetchProfile nos permite obtener información del encabezado, o flags, para muchos mensajes, obtener contenido de múltiples mensajes NO es posible directamente.

Afortunadamente, podemos escribir nuestro propio comando IMAP para evitar esta “limitación” (se hizo de esta manera para evitar errores de falta de memoria: recuperar cada correo en la memoria en un comando puede ser bastante pesado).

Aquí está mi código:

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

la función getContents (MimeMessage mm, int i) es una función clásica que imprime recursivamente los contenidos del mensaje en un archivo (muchos ejemplos disponibles en la red).

Para evitar errores de falta de memoria, simplemente establezco un límite de maxDocs y maxSize (esto se ha hecho de forma arbitraria y probablemente pueda mejorarse) usando de la siguiente manera:

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

No es que aquí estoy usando números de mensajes, que es inestable (estos cambian si los mensajes se borran del servidor). ¡Un mejor método sería usar UID! Luego, cambiaría el comando de FETCH a UID FETCH.

Espero que esto ayude!

Necesita agregar un FetchProfile a la bandeja de entrada antes de repetir los mensajes. El mensaje es un objeto de carga diferido, regresará al servidor por cada mensaje y por cada campo que no se proporcione con el perfil predeterminado. p.ej

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

Si desea mostrar como una lista de la bandeja de entrada de decir solo de, Asunto, Enviado, Adjunto, etc., usaría algo como lo siguiente

  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 } 

Espero que ayude.

El tiempo total incluye el tiempo requerido en operaciones criptográficas. Las operaciones criptográficas necesitan una sembradora aleatoria. Existen diferentes implementaciones de siembra aleatoria que proporcionan bits aleatorios para usar en la criptografía. Por defecto, Java usa / dev / urandom y esto se especifica en su java.security como se muestra a continuación:

 securerandom.source=file:/dev/urandom 

En Windows, Java usa la funcionalidad inicial de Microsoft CryptoAPI, que generalmente no tiene problemas. Sin embargo, en Unix y Linux, Java usa de forma predeterminada / dev / random para la creación aleatoria. Y las operaciones de lectura en / dev / random algunas veces se bloquean y toma mucho tiempo en completarse. Si está utilizando las plataformas * nix, el tiempo invertido en esto se contabilizará en el tiempo total.

Como no sé qué plataforma está utilizando, no puedo asegurar que este sea su problema. Pero si lo es, entonces esta podría ser una de las razones por las cuales sus operaciones tardan mucho tiempo. Una de las soluciones podría ser utilizar / dev / urandom en lugar de / dev / random como su semilla al azar, que no bloquea. Esto se puede especificar con la propiedad del sistema “java.security.egd”. Por ejemplo,

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

La especificación de esta propiedad del sistema anulará la configuración de securerandom.source en su archivo java.security. Puedes darle una oportunidad. Espero eso ayude.