Designing a WebSocket Server in Golang | Reformers Golang Implementation Strategy

Uddeshya Singh
Geek Culture
Published in
6 min readMar 14, 2021

Welcome to the second part of the Re-Formers project (Collaborative form editor using WebSockets). This post is about the WebSocket server which handles the entire form synchronization for each element. You can find the other posts in the series right here:

  1. Collaborative form editor using WebSockets | Web Sockets in Golang and React
  2. Designing a WebSocket server in Golang | Reformers backend implementation strategy
  3. Designing a WebSocket client with notifications in ReactJS | Reformers frontend implementation strategy

TL; DR

The following GitHub repository will take you to the server code: re-formers-server and to workout on a docker container, you can access the container image using

$ docker pull uddeshyasingh/re-formers-server
$ docker run -p 1337:1337 uddeshyasingh/re-formers-server:latest

Folder Description Overview 🕵️

The entire server module comprises of a Dockerfile, Procfile, some configuration elements, and 2 modules, Server and Utils

Program folder

/server : It comprises the entire primary server module which is later on called in main.go. This is a singleton package and hence can be scaled easily.

/utils : It comprises of utility function which handles jobs like giving the client a name + color combo, mapping entry tokens to clients, assigning them to edit hooks, etc.

/tmp : This is a temporary file created due to usage of air live debugger and you can ignore this in the production containers.

What all does it do? 📚

Method Diagram for server

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

User Handler

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

Lock Handler

  1. 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

  1. This helps in adding new questions, deleting old ones, and editing existing ones.
  2. Multiple handlers are spawned within the server to help and perform requests at a faster pace.
  3. 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:

  1. 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 📎

--

--

Uddeshya Singh
Geek Culture

Software Engineer @Gojek | GSoC’19 @fossasia | Loves distributed systems, football, anime and good coffee. Writes sometimes, reads all the time.