Tengo que ir rápido. Optimizar las solicitudes de contenido de correo electrónico IMAP

¡Hola a todos! En un artículo anterior, hablé sobre cómo puede sincronizar rápidamente el contenido de un cuadro en la memoria caché local. Aquí quiero hablar sobre las características de solicitar el contenido de las cartas y la mejor forma de solicitar contenido, sin tener miedo al alto consumo de tráfico.

imagen

Recordemos rápidamente lo que aprendimos en el último artículo:

  • IMAP es un protocolo con estado
  • Para ver el contenido de la bandeja de entrada, primero debe seleccionarlo con el comando SELECCIONAR
  • Para sincronizar rápidamente el cuadro en el que estamos, puede usar el comando NOOP
  • Para no ordenar los mensajes del almacenamiento local para actualizar el buzón que ya nos queda, puede usar CONDSTORE y QRESYNC, siempre que su servidor admita los datos de extensión de protocolo

¡Suficiente!


Déjame recordarte el comando para solicitar el cuerpo de la carta:

1 FETCH number (BODY[])

Esto creará una solicitud para obtener todo el cuerpo de la carta y todos los archivos adjuntos. Solo vea cuánto tarda en llegar un mensaje en 42 párrafos de Lorem Ipsum y con una imagen de 2 megabytes.

Primero, pregunte el tamaño del mensaje en el servidor. Esto se hace mediante el comando:

1 FETCH 18871 (RFC822.SIZE)

RFC822.SIZE devuelve el tamaño del mensaje en bytes:

* 18871 FETCH (RFC822.SIZE 3937793)

Es decir, como resultado, nuestro mensaje ocupa casi 4 megabytes.

Ahora, sin embargo, utilizaremos la solicitud del cuerpo completo de la carta y echaremos un vistazo a la hora:

1 OK Fetch completed (0.007 + 3.265 secs).

3,3 segundos! Y este es solo un mensaje con el archivo adjunto, e imagínense que tal será el cuadro completo. Luego, llevará más de un minuto descargar al menos las veintiuna.

Debe admitir que el negocio de un cliente que en 2020 no puede sincronizar el correo más rápido que en un minuto es malo. ¿Pero qué hacer?

Dame un bocado una vez


Si susurra RFC3501 en la cláusula 6.5.4 , que describe los posibles parámetros para el comando FETCH, notará una solicitud interesante:

BODY[<section>]<<partial>>

  • sección: qué parte de la carta obtener
  • parcial - el tamaño de esta parte

¿Cómo se construye parcial? Y es muy fácil. Primero, el byte desde el que necesita comenzar a leer se escribe a través del punto, y luego cuántos bytes en general se deben leer:

BODY[<section>]<<0.1024>>

Aquí solicitamos la parte de la letra desde cero byte hasta 1024.

Bien, ¿qué es la sección? Primero, hablaré sobre un parámetro tan útil en una consulta FETCH como BODYSTRUCTURE:

1 FETCH 18871 (BODYSTRUCTURE)

Este parámetro, como probablemente entendió de la firma, devuelve la estructura de la letra en la forma descrita en MIME-IMB .

* 18871 FETCH (BODYSTRUCTURE ((("text" "plain" ("charset" "utf-8") NIL NIL "quoted-printable" 25604 337 NIL NIL NIL NIL)("text" "html" ("charset" "utf-8") NIL NIL "quoted-printable" 29593 390 NIL NIL NIL NIL) "alternative" ("boundary" "--=_Part_763_774309787.1586268692") NIL NIL NIL)("image" "jpeg" ("name" "IMG_20200217_000236.jpg") NIL NIL "base64" 3880726 NIL ("attachment" ("filename" "IMG_20200217_000236.jpg")) NIL NIL) "mixed" ("boundary" "--=_Part_210_297656922.1586268692") NIL NIL NIL))


Solo un vistazo a esta estructura, ¿y tu cabeza está girando? No tengas miedo, ahora lo resolveremos. Compare los soportes de apertura y cierre.

(
BODYSTRUCTURE 
(
[1] (
[1.1] ("text" "plain" ("charset" "utf-8") NIL NIL "quoted-printable" 25604 337 NIL NIL NIL NIL)
[1.2] ("text" "html" ("charset" "utf-8") NIL NIL "quoted-printable" 29593 390 NIL NIL NIL NIL) "alternative" ("boundary" "--=_Part_763_774309787.1586268692") NIL NIL NIL
)
[2] ("image" "jpeg" ("name" "IMG_20200217_000236.jpg") NIL NIL "base64" 3880726 NIL ("attachment" ("filename" "IMG_20200217_000236.jpg")) NIL NIL) "mixed" ("boundary" "--=_Part_210_297656922.1586268692") NIL NIL NIL
)
)


