Что такое WebSocket и как он работает
Что проверяют
Понимаете ли вы, что после апгрейда соединение уходит из обычного цикла net/http, и умеете ли обращаться с ним по правилам Go: кто пишет, кто читает и кто гарантированно завершает горутины.
Как отвечать
WebSocket начинается с HTTP-запроса с заголовком Upgrade, сервер отвечает кодом 101 и перехватывает TCP-соединение, дальше идут кадры в обе стороны. В стандартной библиотеке Go сервера нет, берут gorilla/websocket или coder/websocket, апгрейд делается прямо из обычного http.Handler.
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
go writePump(conn, send) // единственный писатель, сообщения через канал
readPump(conn) // единственный читательДальше про правила, которые и проверяют. На соединение допускается одна читающая горутина и одна пишущая, поэтому все отправки идут через канал. Читатель ставит SetReadLimit и дедлайн чтения, обработчик pong продлевает дедлайн, писатель по тикеру шлёт ping. Закрытие делается один раз, через defer conn.Close() и отмену контекста, чтобы обе горутины завершились. Для рассылки многим клиентам заводят горутину-хаб с реестром соединений, а между инстансами ставят брокер.
Где спотыкаются
Пишут в соединение из нескольких горутин, например отвечают клиенту прямо из обработчика бизнес-события. Библиотека этого не допускает и паникует на конкурентной записи. Спасает один пишущий цикл и буферизированный канал, у которого есть политика на переполнение.
Не ставят дедлайны и ping. Оборванное на уровне сети соединение сервер не замечает, читающая горутина висит бесконечно, число горутин и память растут ровно до перезапуска процесса.
Отключают проверку Origin, чтобы заработало локально, и оставляют так в проде. Это открывает перехват соединения со стороннего сайта: правило одного источника на WebSocket не распространяется, а куки браузер отправит. Origin сверяют со списком, а сессию подтверждают билетом или подпротоколом.