In this blog we will get straight to building chat app using websockets in golang and react. If you want to learn about web sockets and how to use it in golang then read this beginners guide to ws
Building chat server
Main function
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func main() {
// this http request is initially sent by client to connect to websocket connection
http.HandleFunc("/ws", SocketHandler)
fmt.Println("web server running on port 8000")
err := http.ListenAndServe(":8000", nil)
if err != nil {
fmt.Println(err)
}
}
// socket handler handles the messageing form respective client's websocket
func SocketHandler(w http.ResponseWriter, r *http.Request) {
// an upgrader takes in clients conn info and converts it to websocket connection
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("there was a connection error : ", err)
return
}
// this function will run at the end of function
// to close the connection for that client
defer ws.Close()
for {
_, bytes, err := ws.ReadMessage()
if err != nil {
handleDisconnection(ws) // removing connection from our ws pool
break
}
err1 := handleIncomingMessage(ws, bytes) // this handles all the messages sent by user
if err1 != nil {
log.Print("Error handling message", err1)
}
}
}
Websocket Pools
// this pool/map stores the connectionIds and its respective usernames
// connId/ws is just the clients address
// this can be used to check if user already exists and while disconnection of client ws
var WebSocketToUsername = make(map[*websocket.Conn]string)
// this pool/map can be used to get the address of user we want to send our message to
// with which we can directly send that message to the respective client
var UsernameToWebSocket = make(map[string]*websocket.Conn)
Disconnect function
func handleDisconnection(sender *websocket.Conn) {
// this function removes the client from our websocket pools
user_id, _ := WebSocketToUsername[sender]
delete(WebSocketToUsername, sender)
delete(UsernameToWebSocket, user_id)
}
Message structure
// this is the api structure that we are using to communicate between client and server
type msg struct {
MessageType string // tells us the type of msg. ex ChatMsg, LoginMsg
Data string // the actual message by user
Reciever string
Sender string
}
const ChatMsg = "chatmsg"
const LoginMsg = "loginmsg"
Message handler
// this function takes in all the messages sent by client and does appropriate action
func handleIncomingMessage(sender *websocket.Conn, data []byte) error {
// converting data into json
var DataRecieved msg
err := json.Unmarshal(data, &DataRecieved)
if err != nil {
return err
}
// checking the msg type and doing appropriate action on it
switch DataRecieved.MessageType {
case ChatMsg:
// How this works is,
// the username of reciever will be in the msg so we take that username
// and check for its ws conn using UsernameToWebSocket map
// and forward that message to that connection
UsernameToWebSocket[DataRecieved.Reciever].WriteJSON(DataRecieved)
// you can also just send the msg only like this
// .WriteMessage(1, []byte(DataRecieved.Data))
case LoginMsg:
// To login a user we must first check if that username is already in the pool
// if it exists we will just return the error msg
if _, ok := UsernameToWebSocket[DataRecieved.Sender]; ok {
sender.WriteJSON("User already exists")
return nil
}
// if the username is not in the pool then we will add it
WebSocketToUsername[sender] = DataRecieved.Sender
UsernameToWebSocket[DataRecieved.Sender] = sender
}
return nil
}
Testing using postman
logging in user with username tan
logging in user with username yan
sending a message to tan from yan
message received to tan
And there you have it!
Let me know if theres any improvement that can be done in explanation. And thanks for reading.