WebSockets em Angular: crie um Serviço Angular para trabalhar com soquetes da Web

imagem
Neste artigo, tentarei abordar em detalhes o escopo restrito da tecnologia dentro da estrutura da estrutura Angular e seu assistente já integrante - RxJs, enquanto não tocaremos deliberadamente em implementações de servidor, como Este é um tópico completo para um artigo separado.

Este texto será útil para aqueles que já estão familiarizados com o Angular, mas desejam aprofundar seus conhecimentos diretamente sobre o assunto.

Primeiro, algumas informações básicas.

O que é o WebSocket e por que você precisa dele


Segundo a Wikipedia , o WebSocket é um “protocolo de comunicação duplex (pode transmitir e receber ao mesmo tempo) por uma conexão TCP, projetado para mensagens em tempo real entre um navegador e um servidor da web.
O WebSocket foi projetado para ser implementado em navegadores e servidores da Web, mas pode ser usado para qualquer aplicativo cliente ou servidor. O protocolo WebSocket é um protocolo independente baseado no protocolo TCP. Permite uma interação mais próxima entre o navegador e o site, facilitando a distribuição de conteúdo interativo e a criação de aplicativos em tempo real. ”

Em outras palavras, o WebSocket permite que o servidor receba solicitações do cliente e envie solicitações ao cliente a qualquer momento desejado, assim, o navegador (cliente) e o servidor recebem direitos iguais e a capacidade de trocar mensagens ao se conectar. Uma solicitação AJAX regular requer a transmissão de cabeçalhos HTTP completos, o que significa aumento do tráfego nas duas direções, enquanto a sobrecarga dos soquetes da web após o estabelecimento de uma conexão é de apenas dois bytes. O soquete da Web reduz a quantidade de informações transmitidas nos cabeçalhos HTTP em centenas e milhares de vezes e reduz significativamente o tempo de espera. As conexões de soquete da Web oferecem suporte a vários domínios, como o CORS.

No lado do servidor, existem pacotes para suportar o soquete da Web, no cliente é a API WebSocket HTML5, que possui uma interface de três métodos:

WebSocket - a interface principal para conectar-se a um servidor WebSocket e depois enviar e receber dados pela conexão;
CloseEvent - um evento despachado pelo objeto WebSocket quando a conexão foi fechada;
MessageEvent - Um evento despachado por um WebSocket quando uma mensagem é recebida do servidor.

É assim que olha o nível de implementação do JavaSript:

const ws = new WebSocket("ws://www.example.com/socketserver", "protocolOne"); ws.onopen = () => { ws.onmessage = (event) => { console.log(event); } ws.send("Here's some text that the server is urgently awaiting!"); }; 

onmessage - ouve mensagens do servidor
enviar - envie suas mensagens para o servidor

Ou seja, em sua forma básica, tudo é extremamente simples, mas se você deseja se aprofundar no tópico, pode recorrer ao MDN Web Docs e ao mesmo tempo estudar bibliotecas que implementam suas próprias camadas na parte superior desta API.

Por que não ter medo de usar o WebSocket


O primeiro ponto que pode assustar é o suporte ao navegador . Hoje não existe esse problema - o WebSocket é suportado quase completamente na web e no segmento móvel.

imagem

imagem

https://caniuse.com/#feat=websockets

O segundo ponto é a facilidade de implementação . Sim, a princípio isso é desanimador.

A API é tão simples que, à primeira vista, pode ser difícil entender como trabalhar com um número tão modesto de métodos, porque todos eles, exceto um, relatam erros ou conexões, e apenas um deles - na mensagem - carrega para quais soquetes da web são usados, ou seja, para receber dados do servidor.

Ao mesmo tempo, o problema é que o servidor geralmente envia dados diferentes; portanto, precisamos de várias mensagens diferentes? Ou você precisa criar sua própria conexão para cada modelo de dados?

Portanto, a tarefa: você precisa aceitar o modelo do usuário e as últimas notícias do servidor, e talvez até outra coisa.

