Введение: JSON-RPC и WebSocket — брак, заключённый в аду (но это работает)
JSON-RPC — это как SMS от вашего сервера: “Эй, выполни метод X и ответь”. А WebSocket — это бесконечный чат, где сервер и клиент могут болтать без остановки. Соединить их — всё равно что научить кота приносить тапки: звучит странно, но возможно. Зачем? Чтобы ваше приложение общалось быстро, асинхронно и без лишних HTTP-рукопожатий. А Gin здесь — как бодрый бариста, который быстро настраивает роуты и middleware, чтобы вы не варили кофе вручную.
Почему Gin + WebSocket?
- Gin — это Go-фреймворк, который не грузит вас тоннами абстракций (и не заставляет читать мантры в документации).
- WebSocket — канал для мгновенных сообщений, чтобы RPC-запросы и ответы летали как снайперские пули, а не как письма голубиной почтой.
Идеальный дуэт для микросервисов, чатов или игр, где задержка — враг №1.
Раздел: «Где и зачем это использовать? Или как не изобретать велосипед с реактивным двигателем»
Проблемы, которые решает JSON-RPC через WebSocket
- HTTP-оверхед:
Каждый HTTP-запрос — это установка соединения, заголовки, закрытие. WebSocket держит канал открытым, как беседа с болтливым другом, который не хочет уходить.
Пример: Если ваше приложение шлёт 100 RPC-запросов в секунду, через HTTP вы потратите 90% времени на рукопожатия. WebSocket режет задержки как ниндзя. - Асинхронность:
Клиент отправил запрос и может заниматься другими делами, пока сервер готовит ответ. Это как заказать пиццу и не стоять под дверью курьера.
Пример: В трейдинговой платформе цена меняется каждую миллисекунду — клиент подписывается на обновления черезnotify
и получает данные, когда они есть. - Структура против хаоса:
Чистый JSON-RPC даёт чёткий контракт (method, params, id), в отличие от “ватсаппинга” сырыми JSON-ками через WebSocket.
Пример:
Хаос:{"type": "userEvent", "data": {"action": "login", ...}}
→ парсинг черезif type == ...
.
Порядок:{"method": "user.login", "params": [...]}
→ диспетчер сразу вызывает нужный метод.
Сценарии использования
1. Микросервисы в реальном времени
Когда сервисы должны общаться быстро и без бюрократии:
- Сервис A вызывает метод сервиса B через JSON-RPC и не ждёт ответа (если не нужно).
- Архитектура: Брокеры сообщений (RabbitMQ/Kafka) слишком тяжелы? WebSocket + RPC дадут лаконичный вариант.
Пример:
Сервис оплаты отправляет уведомление в сервис нотификаций через notify.sendEmail
, а тот отвечает асинхронно:
{ "jsonrpc": "2.0", "method": "notify.sendEmail", "params": ["user@mail.com", "Платеж прошел!"], "id": null }
2. Десктопные приложения (Hello, Wails!)
Wails — это фреймворк для Go + JS десктопных приложений. Ваш фронтенд (Vue.js/React) может вызывать методы бэкенда на Go через RPC, а WebSocket сделает это:
- Без тормозов IPC.
- С возможностью подписываться на события (например, обновления данных).
Пример для Wails:
Бэкенд (Go):
// Используем тот же Gin-сервер из статьи func main() { r := gin.Default() r.GET("/rpc", handleWebSocketRPC) // ... Wails инициализация ... }
Фронтенд (JS):
const socket = new WebSocket('ws://localhost:8080/rpc'); // Вызываем метод бэкенда socket.send(JSON.stringify({ jsonrpc: "2.0", method: "getUserData", params: [userId], id: 1 })); // Слушаем ответы socket.onmessage = (event) => { const response = JSON.parse(event.data); if (response.id === 1) { console.log('Данные пользователя:', response.result); } };
3. Игры и collaborative-приложения
- Игровой сервер рассылает состояние игры через
notify
всем клиентам. - Клиенты вызывают
player.move
илиchat.send
, а сервер валидирует и обновляет данные.
Пример:
// Клиент шлёт: { "jsonrpc": "2.0", "method": "player.move", "params": {"x": 10, "y": 5}, "id": 42 } // Сервер отвечает: { "jsonrpc": "2.0", "result": { "status": "moved", "cooldown": 2 }, "id": 42} // И рассылает остальным: { "jsonrpc": "2.0", "method": "notify.playerMoved", "params": { "id": "player1", "x": 10, "y": 5 } }
Архитектуры, где это работает лучше всего
- Event-Driven Architecture (EDA):
Сервер и клиенты обмениваются событиями (например, уведомления о изменениях данных).
Паттерн: «Подписка» через RPC-методsubscribeToUpdates
, а сервер шлёт ответы какnotify
. - Микросервисы с низкой задержкой:
Сервисы общаются через WebSocket вместо REST, если нужно меньше оверхеда. - Приложения с долгоживущими сессиями:
Например, админка, где юзер открывает её на 8 часов и должен моментально получать оповещения.
Когда НЕ стоит это использовать
- Простой CRUD: Если ваше приложение — это «получить список пользователей раз в минуту», хватит REST.
- Оффлайн-приложения: WebSocket без сети — как кофе без кофеина.
- Системы с жесткими требованиями к безопасности: WebSocket сложнее защищать, чем HTTP (но возможно, с WSS и токенами).
Пример из реальной жизни: Wails + Gin + JSON-RPC
Допустим, вы делаете десктопное приложение для трейдинга:
- Фронтенд (Wails+Vue): рисует графики, портфель, кнопки «Купить/Продать».
- Бэкенд (Go): держит соединение с биржей через WebSocket, считает риски.
Как они общаются:
- При запуске фронтенд подключается к бэкенду через WebSocket (
ws://localhost:8080/rpc
). - Когда пользователь жмёт «Купить», фронтенд вызывает:
{ "jsonrpc": "2.0", "method": "order.place", "params": { "symbol": "BTC", "side": "buy" }, "id": 100 }
- Бэкенд валидирует заявку, отправляет на биржу и возвращает ответ:
{ "jsonrpc": "2.0", "result": { "orderId": "123", "status": "filled" }, "id": 100 }
- Сервер при этом асинхронно шлёт обновления рынка через
notify
:{ "jsonrpc": "2.0", "method": "notify.priceUpdate", "params": { "symbol": "BTC", "price": 42000 } }
Когда ваш проект кричит «Мне нужно это!»
- Вы строите десктопное приложение (Wails, Electron) и хотите, чтобы бэкенд и фронтенд общались как соседи по комнате.
- Вам нужна гибридная архитектура (часть запросов по HTTP, часть — реальный time).
- Вы любите Go за скорость, но хотите, чтобы фронтенд не отставал.
Практика
Настройка проекта: Go, Gin и немножко магии
- Создаём проект:
mkdir gin-jsonrpc && cd gin-jsonrpc go mod init github.com/yourname/gin-jsonrpc
- Ставим зависимости:
go get github.com/gin-gonic/gin go get github.com/gorilla/websocket
- Код для инициализации Gin:
package main import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } func main() { r := gin.Default() r.GET("/rpc", func(c *gin.Context) { // Здесь будет логика WebSocket }) r.Run(":8080") }
WebSocket в Gin: превращаем HTTP в реальное время
Обновляем HTTP-соединение до WebSocket — как апгрейд телеги до Tesla:
r.GET("/rpc", func(c *gin.Context) { conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { c.AbortWithError(500, err) // Если что-то пошло не так, делаем вид, что это не мы return } defer conn.Close() // Не забываем закрыть дверь, а то улетит for { // Читаем сообщение от клиента _, msg, err := conn.ReadMessage() if err != nil { break // Клиент сбежал? Не беда! } // TODO: Здесь обработаем JSON-RPC запрос // Отвечаем (пока пустышкой) conn.WriteJSON(map[string]string{"response": "Я живой!"}) } })
JSON-RPC: разбираем запросы как Шерлок
Структура JSON-RPC запроса:
{ "jsonrpc": "2.0", "method": "sum", "params": [1, 2], "id": 1 }
Создадим структуры в Go для парсинга:
type Request struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` Params []interface{} `json:"params"` ID interface{} `json:"id"` } type Response struct { JSONRPC string `json:"jsonrpc"` Result interface{} `json:"result,omitempty"` Error interface{} `json:"error,omitempty"` ID interface{} `json:"id"` }
Обработчик методов: ваш RPC-диспетчер
Добавим роутинг методов. Например, обработчик для sum
:
func handleMethod(req Request) Response { switch req.Method { case "sum": if len(req.Params) != 2 { return Response{JSONRPC: "2.0", Error: "Нужно 2 числа!", ID: req.ID} } a, ok1 := req.Params[0].(float64) b, ok2 := req.Params[1].(float64) if !ok1 || !ok2 { return Response{JSONRPC: "2.0", Error: "Числа, Карл!", ID: req.ID} } return Response{JSONRPC: "2.0", Result: a + b, ID: req.ID} default: return Response{JSONRPC: "2.0", Error: "Метод не найден. Может, чаю попить?", ID: req.ID} } }
Обновляем цикл чтения сообщений:
for { _, msg, err := conn.ReadMessage() if err != nil { break } var req Request if err := json.Unmarshal(msg, &req); err != nil { conn.WriteJSON(Response{JSONRPC: "2.0", Error: "Невалидный JSON. Ты это серьёзно?", ID: nil}) continue } response := handleMethod(req) conn.WriteJSON(response) }
Тестируем: как отправить RPC-запрос через WebSocket
- Установите
wscat
(инструмент для командной строки):npm install -g wscat
- Подключитесь к серверу:
wscat -c ws://localhost:8080/rpc
- Отправьте запрос:
{ "jsonrpc": "2.0", "method": "sum", "params": [5, 3], "id": 1 }
- Сервер ответит:
{ "jsonrpc":"2.0", "result":8, "id":1 }
Подводные камни (и как не напороться)
- Валидация параметров: Если клиент пришёл без
method
илиid
, отправьте ему ошибку в стиле “Ты вообще читал документацию?” - Мультиплексирование: Для тяжёлых задач используйте пул горутин, чтобы не блокировать соединения.
- Безопасность: Не забудьте CORS и проверку Origin в
upgrader
, иначе ваш RPC станет добычей хакеров.
Заключение: Gin, WebSocket и JSON-RPC — трио, достойное хита
Теперь у вас есть сервер, который:
- Общается в реальном времени через WebSocket.
- Понимает JSON-RPC запросы.
- Может даже сложить 2 числа (вау!).
Дальше можно добавить авторизацию, логирование или подпись сообщений — но это уже тема для следующей статьи.