Puede notar que pongo números cerca de algunos corchetes. Esta es la seccion.
¿Cómo calcularlos? El primer paréntesis debe omitirse, ya que simplemente contiene la respuesta a la solicitud, luego cada paréntesis de apertura debe numerarse de acuerdo con la regla, ya que los títulos de los documentos están numerados:

  • Numeramos cada soporte de apertura teniendo en cuenta la sección anterior
  • Si la sección está anidada, la actual a través del punto se agrega al número anterior
  • Si la sección no está anidada, aumente su número en uno


Por ejemplo, en este caso, en la primera parte que termina con “alternativa” (es decir, esta es la parte de la carta multiparte / alternativa, donde somos libres de elegir qué partes mostrar al usuario), hay dos secciones que están numeradas a través de un punto. Conocí un servidor donde puede haber anidamiento de tres niveles (es decir, [1.1.1], [1.1.2], etc.).
Analicemos la parte [1.1] mirando la estructura de todo esto en el documento MIME-IMB . A juzgar por ello, el encabezado Content-Type es el primero. Incluye:

  • Tipo MIME, aquí está text / plain
  • Codificación (charset = utf8)


Los siguientes son dos parámetros que se escriben como NIL. Francamente, no entendí lo que era, pero hasta ahora no lo he necesitado, así que lo extrañaré. Pido disculpas por tal frivolidad.
Luego está el encabezado Content-Transfer-Encoding, se incluye en él, que describe el mecanismo de codificación, aquí se puede imprimir. 
Los siguientes dos números describen el tamaño de la parte en bytes y el número de líneas, si es posible. Con su ayuda, podemos calcular cuántos bytes tomar para mostrar un cierto número de líneas.
Las siguientes líneas que no están en esta parte:

  • Content-Id, que se usa en la línea de la carta
  • Descripción del contenido, una línea que describe qué es esta parte


Para los otros dos parámetros, no pude encontrar una respuesta definitiva de qué se trata, pero uno de estos parámetros puede contener partes MD5, que a veces pueden ser útiles.
Para la parte [2], todo es igual, excepto que es una imagen, un archivo adjunto con un nombre y una codificación base64. Si aún no está completamente claro lo que está sucediendo aquí, entonces en este sitio se presenta perfectamente cómo calcular la sección.

Que da Y el hecho de que en la etapa de mostrar la carta ya podemos solicitar solo la parte superior del contenido y no cargar los archivos adjuntos hasta que el propio usuario ingrese el mensaje y haga clic en el botón "descargar". Toda la información para mostrar archivos adjuntos nos llega en BODYSCTRUCTURE para que el nombre, el formato y el tamaño se puedan mostrar sin cargar el archivo adjunto. 

Pasemos a practicar. Solicitaremos un kilobyte de contenido del mensaje sin archivos adjuntos, solo para saber lo que nos enviaron.

1 fetch 18871 (body[1.1]<0.1024>)
* 18871 FETCH (BODY[1.1]<0> {1024}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus consecte=
tur enim in nisi venenatis, id varius tellus viverra. Praesent et enim te=
llus. Nunc vestibulum diam tortor, id posuere turpis tempor luctus. Vivam=
us molestie non nunc nec placerat. Cras finibus ut erat et tristique. Cur=
abitur vitae commodo risus. Etiam sed scelerisque erat. Quisque cursus bl=
andit finibus. Nullam ac lectus accumsan, molestie quam non, mollis urna.=
 Nulla at arcu in libero condimentum mollis ut non velit. Vestibulum sed =
risus et magna congue iaculis. Vestibulum nec interdum elit, ut commodo m=
auris. Nulla ipsum leo, vestibulum nec ligula non, elementum ullamcorper =
risus. Nunc et malesuada sem, id venenatis massa. Integer dolor ante, max=
imus in eleifend nec, ultricies ut risus. Mauris posuere eget tortor at p=
orttitor.=0AIn porta elementum ornare. Suspendisse aliquam, tortor sed al=
iquam bibendum, nulla ante rhoncus elit, placerat accumsan augue nibh non=
 est. Duis finibus vel tortor finibu)
1 OK Fetch completed (0.073 + 0.000 + 0.072 secs).


¡Unos 100 milisegundos y ya vemos parte del contenido de la carta! Este es solo un excelente resultado, dado que anteriormente nos tomó casi 4 segundos descargar el contenido de una carta. Luego, simplemente puede cargar todo el contenido de la letra en la secuencia de fondo, desde el exterior parecerá que las letras se cargan al instante. Todo lo que se requería era mirar la estructura de la carta y descargar solo lo que se requiere para una visualización rápida. 
Solo un momento. Esta solicitud hará que el mensaje en el servidor aparezca como leído. Pero puede solucionar esto agregando solo PEEK a la solicitud del cuerpo

