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.