import ballerina/http;
import ballerina/log;final string NAME = "NAME";
final string AGE = "AGE";@http:ServiceConfig {
    basePath: "/chat"
}
service chatAppUpgrader on new http:Listener(9090) {
    @http:ResourceConfig {
        webSocketUpgrade: {
            upgradePath: "/{name}",
            upgradeService: chatApp
        }
    }
    resource function upgrader(http:Caller caller, http:Request req,
    string name) {
        map<string[]> queryParams = req.getQueryParams();
        if (!queryParams.hasKey("age")) {
            var err = caller->cancelWebSocketUpgrade(400, "Age is required");
            if (err is http:WebSocketError) {
                log:printError("Error cancelling handshake", err);
            }
            return;
        }
        map<string> headers = {};
        http:WebSocketCaller | http:WebSocketError wsEp = caller->acceptWebSocketUpgrade(headers);
        if (wsEp is http:WebSocketCaller) {
            wsEp.setAttribute(NAME, name);
            string? ageValue = req.getQueryParamValue("age");
            string age = ageValue is string ? ageValue : "";
            wsEp.setAttribute(AGE, age);
            string msg =
            "Hi " + name + "! You have successfully connected to the chat";
            var err = wsEp->pushText(msg);
            if (err is http:WebSocketError) {
                log:printError("Error sending message", err);
            }
        } else {
            log:printError("Error during WebSocket upgrade", wsEp);
        }
    }
}
map<http:WebSocketCaller> connectionsMap = {};service chatApp = @http:WebSocketServiceConfig {} service {
    resource function onOpen(http:WebSocketCaller caller) {
        string msg;
        msg = getAttributeStr(caller, NAME) + " with age "
        + getAttributeStr(caller, AGE) + " connected to chat";
        broadcast(msg);
        connectionsMap[caller.getConnectionId()] = caller;
    }
    resource function onText(http:WebSocketCaller caller, string text) {
        string msg = getAttributeStr(caller, NAME) + ": " + text;
        log:printInfo(msg);
        broadcast(msg);
    }
    resource function onClose(http:WebSocketCaller caller, int statusCode,
    string reason) {
        _ = connectionsMap.remove(caller.getConnectionId());
        string msg = getAttributeStr(caller, NAME) + " left the chat";
        broadcast(msg);
    }
};
function broadcast(string text) {
    foreach var con in connectionsMap {
        var err = con->pushText(text);
        if (err is http:WebSocketError) {
            log:printError("Error sending message", err);
        }
    }
}function getAttributeStr(http:WebSocketCaller ep, string key)
returns (string) {
    var name = ep.getAttribute(key);
    return name.toString();
}

Chat Application

This example explains how to write a simple chat application using Ballerina.

import ballerina/http;
import ballerina/log;
final string NAME = "NAME";
final string AGE = "AGE";
@http:ServiceConfig {
    basePath: "/chat"
}
service chatAppUpgrader on new http:Listener(9090) {
    @http:ResourceConfig {
        webSocketUpgrade: {
            upgradePath: "/{name}",
            upgradeService: chatApp
        }
    }
    resource function upgrader(http:Caller caller, http:Request req,
    string name) {

Upgrade from HTTP to WebSocket and define the service the WebSocket client needs to connect to.

        map<string[]> queryParams = req.getQueryParams();

Retrieve query parameters from the http:Request.

        if (!queryParams.hasKey("age")) {
            var err = caller->cancelWebSocketUpgrade(400, "Age is required");
            if (err is http:WebSocketError) {
                log:printError("Error cancelling handshake", err);
            }
            return;
        }
        map<string> headers = {};
        http:WebSocketCaller | http:WebSocketError wsEp = caller->acceptWebSocketUpgrade(headers);
        if (wsEp is http:WebSocketCaller) {

Cancel the handshake by sending a 400 status code if the age parameter is missing in the request.

            wsEp.setAttribute(NAME, name);
            string? ageValue = req.getQueryParamValue("age");
            string age = ageValue is string ? ageValue : "";
            wsEp.setAttribute(AGE, age);
            string msg =
            "Hi " + name + "! You have successfully connected to the chat";
            var err = wsEp->pushText(msg);
            if (err is http:WebSocketError) {
                log:printError("Error sending message", err);
            }
        } else {
            log:printError("Error during WebSocket upgrade", wsEp);
        }
    }
}

The attributes of the caller is useful for storing connection-specific data. In this case, the NAMEand AGE are unique to each connection.

map<http:WebSocketCaller> connectionsMap = {};

Stores the connection IDs of users who join the chat.

service chatApp = @http:WebSocketServiceConfig {} service {
    resource function onOpen(http:WebSocketCaller caller) {
        string msg;
        msg = getAttributeStr(caller, NAME) + " with age "
        + getAttributeStr(caller, AGE) + " connected to chat";
        broadcast(msg);
        connectionsMap[caller.getConnectionId()] = caller;
    }

Once a user connects to the chat, store the attributes of the user, such as username and age, and broadcast that the user has joined the chat.

    resource function onText(http:WebSocketCaller caller, string text) {
        string msg = getAttributeStr(caller, NAME) + ": " + text;
        log:printInfo(msg);
        broadcast(msg);
    }

Broadcast the messages sent by a user.

    resource function onClose(http:WebSocketCaller caller, int statusCode,
    string reason) {
        _ = connectionsMap.remove(caller.getConnectionId());
        string msg = getAttributeStr(caller, NAME) + " left the chat";
        broadcast(msg);
    }
};

Broadcast that a user has left the chat once a user leaves the chat.

function broadcast(string text) {
    foreach var con in connectionsMap {
        var err = con->pushText(text);
        if (err is http:WebSocketError) {
            log:printError("Error sending message", err);
        }
    }
}

Function to perform the broadcasting of text messages.

function getAttributeStr(http:WebSocketCaller ep, string key)
returns (string) {
    var name = ep.getAttribute(key);
    return name.toString();
}
# To start the service, navigate to the directory that contains the
# `.bal` file and use the `ballerina build` command.
$ ballerina build websocket_chat_application.bal
# Run the sample using the `run` command on the jar file generated:
$ ballerina run websocket_chat_application-executable.jar
# To check the sample, use the Chrome or Firefox JavaScript console and run the following commands. <br>
# Run the first 3 lines of the following code in two or more different consoles and see how the messages are received.
# Change the names and/or age in the `/chat/fistName?age` URI so that they are different for each client.
$ var ws = new WebSocket("ws://localhost:9090/chat/bruce?age=30");
$ ws.onmessage = function(frame) {console.log(frame.data)};
$ ws.onclose = function(frame) {console.log(frame)};
# Send messages.
$ ws.send("hello world");