Evite que o app fique sobrecarregado por mensagens WebSocket ou inunde um servidor WebSocket com mensagens aplicando a contrapressão.
Contexto
A API WebSocket oferece uma interface JavaScript para o protocolo WebSocket, o que possibilita 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 para um servidor e receber respostas orientadas a eventos sem consultar o servidor para uma resposta.
API Streams
A API Streams permite que o JavaScript acesse de forma programática fluxos de blocos de dados recebidos pela rede e os processe como quiser. Um conceito importante no contexto de transmissões é a contrapressão. Esse é o processo pelo qual um único stream ou uma cadeia de pipe 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 blocos, ele envia um sinal de volta pela cadeia para diminuir a entrega, conforme apropriado.
O problema com a API WebSocket atual
Não é possível aplicar pressão de retorno às mensagens recebidas.
Com a API WebSocket atual, a reação a uma mensagem acontece em
WebSocket.onmessage
,
uma EventHandler
chamada quando uma mensagem é recebida do servidor.
Vamos supor que você tenha um aplicativo que precisa realizar operações pesadas de processamento de dados sempre que uma nova mensagem é recebida.
Você provavelmente configuraria o fluxo de forma semelhante ao código abaixo.
Como você await
o resultado da chamada process()
, tudo deve estar certo, 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 a contrapressão.
Quando as mensagens chegam mais rapidamente do que o método process()
consegue processá-las,
o processo de renderização preenche a memória armazenando essas mensagens em buffer,
não responde devido ao uso de 100% da CPU ou ambos.
A aplicação de contrapressão às mensagens enviadas não é ergonômica
É possível aplicar pressão de retorno às mensagens enviadas, mas 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 enfileirados
usando chamadas para
WebSocket.send()
,
mas que ainda não foram transmitidos para a rede.
Esse valor é redefinido para zero depois que todos os dados da fila são enviados,
mas se você continuar chamando WebSocket.send()
,
ele vai continuar subindo.
O que é a API WebSocketStream?
A API WebSocketStream lida com o problema de backpressure inexistente ou não ergonômico integrando streams com a API WebSocket. Isso significa que a contrapressão pode ser aplicada "de graça", sem custo extra.
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 em vídeos e compartilhamento de tela.
- Da mesma forma, a captura de vídeo e outros aplicativos 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
Etapa | Status |
---|---|
1. Criar uma explicação | Concluído |
2. Criar um rascunho inicial da especificação | Em andamento |
3. Coletar feedback e iterar no 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 moderno de JavaScript.
Para começar, construa uma nova WebSocketStream
e transmita a ela o URL do servidor WebSocket.
Em seguida, aguarde até que a conexão seja opened
,
o que resulta em uma
ReadableStream
e/ou uma
WritableStream
.
Ao chamar o método
ReadableStream.getReader()
, você finalmente recebe um
ReadableStreamDefaultReader
,
do qual você pode read()
dados até que o stream seja concluído, ou seja, até ele retornar um objeto no formato
{value: undefined, done: true}
.
Assim, ao chamar o método
WritableStream.getWriter()
,
você finalmente recebe um
WritableStreamDefaultWriter
,
para o qual pode enviar dados
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 contrapressão prometido?
Você recebe "de graça", sem etapas extras.
Se process()
demorar mais, a próxima mensagem só será consumida quando o pipeline estiver pronto.
Da mesma forma, a etapa WritableStreamDefaultWriter.write()
só continua se for seguro.
Exemplos avançados
O segundo argumento para WebSocketStream é um pacote de opções que permite 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 o extensions
em potencial fazem parte do dicionário
disponível pela promessa WebSocketStream.opened
.
Todas as informações sobre a conexão em tempo real 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 a conexão WebSocketStream fechada
As informações disponibilizadas pelos 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 é 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 fechar uma conexão WebSocketStream
Uma WebSocketStream pode ser fechada 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 a finalidade principal dele é permitir a especificação do
código
e da razão para o envio ao servidor.
wss.close({code: 4000, reason: 'Game over'});
Aprimoramento progressivo e interoperabilidade
No momento, o Chrome é o único navegador que implementa a API WebSocketStream.
Para interoperabilidade com a API WebSocket clássica,
não é possível aplicar contrapressão às mensagens recebidas.
É possível aplicar pressão de retorno às mensagens enviadas, mas 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, é possível ver a API WebSocketStream em ação no iframe incorporado ou diretamente no Glitch.
Feedback
A equipe do Chrome quer saber sobre sua experiência com a API WebSocketStream.
Fale sobre o design da API
Há algo na API que não funciona como esperado? Ou faltam métodos ou propriedades que você precisa para implementar sua ideia? Tem alguma dúvida ou comentário sobre o modelo de segurança? Registre um problema de especificação no repositório do GitHub (link em inglês) correspondente ou adicione sua opinião a um problema.
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 digite Blink>Network>WebSockets
na caixa Components.
O Glitch é ótimo para compartilhar casos de reprodução rápidos e fáceis.
Mostrar suporte à 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 informe onde e como você está usando essa hashtag.
Links úteis
- Explicações públicas
- Demonstração da API WebSocketStream | Fonte da demonstração da API WebSocketStream
- Rastreamento de bug
- Entrada de ChromeStatus.com
- Componente do Blink:
Blink>Network>WebSockets
Agradecimentos
A API WebSocketStream foi implementada por Adam Rice e Yutaka Hirano.