import ballerina/log;
import ballerina/http;@final string ASSOCIATED_CONNECTION = "ASSOCIATED_CONNECTION";
@final string REMOTE_BACKEND = "wss://echo.websocket.org";endpoint http:Listener serviceEndpoint {
port:9090
};service <http:Service> proxy bind serviceEndpoint {
@http:ResourceConfig {
webSocketUpgrade: {
upgradePath: "/ws",
upgradeService: typeof SimpleProxyServer
}
}
upgrader(endpoint ep, http:Request req, string name) {
endpoint http:WebSocketClient wsClientEp {
url:REMOTE_BACKEND,
callbackService:typeof ClientService
};
endpoint http:WebSocketListener wsServerEp;
wsServerEp = ep -> acceptWebSocketUpgrade({"custom":"header"});
wsClientEp.attributes[ASSOCIATED_CONNECTION] = wsServerEp;
wsServerEp.attributes[ASSOCIATED_CONNECTION] = wsClientEp;
}
}@http:WebSocketServiceConfig {
basePath:"/proxy/ws"
}
service<http:WebSocketService> SimpleProxyServer bind serviceEndpoint { onText (endpoint ep, string text) {
endpoint http:WebSocketClient clientEp = getAssociatedClientEndpoint(ep);
clientEp -> pushText(text) but {error e => log:printErrorCause("Error occured when sending text message", e)};
} onBinary (endpoint ep, blob data) {
endpoint http:WebSocketClient clientEp = getAssociatedClientEndpoint(ep);
clientEp -> pushBinary(data) but {error e => log:printErrorCause("Error occured when sending binary message", e)};
} onClose (endpoint ep, int statusCode, string reason) {
endpoint http:WebSocketClient clientEp = getAssociatedClientEndpoint(ep);
clientEp -> close(statusCode, reason) but {error e => log:printErrorCause("Error occured when closing the connection", e)};
_ = ep.attributes.remove(ASSOCIATED_CONNECTION);
}
}
@http:WebSocketServiceConfig {}
service<http:WebSocketClientService> ClientService { onText (endpoint ep, string text) {
endpoint http:WebSocketListener parentEp = getAssociatedServerEndpoint(ep);
parentEp -> pushText(text) but {error e => log:printErrorCause("Error occured when sending text message", e)};
} onBinary (endpoint ep, blob data) {
endpoint http:WebSocketListener parentEp = getAssociatedServerEndpoint(ep);
parentEp -> pushBinary(data) but {error e => log:printErrorCause("Error occured when sending binary message", e)};
} onClose (endpoint ep, int statusCode, string reason) {
endpoint http:WebSocketListener parentEp = getAssociatedServerEndpoint(ep);
parentEp -> close(statusCode, reason) but {error e => log:printErrorCause("Error occured when closing the connection", e)};
_ = ep.attributes.remove(ASSOCIATED_CONNECTION);
}}function getAssociatedClientEndpoint (http:WebSocketListener ep) returns (http:WebSocketClient) {
http:WebSocketClient client = check <http:WebSocketClient> ep.attributes[ASSOCIATED_CONNECTION];
return client;
}function getAssociatedServerEndpoint (http:WebSocketClient ep) returns (http:WebSocketListener) {
http:WebSocketListener wsEndpoint = check <http:WebSocketListener> ep.attributes[ASSOCIATED_CONNECTION];
return wsEndpoint;
}
WebSocket Proxy ServerThis example explains how ballerina can act as a simple WebSocket proxy server. When new client is connected to the WebSocket endpoint, onHandshake new WebSocket client connection is created and stored in the connection attributes. When the endpoint client sends a message, get the client connection for remote server from attributes and send the same message to remote server. That server will echo it back and it will be received to "ClientService" which will print the echoed message. Then by the client connection from "onTextMessage" resource in "ClientService", get the associated server connection and send it. |
|
import ballerina/log;
import ballerina/http;
|
|
@final string ASSOCIATED_CONNECTION = "ASSOCIATED_CONNECTION";
@final string REMOTE_BACKEND = "wss://echo.websocket.org";
|
|
endpoint http:Listener serviceEndpoint {
port:9090
};
|
|
service <http:Service> proxy bind serviceEndpoint {
|
|
@http:ResourceConfig {
webSocketUpgrade: {
upgradePath: "/ws",
upgradeService: typeof SimpleProxyServer
}
}
upgrader(endpoint ep, http:Request req, string name) {
endpoint http:WebSocketClient wsClientEp {
url:REMOTE_BACKEND,
callbackService:typeof ClientService
};
endpoint http:WebSocketListener wsServerEp;
wsServerEp = ep -> acceptWebSocketUpgrade({"custom":"header"});
wsClientEp.attributes[ASSOCIATED_CONNECTION] = wsServerEp;
wsServerEp.attributes[ASSOCIATED_CONNECTION] = wsClientEp;
}
}
|
Create a client connection to a remote server from Ballerina when a new client connects to this service endpoint. |
@http:WebSocketServiceConfig {
basePath:"/proxy/ws"
}
service<http:WebSocketService> SimpleProxyServer bind serviceEndpoint {
|
|
onText (endpoint ep, string text) {
endpoint http:WebSocketClient clientEp = getAssociatedClientEndpoint(ep);
clientEp -> pushText(text) but {error e => log:printErrorCause("Error occured when sending text message", e)};
}
|
|
onBinary (endpoint ep, blob data) {
endpoint http:WebSocketClient clientEp = getAssociatedClientEndpoint(ep);
clientEp -> pushBinary(data) but {error e => log:printErrorCause("Error occured when sending binary message", e)};
}
|
|
onClose (endpoint ep, int statusCode, string reason) {
endpoint http:WebSocketClient clientEp = getAssociatedClientEndpoint(ep);
clientEp -> close(statusCode, reason) but {error e => log:printErrorCause("Error occured when closing the connection", e)};
_ = ep.attributes.remove(ASSOCIATED_CONNECTION);
}
}
|
|
@http:WebSocketServiceConfig {}
service<http:WebSocketClientService> ClientService {
|
Client service to receive frames from remote server |
onText (endpoint ep, string text) {
endpoint http:WebSocketListener parentEp = getAssociatedServerEndpoint(ep);
parentEp -> pushText(text) but {error e => log:printErrorCause("Error occured when sending text message", e)};
}
|
|
onBinary (endpoint ep, blob data) {
endpoint http:WebSocketListener parentEp = getAssociatedServerEndpoint(ep);
parentEp -> pushBinary(data) but {error e => log:printErrorCause("Error occured when sending binary message", e)};
}
|
|
onClose (endpoint ep, int statusCode, string reason) {
endpoint http:WebSocketListener parentEp = getAssociatedServerEndpoint(ep);
parentEp -> close(statusCode, reason) but {error e => log:printErrorCause("Error occured when closing the connection", e)};
_ = ep.attributes.remove(ASSOCIATED_CONNECTION);
}
|
|
}
|
|
function getAssociatedClientEndpoint (http:WebSocketListener ep) returns (http:WebSocketClient) {
http:WebSocketClient client = check <http:WebSocketClient> ep.attributes[ASSOCIATED_CONNECTION];
return client;
}
|
|
function getAssociatedServerEndpoint (http:WebSocketClient ep) returns (http:WebSocketListener) {
http:WebSocketListener wsEndpoint = check <http:WebSocketListener> ep.attributes[ASSOCIATED_CONNECTION];
return wsEndpoint;
}
|
|
$ ballerina run websocket-proxy-server.bal
|
To run the program, put the code in |
Now, we can run this program using any WebSocket client with “ws://localhost:9090/proxy/ws” |
|
$ var ws = new WebSocket("ws://localhost:9090/proxy/ws");
$ ws.onmessage = function(frame) {console.log(frame.data)};
$ ws.onclose = function(frame) {console.log(frame)};
|
To check the sample, you can use Chrome or Firefox JavaScript console and run the following commands |
$ ws.send("hello world");
|
To send messages |
$ ws.close(1000, "I want to go");
|
To close the connection |