Websocket

Mahakam implements rfc6455 websocket protocol. It’s written from scratch and has a built-in extension implementation like room and broadcast.

💡 Tip

Since Mahakam needs http.ResponseWriter and http.Request to upgrade the connection, it’s mean you can use it with net/http package.

You actually can still use on another framework like gin or echo as long as you know how to upgrade the connection. You can check out the frame.go file to encode and decode the websocket frames.

Example

Here is an minimal example of a websocket server.

// main.go
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/seiortech/mahakam"
	"github.com/seiortech/mahakam/websocket"
)

func main() {
	ws := websocket.Websocket{}

	mux := http.NewServeMux()

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "public/index.html")
	})

	mux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		client, err := ws.Upgrade(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer client.Close(nil, websocket.STATUS_CLOSE_NORMAL_CLOSURE)

		for {
			data := make([]byte, 1024)
			frame, _, err := client.Read(data)
			if err != nil {
				log.Println("Error reading from WebSocket:", err)

				continue
			}

			fmt.Println("Received message:", string(frame.Payload))

			_, err = client.Write([]byte("hello from server!"), websocket.TEXT)
			if err != nil {
				log.Println("Error writing to WebSocket:", err)
			}
		}
	})

	server := mahakam.NewServer(":8080", mux)

	if err := server.ListenAndServe(); err != nil {
		panic(err)
	}
}

now, we need the html file to connect to the websocket server.

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Client</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
    <div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
        <h1 class="text-2xl font-bold text-gray-800 mb-6 text-center">WebSocket Client</h1>
        
        <div class="mb-4">
            <button id="connectBtn" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded transition duration-200">
                Connect
            </button>
        </div>
        
        <div class="mb-4">
            <input type="text" id="messageInput" placeholder="Enter your message..." 
                   class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
        </div>
        
        <div class="mb-4">
            <button id="sendBtn" disabled class="w-full bg-green-500 hover:bg-green-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white font-semibold py-2 px-4 rounded transition duration-200">
                Send Message
            </button>
        </div>
        
        <div class="mb-2">
            <span class="text-sm font-medium text-gray-700">Status: </span>
            <span id="status" class="text-sm text-red-500">Disconnected</span>
        </div>
        
        <div class="bg-gray-50 p-4 rounded h-64 overflow-y-auto">
            <h3 class="text-sm font-medium text-gray-700 mb-2">Messages:</h3>
            <div id="messages" class="text-sm text-gray-600 space-y-1"></div>
        </div>
    </div>

    <script>
        let socket = null;
        const connectBtn = document.getElementById('connectBtn');
        const sendBtn = document.getElementById('sendBtn');
        const messageInput = document.getElementById('messageInput');
        const status = document.getElementById('status');
        const messages = document.getElementById('messages');

        function addMessage(message, type = 'info') {
            const messageDiv = document.createElement('div');
            messageDiv.className = type === 'sent' ? 'text-blue-600' : type === 'received' ? 'text-green-600' : 'text-gray-500';
            messageDiv.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
            messages.appendChild(messageDiv);
            messages.scrollTop = messages.scrollHeight;
        }

        connectBtn.addEventListener('click', () => {
            if (socket && socket.readyState === WebSocket.OPEN) {
                socket.close();
                return;
            }

            socket = new WebSocket('ws://localhost:8080/ws');
            
            socket.onopen = () => {
                status.textContent = 'Connected';
                status.className = 'text-sm text-green-500';
                connectBtn.textContent = 'Disconnect';
                connectBtn.className = 'w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-2 px-4 rounded transition duration-200';
                sendBtn.disabled = false;
                sendBtn.className = 'w-full bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-4 rounded transition duration-200';
                addMessage('Connected to WebSocket server');
            };

            socket.onmessage = (event) => {
                addMessage(`Server: ${event.data}`, 'received');
            };

            socket.onclose = () => {
                status.textContent = 'Disconnected';
                status.className = 'text-sm text-red-500';
                connectBtn.textContent = 'Connect';
                connectBtn.className = 'w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded transition duration-200';
                sendBtn.disabled = true;
                sendBtn.className = 'w-full bg-green-500 hover:bg-green-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white font-semibold py-2 px-4 rounded transition duration-200';
                addMessage('Disconnected from WebSocket server');
            };

            socket.onerror = (error) => {
                addMessage('WebSocket error occurred', 'error');
                console.error('WebSocket error:', error);
            };
        });

        sendBtn.addEventListener('click', () => {
            const message = messageInput.value.trim();
            if (message && socket && socket.readyState === WebSocket.OPEN) {
                socket.send(message);
                addMessage(`You: ${message}`, 'sent');
                messageInput.value = '';
            }
        });

        messageInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                sendBtn.click();
            }
        });
    </script>
</body>
</html>

Implementation

You can check out the Mahakam repository for the chat application example using websocket.

Extension

ℹ️ Info
Mahakam websocket extension is still in development. I’m still working on it.

extension.Room

This extension allows you to join a room and broadcast messages to all the clients in the room.

You can check out the Mahakam repository for the chat application example using websocket with room extension.