Overview
Update: September 2021
Websocket API for Pricing Streaming and Real-Time Service (aka Websocket API) enables easy integration into a multitude of client technology environments such as scripting and web. This API runs directly on your Refinitiv Real-Time Distribution System and presents data in an open (JSON) readable format. The API supports all Refinitiv data models and can be integrated into multiple client technology standards e.g. JavaScript, Python, R, .Net, etc.
The web browsers JavaScript runtime is a single-threaded environment by default. However, the HTML standard lets developers implement multi threads JavaScript application in web browser by introducing the Web Workers feature that lets web browsers run JavaScripts in a main thread and a background thread (workers thread).
This article shows how developers may use the JavaScrip Web Workers to implement the WebSocket API web application. It allows the Web Workers thread to handle the connection logic with Refinitiv Real-Time Advanced Distribution server while the main thread handles the UI interaction events and displaying data.
Figure-1: Web Workers and WebSocket connection diagram
*Note: The initial release of this API is for deployed Refinitiv Real-Time Advanced Distribution Server customers only (i.e. to use it you will need an installed version of Refinitiv Real-Time Advanced Distribution Server 3.2.1 and above).
WebSocket Overview
WebSocket specification defines an API that enables web pages to use the WebSockets protocol for two-way communication with a remote host. It introduces the WebSocket interface and defines a full-duplex communication channel that operates through a single socket over the Web. This specification also later applied to other client technology sides like Python, Ruby, Go, Java, etc.
Figure-2: WebSocket connection diagram
Web Workers Overview
Web Workers allows for concurrent execution of the browser threads and one or more JavaScript threads running in the background. The main JavaScript application thread communicates with the Workers thread application via an event model and the postMessage() function. The postMessage() function can accept either a string or JSON object as its single argument.
Figure-3: Web Workers communication diagram
Please see an example code below.
Main.js:
let wk = new Worker("Workers.js"); //Create a workers object
//Receive events/messages from Web Workers Worker.js file
wk.addEventListener("message", function (oEvent) {
console.log('Message from Workers: ', e.data);
}, false);
wk.postMessage('This is from Main.js'); //send message to Worker.js
Worker.js:
//Receive events/messages from Main.js file
self.addEventListener('message', function(e) {
self.postMessage(e.data);
}, false);
self.postMessage('This is from Workers.js'); //send message to Main.js
There are two types of Web Workers, Dedicated Workers, and Shared Workers. This article covers only how to implement the WebSocket API with JavaScript web browser application with Dedicated Workers.
Supported Web Browsers
This example supports the following web browsers (based on the WebSocket and Web Workers browser supported)
- Chrome
- Firefox
- IE11
- Microsoft Edge (Chrome-based version)
Application files
The web application contains the following example files and folder:
- index.html: The application HTML page
- app/market_price_app.js: The application main file
- app/ws_workers.js: The application Web Workers file
- css/cover.css: The application CSS file
- libs/jquery-3.2.1.min.js: jQuery library file
- bootstrap/css, bootstarp/fonts and bootstrap/js folders: The folders for Bootstrap CSS and libraries files
- node_modules folder: Folder for Node.js and Express.js modules for webserver running
- server.js: A web server application file
- package.json: The Project npm dependencies file
Message Structure
The message between market_price_app.js and ws_worker.js care in JSON format. They use JSON property 'command' to tell what should they handle the incoming message.
{
'commandObj': <JSON message for sending to Refinitiv Real-Time Advanced Distribution Server>,
'command': '<command>'
}
The list of command values between market_price_app.js and ws_workers.js are following:
- 'connect': Inform ws_workers.js to establish a WebSocket connection with Refinitiv Real-Time Advanced Distribution Server
- 'login': Inform ws_workers.js to send the Login Request message to Refinitiv Real-Time Advanced Distribution Server
- 'requestitem': Inform ws_workers.js to send the Item Request message to Refinitiv Real-Time Advanced Distribution Server
- 'closeitem': Inform ws_workers.js to send the Item Close Request message to Refinitiv Real-Time Advanced Distribution Server
- 'pong': Inform ws_workers.js to send the Pong heartbeat message to Refinitiv Real-Time Advanced Distribution Server
- 'logout': Inform ws_workers.js to disconnect Refinitiv Real-Time Advanced Distribution Server
- 'incomingMsg': Inform market_price_app.js to display incoming data in the web browser
Application Code Implementation Details
This article focus on the market_price_app.js and ws_workers.js files which demonstrate how to implement the WebSocket application with Web Workers.
Firstly, we create JavaScript market_price_app.js and ws_workers.js files in "app" folder.
- We define all required variables in the market_price_app.js and ws_workers.js files. All variables will be used for keeping the application WebSocket and Web Workers information.
/**market_price_app.js**/
(function ($) {
//Define variables
var serverurl = '',
username = '',
itemID = 0,
wk = new Worker("./app/ws_workers.js"); //Create a workers object
const protocol = 'tr_json2';
const loginID = 1;
})($);
/**ws_workers.js**/
(function () {
//Define WebSocket and protocol variables
let ws = null;
//Define based object response to market_price_app.js
let onMessage_response = {
'command': 'incomingMsg',
'msg': null
};
})();
3. Next, we create the addEventListener() functions callback in both files for receiving the messages from each other. The addEventListener() functions check the message's data and message's command properties to verify the operation.
/**market_price_app.js**/
(function ($) {
//Define variables
...
//Receive events from Web Workers ws_worker.js file
wk.addEventListener("message", function (oEvent) {
let response = oEvent.data;
//Get command parameter to identify operation
let command = response.command;
}, false);
})($);
/**ws_workers.js**/
(function () {
//Define WebSocket variable
...
//Receive message from market_price_app.js
self.addEventListener('message', function (e) {
let data = e.data;
//Get command parameter to identify operation
let command = data.command;
}, false);
})();
4. Then we define all WebSocket required functions in ws_workers.js file.
/**ws_workers.js**/
//Establish WebSocket connection success
function onOpen(event) {
//Inform market_price_app.js the connection request is success
}
//Receives incoming message from WebSocket
function onMessage(event) {
//Send incoming Refinitiv Real-Time Advanced Distribution Server message to market_price_app.js
}
5. Next, we implement a WebSocket connection logic. The market_price_app.js receives a user input for Refinitiv Real-Time Advanced Distribution Server IP Address and WebSocket port from the index.html web page, then market_price_app.js sends the WebSocket URL to ws_wokers.js file with the command 'connect' to establish a connection via postMessage() function. The ws_workers.js calls the WebSocket connect() function to establish a WebSocket connection.
/**market_price_app.js**/
$('#btnConnect').click(function () {
serverurl = 'ws://' + $('#txtServerurl').val() + '/WebSocket';
connect(serverurl);
});
//Establish WebSocket connection
function connect(serverurl) {
$('#commandsPre').html('ws = new WebSocket("' + serverurl + '", "' + protocol + '");');
let connectObj = {
'commandObj': {
'serverurl': serverurl,
'protocol': protocol
},
'command': 'connect'
};
//Send message to Web Workers
wk.postMessage(connectObj);
}
/**ws_workers.js**/
//Receive message from market_price_app.js
self.addEventListener('message', function (e) {
let data = e.data;
//Get command parameter to identify operation
let command = data.command;
if (command === 'connect') {
connect(data.commandObj); //Establish WebSocket connection
}
}, false);
//Establish WebSocket connection and binding all events functions
function connect(commandObj) {
ws = new WebSocket(commandObj.serverurl, commandObj.protocol);
ws.onopen = onOpen;
ws.onmessage = onMessage;
...
}
6. Next, we implement the ws_workers.js's onOpen() function callback to handle a WebSocket connect event. When the application success connect to Refinitiv Real-Time Advanced Distribution Server, this onOpen() function sends a message to notify market_price_app.js that the connection is established. The market_price_app.js will change the Web UI to inform users regarding this connection event.
/**ws_workers.js**/
function onOpen(event) {
var onOpen_response = {
'command': 'connect',
'msg': 'Connected'
};
self.postMessage(onOpen_response);
}
/**market_price_app.js**/
wk.addEventListener("message", function (oEvent) {
let response = oEvent.data;
//Get command parameter to identify operation
let command = response.command;
if (command === 'connect') { //WebSocket connection event
processConnectionEvent(response);
}
}, false);
function processConnectionEvent(response) {
$('#btnConnect').html(response.msg);
}
7. When users click a connect button on index.html page, the market_price_app.js receives a user name for creating a login command. The market_price_app.js creates the JSON Login request message and sends it to ws_workers.js for sending this message to Refinitiv Real-Time Advanced Distribution Server.
/**market_price_app.js**/
$(document).ready(function () {
//connect
//handle login button
$('#btnLogin').click(function () {
let username = $('#txtUsername').val();
sendLoginrequest(username);
});
}
//Send a Login Request message to Real-Time Advanced Distribution Server WebSocket
function sendLoginrequest(username) {
//Create Login request message
let loginMsg = {
'ID': loginID,
'Domain': 'Login',
'Key': {
'Elements': {
'ApplicationId': '256',
'Position': '127.0.0.1'
},
'Name': ''
}
};
loginMsg.Key.Name = username;
$('#commandsPre').html('Sending Login request message to Web Workers: WebWorkers.post(' + JSON.stringify(loginMsg, undefined, 2) + ');');
//Printing the JSON Login request message in Web UI
let loginObj = {
'commandObj': loginMsg,
'command': 'login'
}
//Send Login message to Web Workers
wk.postMessage(loginObj);
}
/**ws_workers.js**/
//Receive message from market_price_app.js
self.addEventListener('message', function (e) {
let data = e.data;
//Get command parameter to identify operation
let command = data.command;
if (command === 'connect') {
connect(data.commandObj); //Establish WebSocket connection
} else {
sendOMMmessage(data.commandObj);
}
}, false);
//Send message to Real-Time Advanced Distribution Server WebSocket
function sendOMMmessage(commandObj) {
ws.send(JSON.stringify(commandObj));
}
8. Next, we implement the WebSocket onMessage() function in ws_workers.j file to receive an incoming message from the Refinitiv Real-Time Advanced Distribution Server WebSocket server and dispatch it to market_price_app.js file. The market_price_app.js sends it to the processData() function displaying that data in Web UI.
/**ws_workers.js**/
//Receives incoming message from WebSocket
function onMessage(event) {
let incomingdata = JSON.parse(event.data.toString());
//Iterate each JSON message and send it to market_price_app.js
for (let index = 0; index < incomingdata.length; index++) {
onMessage_response.msg = incomingdata[index];
self.postMessage(onMessage_response); //send message to market_price_app.js
}
}
/**market_price_app.js**/
//Receive events from Web Workers ws_worker.js file
wk.addEventListener("message", function (oEvent) {
let response = oEvent.data;
//Get command parameter to identify operation
let command = response.command;
if (command === 'connect') { //WebSocket connection event
processConnectionEvent(response);
} else if (command === 'incomingMsg') { //Receive incoming messages from Refinitiv Real-Time Advanced Distribution Server WebSocket
processData(response.msg);
}
}, false);
9. We implement the processData() function in market_price_app.js file to display incoming Refinitiv Real-Time Advanced Distribution Server JSON messages (Login, data, status, etc) in the web page. We start by enhance the processData() function to support the REFFRESH_RESP message for the OMM Login domain.
/**market_price_app.js**/
//Process incoming messages from Real-Time Advanced Distribution Server WebSocket
function processData(data) {
let msgtype = data.Type;
//Clear previous message
$('#messagesPre').html('');
//If incoming message is REFRESH_RESP
if (msgtype === 'Refresh') {
if (data.Domain === 'Login') {
$('#messagesPre').html('Receive: Login REFRESH_RESP:<br/>'); //Login Refresh_resp
$('#messagesPre').html($('#messagesPre').html() + JSON.stringify(data, undefined, 2)); //Display REFRESH_RESP
}
}
}
10. Now the application can establish a connection and log in with Real-Time Advanced Distribution Server. The next step is requesting Market Price data from the Real-Time Advanced Distribution Server. The market_price_app.js file receives user input item name in the RIC code format (and optionally, a service name) from the index.html page, then creates a JSON item request message and sends it to Refinitiv Real-Time Advanced Distribution Server WebSocket server via ws_workers.js file.
/**market_price_app.js**/
$(document).ready(function () {
$('#btnSubscribe').click(function () {
let servicename = $('#txtServiceName').val();
let itemname = $('#txtItemName').val();
sendItemrequest(servicename, itemname);
});
}
//Send Item Request message to Real-Time Advanced Distribution Server WebSocket
function item sendItemrequest(service, itemname) {
//Create stream ID, must not be 1 (Login)
if (itemID === 0) {
itemID = loginID + 1;
} else {
itemID += 1;
}
//create Market Price request message
let itemrequestMsg = {
'ID': itemID,
'Key': {
'Name': itemname,
'Service': service
}
};
let itemrequestObj = {
'commandObj': itemrequestMsg,
'command': 'requestitem'
}
//Send Item Request message to Web Workers
wk.postMessage(itemrequestObj);
}
/**ws_workers.js**/
//Receive message from market_price_app.js
self.addEventListener('message', function (e) {
let data = e.data;
//Get command parameter to identify operation
let command = data.command;
if (command === 'connect') {
connect(data.commandObj); //Establish WebSocket connection
} else {
sendOMMmessage(data.commandObj);
}
}, false);
//Send message to Refinitiv Real-Time Advanced Distribution Server WebSocket
function sendOMMmessage(commandObj) {
ws.send(JSON.stringify(commandObj));
}
11. The ws_workers.js file receives incoming Market Price message via the WebSocket onMessage() function.The ws_workers.js file dispatches it to the market_price_app.js's processData() function, then we implement the processData() function to support incoming REFRESH_RESP and UPDATE_RESP messages for Market Price domain message.
/**market_price_app.js**/
//Process incoming messages from Real-Time Advanced Distribution Server WebSocket
function processData(data) {
let msgtype = data.Type;
//Clear previous message
$('#messagesPre').html('');
//If incoming message is REFRESH_RESP
if (msgtype === 'Refresh') {
if (data.Domain === 'Login') {
$('#messagesPre').html('Receive: Login REFRESH_RESP:<br/>'); //Login Refresh_resp
} else {
$('#messagesPre').html('Receive: Data REFRESH_RESP:<br/>'); //Data Refresh_resp
}
$('#messagesPre').html($('#messagesPre').html() + JSON.stringify(data, undefined, 2)); //Display REFRESH_RESP
} else if (msgtype === 'Update') { //If incoming message is UPDATE_RESP
$('#messagesPre').html('Receive: UPDATE_RESP:<br/>' + JSON.stringify(data, undefined, 2)); //Display Update_resp
}
}
12. The last step is handling the Ping and Pong messages. The Ping and Pong messages are the handshake message in JSON format ({ "Type": "Ping" } and { "Type": "Pong" } messages) between the Refinitiv Real-Time Advanced Distribution Server WebSocket server and client for monitoring connection health. The Real-Time Advanced Distribution Server periodically sends Ping messages to applications and applications must be prepared to send Pong messages as a response to any Ping message they receive.
/**market_price_app.js**/
//Process incoming messages from Real-Time Advanced Distribution Server WebSocket
function processData(data) {
let msgtype = data.Type;
//Clear previous message
$('#messagesPre').html('');
//If incoming message is REFRESH_RESP
if (msgtype === 'Refresh') {
//Handle Refresh message
} else if (msgtype === 'Update') { //If incoming message is UPDATE_RESP
//Handle Update message
} else if (msgtype === 'Ping') { //If incoming message is PING (server ping)
$('#messagesPre').html('Recieve Ping:</br>' + JSON.stringify(data, undefined, 2)); //Server Ping
sendPong();
}
}
//Send { 'Type': 'Pong' } for acknowledge Server PING
function sendPong() {
let pongObj = {
'commandObj': { 'Type': 'Pong' },
'command': 'pong'
}
//Send PONG message to Web Workers
wk.postMessage(pongObj);
$('#commandsPre').html('Sending Client Pong: ws.send(' + JSON.stringify({ 'Type': 'Pong' }, undefined, 2) + ');');
}
Running the application
The application source code is available at GitHub. You can get it via the following git command
$>git clone git@github.com:Refinitiv-API-Samples/Article.EWA.JavaScript.WebWorkersApp.git
This example application requires the followinng libraries and runtime
- jQuery 3.2.1 JavaScript library (included with the project)
- Bootstrap 3.3.7 css library (included with the project)
- Node.js runtime
please check the README.md in the source code project for more detail.
How to run this example
Firstly, get the project via the above git command, and then run npm install command in the command prompt to install all the dependencies required to run the sample in a subdirectory called node_modules/
If the machine is behind a proxy server, you need to configure Node.js uses proxy instead of a direct HTTP connection via the following command in command prompt
set https_proxy=http://<proxy.server>:<port>
Run node server.js command in the command prompt to start the web server at HTTP port 8080
Open http://localhost:8080/index.html in the web browsers (IE11, Chrome/Microsoft Edge (Chrome-based version), and Firefox)
Figure-6: The application page that receives user input
Then, input the Refinitiv Real-Time Advanced Distribution Server WebSocket IP and port and click connect to establish a connection. Once connection success, the connect button label changes to "connected".
Figure-7: The WebSocket application has now established a connection with Real-Time Advanced Distribution Server
Then input the user name and click a Login button to send a Login request message to Real-Time Advanced Distribution Server.
Figure-8: The application is now logged in with the Refinitiv Real-Time Advanced Distribution Server server
Then, input the item name in RIC code format (and optionally, service name), click subscribe. The page shows outgoing request messages and incoming Refresh, Update messages from Real-Time Advanced Distribution Server.
Figure-9: The application is now subscribing Market Price data with Real-Time Advanced Distribution Server.
Finally, the application is automatic sends a Pong message back to the Real-Time Advanced Distribution Server when it receives a Ping handshake message from Refinitiv Real-Time Advanced Distribution Server.
Figure-10: The application is now sending Ping-Pong message with Real-Time Advanced Distribution Server.
References
For further details, please check out the following resources:
- Refinitiv Real-Time & Distribution Family page on the Refinitiv Developer Community web site.
- WebSocket API page.
- Developer Webinar Recording: Introduction to Websocket API
- Mozilla Developer Network: Web Workers API page.
- Google HTML5 Rocks: The Basics of Web Workers page
- Mozilla Developer Network: WebSocket API page
For any question related to this example or WebSocket API, please use the Developer Community Q&A Forum.