The Solace Web Streaming API supports the WebSocket Protocol as a preferred transport. I have been playing around with using the Solace streaming API in Node.js as well as on my laptop, iPad, and Android phone.
In each case, the initialization time for apps is visibly faster when using a browser that supports WebSocket. I wrote a simple javascript app to display the init time, the protocols used, as well as continuously monitor the round trip time for the connection. The plotting of the round trip time (RTT) uses SmoothieCharts which I think has a nice look and feel.
Why is the init time faster?
The Solace javascript client library always tries the WS_BINARY WebSocket transport first before falling back to one of several other methods for providing streaming data (such as polling with XMLHttpRequest). WebSocket supports bi-directional sending and receiving of streaming binary data without having to encode it as text first. When running on a non-websocket transport the Solace client libraries have to figure out the capabilities of the underlying browser an network and pick the “best” transport and encoding method (HTTP_BINARY_STREAMING, HTTP_BINARY, or HTTP_BASE64). This automatic protocol selection ensures that your app will work, even in Microsoft IE, with the best available method of transport, and will be portable without the need for any changes or conditional checking in your code.
- HTTP_BASE64 – A COMET model that uses base64 payload encoding. HTTP responses have a defined Content-Length.
- HTTP_BINARY – A COMET model that uses binary payload encoding. HTTP responses have a defined Content-Length.
- HTTP_BINARY_STREAMING – A COMET model that uses binary payload encoding. HTTP responses use Chunked Transfer-Encoding to stream data from the appliance to the client without needing to terminate the HTTP response.
- WS_BINARY – A WebSocket communication channel uses binary payload encoding and provides full-duplex communication between the client and the appliance over a single TCP connection.
Want more control?
You can always explicitly set the transport protocol you want if you know ahead of time which one is going to work for your target platform.
mySessionProperties.transportProtocol = solace.TransportProtocol.HTTP_BASE64;
You can also control the amount of time to wait before the session will abandon a connection attempt with the current transport protocol and try a downgraded transport. This can be used to either speed up or slow down the process of walking from the best (WS_BINARY) to lowest common denominator (HTTP_BASE64) transport protocol.
mySessionProperties.transportDowngradeTimeoutInMsecs = 1000; //default is 10000ms
Try for yourself
The code for this app is both listed below, and available for download. The only external includes are the standard solclient.js web streaming library and the smoothie.js library. Everything else is inline with comments to make it more easily understood.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html > <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Solace Session Init Timer</title> <script type="text/javascript" src="solclient.js"></script> <script type="text/javascript" src="smoothie.js"></script> <script type="text/javascript"> var generateUid = function (separator) { /// <summary> /// Creates a unique id for identification purposes. /// </summary> /// <param name="separator" type="String" optional="true"> /// The optional separator for grouping the generated segmants: default "-". /// </param> var delim = separator || "-"; function S4() { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); } return (S4() + S4() + delim + S4() + delim + S4() + delim + S4() + delim + S4() + S4() + S4()); }; function init() { // Initialize SmoothChart Canvas var smoothie = new SmoothieChart({millisPerPixel:50, grid:{millisPerLine:3000}}); smoothie.streamTo(document.getElementById("mycanvas"), 900); var line1 = new TimeSeries(); smoothie.addTimeSeries(line1, { strokeStyle:'rgb(0, 255, 0)', lineWidth:3 }); //Initialize Text Display Canvas var c=document.getElementById("inittime"); var tx=c.getContext("2d"); tx.font="20px Arial"; tx.fillStyle="#FF0000"; // Set some Solace session properties var mySessionProperties = new solace.SessionProperties(); mySessionProperties.connectTimeoutInMsecs = 30000; mySessionProperties.readTimeoutInMsecs = 30000; mySessionProperties.keepAliveIntervalInMsecs = 3000; mySessionProperties.keepAliveIntervalsLimit = 3; mySessionProperties.vpnName = "demo"; mySessionProperties.userName = "demouser"; mySessionProperties.password = "password"; mySessionProperties.url = 'http://69.20.234.126:8134/solace/smf'; // Callback that handled session events // Note that we wait for a "session up" notification before doing lots of stuff that requires a connection var sessionEventCb = function(session, event) { if (event.sessionEventCode === solace.SessionEventCode.UP_NOTICE) { console.log( 'Session up'); sp = session.getSessionProperties(); console.log('transport protocol in use is :' + sp.transportProtocolInUse ); // Display the session init time and protocol on the inittime canvas (in HTML <body> below) now = new Date().getTime(); var it = now - start; tx.clearRect ( 0 , 0 , 800 , 150 ); //clear canvas tx.fillText( 'Init Time: ' + it + 'ms Transport Protocol: ' + sp.transportProtocolInUse, 10, 50); //make a guid so multiple instances work uid = generateUid('-'); console.log('uid = ' + uid); // subscribe to a unique topic topic = solace.SolclientFactory.createTopic( uid ); try { mySession.subscribe(topic, true); } catch (error) { console.error("failed to subscribe: " + error.toString()); } // create a heartbeat message every second iv = setInterval(function() { var msg = solace.SolclientFactory.createMessage(); msg.setDestination( topic ); t = new Date().getTime(); msg.setXmlContent( t.toString() ); // Publish message try { mySession.send(msg); } catch (error) { // failed to send, therefore stop publishing and log the error thrown console.error("Failed to send solace message '" + msg.toString() + "'"); console.error(error.toString() + error.Message); } }, 1000); } else if (event.sessionEventCode === solace.SessionEventCode.DISCONNECTED) { //cleanup when the link goes down and then re-initialize again console.log( 'Session disconnected'); if (typeof iv != 'undefined') { clearInterval(iv); } mySession.dispose(); setTimeout(function() { init(); }, 100); } } //Callback that handles incoming messages function messageEventCb (session, message) { // Message received try { d = message.getXmlContent(); now = new Date().getTime(); //calc RTT rtt = now - d; } catch ( e) { console.error( 'Date Error: ' + e.toString()); } line1.append( now, rtt ); } //Create the session and register the callbacks we defined mySession = solace.SolclientFactory.createSession(mySessionProperties, new solace.MessageRxCBInfo( messageEventCb, this), new solace.SessionEventCBInfo( sessionEventCb, this)); try{ var start = new Date().getTime(); mySession.connect(); } catch (error) { console.error(' Error connecting to mySession: ' + error); } } </script> </head> <body onload="init()" style="background-color:#333333"> <FONT COLOR="00ff00"><h4>RTT (milliseconds)</h4></FONT> <canvas id="mycanvas" width="800" height="250"></canvas> <p> <canvas id="inittime" width="800" height="150"></canvas> </body> </html>