Yes you read it right! This blog provides a comprehensive guide on creating a straightforward broadcast chat application in under 100 lines of Golang code.
The blog is structured into two parts.
The initial segment delves into the implementation of a chat application using the Gorilla websockets package in Golang, accompanied by a basic static HTML page for the chat interface. In the subsequent section, we will explore enhancing the scalability of the application by incorporating Redis pub-sub.
Why Websockets over HTTP ?
Let’s dive into how we can implement a websocket based chat application in Golang.
Let’s start by creating a directory called go_chat
and initializing a new go module.
mkdir go_chat
cd go_chat
go mod init go_chat
Let’s install the dependencies using go get
go get github.com/gin-gonic/gin
go get github.com/gorilla/websocket
Let's write a simple main.go file with a main() function that initializes a Gin server.
package main
import (
"log"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
err := router.Run()
if err != nil {
log.Fatalf("Unable to start server. Error %v", err)
}
log.Println("Server started successfully.")
}
Next, let’s write a gin handler to handle websocket connections
func serveWs(c *gin.Context) {
upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("Error in upgrading web socket. Error: %v", err)
return
}
go handleClient(conn)
}
Let's break down the serveWs function.
Now that we have the websocket connection, let’s write a goroutine handles the connection.
var clients = make(map[*websocket.Conn]struct{})
type Message struct {
From string `json:"from"`
Message string `json:"message"`
}
func handleClient(c *websocket.Conn) {
defer func() {
delete(clients, c)
log.Println("Closing Websocket")
c.Close()
}()
clients[c] = struct{}{}
for {
var msg Message
err := c.ReadJSON(&msg)
if err != nil {
log.Printf("Error in reading json message. Error : %v", err)
return
}
// process the message
broadcast(msg)
}
}
Let’s see what we are doing here.
clients
to store the websocket connectionsNow that we’ve read the message, we’ll see more on how we can broadcast it to all other websocket connections
func broadcast(msg Message) {
for conn := range clients {
conn.WriteJSON(msg)
}
}
The above function simply takes in a message and writes it to all websocket connections using the WriteJSON
method using a for loop.
Now, let’s write a simple index.html file and save it inside a static folder that will act as the chat interface.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go chat 🚀</title>
</head>
<body>
<div style="display: flex;flex-direction: column;">
<div id="inputs">
<form onsubmit="handleSubmit(event)" method="post">
<input id="username" type="text" name="username" placeholder="username" required>
<input id="message" type="text" name="message" placeholder="what's on your mind ?" required>
<input type="submit" value="Send">
</form>
</div>
<div id="messages" style="display: flex;flex-direction: column;"></div>
</div>
</body>
<script>
const websocket = new WebSocket("ws://localhost:8080/ws");
function handleSubmit(e) {
e.preventDefault();
websocket.send(JSON.stringify({
"from": e.target.username.value,
"message": e.target.message.value
}))
}
websocket.onmessage = function (event) {
const message = JSON.parse(event.data)
messages.innerHTML += `<p><b>${message.from}</b> says ${message.message}</p>`
}
</script>
</html>
The above HTML file, when opened, creates a websocket connection with the backend and sends a message whenever the form is submitted. It also listens for incoming messages using the onmessage handler.
Let’s update the main function to serve the index.html and also wire up the websocket handler.
...
func main() {
...
router.StaticFile("/", "./static/index.html")
router.GET("/ws", serveWs)
...
}
...
The final main.go file looks something like this.
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
func main() {
router := gin.Default()
router.StaticFile("/", "./static/index.html")
router.GET("/ws", serveWs)
err := router.Run()
if err != nil {
log.Fatalf("Unable to start server. Error %v", err)
}
log.Println("Server started successfully.")
}
func serveWs(c *gin.Context) {
upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("Error in upgrading web socket. Error: %v", err)
return
}
go handleClient(conn)
}
var clients = make(map[*websocket.Conn]struct{})
type Message struct {
From string `json:"from"`
Message string `json:"message"`
}
func broadcast(msg Message) {
for conn := range clients {
conn.WriteJSON(msg)
}
}
func handleClient(c *websocket.Conn) {
defer func() {
delete(clients, c)
log.Println("Closing Websocket")
c.Close()
}()
clients[c] = struct{}{}
for {
var msg Message
err := c.ReadJSON(&msg)
if err != nil {
log.Printf("Error in reading json message. Error : %v", err)
return
}
broadcast(msg)
}
}
Now let’s run the application and open localhost:8080
in a browser.
go run .
Ta-da ✨! We’ve successfully built a simple chat application with Websockets in less than 100 lines of code.
However, this solution has one big drawback i.e, it cannot be deployed in a scalable manner.
In the next part, we’ll go through in detail about this problem and how we can solve it with Redis pub sub. Stay tuned !
Источник: dev.to
Наш сайт является информационным посредником. Сообщить о нарушении авторских прав.
go websockets webdev systemdesign