Evite que seu app seja inundado com mensagens do WebSocket ou que um servidor WebSocket seja inundado com mensagens aplicando contrapressão.
Contexto
A API WebSocket fornece uma interface JavaScript para o protocolo WebSocket, que permite abrir 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 orientadas por eventos sem fazer polling do servidor para uma resposta.
API Streams
A API Streams permite que o JavaScript acesse programaticamente fluxos de blocos de dados recebidos pela rede e os processe conforme desejado. Um conceito importante no contexto de streams é a contrapressão. É o processo pelo qual um único stream ou uma cadeia de pipes regula a velocidade de leitura ou gravação. Quando o stream ou um stream posterior na cadeia de pipe ainda está ocupado e não está pronto para aceitar mais partes, ele envia um sinal para trás na cadeia para diminuir a entrega conforme necessário.
O problema com a API WebSocket atual
Não é possível aplicar contrapressão à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 realizar operações pesadas de tratamento de dados
sempre que uma nova mensagem é recebida.
Você provavelmente configuraria o fluxo de maneira semelhante ao código abaixo e, como await
o resultado da chamada process()
, tudo estaria bem, certo?
// 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 contrapressão.
Quando as mensagens chegam mais rápido do que o método process()
pode processá-las, o processo de renderização preenche a memória armazenando em buffer essas mensagens, fica sem resposta devido ao uso de 100% da CPU ou ambos.
Aplicar contrapressão às mensagens enviadas não é ergonômico
É possível aplicar contrapressão às mensagens enviadas, mas isso envolve a sondagem da propriedade WebSocket.bufferedAmount
, o que é ineficiente e não ergonômico.
Essa propriedade somente leitura retorna o número de bytes de dados que foram enfileirados
usando chamadas para
WebSocket.send()
,
mas ainda não transmitidos para a rede.
Esse valor é redefinido para zero quando todos os dados enfileirados são enviados, mas se você continuar chamando WebSocket.send()
, ele vai continuar aumentando.
O que é a API WebSocketStream?
A API WebSocketStream lida com o problema de contrapressão inexistente ou não ergonômica integrando streams com a API WebSocket. Isso significa que a contrapressão pode ser aplicada "sem custo financeiro".
Casos de uso sugeridos para a API WebSocketStream
Exemplos de sites que podem usar essa API:
- Aplicativos WebSocket de alta largura de banda que precisam manter a interatividade, principalmente vídeo e compartilhamento de tela.
- Da mesma forma, a captura de vídeo e outros aplicativos que geram muitos dados no navegador precisam ser enviados para o servidor. Com a contrapressão, o cliente pode parar de produzir dados em vez de acumulá-los na memória.
Status atual
Etapa | Status |
---|---|
1. Criar explicação | Concluído |
2. Criar o rascunho inicial da especificação | Em andamento |
3. Coletar feedback e iterar o design | Em andamento |
4. Teste de origem | Concluído |
5. Lançamento | Não iniciado |
Como usar a API WebSocketStream
A API WebSocketStream é baseada em promessas, o que facilita o trabalho com ela
em um mundo JavaScript moderno.
Comece construindo um novo WebSocketStream
e transmitindo o URL do servidor WebSocket.
Em seguida, aguarde a conexão ser opened
,
o que resulta em um
ReadableStream
e/ou um
WritableStream
.
Ao chamar o método
ReadableStream.getReader()
, você finalmente recebe um
ReadableStreamDefaultReader
,
que pode read()
dados até que o fluxo seja concluído, ou seja, até que ele retorne um objeto do formulário
{value: undefined, done: true}
.
Assim, ao chamar o método
WritableStream.getWriter()
, você finalmente recebe um
WritableStreamDefaultWriter
,
que pode ser usado para write()
dados.
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);
}
Limitação de capacidade
E o recurso de contrapressão prometido?
Você recebe o recurso "sem custo financeiro", sem precisar fazer nada.
Se process()
levar mais tempo, a próxima mensagem só será consumida quando o pipeline estiver pronto.
Da mesma forma, a etapa WritableStreamDefaultWriter.write()
só prossegue se for seguro.
Exemplos avançados
O segundo argumento de WebSocketStream é um conjunto de opções para permitir extensões futuras.
A única opção é protocols
, que se comporta da mesma forma que o
segundo argumento do construtor WebSocket:
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
O protocol
selecionado e os possíveis 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, já que não é relevante se a conexão falhar.
const {readable, writable, protocol, extensions} = await chatWSS.opened;
Informações sobre uma conexão WebSocketStream fechada.
As informações disponíveis nos eventos
WebSocket.onclose
e
WebSocket.onerror
na API WebSocket agora estão disponíveis pela promessa WebSocketStream.closed
.
A promessa é rejeitada em caso de fechamento incorreto. Caso contrário, ela é resolvida com o código e o motivo enviados pelo servidor.
Todos os códigos de status possíveis e seus significados são explicados na
lista de códigos de status CloseEvent
.
const {code, reason} = await chatWSS.closed;
Como fechar uma conexão WebSocketStream
Um WebSocketStream pode ser fechado com um
AbortController
.
Portanto, transmita um AbortSignal
para o construtor WebSocketStream
. AbortController.abort()
funciona apenas antes do handshake, não depois.
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 enviados ao servidor.
wss.close({closeCode: 4000, reason: 'Game over'});
Aprimoramento progressivo e interoperabilidade
No momento, o Chrome é o único navegador a implementar a API WebSocketStream.
Para interoperabilidade com a API WebSocket clássica, não é possível aplicar contrapressão às mensagens recebidas.
É possível aplicar contrapressão às mensagens enviadas, mas isso envolve a sondagem da propriedade WebSocket.bufferedAmount
, o que é ineficiente e não ergonômico.
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, é possível conferir 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
Há algo na API que não funciona como esperado? Ou há métodos ou propriedades ausentes que você precisa implementar sua ideia? Tem uma dúvida ou um 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.
Mostrar suporte para a API
Você planeja usar a API WebSocketStream? Seu apoio público ajuda a equipe do Chrome a priorizar recursos e mostra a outros fornecedores de navegadores a importância de oferecer suporte a eles.
Envie um tweet para @ChromiumDev usando a hashtag
#WebSocketStream
e conte para nós onde e como você está usando.
Links úteis
- Explicação para o público
- Demonstração da API WebSocketStream | Fonte da demonstração da API WebSocketStream
- Bug de rastreamento
- Entrada do ChromeStatus.com
- Componente Blink:
Blink>Network>WebSockets
Agradecimentos
A API WebSocketStream foi implementada por Adam Rice e Yutaka Hirano.