Aplique uma pressão de retorno para impedir que seu aplicativo se afogue em mensagens WebSocket ou sobrecarregue um servidor WebSocket com mensagens.
Contexto
API WebSocket
A API WebSocket fornece uma interface JavaScript para o protocolo WebSocket, que possibilita a abertura de uma sessão de comunicação interativa bidirecional entre o navegador do usuário e um servidor. Com essa API, é possível enviar mensagens a um servidor e receber respostas baseadas em eventos sem pesquisar uma resposta no servidor.
API Streams
A API Streams permite que o JavaScript acesse de maneira programática os fluxos de blocos de dados recebidos pela rede e os processe conforme desejado. Um conceito importante no contexto de streams é a contrapressão. Esse é o processo em que um único stream ou cadeia de pipeline regula a velocidade de leitura ou gravação. Quando o próprio stream ou um stream mais adiante na cadeia de pipelines ainda estiver ocupado e não estiver pronto para aceitar mais blocos, ele enviará um sinal para trás na cadeia para diminuir a velocidade da entrega, conforme apropriado.
O problema com a atual API WebSocket
É impossível aplicar a pressão de retorno às mensagens recebidas
Com a API WebSocket atual, a reação a uma mensagem acontece em
WebSocket.onmessage
,
um EventHandler
chamado quando uma mensagem é recebida do servidor.
Vamos supor que você tenha um aplicativo que precisa executar operações pesadas de processamento de dados
sempre que uma nova mensagem é recebida.
Você provavelmente configuraria o fluxo de maneira semelhante ao código abaixo,
e, como você aplica await
no resultado da chamada process()
, está tudo pronto.
// A heavy data crunching operation.
const process = async (data) => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('WebSocket message processed:', data);
return resolve('done');
}, 1000);
});
};
webSocket.onmessage = async (event) => {
const data = event.data;
// Await the result of the processing step in the message handler.
await process(data);
};
Errado! O problema com a API WebSocket atual é que não há como aplicar pressão de retorno.
Quando as mensagens chegam mais rápido do que o método process()
pode processá-las,
o processo de renderização enche a memória armazenando essas mensagens em buffer,
para de responder devido ao uso total da CPU ou ambos.
A opção de pressão de retorno nas mensagens enviadas não é ergonômica
É possível aplicar a pressão de retorno às mensagens enviadas, mas isso envolve a pesquisa da propriedade
WebSocket.bufferedAmount
, que é ineficiente e não ergonômica.
Essa propriedade somente leitura retorna o número de bytes de dados que foram colocados na fila
usando chamadas para
WebSocket.send()
,
mas ainda não transmitidos para a rede.
Esse valor será redefinido como zero quando todos os dados na fila forem enviados.
No entanto, se você continuar chamando WebSocket.send()
,
ele vai continuar subindo.
O que é a API WebSocketStream?
A API WebSocketStream lida com o problema da pressão de retorno inexistente ou não ergonômica ao integrar fluxos com a API WebSocket. Isso significa que a pressão de retorno pode ser aplicada "sem custo financeiro", sem nenhum custo extra.
Casos de uso sugeridos para a API WebSocketStream
Exemplos de sites que podem usar essa API incluem:
- Aplicativos WebSocket de alta largura de banda que precisam manter interatividade, em especial o compartilhamento de vídeo e tela.
- Da mesma forma, a captura de vídeo e outros aplicativos que geram muitos dados no navegador que precisam ser enviados para o servidor. Com a contrapressão, o cliente pode parar de produzir dados em vez de acumular dados na memória.
Status atual
Como usar a API WebSocketStream
Exemplo introdutório
A API WebSocketStream é baseada em promessas, o que faz com que lidar com ela pareça natural
em um mundo moderno do JavaScript.
Comece construindo um novo WebSocketStream
e transmita a ele o URL do servidor WebSocket.
Em seguida, aguarde a conexão ser opened
,
o que resulta em um
ReadableStream
e/ou uma
WritableStream
.
Ao chamar o método
ReadableStream.getReader()
, você finalmente recebe um
ReadableStreamDefaultReader
,
do qual pode enviar dados read()
até que o stream seja concluído, ou seja, até retornar um objeto do formulário
{value: undefined, done: true}
.
Da mesma forma, ao chamar o método
WritableStream.getWriter()
, você finalmente consegue um
WritableStreamDefaultWriter
,
para receber dados de
write()
.
const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
const result = await process(value);
await writer.write(result);
}
Contrapressão
E o recurso de pressão de retorno prometido?
Como escrevi acima, você recebe "sem custo financeiro", sem necessidade de outras etapas.
Se o process()
demorar mais, a próxima mensagem só será consumida quando o pipeline estiver pronto.
Da mesma forma, a etapa WritableStreamDefaultWriter.write()
só vai prosseguir se for seguro.
Exemplos avançados
O segundo argumento para WebSocketStream é um bag de opções para permitir extensão futura.
Atualmente, a única opção é protocols
,
que se comporta da mesma forma que o
segundo argumento para o construtor do WebSocket:
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
O protocol
selecionado e o possível extensions
fazem parte do dicionário disponível pela promessa WebSocketStream.opened
.
Todas as informações sobre a conexão ativa são fornecidas por essa promessa,
porque não será relevante se a conexão falhar.
const {readable, writable, protocol, extensions} = await chatWSS.opened;
Informações sobre a conexão WebSocketStream fechada
As informações que estavam disponíveis nos eventos
WebSocket.onclose
e
WebSocket.onerror
na API WebSocket agora estão disponíveis pela promessa WebSocketStream.closed
.
A promessa é rejeitada no caso de um fechamento não limpo. Caso contrário, ela será resolvida com o código e o motivo enviados pelo servidor.
Todos os códigos de status possíveis e o significado deles são explicados na
lista de códigos de status CloseEvent
.
const {code, reason} = await chatWSS.closed;
Como encerrar uma conexão do WebSocketStream
Um WebSocketStream pode ser fechado com um
AbortController
.
Portanto, transmita um AbortSignal
para o construtor WebSocketStream
.
const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);
Como alternativa, você também pode usar o método WebSocketStream.close()
,
mas o objetivo principal dele é permitir a especificação do
código
e do motivo que é enviado ao servidor.
wss.close({code: 4000, reason: 'Game over'});
Aprimoramento progressivo e interoperabilidade
O Chrome é atualmente o único navegador a implementar a API WebSocketStream.
Para interoperabilidade com a API WebSocket clássica,
não é possível aplicar pressão de retorno a mensagens recebidas.
É possível aplicar a pressão de retorno às mensagens enviadas, mas isso envolve a pesquisa da propriedade
WebSocket.bufferedAmount
, que é ineficiente e não ergonômica.
Detecção de recursos
Para verificar se a API WebSocketStream é compatível, use:
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
Demonstração
Em navegadores compatíveis, você pode ver a API WebSocketStream em ação no iframe incorporado ou diretamente no Glitch.
Feedback
A equipe do Chrome quer saber sobre suas experiências com a API WebSocketStream.
Fale sobre o design da API
Algo na API não funciona como esperado? Ou há métodos ou propriedades ausentes que você precisa para implementar sua ideia? Você tem alguma dúvida ou comentário sobre o modelo de segurança? Registre um problema de especificação no repositório do GitHub correspondente ou adicione suas ideias a um problema existente.
Informar um problema com a implementação
Você encontrou um bug na implementação do Chrome?
Ou a implementação é diferente da especificação?
Registre um bug em new.crbug.com.
Inclua o máximo de detalhes possível, instruções simples para reprodução
e insira Blink>Network>WebSockets
na caixa Componentes.
O Glitch funciona muito bem para compartilhar casos de reprodução rápidos e fáceis.
Mostrar suporte para a API
Você pretende usar a API WebSocketStream? Seu suporte público ajuda a equipe do Chrome a priorizar recursos e mostra a outros fornecedores de navegador como esse suporte é essencial.
Envie um tweet para @ChromiumDev usando a hashtag
#WebSocketStream
e informe onde e como você está usando.
Links úteis
- Explicações públicas
- Demonstração da API WebSocketStream | Origem da demonstração da API WebSocketStream
- Bug de rastreamento
- Entrada ChromeStatus.com
- Componente Blink:
Blink>Network>WebSockets
Agradecimentos
A API WebSocketStream foi implementada por Adam Rice e Yutaka Hirano. Imagem principal de Daan Mooij no Unsplash (links em inglês).