Eu me deparei com uma implementação tão "elegante":

 const wsUser = new WebSocket("ws://www.example.com/user"); wsUser.onmessage = (event) => { // ... }; const wsNews = new WebSocket("ws://www.example.com/news"); wsNews.onmessage = (event) => { // ... }; const wsTime = new WebSocket("ws://www.example.com/time"); wsTime.onmessage = (event) => { // ... }; const wsDinner = new WebSocket("ws://www.example.com/dinner"); wsDinner.onmessage = (event) => { // ... }; const wsCurrency = new WebSocket("ws://www.example.com/currency"); wsCurrency.onmessage = (event) => { // ... }; const wsOnline = new WebSocket("ws://www.example.com/online"); wsOnline.onmessage = (event) => { // ... }; const wsLogin = new WebSocket("ws://www.example.com/login"); wsLogin.onmessage = (event) => { // ... }; const wsLogout = new WebSocket("ws://www.example.com/logout"); wsLogout.onmessage = (event) => { // ... }; 

À primeira vista, tudo é lógico. Mas agora imagine como ficará se houver dezenas ou centenas deles. Em um dos projetos nos quais eu trabalhei, houve cerca de trezentos eventos.

imagem

Nós resolvemos o problema.

Todas as bibliotecas de terceiros para trabalhar com soquetes da Web permitem que você assine mensagens do tipo addEventListener. É assim:

 ws.on("user", (userData) => { / .. }) 

Como sabemos, podemos operar com um único método - na mensagem , que recebe todos os dados como parte de sua conexão, portanto esse código parece um tanto incomum. Isso é implementado da seguinte maneira: onmessage retorna um MessageEvent que contém o campo de dados . São dados que contêm as informações que o servidor nos envia. Este objeto fica assim:

 { "event": "user", "data": { "name": "John Doe", ... } } 

onde event é a chave pela qual é possível determinar quais informações o servidor enviou. Em seguida, no lado frontal, é criado um barramento que filtra as informações por evento e as envia para o endereço desejado:

 const ws = new WebSocket("ws://www.example.com"); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.event === 'user') { // ... } if (data.event === 'news') { // ... } }; 


Isso possibilita o recebimento de dados diferentes na mesma conexão e a assinatura deles através de uma sintaxe semelhante à usual para eventos JS.

WebSockets em Angular


Finalmente, chegamos ao ponto mais importante - usando WebSockets diretamente no Angular.

Apesar da simplicidade de trabalhar com a API WebSocket nativa, neste artigo usaremos RxJs, o que, é claro, porque estamos falando de Angular.

A API WebSocket nativa pode ser usada em aplicativos Angular, criar uma interface fácil de usar, RxJs Observable, assinar as mensagens necessárias etc., mas RxJs já fez o trabalho principal para você: WebSocketSubject é um invólucro reativo no WebSocket padrão API Ele não cria um barramento de eventos ou reconecta o processamento. Este é um assunto comum com o qual você pode trabalhar com soquetes da Web de estilo reativo.

RxJs WebSocketSubject


Portanto, o WebSocketSubject espera que o WebSocketSubjectConfig e um destino opcional, no qual você possa passar um link para o Assunto observado, crie um Observable através do qual você possa ouvir e enviar mensagens para os soquetes da Web.

Simplificando, você passa o URL da conexão como o argumento WebSocketSubject e assina toda a atividade do soquete da Web da maneira usual para RxJs. E se você precisar enviar uma mensagem para o servidor, use o mesmo método usual webSocketSubject.next (data).

Prestamos um serviço para trabalhar com o WebSocket Angular


Descreva brevemente o que esperamos do serviço:

  • Interface unificada e concisa;
  • Possibilidade de configuração no nível de conexão das dependências de DI ;
  • A possibilidade de reutilização;
  • Digitação;
  • A capacidade de se inscrever para receber informações por chave;
  • Capacidade de encerrar uma assinatura;
  • Enviando mensagens para o servidor;
  • Reconecte.

Vale a pena prestar atenção ao último ponto. A reconexão ou a organização da reconexão com o servidor é um fator primordial ao trabalhar com soquetes da Web, pois quebras de rede, falhas no servidor ou outros erros que causem uma falha na conexão podem causar falhas no aplicativo.

É importante observar que as tentativas de reconexão não devem ser muito frequentes e não devem continuar indefinidamente, pois esse comportamento pode suspender o cliente.

Vamos começar.

Primeiro, criaremos uma interface de configuração de serviço e um módulo que fornecerá a capacidade de configurar quando conectado.

Se possível, reduzirei o código, a versão completa que você pode ver na comunidade Angular de língua russa no GitHub .

 export interface WebSocketConfig { url: string; reconnectInterval?: number; reconnectAttempts?: number; } export class WebsocketModule { public static config(wsConfig: WebSocketConfig): ModuleWithProviders { return { ngModule: WebsocketModule, providers: [{ provide: config, useValue: wsConfig }] }; } } 

Em seguida, precisamos descrever a interface da mensagem do soquete da web:

 export interface IWsMessage<T> { event: string; data: T; } 

onde event é a chave e os dados obtidos pela chave são um modelo digitado.

A interface pública do serviço se parece com isso:

 export interface IWebsocketService { on<T>(event: string): Observable<T>; send(event: string, data: any): void; status: Observable<boolean>; } 

O serviço possui campos:

 //   WebSocketSubject private config: WebSocketSubjectConfig<IWsMessage<any>>; private websocketSub: SubscriptionLike; private statusSub: SubscriptionLike; // Observable    interval private reconnection$: Observable<number>; private websocket$: WebSocketSubject<IWsMessage<any>>; // ,      private connection$: Observer<boolean>; //  Observable       private wsMessages$: Subject<IWsMessage<any>>; //       private reconnectInterval: number; //    private reconnectAttempts: number; //      private isConnected: boolean; //   public status: Observable<boolean>; 

No construtor da classe de serviço, obtemos o objeto WebSocketConfig especificado quando o módulo foi conectado:

 constructor(@Inject(config) private wsConfig: WebSocketConfig) { this.wsMessages$ = new Subject<IWsMessage<any>>(); //  ,  ,     this.reconnectInterval = wsConfig.reconnectInterval || 5000; this.reconnectAttempts = wsConfig.reconnectAttempts || 10; //      connection$   websocket$ this.config = { url: wsConfig.url, closeObserver: { next: (event: CloseEvent) => { this.websocket$ = null; this.connection$.next(false); } }, //     connection$ openObserver: { next: (event: Event) => { console.log('WebSocket connected!'); this.connection$.next(true); } } }; // connection status this.status = new Observable<boolean>((observer) => { this.connection$ = observer; }).pipe(share(), distinctUntilChanged()); //      this.statusSub = this.status .subscribe((isConnected) => { this.isConnected = isConnected; if (!this.reconnection$ && typeof(isConnected) === 'boolean' && !isConnected) { this.reconnect(); } }); // ,  -    this.websocketSub = this.wsMessages$.subscribe( null, (error: ErrorEvent) => console.error('WebSocket error!', error) ); //  this.connect(); } 

O próprio método de conexão é simples:

 private connect(): void { this.websocket$ = new WebSocketSubject(this.config); //  //   ,    , //  ,  // ,    this.websocket$.subscribe( (message) => this.wsMessages$.next(message), (error: Event) => { if (!this.websocket$) { // run reconnect if errors this.reconnect(); } }); } 

Reconectar é um pouco mais complicado:

 private reconnect(): void { //  interval    reconnectInterval this.reconnection$ = interval(this.reconnectInterval) .pipe(takeWhile((v, index) => index < this.reconnectAttempts && !this.websocket$)); //     ,        this.reconnection$.subscribe( () => this.connect(), null, () => { // Subject complete if reconnect attemts ending this.reconnection$ = null; if (!this.websocket$) { this.wsMessages$.complete(); this.connection$.complete(); } }); } 

O método on , também é extremamente simples, não há nada para comentar.

 public on<T>(event: string): Observable<T> { if (event) { return this.wsMessages$.pipe( filter((message: IWsMessage<T>) => message.event === event), map((message: IWsMessage<T>) => message.data) ); } } 

O método de envio é ainda mais simples:

 public send(event: string, data: any = {}): void { if (event && this.isConnected) { //   any ,   ""   string //      :) this.websocket$.next(<any>JSON.stringify({ event, data })); } else { console.error('Send error!'); } } 

Esse é todo o serviço. Como você pode ver, a maior parte do código recaiu sobre a organização da reconexão.

Vamos agora ver como usá-lo. Conecte o módulo WebsocketModule:

 imports: [ WebsocketModule.config({ url: environment.ws //      'ws://www.example.com' }) ] 

No construtor de componentes, injetamos o serviço e assinamos mensagens de ' messages ', enviamos o texto de volta ao servidor:

 constructor(private wsService: WebsocketService) { this.wsService.on<IMessage[]>('messages') .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send('text', 'Test Text!'); }); } 

O nome dos eventos é mais conveniente para colocar constantes ou enumerações. Criamos em algum lugar o arquivo websocket.events.ts e o gravamos nele:

 export const WS = { ON: { MESSAGES: 'messages' }, SEND: { TEXT: 'text' } }; 

Reescreva assinaturas usando o objeto WS criado:

 this.wsService.on<IMessage[]>(WS.ON.MESSAGES) .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send(WS.SEND.TEXT, 'Test Text!'); }); 

imagem

Em conclusão


Isso, de fato, é tudo. Esse é o mínimo necessário que um desenvolvedor Angular precisa saber sobre WebSockets. Espero ter coberto esse tópico com muita clareza. A versão completa do serviço pode ser encontrada no GitHub .

Para todas as perguntas, você pode entrar em contato nos comentários, no Telegram ou no canal Angular, no mesmo local.

Source: https://habr.com/ru/post/pt416155/


All Articles