WebSocket в Go: библиотека gorilla/websocket и стандартная реализация

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

WebSocket библиотека Go (Golang): gorilla/websocket и стандартный net/http

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

В экосистеме Go (Golang) существует несколько способов работы с WebSocket. Самой популярной и проверенной временем является библиотека gorilla/websocket. Однако, начиная с Go 1.22, появилась возможность реализовать базовый WebSocket-сервер без сторонних зависимостей, используя стандартный пакет net/http. В этой статье мы подробно разберём оба подхода.

Установка библиотеки gorilla/websocket

Для начала работы с gorilla/websocket установите её через go get:

go get github.com/gorilla/websocket

Библиотека предоставляет удобный API для создания как серверной, так и клиентской части. Она обрабатывает рукопожатие (handshake), фрейминг сообщений, поддерживает сжатие и автоматический пинг/понг.

Сервер WebSocket на gorilla/websocket

Рассмотрим пример простого эхо-сервера. Клиент отправляет сообщение, сервер возвращает его обратно:

package main

import (
    "log"
    "net/http"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true // разрешаем любые источники (для разработки)
    },
}

func echoHandler(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Print("Upgrade failed:", err)
        return
    }
    defer conn.Close()

    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            break
        }
        log.Printf("Received: %s", message)

        if err := conn.WriteMessage(messageType, message); err != nil {
            log.Println("Write error:", err)
            break
        }
    }
}

func main() {
    http.HandleFunc("/echo", echoHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Здесь Upgrader преобразует обычный HTTP-запрос в WebSocket-соединение. Функция CheckOrigin контролирует, с каких доменов разрешены подключения. В production рекомендуется проверять конкретные источники.

Клиент WebSocket на gorilla/websocket

Пример клиента, который подключается к нашему серверу и отправляет сообщение:

package main

import (
    "log"
    "os"
    "os/signal"
    "time"
    "github.com/gorilla/websocket"
)

func main() {
    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, os.Interrupt)

    url := "ws://localhost:8080/echo"
    c, _, err := websocket.DefaultDialer.Dial(url, nil)
    if err != nil {
        log.Fatal("dial:", err)
    }
    defer c.Close()

    done := make(chan struct{})

    // Горутина для чтения ответов
    go func() {
        defer close(done)
        for {
            _, message, err := c.ReadMessage()
            if err != nil {
                log.Println("read:", err)
                return
            }
            log.Printf("recv: %s", message)
        }
    }()

    // Отправка сообщения
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-done:
            return
        case t := <-ticker.C:
            err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
            if err != nil {
                log.Println("write:", err)
                return
            }
        case <-interrupt:
            log.Println("interrupt")
            // Чистое закрытие соединения
            err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
            if err != nil {
                log.Println("write close:", err)
                return
            }
            select {
            case <-done:
            case <-time.After(time.Second):
            }
            return
        }
    }
}

Стандартный net/http (Go 1.22+)

Начиная с Go 1.22, пакет net/http содержит встроенную поддержку WebSocket. Это удобно, когда не хочется добавлять внешние зависимости. Пример сервера:

package main

import (
    "io"
    "log"
    "net/http"
)

func wsHandler(w http.ResponseWriter, r *http.Request) {
    // Проверяем, что это WebSocket-запрос
    if r.Header.Get("Upgrade") != "websocket" {
        http.Error(w, "Not a websocket request", 400)
        return
    }

    // Получаем соединение
    conn, err := http.Upgrade(w, r, nil)
    if err != nil {
        log.Println("Upgrade error:", err)                    

Рекомендаций