1 fetch 18871 (BODY.PEEK[1.1]<0.1024>)
* 18871 FETCH (BODY[1.1]<0> {1024}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus consecte=
tur enim in nisi venenatis, id varius tellus viverra. Praesent et enim te=
llus. Nunc vestibulum diam tortor, id posuere turpis tempor luctus. Vivam=
us molestie non nunc nec placerat. Cras finibus ut erat et tristique. Cur=
abitur vitae commodo risus. Etiam sed scelerisque erat. Quisque cursus bl=
andit finibus. Nullam ac lectus accumsan, molestie quam non, mollis urna.=
 Nulla at arcu in libero condimentum mollis ut non velit. Vestibulum sed =
risus et magna congue iaculis. Vestibulum nec interdum elit, ut commodo m=
auris. Nulla ipsum leo, vestibulum nec ligula non, elementum ullamcorper =
risus. Nunc et malesuada sem, id venenatis massa. Integer dolor ante, max=
imus in eleifend nec, ultricies ut risus. Mauris posuere eget tortor at p=
orttitor.=0AIn porta elementum ornare. Suspendisse aliquam, tortor sed al=
iquam bibendum, nulla ante rhoncus elit, placerat accumsan augue nibh non=
 est. Duis finibus vel tortor finibu)
1 OK Fetch completed (0.001 + 0.000 secs).


¡Y voilá! La carta permanece sin leer y parte del contenido que recibimos.
Todo se vuelve aún más fácil si la función de solicitud PREVIEW se implementa en su servidor. 

1 fetch 18871 (PREVIEW)
* 18871 FETCH (PREVIEW (FUZZY "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus consectetur enim in nisi venenatis, id varius tellus viverra. Praesent et enim tellus. Nunc vestibulum diam tortor, id posuere turpis t"))
1 OK Fetch completed (0.001 + 0.000 secs).


Aquí no dedicamos tiempo a consultar la estructura y obtenemos la vista previa del mensaje al instante. Pero no olvide que la estructura de consulta es útil para definir archivos adjuntos para que no se carguen en inactivo.

Espere


Casi cualquier cliente de correo implementa el botón "actualizar" si el usuario desea recibir nuevas cartas en este momento. Pero de alguna manera esto no es bueno para nuestro tiempo, donde hay notificaciones tanto en dispositivos como en navegadores. ¿Qué dice IMAP sobre esto? Y él dice inactivo . Esta operación mantiene la conexión a la carpeta y le notifica los cambios en la carpeta. Tenga en cuenta, no el buzón, sino las carpetas. Para hacer esto, necesita que el servidor implemente la función IDLE. 

Primero, seleccione la carpeta para la cual el servidor enviará alertas, y luego habilite IDLE

1 SELECT Inbox
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded $MDNSent)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded $MDNSent \*)] Flags permitted.
* 18872 EXISTS
* 0 RECENT
* OK [UNSEEN 18685] First unseen.
* OK [UIDVALIDITY 1532079879] UIDs valid
* OK [UIDNEXT 20155] Predicted next UID
* OK [HIGHESTMODSEQ 26338] Highest
1 OK [READ-WRITE] Select completed (0.002 + 0.000 + 0.001 secs).
1 IDLE
+ idling


La respuesta "+ inactivo" notifica sobre la inclusión del inactivo en la carpeta. ¿Qué sucede si llega una nueva carta?

* 18873 EXISTS
* 1 RECENT


Me envié la misma carta e Idle me informó que debía solicitar la carta 18873, que había 18873 cartas en la carpeta y que acababa de llegar una carta.
A continuación, solicitaré esta carta en otra conexión, estamos interesados ​​en la carta con la respuesta EXISTE.

