Beginner's Guide to websockets with Golang
What are Web Sockets?
Well in simple terms its just a persistent (continuous) connection between a server and client. Unlike http connection which gets closed as soon as the query is complete.
Also in websocket a communication is realtime and bidirectional, which means both client and server can send message to each other at anytime. Where as in http server can only send a response to a request client sends first. which does sound very cool and is used in applications like
Chat applications
Location-based apps
Social feeds
Collaborative workspaces
Multiplayer gaming
Live streaming
Financial and sports updates
But it has a major drawback that is, it has to maintain all the connections on the server and it takes lot of resources to do so. So one may say just create create bunch of similar servers and connect it via proxy like how we do in http. But it doesn’t work like that in websockets. And we will look into it later in the series why and how to scale such systems.
For now lets start with building a basic web socket server in golang.
How to use it in golang
Golang has builtin library for websockets but it is recommended to used websockets library by Gorilla. so we will be using that library instead of inbuilt one.
Initiating project
go mod init websocbasics
Importing gorilla/websockets library in our project
go get github.com/gorilla/websocket
creating main.go file
touch main.go
./main.go
package main
import (
"fmt"
"net/http"
"github.com/gorilla/websocket"
)
// upgrader is used to upgrade our http connection to ws conn
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
// Allow all connections by returning true or
// implement your logic to check the origin here
return true
},
}
func main() {
http.HandleFunc("/ws", handleWebSoc)
fmt.Println("web soc running on port 8000")
err := http.ListenAndServe(":8000", nil)
if err != nil {
fmt.Println(err)
}
}
func handleWebSoc(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println(err)
return
}
defer conn.Close() // this will run as soon as client disconnects
// handle the ws connection we just created
for {
messType, data, err := conn.ReadMessage()
// Here data is the data sent by client
// messtype is the type of message we are recieving
// there are following message types -
// websocket.TextMessage
// websocket.BinaryMessage
// websocket.CloseMessage
// websocket.PingMessage
// websocket.PongMessage
// we are only concerned with close and text message
if err != nil {
fmt.Println(err)
return
}
// we are converting data to string as data is recieved in []byte form
fmt.Println("Recieved message : ", string(data))
conn.WriteMessage(messType, []byte("data recieved successfully"))
}
}
How this works is first client send a http request to our server to connect to a websocket. Then our server runs the functions associated with that route in this case handleWebSoc()
. This function takes in that request and converts it to a websocket connection. After a connection is established the for loop keeps the connection persistent and keeps listening for messages from client.
And our websocket connection is done… or so may one think. we are just doing what http does.
How can a server send messages asynchronously?
well all what we gotta do is keep track of connections we are have using a Hashmap.
Add following lines in above code
// this keeps track of all the connections that are getting connected
var WebSocketPool = make(map[*websocket.Conn]string)
func handleWebSoc(w http.ResponseWriter, r *http.Request) {
// ...
WebSocketPool[conn] = "" // adding a connetion to the pool
defer delete(WebSocketPool, conn) // removing the connection from pool after its closed
defer conn.Close()
// ...
}
// this is the function that will be sending message to our client asyncronously
func sendYo() {
for {
time.Sleep(time.Second * 2)
for con := range WebSocketPool {
con.WriteMessage(1, []byte("yooooo"))
}
}
}
// now all what we need to do is iniate this function before starting server
func main() {
go sendYo()
// we are using `go` keyword to push this function in the background on different thread
// so the function runs without hindering our server
// ...
}
Testing websockets using postman
go run main.go
now send this request using postman
And Thats how you use websockets in golang
Now we will look at how to make a basic chat app with websockets in golang.