Designing a WebSocket Server in Golang | Reformers Golang Implementation Strategy
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:
- Collaborative form editor using WebSockets | Web Sockets in Golang and React
- Designing a WebSocket server in Golang | Reformers backend implementation strategy
- 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
/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? 📚
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.
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 📎
- Heroku Deployment for Go apps
- Github Repository for the server (uds5501/re-formers-server)
- Dockerhub repository for the server (uddeshyasingh/re-formers-server)
- Gorilla WebSockets Github Repository