
O relatório, quando finalmente chega, é que a página deixou de atualizar. Nenhum erro apareceu. Os números simplesmente pararam de se mover, e um recarregamento resolveu o problema.
É por isso que raramente se ouve falar sobre isto. A pessoa que se depara com isto tem uma reparação que funciona nas mãos em questão de segundos, portanto a maioria delas nunca vos diz. As que vos dizem não têm nada para descrever a não ser uma ausência.
Um socket fechado não é um erro
A primeira coisa a desaprender é que isto chegará como um pedido falhado. Não chegará.
Um WebSocket dispara um evento error, e o MDN descreve o seu tipo em três palavras: “Um Event genérico.” Não existe nenhuma interface personalizada sob ela, o que significa nenhum código, nenhuma razão e nenhum estado no objeto que vos é entregue. Nada lança uma exceção, portanto nada chega a um try/catch. A conexão termina, e a única coisa que carrega informações é o evento close que se segue.
Portanto, o código de encerramento não é um detalhe. É toda a mensagem.
Os códigos que significam algo
Quatro vale a pena conhecer de cor, nas próprias palavras do MDN.
- 1000, encerramento normal: “A conexão completou com sucesso o objetivo para o qual foi criada.” Nada para investigar.
- 1001, saída: “O endpoint está a desaparecer, seja por causa de uma falha do servidor ou porque o navegador está a abandonar a página que abriu a conexão.”
- 1011, erro interno: “O servidor está a terminar a conexão porque encontrou uma condição inesperada que o impediu de cumprir o pedido.”
- 1006, encerramento anormal, que é aquele que efetivamente irá ver e de que o resto deste artigo trata.
Leia 1001 duas vezes, porque cobre dois eventos não relacionados com um número. Um servidor a desligar-se e um utilizador a clicar numa ligação produzem ambos. Se estiver a contar 1001s como incidentes, está a contar pessoas que deixam o seu site como incidentes, e o número acompanhará o seu tráfego em vez da sua fiabilidade.
1011 é o oposto e é aquele que vale a pena alertar. É o servidor admitindo a falha nas suas próprias palavras.
O código que vê mais não pode ter vindo de ninguém
Aqui está a parte que quase nunca é escrita, e muda a forma como investiga.
RFC 6455 define 1006 assim:
1006 é um valor reservado e NÃO DEVE ser definido como código de estado numa frame de controlo Close por um endpoint. É designado para utilização em aplicações que esperam um código de estado para indicar que a conexão foi fechada anormalmente, por exemplo, sem enviar ou receber uma frame Close.
1005 carrega a mesma proibição: “1005 é um valor reservado e NÃO DEVE ser definido como código de estado numa frame de controlo Close por um endpoint.”
Portanto, nenhum deles pode ser enviado. Não pelo seu servidor, não pelo navegador, não por nada no meio. São proibidos na rede. Quando o seu manipulador lê 1006, está a ler um número que o seu próprio navegador inventou localmente para descrever o facto de que a conexão terminou e nenhuma frame de encerramento chegou.
Essa é uma afirmação mais forte do que parece. Significa que não há nenhum evento correspondente em nenhum outro lugar para ir e encontrar.
- O que as pessoas procuram
- Uma linha de registo de servidor explicando o 1006, com o pressuposto de que o servidor o enviou
- O que existe
- Nada. O código foi gerado no navegador. O servidor pode não saber que a conexão desapareceu ainda, e o que causou o encerramento muitas vezes não é o servidor em absoluto
1006 é portanto não uma causa. É a forma da evidência: algo entre os dois extremos deixou de retransmitir, e nenhum dos extremos conseguiu encerrar a conexão corretamente.
Quem é que efetivamente encerra uma conexão que ninguém encerrou
Uma vez que aceite que o navegador está a relatar silêncio, os suspeitos são as coisas que ficam no meio e têm opiniões sobre conexões inativas.
Um proxy inverso ou equilibrador de carga com um limite de inatividade é o comum. Esses prazos são configuráveis e frequentemente curtos, e um WebSocket não carregando nada entre atualizações parece exatamente uma conexão abandonada. O equipamento de rede corporativo faz o mesmo, mais agressivamente. Um telefone a mudar de rede sem fios para dados móveis altera o endereço do cliente debaixo de um socket aberto, o que encerra isto sem cerimónia. Os computadores portáteis em suspensão fazem o mesmo.
Note o que cada um deles tem em comum: o seu código de aplicação está correto, o seu servidor está saudável, e nada está errado com os dados. O transporte desapareceu. É também por isso que atinge alguns utilizadores constantemente e outros nunca, que é a assinatura que envia um relatório de erro marcado como “não é reproduzível”.
A defesa padrão é um batimento cardíaco: enviar algo pequeno num temporizador, mais frequentemente do que o limite de inatividade mais curto entre si e os seus utilizadores. Não repara uma conexão perdida. Para uma inativa de ser confundida com uma morta, que é um problema diferente que produz o mesmo relatório.
O que registar, porque o evento de encerramento é a única testemunha
O evento de encerramento acontece uma vez, no navegador, e depois a página normalmente reconecta e a evidência desaparece. Quatro campos valem a pena capturar nesse momento.
socket.addEventListener('close', (event) => {
report({
code: event.code, // 1006 significa que ninguém enviou um código
wasClean: event.wasClean, // verdadeiro se a conexão foi fechada corretamente
reason: event.reason, // normalmente vazio, e vazio é informativo
secondsSinceLastMessage: (Date.now() - lastMessageAt) / 1000,
reconnects: reconnectCount
});
});
wasClean é o sinal mais barato dos quatro: MDN define-o como true “se a conexão foi fechada corretamente”, portanto um false distingue uma conexão que simplesmente terminou de uma que foi encerrada corretamente. E secondsSinceLastMessage é o campo que apanha um limite de inatividade, porque o intervalo vai agrupar-se em torno do mesmo valor para cada utilizador afetado. Um aglomerado de pouco menos de sessenta segundos não é uma coincidência, é um número configurado em algum lugar do caminho.
Session Replay
Extensão gratuita do Chrome. Um clique na página que se comporta mal captura a imagem, a consola e o registo de rede, e fornece-lhe uma ligação para colar no bilhete.
A pergunta a fazer à pessoa que a reportou
Uma pergunta, e não é uma técnica: um recarregamento resolveu o problema?
Se resolveu, os dados nunca estavam errados e a sua renderização nunca estava errada. Uma conexão fresca era tudo o que era necessário, o que aponta para o transporte e longe de tudo o resto. Essa resposta elimina mais do que qualquer quantidade de suposição sobre a sua rede, e podem dar-la sem saber o que é um WebSocket.
O acompanhamento que vale a pena perguntar é há quanto tempo a página estava aberta. Um separador deixado aberto durante o almoço e um separador aberto há um minuto são casos diferentes, e o relator sabe a resposta.
O que tirar de tudo isto
A falha é silenciosa por construção. Nada lança uma exceção, o evento error não carrega detalhes, e a página continua a renderizar a última coisa que lhe foi dito.
O código de encerramento é a mensagem inteira, e o mais comum é uma invenção local: 1006 é o seu navegador a dizer que nada chegou, não o seu servidor a dizer por que é que saiu. Códigos que ninguém pode enviar são códigos que ninguém pode explicar, portanto pare de procurar pela metade do lado do servidor e comece a procurar pelo que fica entre si e o utilizador.
A especificação em si é RFC 6455, e os códigos de encerramento com os seus significados de uma linha estão em página CloseEvent.code do MDN.