Когда уже совсем много запросов на сервер или…

от автора

Когда “еще один пуллинг каждый N секунд” стучится вам в код. Время подумать про вебсокеты a.k.a полнодуплексное соединение.

Речь пойдет про socket.io , не совсем web socket а скорее микс при участии web socket. Но очень удобный в использовании сразу из коробки. 

Кейс состоит в следующем: 

  • Есть многопользовательское приложение в котором пользователи запускают асинхронные операции на сервере. Другими словами нажимают на кнопку в приложении и ждут когда сервер выполнит все операции а походу еще и расскажет про текущее состояние.

Вроде бы можно обойтись лонг пуллингом, но некоторые действия хочется заблокировать пользователям которые находятся за другими мониторами на той же странице. Да и вообще говоря сама библиотека при отсутствии возможности ws/wss соединения будет использовать пуллинг. Так что вроде бы только плюсы, из минусов только еще один пакет на клиенте и сервере.

Для клиента необходима библиотека socket-io.client, для фронта все написано на React и само соединение можно установить/разорвать через хуки. Для тех кто использует redux-toolkit все можно сделать через RTK.

У вас должен быть базовый объект createApi({ … }) который описывает все эндпоинты и который потом, так же можно расширить.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';  export const baseApi = createApi({     reducerPath: 'baseApi',     baseQuery: fetchBaseQuery({ baseUrl: '/' }),     endpoints: () => ({}), });

Добавляем в корневое состояния приложения новый редьюсер, если его конечно нет.

const appReducer = combineReducers({ …, [baseApi.reducerPath]: baseApi.reducer, … });

В middleware добавляем =>  baseApi.middleware

И расширяем свой базовый baseApi функционалом для создания вебсокета.

import { baseApi } from '../api'; import { io } from 'socket.io-client';  export const wsApi = baseApi.injectEndpoints({     endpoints: build => ({         subscribeToEvents: build.query<any, void>({             queryFn: () => ({ data: [] }),             async onCacheEntryAdded(_arg, { dispatch, updateCachedData, cacheEntryRemoved }) {                 // Path is a prefix that will be used right after domain name                 const socket = io(`${your_url}/events`, {                     path: '/socket.io',                 });                  socket.on('disconnect', reason => {                     if (reason === 'io server disconnect') {                         // the disconnection was initiated by the server, you need to reconnect manually                         socket.connect();                     }                     // else the socket will automatically try to reconnect                 });                  socket.on(‘EVENT_TYPE’, (event: ServerEvent) => {                     // Here we should add the logic                     updateCachedData(draft => {                         draft.push(event);                     });                 });                  await cacheEntryRemoved;                 socket.close();             },         }),     }),     overrideExisting: false, });  export const { useSubscribeToEventsQuery } = wsApi;

injectEndpoints работает как раз для расширения эндпоинтов, только не забудьте добавить overrideExisting: false чтобы расширить а не переопределить существующий функционал.

useSubscribeToEventsQuery(); можно использовать как обычный хук, в том компоненте в котором желаете подписаться на события, например в App. Еще в api есть свойство keepUnusedDataFor для того чтобы задать время в секундах существования подключения/данных после последнего unsubscribe, по умолчанию 60 секунд.

На сервере используется Ts.Ed (Node js), но также существуют готовые библиотеки на других языках. Нужно проинсталлировать пакеты связанные с socket.io для сервера. И дела за малым, добавить конфиг:

socketIO: { path: '/socket.io', cors: { origin: '*' // put your servers } } 

И добавить сервис который выполняет подключение/отключение клиента а так же отправляет и принимает сообщения.

import { IO, Nsp, Socket, SocketService, SocketSession } from "@tsed/socketio"; import * as SocketIO from "socket.io";  @SocketService("/events") // namespace right after path ‘socket.io’ export class MySocketService {      @Nsp nsp: SocketIO.Namespace | undefined;      // a map to keep clients by any id you like, a userId or whatever.     public clients: Map<string, SocketIO.Socket> = new Map();       constructor(@IO private io: SocketIO.Server) {     }      /**      * Triggered when a new client connects to the Namespace.      */     $onConnection(@Socket socket: SocketIO.Socket, @SocketSession session: SocketSession) {         this.clients.set(socket.id, socket);     }      // setup a method to send data to all clients     // you can use this from any other service or controller.     broadcast(someData: any): void {         this.nsp.emit(‘EVENT_TYPE’, { … }: ServerEvent);     }      // method to send to a targeted client     sendToSingleClient(idToSendTo: string, someData: any): void {         const socket = this.clients.get(idToSendTo);         if (!socket) return;         socket.emit(‘EVENT_TYPE’, { … }: ServerEvent);     }  }

Namespace в socket.io это путь "/event" после основного пути в настройках socketIO: { path: 'socket.io' }. Теперь можно в любом необходимом месте заинжектить сервис и отправить сообщение клиентам.

Вообщем то как-то так, после этого клиент может получать сообщения и выполнять необходимые side effects. ??


ссылка на оригинал статьи https://habr.com/ru/articles/594307/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *