Tenho que ir rápido. Otimizando solicitações de conteúdo de email IMAP

Olá a todos! Em um artigo anterior, falei sobre como você pode sincronizar rapidamente o conteúdo de uma caixa no cache local. Aqui, quero falar sobre os recursos de solicitar o conteúdo das cartas e a melhor forma de solicitar o conteúdo, sem receio pelo alto consumo de tráfego.

imagem

Vamos lembrar rapidamente o que aprendemos no último artigo:

  • IMAP é um protocolo stateful
  • Para ver o conteúdo da caixa de entrada, você deve primeiro selecioná-lo com o comando SELECT
  • Para sincronizar rapidamente a caixa em que estamos, você pode usar o comando NOOP
  • Para não classificar as mensagens do armazenamento local para atualizar a caixa de correio que já deixamos, você pode usar CONDSTORE e QRESYNC, desde que o servidor suporte os dados de extensão de protocolo

O suficiente!


Deixe-me lembrá-lo do comando para solicitar o corpo da carta:

1 FETCH number (BODY[])

Isso criará uma solicitação para obter todo o corpo da carta e todos os anexos. Basta ver quanto tempo leva para receber uma mensagem no parágrafo 42 de Lorem Ipsum e com uma imagem de 2 megabytes.

Primeiro, pergunte o tamanho da mensagem no servidor. Isso é feito pelo comando:

1 FETCH 18871 (RFC822.SIZE)

RFC822.SIZE retorna o tamanho da mensagem em bytes:

* 18871 FETCH (RFC822.SIZE 3937793)

Ou seja, como resultado, nossa mensagem ocupa quase 4 megabytes.

Agora, no entanto, usaremos a solicitação para todo o corpo da carta e daremos uma olhada no momento:

1 OK Fetch completed (0.007 + 3.265 secs).

3,3 segundos! E esta é apenas uma mensagem com o anexo, e imagine que essa será a caixa inteira. Depois, levará mais de um minuto para baixar pelo menos os vinte e um.

Você deve admitir que os negócios de um cliente que em 2020 não pode sincronizar emails mais rapidamente do que em um minuto são ruins. Mas o que fazer?

Me dê uma mordida uma vez


Se você usar o RFC3501 na cláusula 6.5.4 , que descreve os possíveis parâmetros para o comando FETCH, notará uma solicitação interessante:

BODY[<section>]<<partial>>

  • seção - qual parte da carta obter
  • parcial - o tamanho desta parte

Como é construído parcialmente? E é muito fácil. Primeiro, o byte a partir do qual você precisa iniciar a leitura é escrito através do ponto e, em seguida, quantos bytes em geral devem ser lidos:

BODY[<section>]<<0.1024>>

Aqui solicitamos a parte da carta de zero byte a 1024.

Ok, o que é a seção? Primeiro, falarei sobre um parâmetro tão útil em uma consulta FETCH como BODYSTRUCTURE:

1 FETCH 18871 (BODYSTRUCTURE)

Este parâmetro, como você provavelmente entendeu a partir da assinatura, retorna a estrutura da carta no formato descrito em 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))


Basta dar uma olhada nessa estrutura e sua cabeça está girando? Não tenha medo, agora vamos descobrir. Compare os colchetes de abertura e fechamento.

(
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
)
)


Você pode notar que eu coloquei números perto de alguns colchetes. Esta é a seção.
Como calculá-los? O primeiro colchete deve ser ignorado, porque simplesmente contém a resposta à solicitação e, em seguida, cada colchete de abertura deve ser numerado de acordo com a regra, à medida que os títulos nos documentos são numerados:

  • Nós numeramos cada colchete de abertura levando em consideração a seção anterior
  • Se a seção estiver aninhada, a atual através do ponto será adicionada ao número anterior
  • Se a seção não estiver aninhada, aumente seu número em um


