Привет, Хабр!
WebSocket позволяет открыть интерактивный коммуникационный сеанс между пользовательским браузером и сервером. Здесь большое отличие от традиционного HTTP, который ограничен моделью запрос-ответ и не подходит для сценариев, требующих постоянного обмена данными
Go с помощью своей простоты и поддержкой конкурентности становится хорошим кандидатом для работы с WebSocket.
Значение конкурентности в Go для работы с WebSocket.
Горутины – это легковесные потоки исполнения, управляемые Go runtime. Они значительно более эффективны по сравнению с традиционными потоками операционной системы благодаря меньшему потреблению памяти и более низким накладным расходам на их создание и управление. Горутины позволяют писать асинхронный код, который может одновременно обрабатывать множество соединений или задач без блокировки и значительных накладных расходов.
Каналы в Go это средство для обмена данными между горутинами, обеспечивающий синхронизацию без явного использования блокировок или условий состояния. Они предоставляют безопасный и удобный способ передачи сообщений между горутинами, что мегаважно для обработки данных в реал таймме, как в случае с WebSocket.
WebSocket-серверы часто требуют одновременной обработки множества активных соединений. Каждое соединение WebSocket требует постоянной активности для поддержания связи и обмена данными в реальном времени. Используя горутины, Go позволяет управлять множеством параллельных соединений, где каждое соединение WebSocket может быть обработано отдельной горутиной. Это обеспечивает оч хорошую производительность и отзывчивость при минимальном оверхеде.
WebSocket предполагает двустороннюю коммуникацию, где сообщения могут исходить как от клиента, так и от сервера. Асинхронная природа горутин позволяет легко реализовать обработку входящих и исходящих сообщений одновременно. Каналы в Go могут использоваться для передачи сообщений между горутинами, занимающимися обработкой соединений и бизнес-логикой приложения.
WebSocket-сервер на Go
Предполагается, что вы уже имеете golang ^^
Создайте новую директорию для вашего проекта и инициализируйте её как модуль Go, используя команду go mod init your_project_name. Это создаст новый файл go.mod, управляющий зависимостями вашего проекта.
Про гориллу
Для работы с WebSocket мы будем использовать попсовую библиотеку gorilla/websocket. Да, вот причем тут горилла. Немного про неё:
websocket.Upgrader используется для обновления HTTP-соединения до протокола WebSocket. Это основной компонент для создания WebSocket-сервера. Он позволяет настроить различные параметры, такие как размеры буфера чтения и записи, проверку исходящего запроса и другие опции безопасности.
websocket.Conn представляет собой WebSocket-соединение. Этот тип обеспечивает интерфейсы для чтения и записи сообщений WebSocket. Он поддерживает текстовые и двоичные сообщения и позволяет управлять такими деталями, как время ожидания, закрытие соединения и управление пингами/понгами.
Немного про методы с этими фунциями:
Upgrader.Upgrade используется для преобразования HTTP-запроса в WebSocket-соединение. Этот метод возвращает *websocket.Conn и используется на стороне сервера для начала сессии WebSocket.
Conn.ReadMessage() и Conn.WriteMessage() используются для чтения и записи сообщений. ReadMessage блокирует вызывающий поток до получения сообщения или возникновения ошибки. WriteMessage используется для отправки сообщений клиенту.
NextWriter и NextReaderпредоставляют более низкоуровневый доступ к потокам чтения и записи WebSocket. NextWriter возвращает writer для следующего сообщения, а NextReader очевидно возвращает reader для чтения следующего сообщения.
Также:
Библиотека поддерживает управление закрытием соединения, включая отправку и обработку соответствующих управляющих сообщений WebSocket. В библиотеке есть поддержка пинг/понг обработчиков для поддержания активности соединения и определения его состояния.
Поддерживает сжатие сообщений WebSocket, что может быть полезно для уменьшения объема передаваемых данных.
Позволяет управлять фреймами данных.
Чтобы добавить её в ваш проект, выполните команду go get github.com/gorilla/websocket.
Подробнее про библиотеку на гит хабе.
Напишем код для создания простого сервера
Импортируем нашу гориллу:
import ( "net/http" "github.com/gorilla/websocket" )
websocket.Upgraderбудем юзать для обновления HTTP-соединений до протокола WebSocket
var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, }
Создадим функцию, которая будет обрабатывать входящие WebSocket-соединения:
func handleConnections(w http.ResponseWriter, r *http.Request) { // обновление соединения до WebSocket ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Fatal(err) } defer ws.Close() // цикл обработки сообщений for { messageType, message, err := ws.ReadMessage() if err != nil { log.Println(err) break } log.Printf("Received: %s", message) // эхо ансвер if err := ws.WriteMessage(messageType, message); err != nil { log.Println(err) break } } }
Зарегистрируем функцию handleConnections как обработчик маршрута:
func main() { http.HandleFunc("/ws", handleConnections) log.Println("http server started on :8000") err := http.ListenAndServe(":8000", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } }
Как тестировать?
Выполняем команду go run your_project_name.go для запуска сервера и проверяем, что сервер запускается без ошибок и доступен на http://localhost:8000/ws.
Можно также использовать клиент WebSocket, например, браузерное расширение, чтобы установить соединение с вашим сервером и отправить сообщения.
Поддержание
WebSocket-соединение начинается с HTTP-запроса, который затем «обновляется» до протокола WebSocket. Этот процесс называется «Handshake». Начальный запрос должен соответствовать стандартам WebSocket, включая правильные заголовки (Upgrade: websocket и Connection: Upgrade).
На стороне сервера важно проверять поле Origin в HTTP-запросе, чтобы предотвратить атаки типа Cross-Site WebSocket Hijacking.
Используя гориллу в Go, можно настроить параметры соединения, включая размеры буферов, таймауты и механизмы сжатия:
Настроим Upgrader:
import ( "net/http" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, // Размер буфера чтения WriteBufferSize: 1024, // Размер буфера записи // Позволяет определить, должен ли сервер сжимать сообщения EnableCompression: true, } func handleUpgrade(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { // обработка ошибки return } defer conn.Close() // дальнейшая обработка соединения }
upgrader, который используется для преобразования HTTP-запросов в WebSocket-соединении
Таймауты:
func handleConnections(conn *websocket.Conn) { // Установка таймаута для чтения сообщения conn.SetReadDeadline(time.Now().Add(60 * time.Second)) for { _, message, err := conn.ReadMessage() if err != nil { // обработка ошибки break } // обработка сообщения // Обновление таймаута после успешного чтения сообщения conn.SetReadDeadline(time.Now().Add(60 * time.Second)) } }
Устанавливаем таймаут для операций чтения на WebSocket-соединении. SetReadDeadline используется для определения времени, по истечении которого соединение будет закрыто, если не будет получено новое сообщение.
WebSocket поддерживает фреймы управления, такие как пинг (ping) и понг (pong).
Отправка пингов с сервера на клиент помогает удостовериться, что клиент все еще подключен и активен.
Как это можно реализовать:
На стороне клиента необходимо настроить обработчик пингов, который будет отвечать понгами:
conn.SetPingHandler(func(appData string) error { return conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(writeWait)) })
На сервере можно реализовать горутину, которая будет периодически отправлять пинги клиентам:
ticker := time.NewTicker(pingPeriod) defer ticker.Stop() for { select { case <-ticker.C: conn.SetWriteDeadline(time.Now().Add(writeWait)) if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil { return // или обработать ошибку } } }
Понги используются в ответ на пинги и помогают серверу узнать, что клиент все еще подключен.
На сервере можно настроить обработчик понгов для обновления состояния соединения:
conn.SetPongHandler(func(string) error { conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
Для защиты данных, передаваемых по WebSocket, следует использовать WSS (аналог HTTPS для WebSocket), который обеспечивает шифрование данных. На сервере стоит установить ограничения на количество одновременно открытых соединений, размер принимаемых сообщений и другие параметры для защиты от перегрузок и атак.
Про масштабирование
WebSocket поддерживает длительные соединения. Однако, при масштабировании, оч важно управлять этими соединениями. В Go, это обычно достигается за счет использования горутин для каждого соединения:
package main import ( "net/http" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } func handler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } defer conn.Close() go handleConnection(conn) } func handleConnection(conn *websocket.Conn) { for { messageType, p, err := conn.ReadMessage() if err != nil { return } // обработка сообщения... } } func main() { http.HandleFunc("/ws", handler) http.ListenAndServe(":8080", nil) }
При масштабировании WebSocket-сервера в Go нужно обеспечить обработку входящего и исходящего трафика. Это может включать использование буферизации, асинхронной отправки/получения данных и обработки ошибок:
func handleConnection(conn *websocket.Conn) { for { // Чтение месседжа _, message, err := conn.ReadMessage() if err != nil { break } // асинхрон отправка go func(msg []byte) { err = conn.WriteMessage(websocket.TextMessage, msg) if err != nil { return } }(message) } }
Горутины и каналы — хороший асинхронный инструмент:
func handleConnection(conn *websocket.Conn) { msgChan := make(chan []byte) go func() { for { message, ok := <-msgChan if !ok { return } conn.WriteMessage(websocket.TextMessage, message) } }() for { _, message, err := conn.ReadMessage() if err != nil { close(msgChan) break } msgChan <- message } }
Часто используют промежуточные слои для управления аутентификацией, логированием, ограничением скорости:
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { log.Println("Получен запрос:", r.URL) next.ServeHTTP(w, r) } } func main() { http.HandleFunc("/ws", loggingMiddleware(handler)) http.ListenAndServe(":8080", nil) }
WebSocket в Go имеет множество возможностей. Это позволяет создавать интерактивные и реагирующие в real-time приложения. Go хороший выбор для вебсокетов, благодаря своей производительности, фичам конкурентности и простоте интеграции.
«Горилла» — это не только сильное животное, но и хороший инструмент в вашем арсенале разработчика.
Про другие, не менее полезные инструменты, мои коллеги из OTUS рассказывают в рамках онлайн-курсов. Также напоминаю о том, что в календаре мероприятий вы можете зарегистрироваться на ряд полезных бесплатных вебинаров.
ссылка на оригинал статьи https://habr.com/ru/articles/781748/
Добавить комментарий