Creating a real-time chat application with private messaging functionality can significantly enhance the user experience, allowing users to communicate directly and securely. Go, with its native concurrency support, is a great option for building a high-performance WebSocket chat application. This article guides you through implementing a private messaging feature in a WebSocket chat app using Go.
Setting Up the WebSocket Server
First, set up a simple WebSocket server in Go. We'll be using the github.com/gorilla/websocket package to handle WebSocket connections:
package main
import (
"net/http"
"github.com/gorilla/websocket"
"log"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type Client struct {
conn *websocket.Conn
send chan []byte
}
func handleConnections(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatalf("Failed to upgrade: %v", err)
}
defer conn.Close()
client := &Client{conn: conn, send: make(chan []byte)}
// Add more setup for client, such as adding to client list...
}Handling Incoming Messages
Clients will need to send messages through their WebSocket connection. Let's add a basic message reading loop:
func (c *Client) readMessages() {
defer c.conn.Close()
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
log.Printf("error: %v", err)
break
}
// Here, process the message, for example, by broadcasting it to clients or handling private messages.
}
}Implementing Private Messaging
To handle private messaging, we'll use a simple protocol where a message may contain a prefix with the recipient's ID. Let's add a function to parse and deliver these messages:
func sendMessageToClient(targetID string, message []byte) {
// Assuming we have a map of clients
target, exists := clients[targetID]
if exists {
target.send <- message
}
}
func (c *Client) handleMessage(message []byte) {
parts := bytes.SplitN(message, []byte(" "), 2)
if len(parts) != 2 {
return
}
recipientID := string(parts[0])
sendMessageToClient(recipientID, parts[1])
}Broadcast vs. Private Messaging
Your application should differentiate between messages meant for everyone and those targeting specific users. A simple approach is prefixing private messages with a user identifier.
func (c *Client) broadcastOrSend(message []byte) {
if strings.HasPrefix(string(message), "@"){ // assuming @ indicates a private message
c.handleMessage(message)
} else {
// Code to broadcast message to all clients
}
}Complete Client Setup
The client setup completes on the WebSocket connection side, incorporating methods to send messages, be it broadcast or private:
func (c *Client) writeMessages() {
defer c.conn.Close()
for message := range c.send {
err := c.conn.WriteMessage(websocket.TextMessage, message)
if err != nil {
log.Printf("write error: %v", err)
break
}
}
}Finally, integrate the read and write processes within the connection handling:
func handleConnections(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatalf("Failed to upgrade: %v", err)
}
defer conn.Close()
client := &Client{conn: conn, send: make(chan []byte)}
go client.readMessages()
go client.writeMessages()
// Save client to global client map...
}Conclusion
In this article, we've discussed creating a private messaging feature using Go and the Gorilla WebSocket package. With this implementation, users can broadcast messages or send direct messages within a chat application. You'll need to manage the `clients` mapping to connect each client correctly and ensure proper handling of concurrent read/writes.