Por exemplo, neste caso, na primeira parte que termina com "alternativa" (ou seja, é a parte da carta multipart / alternativa, onde podemos escolher qual das partes exibir para o usuário), há duas seções numeradas por um ponto. Eu conheci um servidor onde pode haver aninhamento em três níveis (ou seja, [1.1.1], [1.1.2], etc).
Vamos analisar a parte [1.1] da estrutura de todas essas coisas no documento MIME-IMB . A julgar por isso, o cabeçalho Content-Type é o primeiro. Inclui:

  • Tipo MIME, aqui está texto / sem formatação
  • Codificação (charset = utf8)


A seguir estão dois parâmetros que são gravados como NIL. Francamente, eu não entendi o que era, mas até agora não precisei, então sentirei falta. Peço desculpas por essa frivolidade.Em
seguida, está o cabeçalho Content-Transfer-Encoding, que está incluído nele, que descreve o mecanismo de codificação, aqui está imprimível entre aspas. 
Os dois números a seguir descrevem o tamanho da peça em bytes e o número de linhas, se possível. Com a ajuda deles, podemos calcular quantos bytes serão necessários para exibir um determinado número de linhas.
As seguintes linhas que não estão nesta parte:

  • ID do conteúdo, usado na linha da carta
  • Descrição do Conteúdo, uma linha que descreve o que é esta parte


Para os outros dois parâmetros, não consegui encontrar uma resposta definitiva, mas um desses parâmetros pode conter partes MD5, que às vezes podem ser úteis.
Para a parte [2], tudo é o mesmo, exceto que é uma imagem, um anexo com um nome e a codificação base64. Se ainda não está completamente claro o que está acontecendo aqui, então neste site está perfeitamente definido exatamente como calcular a seção.

O que isso dá? E o fato de que, na fase de exibição da carta, já podemos solicitar apenas a parte superior do conteúdo e não carregar os anexos até que o próprio usuário insira a mensagem e clique no botão "baixar". Todas as informações para a exibição de anexos são fornecidas na BODYSCTRUCTURE, para que o nome, o formato e o tamanho possam ser exibidos sem carregar o próprio anexo. 

Vamos seguir praticando. Pediremos um kilobyte de conteúdo da mensagem sem anexos, apenas para saber o que eles nos enviaram.

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).


Cerca de 100 milissegundos e já vemos parte do conteúdo da carta! Esse é apenas um excelente resultado, já que anteriormente demorávamos quase 4 segundos para baixar o conteúdo de uma letra. Em seguida, você pode simplesmente carregar todo o conteúdo da carta no fluxo em segundo plano; do lado de fora, parecerá que as cartas serão carregadas instantaneamente. Tudo o que era necessário era examinar a estrutura da carta e baixar apenas o necessário para uma exibição rápida. 
Apenas um momento. Esta solicitação fará com que a mensagem no servidor apareça como lida. Mas você pode corrigir isso adicionando apenas PEEK à solicitação do corpo

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).


E pronto! A carta permanece não lida e parte do conteúdo que recebemos.
Tudo se torna ainda mais fácil se o recurso de solicitação PREVIEW for implementado no seu 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).


Aqui, não perdemos tempo consultando a estrutura e obtemos a visualização da mensagem instantaneamente. Mas não esqueça que a estrutura da consulta é útil para definir anexos para que eles não sejam carregados no modo inativo.

Esperar


Quase todo cliente de email implementa o botão "atualizar" se o usuário deseja receber novas cartas agora. Mas, de alguma forma, isso não é legal para o nosso tempo, onde há notificações nos dispositivos e nos navegadores. O que o IMAP diz sobre isso? E ele diz ocioso . Esta operação mantém a conexão com a pasta e notifica você sobre alterações na pasta. Observe, não a caixa de correio, mas as pastas. Para fazer isso, você precisa do servidor para implementar o recurso IDLE. 

Primeiro, selecione a pasta para a qual o servidor enviará alertas e ative o 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


A resposta "+ ocioso" notifica sobre a inclusão do ocioso na pasta. O que acontece se uma nova carta chegar?

* 18873 EXISTS
* 1 RECENT


Enviei-me a mesma carta e Idle me notificou que deveria solicitar a carta 18873, que havia 18873 cartas na pasta e que uma carta acabara de chegar.
Em seguida, solicitarei esta carta em outra conexão, estamos interessados ​​na carta com a resposta EXISTA.

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=