1 fetch 18873 (BODY.PEEK[1.1]<0.1024>)
* 18873 FETCH (BODY[1.1]<0> {1024}
---- Original Message ---- Tue, Apr 7, 2020, 17:11=0ASubject=
: Lorem Ipsum=0A  Lorem ipsum dolor sit amet, consectetur adipiscing elit=
. Vivamus consectetur enim in nisi venenatis, id varius tellus viverra. P=
raesent et enim tellus. Nunc vestibulum diam tortor, id posuere turpis te=
mpor luctus. Vivamus molestie non nunc nec placerat. Cras finibus ut erat=


Es muy importante entenderlo. IDLE requiere una conexión separada, por lo que no puede recibir cambios ni solicitar mensajes en la misma sesión.
¿Qué más puede hacer IDLE? Puede notificar sobre letras borradas y letras cuyas banderas han cambiado. Echemos un vistazo a la letra por el simple hecho de poner la bandera "/ visto" y eliminar la letra.

* 18873 FETCH (FLAGS (\Seen \Recent))
* OK Still here
* OK Still here
* 18873 EXPUNGE
* 18871 EXPUNGE
* 0 RECENT


Eliminé la conversación (18873, 18871) y miré otra carta (respuesta FETCH). ¿Por qué esta carta se convirtió en 18871? Porque IMAP cuenta el número de letra si algo ha cambiado. Desde que se convirtió en el principal, su número también cambió. 
Con IDLE, podemos sincronizar rápidamente el estado de la caja, pero es desagradable que requiera una conexión por separado. ¿Podría ser mejor? Por eso estoy aquí.

Grita como te va


¿Qué pasa si te digo que hay una función que te permite recibir notificaciones del servidor en la misma conexión, e incluso configuradas especialmente para buzones, y más de una? Suena como un cuento de hadas, pero no te vuelvas loco, esta es una capacidad real NOTIFICAR . Él sabe mucho, por ejemplo:

  • Configurar carpetas específicas de las cuales esperamos notificaciones
  • Escuchar los cambios de estado de la carpeta (leer letras, letras nuevas)
  • Establecer el formato de notificación, es decir, lo que queremos ver al cambiar la carpeta
  • Escucha los cambios de nombre de carpeta
  • Escuchar cambios de metadatos de carpeta


Veamos un ejemplo de cómo podemos escuchar los cambios de estado de la carpeta

1 notify set (inboxes (MessageNew FlagChange MessageExpunge))
1 OK NOTIFY completed (0.001 + 0.000 secs).


Ahora el servidor nos enviará notificaciones con estados de carpeta, por ejemplo, agregaré un par de mensajes a diferentes carpetas

* STATUS INBOX/Ozon (MESSAGES 312 UIDNEXT 321 UNSEEN 48)
* STATUS "INBOX/Company News" (MESSAGES 178 UIDNEXT 179 UNSEEN 1)
* STATUS "INBOX/Company News" (MESSAGES 177 UIDNEXT 179 UNSEEN 0)


Analizaré el comando:
Primero viene el comando NOTIFY SET. Luego, entre paréntesis, seleccionamos qué carpetas escucharemos:

  • Bandejas de entrada: para todas las carpetas que puede seleccionar
  • Personal: carpetas que están en el espacio de nombres del usuario
  • Suscrito: carpetas a las que está suscrito el usuario
  • Subárbol: subárbol de la carpeta que se especificará
  • Buzones de correo: aquí puede enumerar las carpetas para escuchar.
  • Seleccionado: alerta solo para las carpetas seleccionadas


Y los parámetros que son responsables del filtro de alerta:

  • Mensaje nuevo: si llega un nuevo mensaje
  • FlagChange - si la bandera ha cambiado
  • MessageExpunge: si el mensaje se eliminó o movió


Pero con dicho comando, no podemos recibir los parámetros de un mensaje nuevo, modificado o eliminado. Para hacer esto, seleccione el parámetro Seleccionado y especifique exactamente qué devolver. Podemos agregar otra alerta sin eliminar la anterior.

1 notify set status (selected (MessageNew (uid preview) MessageExpunge))


Aquí dentro de MessageNew especificamos los parámetros que debe devolver la notificación. Elegiré Inbox y nuevamente me lanzaré a mí mismo lorem ipsum.

* 18868 FETCH (UID 20157 PREVIEW (FUZZY "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus consectetur enim in nisi venenatis, id varius tellus viverra. Praesent et enim tellus. Nunc vestibulum diam tortor, id posuere turpis t"))


¿Te gusta eso? Para el inactivo, necesitamos mantener dos conexiones, una de las cuales también solicita mensajes que el inactivo nos devolvió. Inmediatamente nos traen todo en bandeja de plata. 
Y así podemos escuchar los cambios en el nombre de la carpeta

1 notify set (inboxes (MailboxName))


Cambie el nombre de alguna carpeta y vea el resultado.

* LIST () "/" 1111 ("OLDNAME" (aaaa))


Y ahora sabemos que había una carpeta "aaaa" y se convirtió en "1111".
Ahora puede escuchar el cambio de banderas y la eliminación de mensajes. Para hacer esto, use el parámetro FlagChange

1 notify set (selected (MessageNew (uid) FlagChange MessageExpunge))


Y cuando cambias las banderas de mensajes y las borras, obtenemos

* 18865 EXPUNGE
* 18864 FETCH (FLAGS ())
* 18864 FETCH (FLAGS (\Answered))


Que sigue


Todas estas características ayudan al cliente de correo a trabajar de la manera más rápida y conveniente posible para el usuario. IDLE y NOTIFY notifican al usuario los cambios en las carpetas, solicitando que parte de la carta acelere su carga. 
En el artículo final, me gustaría hablar sobre el mecanismo de búsqueda en IMAP y cómo se puede acelerar y reducir la carga en la red. Gracias por leer hasta el final. 

All Articles