Uma mangueira de incêndio com água pingando.

WebSocketStream: integração de streams com a API WebSocket

Evite que seu aplicativo seja afogado em mensagens WebSocket ou inunde um servidor WebSocket com mensagens aplicando contrapressão.

Published on Updated on

Translated to: English, 한국어, 中文, Pусский, 日本語

Histórico

A API WebSocket

A API WebSocket fornece uma interface JavaScript para o protocolo WebSocket, que torna possível abrir uma sessão de comunicação interativa bidirecional entre o navegador do usuário e um servidor. Com essa API, você pode enviar mensagens a um servidor e receber respostas orientadas a eventos sem consultar o servidor para obter uma resposta.

A API Streams

A API Streams permite que o JavaScript acesse programaticamente streams de blocos de dados recebidos pela rede e os processe conforme desejado. Um conceito importante no contexto de streams é a contrapressão. Este é o processo pelo qual um único stream ou pipeline regula a velocidade de leitura ou escrita. Quando o próprio stream ou um stream posterior no pipeline ainda estiver ocupado e ainda não estiver pronto para aceitar mais blocos, ele envia um sinal de volta através do pipeline para retardar a entrega conforme apropriado.

O problema com a API WebSocket atual

Aplicar contrapressão às mensagens recebidas é impossível

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 processamento de dados sempre que uma nova mensagem é recebida. Você provavelmente configuraria o stream de forma semelhante ao código abaixo e, já que chama await para esperar o resultado da process(), 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() é capaz de manipulá-las, o processo de renderização ou vai encher a memória armazenando em buffer essas mensagens ou irá parar de responder devido ao uso de 100% da CPU, ou as duas coisas.

Aplicar contrapressão às mensagens enviadas não é ergonômico

Aplicar contrapressão às mensagens enviadas é possível, mas envolve a monitoração da propriedade WebSocket.bufferedAmount, que é ineficiente e não ergonômica. Esta propriedade somente-leitura retorna o número de bytes de dados que foram enfileirados usando chamadas para WebSocket.send(), mas ainda não foram transmitidos para a rede. Este valor é zerado quando todos os dados enfileirados são enviados, mas se você continuar chamando WebSocket.send(), ele continuará a subir.

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. Isto significa que a contrapressão pode ser aplicada "gratuitamente", 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 a interatividade, em particular vídeo e compartilhamento de tela.
  • Da mesma forma, capturas de vídeo e outros aplicativos que geram muitos dados no navegador que precisam ser transferidos para o servidor. Com a contrapressão, o cliente pode interromper a produção de dados em vez de ficar acumulando dados na memória.

Status atual

PassoStatus
1. Criar um explicadorConcluído
2. Criar o rascunho inicial das especificaçõesEm andamento
3. Obter feedback e repetir o designEm andamento
4. Prova de origemConcluída
5. LançamentoNão iniciado

Como usar a API WebSocketStream

Exemplo introdutório

A API WebSocketStream é baseada em promessas, o que faz com que a forma de usá-la seja natural num mundo JavaScript moderno. Você começa construindo um novo WebSocketStream e passando a ele a URL do servidor WebSocket. Em seguida, você espera que a connection seja estabelecida, o que resulta num ReadableStream e/ou um WritableStream.

Ao chamar o método ReadableStream.getReader(), você finalmente obtém um ReadableStreamDefaultReader, do qual pode chamar read() para ler dados até que o stream seja concluído, ou seja, até que ele retorne um objeto da forma {value: undefined, done: true}.

Da mesma forma, ao chamar o método WritableStream.getWriter(), você finalmente obtém um WritableStreamDefaultWriter, onde você pode chamar write() para gravar dados.

  const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.connection;
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 prometido recurso de contrapressão? Como escrevi acima, você o obtém "de graça", sem a necessidade de passos adicionais. Se process() demorar mais, a mensagem seguinte só será consumida quando o pipeline estiver pronto. Da mesma forma, o passo WritableStreamDefaultWriter.write() só será realizado se for seguro fazê-lo.

Exemplos avançados

O segundo argumento para WebSocketStream é um pacote de opções para permitir extensões futuras. Atualmente, a única opção é protocols, que se comporta da mesma forma que o segundo argumento passado para o construtor WebSocket:

const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.connection;

O protocol selecionado, bem como as extensions potenciais, fazem parte do dicionário disponível por meio da promessa WebSocketStream.connection. Todas as informações sobre a conexão ao vivo são fornecidas por esta promessa, uma vez que elas não são relevantes se a conexão falhar.

const {readable, writable, protocol, extensions} = await chatWSS.connection;

Informações sobre a conexão WebSocketStream fechada

A informação que estava disponível a partir dos eventos WebSocket.onclose e WebSocket.onerror na API WebSocket está agora disponível através da promessa WebSocketStream.closed. A promessa rejeita no caso de um fechamento impuro, caso contrário, resolve para o código e motivo enviado 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;

Fechando uma conexão WebSocketStream

Um WebSocketStream pode ser fechado com um AbortController. Portanto, passe 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 seu objetivo principal é permitir especificar o código e o 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 contrapressão às mensagens recebidas. Aplicar contrapressão às mensagens enviadas é possível, mas envolve monitorar a propriedade WebSocket.bufferedAmount, que é ineficiente e não ergonômica.

Detecção de recursos

Para verificar se a API WebSocketStream é suportada, 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 mais sobre suas experiências com a API WebSocketStream.

Conte-nos sobre o design da API

Existe algo na API que não funciona conforme o esperado? Ou faltam propriedades de que você precisa para implementar sua ideia? Registre um issue de especificação no repositório do GitHub correspondente ou acrescente suas ideias a um issue existente.

Relate um problema com a implementação

Você encontrou um bug na implementação do Chrome? Ou a implementação é diferente da especificação? Informe um bug em new.crbug.com. Certifique-se de incluir o máximo de detalhes possível, fornecer instruções simples para reproduzir o bug e configurar os Componentes Blink>PerformanceAPIs. Glitch funciona muito bem para compartilhar reproduções rápidas e fáceis.

Mostre seu apoio à API

Você está planejando usar a API WebSocketStream? Seu suporte público ajuda a equipe do Chrome a priorizar os recursos e mostra a outros fornecedores de navegadores como o apoio é fundamental.

Envie um tweet para @ChromiumDev usando a hashtag #WebSocketStream e diga-nos onde e como você está usando a API.

Links úteis

Agradecimentos

A API WebSocketStream foi implementada por Adam Rice e Yutaka Hirano. Imagem herói por Daan Mooij no Unsplash.

Updated on Improve article

We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.