Welcome to our next lesson in this course on building a ToDo application with Go and Gin. Today, we will dive into real-time communication, a highly sought-after feature in today's interactive applications. Real-time communication allows applications to push updates and information to users as they happen, creating a seamless and engaging experience.
In our journey so far, we've established a solid foundation with database interactions and SQL queries. Now, let’s explore how we can enhance user engagement by enabling real-time notifications in our ToDo app using WebSockets.
Let's first understand the theoretical aspects of WebSockets before diving into implementation.
WebSockets represent a shift from the traditional HTTP protocol by allowing full-duplex (bidirectional and simultaneous) communication between a client and a server. In HTTP, communication begins with a request from the client, followed by a response from the server, after which the connection is typically closed; for new data exchanges, a new request must be initiated by the client, leading to another round of request/response communication. This model works well for many applications but isn't ideal for real-time updates.
With WebSockets:
- A connection is initiated by the client, similar to an HTTP request.
- Once established, the connection remains open, enabling continuous data exchange without repeated requests.
- Both client and server can send data independently, significantly improving efficiency.
This difference allows us to send real-time updates to users without polling the server or waiting for user actions. In our ToDo app, this means we can notify users immediately when tasks are updated or completed.
Before implementing WebSockets, it’s essential to understand the benefits and challenges they introduce.
Pros:
- Real-time Data: WebSockets provide immediate data transfer, which is ideal for time-sensitive applications.
- Efficiency: After the initial handshake, messages can be exchanged with minimal overhead, unlike HTTP, which carries headers with every request.
- Bi-directional Communication: Both the client and server can send messages independently of each other.
Cons:
- Resource Consumption: Persistent connections can consume significant server resources, especially with many clients.
- Complexity: Implementing WebSockets may add complexity to your app's architecture.
- Network Reliability: WebSocket connections can be disrupted by fluctuating network conditions.
Keep these points in mind as you decide where and how to implement WebSockets in your projects.
Let’s add WebSocket functionality to our ToDo app. First, create a service in todoapp/services/websocket_service.go
to handle WebSocket communication logic:
Go1package services 2 3import ( 4 "fmt" 5 6 "github.com/gorilla/websocket" 7) 8 9func WebSocketLoop(conn *websocket.Conn) { 10 for { 11 _, msg, err := conn.ReadMessage() 12 if err != nil { 13 fmt.Println("[SERVER LOG] Error reading message:", err) 14 break 15 } 16 fmt.Println("[SERVER LOG] Message received:", string(msg)) 17 18 if err := conn.WriteMessage(websocket.TextMessage, []byte("This is the message received by the server: "+string(msg))); err != nil { 19 fmt.Println("[SERVER LOG] Error writing message:", err) 20 break 21 } 22 } 23}
github.com/gorilla/websocket
package: This is a third-party Go package that provides support for WebSockets. This package handles the complexities of WebSocket connections and supports features like upgrading HTTP connections, reading and writing messages, handling binary and text data, and more.- The
WebSocketLoop
function handles the ongoing message exchange between the server and client by reading messages sent by the client and echoing them back to the client. conn.ReadMessage()
andconn.WriteMessage(websocket.TextMessage, []byte("message"))
are used respectively to read and to write messages from and to the WebSocket connection.
Next, in todoapp/controllers/websocket_controller.go
, define a handler to manage WebSocket connections:
Go1package controllers 2 3import ( 4 "net/http" 5 6 "github.com/gin-gonic/gin" 7 "github.com/gorilla/websocket" 8 9 "codesignal.com/todoapp/services" 10) 11 12var upgrader = websocket.Upgrader{ 13 CheckOrigin: func(r *http.Request) bool { 14 // Allow connections only from localhost 15 return r.Host == "localhost:3000" || r.Host == "127.0.0.1:3000" 16 }, 17} 18 19func WebSocketHandler(c *gin.Context) { 20 conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) 21 if err != nil { 22 http.NotFound(c.Writer, c.Request) 23 return 24 } 25 defer conn.Close() // Ensure connection is closed when handler exits 26 services.WebSocketLoop(conn) 27}
Here's what's happening in the handler:
websocket.Upgrader
: This is responsible for upgrading a regular HTTP connection to a WebSocket connection. Thewebsocket.Upgrader
struct is part of thegorilla/websocket
module and manages the handshake process, ensuring it meets the WebSocket protocol requirements.CheckOrigin
: This field inwebsocket.Upgrader
is a function used to validate the origin of incoming requests to prevent unwanted cross-origin requests.- Connection Upgrade: When a client requests
/ws
, the HTTP connection is upgraded to a WebSocket usingupgrader.Upgrade
. - Message Handling: With the connection established, the server continuously reads client messages and swaps back responses via
services.WebSocketLoop
. - Connection Cleanup: A
defer conn.Close()
statement ensures the WebSocket connection is properly closed upon handling completion.
The controller function is then attached to the endpoint /ws
.
To see this in action, use a WebSocket client tool to connect to ws://localhost:3000/ws
. As cURL
does not natively support WebSocket connections, we employ websocat
, which allows connecting to WebSockets from the command line. Please note that websocat
usually needs to be installed manually; refer to the GitHub repository of the project for installation instructions. In our CodeSignal IDE, websocat
is already installed and ready to run.
Once you have installed websocat
, simply run the following command to open the connection:
Bash1websocat ws://localhost:3000/ws
Once the connection is open, you can type messages in the terminal, pressing enter to send them to the server. In the current setup, any message you type into the WebSocket client will be echoed back by the server, accompanied by logs in the server console indicating that the message was received. For example:
-
Client tab:
1Hello, WebSocket server! 2This is the message received by the server: Hello, WebSocket server! 3This is a test message. 4This is the message received by the server: This is a test message.
-
Server tab (in the console running the Gin server):
1[SERVER LOG] Message received: Hello, WebSocket server! 2[SERVER LOG] Message received: This is a test message.
In this lesson, we explored WebSockets and integrated real-time notification capabilities into our ToDo app. We discussed the benefits and challenges of using WebSockets and implemented a basic WebSocket connection with message handling in Gin.
Now that you have a solid understanding of real-time communication with WebSockets, get ready to dive into our practice tasks, where you will test your understanding of WebSocket connections. Keep up the great work!