Designing a WebSocket Server in Golang | Reformers Golang Implementation Strategy
By: Uddeshya Singh
Tags: Golang, Programming, Docker, Servers, Websocket

Basically, our server offers 3 different verticals of services, namely Form Handlers, User Handler, and Lock handler

User Handler

Handles Entry and Exit of clients in the editing room Assigns and refreshes entry token of the incoming clients Tracks which user is currently in the room and helps to relay the same to connected clients. Resets the entire room (participating clients and the current form state) every 6 hours.

Lock Handler

They help in assigning locks to certain form questions so that multiple users can edit them without the fear of the form being deleted by another unaware user

Form Handler

This helps in adding new questions, deleting old ones, and editing existing ones. Multiple handlers are spawned within the server to help and perform requests at a faster pace. Each action in this module has a different workflow which is described later on.

Config Structs ⚙️

There are a total of 7 structs being used, out of which I would like to elaborate on these

Over here, FormVersionControl helps in maintaining data about the version control elements about a particular question, ClientObject as the name suggests, stores client information. FormUpdateElement is an internal struct used for passing on information for updates about the updates required in a particular question, lastly, FormElement stores the information about a particular question in the form.

Why am I using sync.mutex? 🧵

As you might’ve noticed, I am using mutex lock in the elements where it’s sensitive to ensure synchronization among processes. The illustration below is the case where it will give an error if no mutex lock is used.

For example, in ClientObject struct, you cannot push multiple messages at the same time through a WebSocket. Now, to ensure that multiple threads are not using the WebSocket ( clientWebSocket ) at the same time. The illustration given below explains the fix.

Synchronization using mutex locks

A little bit about the server… 💻

My server is basically a struct that can be exported independently using Init() a constructor and called in main.go . It binds WebSocket handler at /ws while spawning a thread each for roomUpdater , pruneClients and handleCustomMessages while 3 threads of formRequestHandler to ensure quicker question request processing.

Each struct has a clients map storing ClientObject struct to a boolean, a clientTokenMap to map entry tokens. clientRoomActivity to store user joining/leaving messages which are handled by handleCustomMessages thread. A requestUpgrader to store in WebSockets. Util for Util module, userTicker to push updates about present users in the channel. resetTicker to reset the entire room.

currFormId to assign a new form Id to new questions while formArray stores all the questions. formMutex to ensure synchronization and no race-around condition as explained above.

For the money shot, our WebSocket Handler! 🥁

Over here, we are initializing an origin check (which we are returning True in any case) and then upgrading the incoming HTTP request into a persistent duplex connection. I am using gorilla WebSockets as our library for WebSocket connections.

A defer function is being used to ensure the registered client is deleted in case something goes wrong in initiating the connection.

After that, an infinite for loop is used to ensure that the endpoint is always listening for messages from the client and we unmarshal that into server readable format i.e. clientRequestObject struct and send it to HandleClientMessage method for further processing and in case something goes wrong here too, we will throw out this connection from mapped client connections.

Dockerfile 🐳

My dockerfile for the project is pretty standard

You can go ahead and deploy it on a VM (like Azure) and it should all work fine, provided you have a TLS certificate to ensure the establishment of a WSS protocol connection, which I didn’t have, so I decided to go for a smooth Heroku deployment.

Heroku Deployment 🇭

The Heroku dev center provides excellent resources for Golang app deployment on their platform. You can follow the steps given here: Getting Started on Heroku with Go to deploy your own version if you want to.

In a nutshell, all you got to do is:

Create a Procfile which should contain

web: bin/re-formers-server

2. In your main function, ensure your app calls for PORT environment variable and auto set it to something in case it’s not present (because Heroku needs it, don’t ask me why)

3. Now just follow the classic Heroku commands

$ heroku create

$ git push heroku main

$ heroku open

Tada!

Phew, finally to the conclusion 😅

So in this post, you got a basic overview of features, what they do, and how is everything kept in sync. In the next post, you will go through how the client works and the overview of the flow of how a question is added, edited, or deleted in a form in our server. Hold tight, you are almost at the end ;)

Resources 📎