WebSocket API: real-time коммуникация с Mock сервером

← Вернуться к статьям

Введение

WebSocket — это протокол для создания постоянного двустороннего соединения между клиентом и сервером, который позволяет обмениваться данными в реальном времени. В отличие от REST API, который работает по модели "запрос-ответ", WebSocket обеспечивает постоянное соединение для real-time коммуникации.

В этом туториале мы создадим WebSocket Mock сервер за 15 минут, настроим Socket.io для клиент-серверной коммуникации, реализуем обработку сообщений и научимся тестировать WebSocket соединения.

✅ Что вы узнаете:

💡 Что такое WebSocket?

WebSocket — это протокол связи, который обеспечивает полноценное двустороннее соединение между клиентом и сервером через одно TCP соединение. Это позволяет серверу отправлять данные клиенту без необходимости запроса от клиента.

📋 Содержание

WebSocket vs REST: когда что использовать? 🤔

WebSocket и REST API решают разные задачи. Вот основные различия:

Критерий REST API WebSocket
Протокол HTTP/HTTPS WS/WSS (WebSocket)
Соединение Временное (запрос-ответ) Постоянное (двустороннее)
Коммуникация Односторонняя (клиент → сервер) Двусторонняя (клиент ↔ сервер)
Overhead Высокий (заголовки HTTP) Низкий (минимальные заголовки)
Real-time Нет (нужны запросы) Да (сервер может отправлять данные)
Использование CRUD операции, статические данные Чат, уведомления, real-time обновления
Сложность Простая Средняя

✅ Используйте REST API когда:

✅ Используйте WebSocket когда:

Шаг 1: Настройка проекта 📦

Создадим простой Node.js проект для WebSocket Mock сервера.

Инициализация проекта:

# Создайте новую директорию
mkdir websocket-mock-server
cd websocket-mock-server

# Инициализируйте npm проект
npm init -y

# Установите зависимости
npm install express socket.io ws cors
npm install -D @types/node @types/ws nodemon typescript

Структура проекта:

websocket-mock-server/
├── src/
│   ├── server.ts          # WebSocket сервер
│   ├── handlers/          # Обработчики сообщений
│   └── types.ts           # TypeScript типы
├── package.json
└── tsconfig.json

Шаг 2: Создание Mock WebSocket сервера 🚀

Создадим простой WebSocket сервер используя нативный WebSocket API Node.js.

// src/server.ts
import WebSocket from 'ws';
import http from 'http';

const server = http.createServer();
const wss = new WebSocket.Server({ server });

// Хранилище подключений
const clients = new Map();

// Обработка подключения
wss.on('connection', (ws: WebSocket, req: http.IncomingMessage) => {
  const clientId = generateClientId();
  clients.set(clientId, ws);

  console.log(`Client connected: ${clientId}`);

  // Отправка приветственного сообщения
  ws.send(JSON.stringify({
    type: 'welcome',
    message: 'Connected to WebSocket Mock Server',
    clientId
  }));

  // Обработка входящих сообщений
  ws.on('message', (data: WebSocket.Data) => {
    try {
      const message = JSON.parse(data.toString());
      handleMessage(clientId, message, ws);
    } catch (error) {
      ws.send(JSON.stringify({
        type: 'error',
        message: 'Invalid JSON format'
      }));
    }
  });

  // Обработка отключения
  ws.on('close', () => {
    clients.delete(clientId);
    console.log(`Client disconnected: ${clientId}`);
  });

  // Обработка ошибок
  ws.on('error', (error) => {
    console.error(`WebSocket error: ${error}`);
    clients.delete(clientId);
  });
});

function generateClientId(): string {
  return `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

function handleMessage(clientId: string, message: any, ws: WebSocket) {
  switch (message.type) {
    case 'ping':
      ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
      break;

    case 'echo':
      ws.send(JSON.stringify({
        type: 'echo',
        data: message.data,
        timestamp: Date.now()
      }));
      break;

    case 'broadcast':
      // Отправка всем подключенным клиентам
      broadcast(message.data, clientId);
      break;

    default:
      ws.send(JSON.stringify({
        type: 'error',
        message: 'Unknown message type'
      }));
  }
}

function broadcast(data: any, senderId: string) {
  const message = JSON.stringify({
    type: 'broadcast',
    data,
    senderId,
    timestamp: Date.now()
  });

  clients.forEach((ws, clientId) => {
    if (clientId !== senderId) {
      ws.send(message);
    }
  });
}

const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
  console.log(`WebSocket Mock Server running on ws://localhost:${PORT}`);
});

Шаг 3: Интеграция Socket.io 🔌

Socket.io — популярная библиотека для real-time коммуникации. Она предоставляет удобный API и автоматически обрабатывает переподключение.

