Недавно я только начал использовать WebSockets в гораздо большем масштабе, и есть много вещей, о которых я не думал, работая над небольшими проектами. В этой статье я поделюсь некоторыми из того, что я узнал.

Что такое веб-сокеты 🤔

Если вы работаете над приложением и хотите отправлять данные в режиме реального времени или отслеживать изменения, происходящие в режиме реального времени, вам необходимо использовать WebSockets.

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

Разбираем 🤓

TCP — это то, как данные передаются через Интернет; полнодуплексная связь означает, что данные могут передаваться и приниматься обеими сторонами одновременно; узлом может быть любое устройство, подключенное к сети

Настроить WebSockets в Node.js можно очень быстро и легко, следуя базовой документации Socket.io или любой другой библиотеке WebSocket JS.

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

Я буду использовать Socket.io для своих объяснений. Первое, что мы рассмотрим, — это запуск промежуточного программного обеспечения на вашем сокет-соединении.

Аутентификация и настраиваемое ПО промежуточного слоя 🕵️

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

io.use((socket, next) => {
    // Middleware function
    return require("./middleware/auth")(socket, next);
})

В экземпляре сокета у вас есть доступ к любым учетным данным или запросам, передаваемым вместе с соединением.

// Client
const socket = io.connect("ws://localhost:4000", {
    auth:{
        token: `${token}`
    },
    query:{
        field: `${value}`
    }
});

Чтобы получить доступ к этой информации на стороне сервера, ваша промежуточная функция должна выглядеть примерно так. Вы также можете добавить информацию об аутентифицированном пользователе в сокет

async function auth(socket, next) {
    try {
        const { token } = socket.handshake.auth;
        const { field } = socket.handshake.query;
        if (!token) return next(new Error("No token provided"));
        if (!field) return next(new Error("No field provided"));
 
        const user = await decode(token)
        socket.$user = user
   } catch (error) {
        return next(error);
   }
}

Чтобы поймать любую ошибку в промежуточном программном обеспечении, клиент должен прослушивать «connection_error».

// Client
socket.on("connect_error", function (error) {
    console.log(error);
});

Как только текущий пользователь уже добавлен в «сокет», его можно использовать в соединении для выполнения пользовательских проверок и логики.

io.use((socket, next) => {
    // Middleware function
    return require("./middleware/auth")(socket, next);
})
.on("connection", (socket) => {
    console.log(socket.$user)
})

Отлов и удаление ошибок ⚠️

Для некоторых событий сокета вам может потребоваться запросить базу данных, вызвать внешний API или запустить какую-то сложную логику; любая из этих вещей может вызвать ошибку

Вместо того, чтобы позволить ошибке вызвать некоторое время простоя, вы можете адекватно поймать ее и отправить сообщение клиенту с соответствующей информацией.

Простой способ сделать это — перехватить все события сокета в блоке try/catch и выдать ошибку, если таковая имеется.

io.use((socket, next) => {
    // Middleware function
    return require("./middleware/auth")(socket, next);
})
.on("connection", (socket) => {
    try{
        // Emit and listener events
    }catch{
        socket.emit("error", error);
    }
})

Это решение также дает вам возможность выдавать пользовательские ошибки и отлавливать их.

if (userNotAllowed) throw new Error("Access blocked")

И обрабатывать это в клиенте будет так

socket.on("error", function (error) {
    console.log(error);
});

Обратные звонки 🙋‍♂️

Обратные вызовы позволяют вам вызывать клиентские функции на сервере; сумасшедший я знаю 🤓

Вы можете думать об этом так: сервер решает, когда функция обратного вызова будет вызвана на клиенте.

Хороший пример использования этого — когда вы присоединяетесь к комнате или отправляете сообщение; вы можете использовать это, чтобы подтвердить на стороне клиента, что событие было успешно обработано

// Client
socket.emit("send-msg", { sender, msg, recipient }, (data) => {
    if(data.success) console.log("message sent succesfully")
    else console.log("message not sent")
})

Функция обратного вызова не вызывается автоматически, ее вызывает сервер

// Server
socket.on("message", (payload, cb) => {
    socket.to(payload.recipient).emit("new-msg", payload)
    cb({ success: true })
})

Эта реализация означает, что при отправке сообщения в консоль на клиенте записывается подтверждающее сообщение.

Функция обратного вызова всегда должна быть последним параметром

Масштабирование на нескольких серверах и адаптерах 🚀🚀

Горизонтальное масштабирование становится все более распространенным в настоящее время, когда несколько экземпляров одного и того же приложения работают одновременно и управляются балансировщиком нагрузки.

Горизонтальное масштабирование с помощью WebSockets не так просто, как просто запуск новых экземпляров сервера.

Это связано с тем, что когда событие отправляется на сервер, информация не распространяется на все экземпляры сервера.

Для этого вам нужно использовать Socket Adapter

Как это работает, так это то, что когда событие отправляется на экземпляр сервера, оно отправляется адаптеру, который перенаправляет данные в базу данных, которая затем пересылается на все экземпляры сервера.

Это пример использования Socket.io Mongo Adapter с MongoDB в качестве базы данных.

После добавления адаптера вам также необходимо включить закрепленные сеансы на балансировщике нагрузки и сократить время ожидания соединения.

Если вы используете AWS ALB, вы измените время ожидания по умолчанию с 60 секунд на что-то немного большее; затем включите липкие сеансы в целевой группе

Интерфейс администратора с Socket.io 🧑‍💼

Socket.io может быть очень сложной вещью, и возможность отслеживать происходящее — огромный плюс.

Просто добавив несколько строк кода, вы можете подключить свой экземпляр Socket.io к официальной панели консоли администратора, где вы можете следить за тем, что происходит, и иметь полный контроль

Вы также можете установить базовый тип аутентификации, который принимает имя пользователя и зашифрованный пароль bcrypt, или вы можете выполнить более сложную аутентификацию с помощью JWT или других параметров.

Проверьте здесь примеры кода

Заключение

Думаю, я становлюсь лучше в написании средних статей 😅😅💃💃

WebSockets, если они не настроены должным образом, могут вызвать серьезные проблемы в будущем.

Примите к сведению все, что есть в этой статье, это сэкономит вам много времени на отладку. Говорю по своему опыту 🥲

Обращайтесь ко мне, здесь и хорошо проводите время🖖