É muito importante entender. O IDLE requer uma conexão separada, para que você não possa receber alterações e solicitar mensagens na mesma sessão.O
que mais o IDLE pode fazer? Ele sabe como notificar sobre letras excluídas e cartas cujas bandeiras foram alteradas. Vamos dar uma olhada na carta com o objetivo de colocar a bandeira "/ visto" nela e excluir a carta.

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


Eu apaguei a conversa (18873, 18871) e olhei para outra carta (resposta FETCH). Por que essa carta se tornou 18871? Porque o IMAP reconta o número da letra se algo mudou. Desde que se tornou o primeiro, seu número também mudou. 
Com o IDLE, podemos sincronizar rapidamente o estado da caixa, mas é desagradável que exija uma conexão separada. Poderia ser melhor? É por isso que estou aqui.

Gritar como vai


E se eu disser a você que existe um recurso que permite que você receba notificações do servidor na mesma conexão, e até especialmente configurado para caixas de correio e mais de uma. Parece um conto de fadas, mas não enlouqueça, essa é uma real capacidade NOTIFICAR . Ele sabe muito, por exemplo:

  • Configurar pastas específicas a partir das quais esperamos notificações
  • Ouvir as alterações de status da pasta (ler cartas, novas letras)
  • Defina o formato da notificação, ou seja, o que queremos ver ao alterar a pasta
  • Ouvir alterações no nome da pasta
  • Ouvir alterações de metadados da pasta


Vejamos um exemplo de como podemos ouvir as alterações no status da pasta

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


Agora, o servidor nos enviará notificações com status de pasta, por exemplo, adicionarei algumas mensagens a pastas diferentes

* 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)


Analisarei o comando:
Primeiro vem o comando NOTIFY SET. Então, entre parênteses, selecionamos quais pastas vamos ouvir:

  • Caixas de entrada - para todas as pastas que você pode selecionar
  • Pessoal - pastas que estão no espaço de nome do usuário
  • Inscrito - pastas nas quais o usuário está inscrito
  • Subárvore - subárvore da pasta a ser especificada
  • Caixas de correio - aqui você pode listar as pastas para ouvir.
  • Selecionado - alerta apenas para pastas selecionadas


E os parâmetros que são responsáveis ​​pelo filtro de alerta:

  • MessageNew - se uma nova mensagem chegar
  • FlagChange - se o sinalizador foi alterado
  • MessageExpunge - se a mensagem foi excluída ou movida


Mas com esse comando, não podemos receber os parâmetros de uma mensagem nova, alterada ou excluída. Para fazer isso, selecione o parâmetro Selected e especifique o que exatamente retornar. Podemos adicionar outro alerta sem excluir o anterior.

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


Aqui em MessageNew, especificamos os parâmetros que a notificação deve retornar. Escolherei a Caixa de entrada e novamente jogarei para mim 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"))


Como é que você gosta? Para o ocioso, precisamos manter duas conexões, uma das quais também solicita mensagens que o ocioso retornou para nós. Imediatamente eles nos trazem tudo em uma bandeja de prata. 
E assim podemos ouvir as alterações no nome da pasta

1 notify set (inboxes (MailboxName))


Renomeie alguma pasta e veja o resultado.

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


E agora sabemos que havia uma pasta "aaaa" e se tornou "1111".
Agora você pode ouvir a alteração de sinalizadores e a remoção de mensagens. Para fazer isso, use o parâmetro FlagChange

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


E quando você altera os sinalizadores de mensagens e exclui, obtemos

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


Qual é o próximo


Todos esses recursos ajudam o cliente de email a trabalhar o mais rápido e conveniente possível para o usuário. IDLE e NOTIFY notificam o usuário sobre alterações nas pastas, solicitando que parte da carta acelere o carregamento. 
No artigo final, gostaria de falar sobre o mecanismo de pesquisa no IMAP e como ele pode ser acelerado e reduzir a carga na rede. Obrigado por ler até o fim. 

All Articles