// src/server-socketio.ts
import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
import cors from 'cors';

const app = express();
app.use(cors());

const httpServer = createServer(app);
const io = new Server(httpServer, {
  cors: {
    origin: '*',
    methods: ['GET', 'POST']
  }
});

// Обработка подключения
io.on('connection', (socket) => {
  console.log(`Client connected: ${socket.id}`);

  // Отправка приветственного сообщения
  socket.emit('welcome', {
    message: 'Connected to Socket.io Mock Server',
    clientId: socket.id
  });

  // Обработка входящих сообщений
  socket.on('message', (data) => {
    console.log(`Message from ${socket.id}:`, data);

    // Echo сообщения обратно
    socket.emit('message-response', {
      ...data,
      timestamp: Date.now(),
      serverId: socket.id
    });
  });

  // Обработка ping
  socket.on('ping', () => {
    socket.emit('pong', { timestamp: Date.now() });
  });

  // Broadcast сообщения
  socket.on('broadcast', (data) => {
    socket.broadcast.emit('broadcast', {
      ...data,
      senderId: socket.id,
      timestamp: Date.now()
    });
  });

  // Присоединение к комнате
  socket.on('join-room', (roomId: string) => {
    socket.join(roomId);
    socket.emit('joined-room', { roomId });
    socket.to(roomId).emit('user-joined', { userId: socket.id });
  });

  // Отправка в комнату
  socket.on('room-message', ({ roomId, message }) => {
    socket.to(roomId).emit('room-message', {
      message,
      senderId: socket.id,
      timestamp: Date.now()
    });
  });

  // Обработка отключения
  socket.on('disconnect', () => {
    console.log(`Client disconnected: ${socket.id}`);
  });
});

const PORT = process.env.PORT || 3001;
httpServer.listen(PORT, () => {
  console.log(`Socket.io Mock Server running on http://localhost:${PORT}`);
});

Шаг 4: Обработка сообщений 💬

Реализуем различные типы сообщений для разных сценариев использования.

// src/handlers/messageHandler.ts
import { Socket } from 'socket.io';

interface Message {
  type: string;
  data?: any;
  timestamp?: number;
}

export class MessageHandler {
  private socket: Socket;

  constructor(socket: Socket) {
    this.socket = socket;
  }

  // Обработка текстовых сообщений
  handleTextMessage(message: Message) {
    this.socket.emit('text-response', {
      ...message,
      timestamp: Date.now(),
      status: 'delivered'
    });
  }

  // Обработка JSON данных
  handleJsonMessage(message: Message) {
    this.socket.emit('json-response', {
      original: message.data,
      processed: this.processData(message.data),
      timestamp: Date.now()
    });
  }

  // Обработка событий
  handleEvent(event: Message) {
    switch (event.type) {
      case 'user-typing':
        this.socket.broadcast.emit('user-typing', {
          userId: this.socket.id,
          ...event.data
        });
        break;

      case 'user-online':
        this.socket.broadcast.emit('user-online', {
          userId: this.socket.id,
          ...event.data
        });
        break;

      default:
        this.socket.emit('error', {
          message: 'Unknown event type',
          type: event.type
        });
    }
  }

  private processData(data: any): any {
    // Пример обработки данных
    if (typeof data === 'object') {
      return {
        ...data,
        processed: true,
        processedAt: Date.now()
      };
    }
    return data;
  }
}

// Использование в сервере
io.on('connection', (socket) => {
  const handler = new MessageHandler(socket);

  socket.on('text-message', (message) => {
    handler.handleTextMessage(message);
  });

  socket.on('json-message', (message) => {
    handler.handleJsonMessage(message);
  });

  socket.on('event', (event) => {
    handler.handleEvent(event);
  });
});

Шаг 5: Тестирование WebSocket 🧪

Напишем тесты для проверки WebSocket соединения и обработки сообщений.

// tests/websocket.test.ts
import { io as Client } from 'socket.io-client';
import { Server } from 'socket.io';
import { createServer } from 'http';

describe('WebSocket Mock Server', () => {
  let server: Server;
  let httpServer: any;

  beforeAll((done) => {
    httpServer = createServer();
    server = new Server(httpServer);

    server.on('connection', (socket) => {
      socket.on('ping', () => {
        socket.emit('pong');
      });

      socket.on('echo', (data) => {
        socket.emit('echo-response', data);
      });
    });

    httpServer.listen(() => {
      done();
    });
  });

  afterAll(() => {
    server.close();
    httpServer.close();
  });

  test('should connect to server', (done) => {
    const client = Client('http://localhost:3001');

    client.on('connect', () => {
      expect(client.connected).toBe(true);
      client.disconnect();
      done();
    });
  });

  test('should receive pong on ping', (done) => {
    const client = Client('http://localhost:3001');

    client.on('connect', () => {
      client.emit('ping');

      client.on('pong', () => {
        expect(true).toBe(true);
        client.disconnect();
        done();
      });
    });
  });

  test('should echo message', (done) => {
    const client = Client('http://localhost:3001');
    const testMessage = { text: 'Hello, WebSocket!' };

    client.on('connect', () => {
      client.emit('echo', testMessage);

      client.on('echo-response', (data) => {
        expect(data).toEqual(testMessage);
        client.disconnect();
        done();
      });
    });
  });
});

