WebSockets in go using gorilla

Damodar Bastakoti
readytowork, Inc.
Published in
7 min readMar 28, 2023

Real-time applications require real-time communication, and that’s where WebSockets come into play. WebSockets are web technology that allows for two-way communication between a client and server over a single, long-lived connection. This makes it possible to build real-time applications that can deliver data quickly and efficiently. In Go, the Gorilla WebSockets package provides support for implementing WebSockets with ease. In this article, we will explore how to use Gorilla WebSockets to build real-time applications in Go.

What are WebSockets?

WebSockets are a web technology that provides a bidirectional, full-duplex communication channel over a single TCP connection. This means that a client and a server can send and receive data at the same time, in real time, without the need for constant requests and responses.

WebSockets are ideal for building real-time applications, such as chat rooms, real-time games, and collaborative applications. They allow for efficient communication between clients and servers, resulting in better user experiences.

What is Gorilla WebSockets?

Gorilla WebSockets is a popular WebSocket package for Go. It provides a simple and efficient way to implement WebSockets in Go. Gorilla WebSockets supports both the WebSocket protocol and the WebSocket Secure protocol (WSS).

Gorilla WebSockets provide a number of useful features, such as the ability to handle multiple connections, read and write messages asynchronously, and use custom handlers for different WebSocket events.

Setting up a Gorilla Websockets Server

To set up a Gorilla Websockets server, we first need to install the Gorilla websocket package. You can do this using the following command:

go get github.com/gorilla/websocket

We will learn web sockets in Go by creating a simple chat server.

Create a Client

// clients.go
package socket

type Client struct {
Server *ChatServer
Conn *websocket.Conn
Send chan []byte
}

The code block provided defines a type Client in Go, which represents a WebSocket connection of the client. The Client type has the following fields:
Conn → pointer to the websocket.Conn struct representing the WebSocket. connection for the client.
Send → it is a channel, which will receive message([]byte) when server broadcast to the clients .

Create a Chat Server

// chatserver.go
package socket

type ChatServer struct {
Client map[*Client]bool
Broadcast chan []byte
Register chan *Client
Unregister chan *Client
}

The code block provided defines a type ChatServer in Go, which represents a simple chat server. The ChatServer type has the following fields:
Client → A map of pointers to Client structs and a boolean value, which indicates whether the client is connection is live or not.

Broadcast → A channel of byte slices, which is used to broadcast messages to all connected clients.

Register → A channel of pointers to Client structs, which is used to register a new client to the chat server.

Unregister →A channel of pointers to Client structs, which is used to unregister a client from the chat server.

Create a Upgrader

//upgrade.go
package socket

import (
"log"
"net/http"

"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
ReadBufferSize: 1024, // bytes
WriteBufferSize: 1024, // bytes
}

// function to upgrade http request to websocket connection

func Upgrade(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return nil, err
}
return conn, err
}

These buffer sizes determine the maximum amount of data that can be read from or written to the WebSocket connection in a single operation.

The Upgrade function takes an http.ResponseWriter and an http.Request as input parameters and returns a pointer to a WebSocket connection and an error value. The function uses the upgrader variable to upgrade the HTTP connection to a WebSocket connection, and it returns a pointer to the resulting WebSocket connection if the upgrade is successful.

The CheckOrigin field of the upgrader variable is set to a function that always returns true. This allows any origin to connect to the WebSocket server, which is necessary for development and testing purposes.

Create a method to read message from client

//client.go
package socket

import (
"log"

"github.com/gorilla/websocket"
)

type Client struct {
Server *ChatServer
Conn *websocket.Conn
Send chan []byte
}

func (c *Client) Read() {
defer func() {
c.Server.Unregister <- c
c.Conn.Close()
}()
for {
_, message, err := c.Conn.ReadMessage()
if err != nil {
log.Panicln(err)
return
}
c.Server.Broadcast <- message
}
}

In the above code, the Read() method is defined on a Client struct, and it listens for incoming messages from a WebSocket connection c.Conn. It is inside a loop that continuously runs until the program is terminated Whenever a message is received, the Read() method sends the message to the Broadcast channel of the chatServer. This allows all currently active clients in the chat server that are listening to the Broadcast channel to receive the message.

Additionally, the Read() method has a defer statement that executes after the method returns. This defer statement sends a message to the Unregister channel of chatServer and closes the connection c.Conn. This ensures that the client is properly unregistered from the chat server and its connection is closed when the method exits.

Create a method to keep track of registering/unregistering clients, and broadcasting messages.

//chatServer.go
package socket

import (
"math/rand"
"time"
)

type ChatServer struct {
Client map[*Client]bool
Broadcast chan []byte
Register chan *Client
Unregister chan *Client
}

func NewChatServer() *ChatServer {
rand.Seed(time.Now().UnixNano())
chatServer := &ChatServer{
Client: make(map[*Client]bool),
Broadcast: make(chan []byte),
Register: make(chan *Client),
Unregister: make(chan *Client),
}
go chatServer.run()
return chatServer
}

func (r *ChatServer) run() {
for {
select {
case client := <-r.Register:
r.Client[client] = true
for client := range r.Client {
println(client)
}
case client := <-r.Unregister:
if _, ok := r.Client[client]; ok {
delete(r.Client, client)
close(client.Send)
}
case message := <-r.Broadcast:
for client := range r.Client {
if err := client.Conn.WriteJSON(string(message)); err != nil {
}
}
}
}
}

The NewChatServer() function initializes a new ChatServer instance and starts a goroutine to run the ChatServer by calling the run() method.
The run() method uses a select statement inside a never-ending for loop to listen for incoming messages on the Register, Unregister, and Broadcast channels, and takes appropriate actions based on the message received on the channel.

When a new client connects to the, the run() the method adds the client to the Client map and broadcasts the message to all connected clients. When a client disconnects, the run() method removes the client from the Client map and closes the client's Send channel. When a message is broadcasted, the run() method sends the message to all connected clients.

Create a chatServer and listen to the connection request

package main

import (
"log"
"net/http"
"test/socket"
)

func serveWs(chatServer *socket.ChatServer, w http.ResponseWriter, r *http.Request) {
conn, err := socket.Upgrade(w, r)
if err != nil {
log.Fatalln(err)
}
client := &socket.Client{
Server: chatServer,
Conn: conn,
}
chatServer.Register <- client
go client.Read()
}


func main() {
chatServer := socket.NewChatServer()
http.HandleFunc("/websocket", func(w http.ResponseWriter, r *http.Request) {
serveWs(chatServer, w, r)
})
http.ListenAndServe(":8000", nil)
}

In the main() function, create a new chat server with an empty map of connected clients and channels to register, unregister, and broadcast messages. The server listens for incoming WebSocket connections on port 8000. When a connection is established, it calls serveWs() to handle the connection.

The serveWs() the function is called whenever a client connects to the server. It upgrades the HTTP connection to a WebSocket connection, creates a new Client object using the connection, and registers the client with the chat server. Running client.Read() in a separate goroutine, it allows the client to continuously listen for new messages without blocking the main thread. This way, the server can handle multiple clients simultaneously and ensure that all clients receive messages in real time.

Its time to test our code

Create a new WebSocket request in Postman

In the request URL, enter the WebSocket server URL you want to connect to.

First, you should send a message after establishing a connection, and then you will receive a message from the server as shown below.

Overall, this chat application provides a simple and effective way for multiple clients to communicate in real time and demonstrates the power and flexibility of using WebSockets in web applications.

--

--