JSON-RPC на стероидах: как заставить Gin и WebSocket работать в унисон без потери нервных клеток

json-rpc

Введение: JSON-RPC и WebSocket — брак, заключённый в аду (но это работает)

JSON-RPC — это как SMS от вашего сервера: “Эй, выполни метод X и ответь”. А WebSocket — это бесконечный чат, где сервер и клиент могут болтать без остановки. Соединить их — всё равно что научить кота приносить тапки: звучит странно, но возможно. Зачем? Чтобы ваше приложение общалось быстро, асинхронно и без лишних HTTP-рукопожатий. А Gin здесь — как бодрый бариста, который быстро настраивает роуты и middleware, чтобы вы не варили кофе вручную.


Почему Gin + WebSocket?

  • Gin — это Go-фреймворк, который не грузит вас тоннами абстракций (и не заставляет читать мантры в документации).
  • WebSocket — канал для мгновенных сообщений, чтобы RPC-запросы и ответы летали как снайперские пули, а не как письма голубиной почтой.

Идеальный дуэт для микросервисов, чатов или игр, где задержка — враг №1.

Раздел: «Где и зачем это использовать? Или как не изобретать велосипед с реактивным двигателем»

Проблемы, которые решает JSON-RPC через WebSocket

  1. HTTP-оверхед:
    Каждый HTTP-запрос — это установка соединения, заголовки, закрытие. WebSocket держит канал открытым, как беседа с болтливым другом, который не хочет уходить.
    Пример: Если ваше приложение шлёт 100 RPC-запросов в секунду, через HTTP вы потратите 90% времени на рукопожатия. WebSocket режет задержки как ниндзя.
  2. Асинхронность:
    Клиент отправил запрос и может заниматься другими делами, пока сервер готовит ответ. Это как заказать пиццу и не стоять под дверью курьера.
    Пример: В трейдинговой платформе цена меняется каждую миллисекунду — клиент подписывается на обновления через notify и получает данные, когда они есть.
  3. Структура против хаоса:
    Чистый 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
    }
}


Архитектуры, где это работает лучше всего

  1. Event-Driven Architecture (EDA):
    Сервер и клиенты обмениваются событиями (например, уведомления о изменениях данных).
    Паттерн: «Подписка» через RPC-метод subscribeToUpdates, а сервер шлёт ответы как notify.
  2. Микросервисы с низкой задержкой:
    Сервисы общаются через WebSocket вместо REST, если нужно меньше оверхеда.
  3. Приложения с долгоживущими сессиями:
    Например, админка, где юзер открывает её на 8 часов и должен моментально получать оповещения.

Когда НЕ стоит это использовать

  • Простой CRUD: Если ваше приложение — это «получить список пользователей раз в минуту», хватит REST.
  • Оффлайн-приложения: WebSocket без сети — как кофе без кофеина.
  • Системы с жесткими требованиями к безопасности: WebSocket сложнее защищать, чем HTTP (но возможно, с WSS и токенами).

Пример из реальной жизни: Wails + Gin + JSON-RPC
Допустим, вы делаете десктопное приложение для трейдинга:

  • Фронтенд (Wails+Vue): рисует графики, портфель, кнопки «Купить/Продать».
  • Бэкенд (Go): держит соединение с биржей через WebSocket, считает риски.

Как они общаются:

  1. При запуске фронтенд подключается к бэкенду через WebSocket (ws://localhost:8080/rpc).
  2. Когда пользователь жмёт «Купить», фронтенд вызывает:
    {
        "jsonrpc": "2.0", 
        "method": "order.place", 
        "params": {
            "symbol": "BTC", 
            "side": "buy"
        }, 
        "id": 100
    }
  3. Бэкенд валидирует заявку, отправляет на биржу и возвращает ответ:
    {
        "jsonrpc": "2.0", 
        "result": {
            "orderId": "123", 
            "status": "filled"
        }, 
        "id": 100
    }
  4. Сервер при этом асинхронно шлёт обновления рынка через notify:
    {
        "jsonrpc": "2.0", 
        "method": "notify.priceUpdate", 
        "params": {
            "symbol": "BTC", 
            "price": 42000
        }
    }

Когда ваш проект кричит «Мне нужно это!»

  • Вы строите десктопное приложение (Wails, Electron) и хотите, чтобы бэкенд и фронтенд общались как соседи по комнате.
  • Вам нужна гибридная архитектура (часть запросов по HTTP, часть — реальный time).
  • Вы любите Go за скорость, но хотите, чтобы фронтенд не отставал.

Практика

Настройка проекта: Go, Gin и немножко магии

  1. Создаём проект:
    mkdir gin-jsonrpc && cd gin-jsonrpc
    go mod init github.com/yourname/gin-jsonrpc
  2. Ставим зависимости:
    go get github.com/gin-gonic/gin
    go get github.com/gorilla/websocket
  3. Код для инициализации 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

  1. Установите wscat (инструмент для командной строки):
    npm install -g wscat
  2. Подключитесь к серверу:
    wscat -c ws://localhost:8080/rpc
  3. Отправьте запрос:
    {
        "jsonrpc": "2.0", 
        "method": "sum", 
        "params": [5, 3], 
        "id": 1
    }
  4. Сервер ответит:
    {
        "jsonrpc":"2.0",
        "result":8,
        "id":1
    }
Совет: Если сервер молчит — проверьте, не перепутали ли вы порт. Это случается чаще, чем кажется (как потеря пульта от TV).

Подводные камни (и как не напороться)

  1. Валидация параметров: Если клиент пришёл без method или id, отправьте ему ошибку в стиле “Ты вообще читал документацию?”
  2. Мультиплексирование: Для тяжёлых задач используйте пул горутин, чтобы не блокировать соединения.
  3. Безопасность: Не забудьте CORS и проверку Origin в upgrader, иначе ваш RPC станет добычей хакеров.

Заключение: Gin, WebSocket и JSON-RPC — трио, достойное хита

Теперь у вас есть сервер, который:

  • Общается в реальном времени через WebSocket.
  • Понимает JSON-RPC запросы.
  • Может даже сложить 2 числа (вау!).

Дальше можно добавить авторизацию, логирование или подпись сообщений — но это уже тема для следующей статьи.

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

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