Шаг 6: Интеграция с фронтендом 🌐

Подключим WebSocket клиент к фронтенд приложению (React пример).

// src/hooks/useWebSocket.ts
import { useEffect, useState, useRef } from 'react';
import { io, Socket } from 'socket.io-client';

interface UseWebSocketReturn {
  socket: Socket | null;
  connected: boolean;
  messages: any[];
  sendMessage: (message: any) => void;
}

export function useWebSocket(url: string): UseWebSocketReturn {
  const [socket, setSocket] = useState(null);
  const [connected, setConnected] = useState(false);
  const [messages, setMessages] = useState([]);
  const socketRef = useRef(null);

  useEffect(() => {
    // Создание подключения
    const newSocket = io(url, {
      reconnection: true,
      reconnectionDelay: 1000,
      reconnectionAttempts: 5
    });

    socketRef.current = newSocket;
    setSocket(newSocket);

    // Обработка подключения
    newSocket.on('connect', () => {
      setConnected(true);
      console.log('Connected to WebSocket server');
    });

    // Обработка отключения
    newSocket.on('disconnect', () => {
      setConnected(false);
      console.log('Disconnected from WebSocket server');
    });

    // Обработка сообщений
    newSocket.on('message', (data) => {
      setMessages(prev => [...prev, data]);
    });

    // Обработка ошибок
    newSocket.on('error', (error) => {
      console.error('WebSocket error:', error);
    });

    // Cleanup при размонтировании
    return () => {
      newSocket.close();
    };
  }, [url]);

  const sendMessage = (message: any) => {
    if (socketRef.current && connected) {
      socketRef.current.emit('message', message);
    }
  };

  return { socket, connected, messages, sendMessage };
}

// Использование в компоненте
function ChatComponent() {
  const { connected, messages, sendMessage } = useWebSocket('http://localhost:3001');
  const [input, setInput] = useState('');

  const handleSend = () => {
    if (input.trim()) {
      sendMessage({ text: input, type: 'text' });
      setInput('');
    }
  };

  return (
    
Status: {connected ? 'Connected' : 'Disconnected'}
{messages.map((msg, idx) => (
{msg.text}
))}
setInput(e.target.value)} />
); }

Шаг 7: Обработка ошибок и переподключение 🔄

Реализуем надёжную обработку ошибок и автоматическое переподключение.

// src/utils/reconnection.ts
import { Socket } from 'socket.io-client';

export class WebSocketReconnection {
  private socket: Socket;
  private maxReconnectAttempts: number;
  private reconnectAttempts: number;
  private reconnectDelay: number;

  constructor(socket: Socket, maxAttempts: number = 5, delay: number = 1000) {
    this.socket = socket;
    this.maxReconnectAttempts = maxAttempts;
    this.reconnectAttempts = 0;
    this.reconnectDelay = delay;
  }

  setupReconnection() {
    this.socket.on('disconnect', () => {
      if (this.reconnectAttempts < this.maxReconnectAttempts) {
        const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);

        setTimeout(() => {
          this.reconnectAttempts++;
          console.log(`Reconnecting... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
          this.socket.connect();
        }, delay);
      } else {
        console.error('Max reconnection attempts reached');
      }
    });

    this.socket.on('connect', () => {
      this.reconnectAttempts = 0; // Reset при успешном подключении
      console.log('Reconnected successfully');
    });
  }
}

// Использование
const socket = io('http://localhost:3001');
const reconnection = new WebSocketReconnection(socket);
reconnection.setupReconnection();

Best Practices для WebSocket API 🌟

✅ Рекомендации:

⚠️ Важные моменты:

Заключение

WebSocket — мощный инструмент для real-time коммуникации. В этом туториале мы создали Mock WebSocket сервер, настроили Socket.io, реализовали обработку сообщений и научились тестировать WebSocket соединения.

💡 Что мы сделали:

Создайте Mock API за 2 минуты

Независимо от типа API (REST, GraphQL), вы можете создать Mock API с LightBox API и начать разработку frontend без ожидания backend. Поддерживает все популярные протоколы и форматы данных.

Попробовать бесплатно →
← Вернуться к статьям