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
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
Passo | Status |
---|---|
1. Criar um explicador | Concluído |
2. Criar o rascunho inicial das especificações | Em andamento |
3. Obter feedback e repetir o design | Em andamento |
4. Prova de origem | Concluída |
5. Lançamento | Nã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
- Explicador público
- Demonstração da API WebSocketStream | Código-fonte da demonstração da API WebSocketStream
- Tracking bug
- Entrada em ChromeStatus.com
- Componente Blink:
Blink>Network>WebSockets
Agradecimentos
A API WebSocketStream foi implementada por Adam Rice e Yutaka Hirano. Imagem herói por Daan Mooij no Unsplash.
Updated on • Improve article