Article

How to implement WebSocket API JavaScript application with HTML Web Workers

Wasin Waeosri
Developer Advocate Developer Advocate

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:

  1. index.html: The application HTML page
  2. app/market_price_app.js: The application main file
  3. app/ws_workers.js: The application Web Workers file
  4. css/cover.css: The application CSS file
  5. libs/jquery-3.2.1.min.js: jQuery library file
  6. bootstrap/css, bootstarp/fonts and bootstrap/js folders: The folders for Bootstrap CSS and libraries files
  7. node_modules folder: Folder for Node.js and Express.js modules for webserver running
  8. server.js: A web server application file
  9. 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.

  1. Firstly, we create JavaScript  market_price_app.js and ws_workers.js files in "app" folder.

  2. 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

  1. jQuery 3.2.1 JavaScript library (included with the project)
  2.  Bootstrap 3.3.7 css library (included with the project)
  3. 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:

For any question related to this example or WebSocket API, please use the Developer Community Q&A Forum.