WebSockets in go using gorilla
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.