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

This site uses cookies to deliver and enhance the quality of its services and to analyze traffic. If you agree, cookies are also used to serve advertising and to personalize the content and advertisements that you see. Learn more about our use of cookies.