Article
· Mar 2, 2023 5m read

Asynchronous Socket in IRIS and connection from JavaScript client

I've been working for some days in the connectivity between NodeJS client applications and IRIS as server using web sockets.

You can get all the information in relation to the web socket connections using IRIS as a client or as a server from this URL: https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

For this example we are going to configure an asynchronous server, that would be really usefull to create a subscription manager for our productions.

First of all, we have created a really simple persistent object in which we are going to store the session id that we are going to use to identify our connection by socket. This id is assigned by the client automatically.

Class User.WebSocketSession Extends %Persistent
{
 Property SessionID As %String;
}

As you can see it's really simple. We are going to create it into USER namespace. The main purpose of this object is to get all the opened sockets and send notifications to their clients. In the case that we want to manage different subscription types we can add new properties and make this object as complex as it's required.

Once we have coded our object to manage the socket connections we can start our socket class:

Class User.WebSocketServer Extends %CSP.WebSocket
{

Method OnPreServer() As %Status
{
        set webSocketSession = ##class(WebSocketSession).%New()
        set webSocketSession.SessionID = ..WebSocketID
        do webSocketSession.%Save()

        Set ..SharedConnection = 1
        Quit $$$OK
}

Method Server() As %Status
{
   
   Quit $$$OK
}

Method OnPostServer() As %Status
{
   Quit $$$OK
}

}

Easy peasy! Our class extends %CSP.WebSocket and we just have to implement for our example OnPreServer() method, this method will be executed when a connection request is received from our client, a step before to open the connection. In the code of OnPreServer method we are going to create an object from our class WebSocketSession and we'll store the web socket id (WebSocketID). We can add some kind of authentication if it's necessary, in that case we have to implement the Server() method.

You can see in our code that we are setting to 1 a property named  SharedConnection, this parameter is used to configure our web socket like an asynchronous socket, it means that we can send messages to the client using our socket connection from any component of our productions.

To connect to our socket we are going to use the following URL: ws(s)://{IRIS_IP}:{IRIS_PORT}/csp/{NAMESPACE}/{SOCKET_CLASS}

In our case the URL is ws://localhost:52774/csp/user/User.WebSocketServer.cls and the calls from our client application will something be like this

function socketConnect() {
    socket = new WebSocket("ws://localhost:52774/csp/user/User.WebSocketServer.cls");
    
    socket.onmessage = function(msg){
        console.log(msg.data);
    };
    
    var auth = { "User": "_SYSTEM", "Password": "SYS"};
	
	// we need to wait before connection is established
	setTimeout(function() {
		socket.send(JSON.stringify(auth));
	},1000); 
}

function socketClose() {
    socket.close();
}

We have added and small message to send to the server an user and a password, just in case that we need to validate the access to the server, in our example we are not going deeper, but I recomend you to send a test message in the connection to be sure that we are properly connected.

No mistery. We have our socker in a class of ObjectScript and this provide to us of an URL to be call from our client directly. The next step will be to start a simple production in IRIS (USER namespace) to check the behaviour of the notifications.

Here is our basic production generated by default, we have added a new Business Process named User.SocketInvocation, this BP will receive an HL7 message as soon as our Business Service HL7FileService detects a new file in a specific folder of our computer.

If we open the BPL definition User.SocketInvocation we are going to see that it's just a Code element.

We can review the code of this element:

  // Query to get the open sockets
  Set result=##class(%ResultSet).%New("%DynamicQuery:SQL")
  Do result.Prepare("SELECT %ID,SessionID FROM WebSocketSession")
  Do result.Execute()
  // Loop to send notification to each opened socket
  while(result.Next()) {
     try {
         Set ws=##class(%CSP.WebSocket).%New()
         Set tSC = ws.OpenServer(result.Data("SessionID"))         
         // testing if socket is opened
         Set data= ws.Read(, .status, )
         If $$$ISERR(status) {
            If $$$GETERRORCODE(status) = $$$CSPWebSocketClosed {
      	       $$$LOGINFO("The socket is closed")
            }
            If $$$GETERRORCODE(status) = $$$CSPWebSocketTimeout {
        	$$$LOGINFO("The socket is in timeout")
            }
            // if socket is closed, delete it from the database
            set sqltext = "DELETE FROM WebSocketSession WHERE SessionID = ?"
  	        set tStatement = ##class(%SQL.Statement).%New()
  	        set qStatus = tStatement.%Prepare(sqltext)
 	        if qStatus'=1 {
	 	       $$$LOGINFO("Error in sql for deleting info")
	        }
  	        set rtn = tStatement.%Execute(result.Data("SessionID"))
  	        if rtn.%SQLCODE=0 {
    	       	$$$LOGINFO("Socket deleted succesfully")
            }
  	        else {
  		        $$$LOGINFO("Error deleting socket session")
  	        }                        
         }
         else {
            //if socket is opened, send a message
            Set tSC = ws.Write("Something has change!")
         }
     } catch err {
         $$$LOGINFO(err.Name)
     }
  }

In this piece of code we are doing the following operations:

  1. Getting the list of open sockets.
  2. Checking the status of each socket stored.
    1. If the socket is already closed we delete it from our WebSocketSession table.
    2. If the socket is open we just send a message to our client.

Now we are going to review how it works opening a connection to our socket from the client application:

If it works right in our socket we could see in our WebSocketServer table a new record with the socket identifier K5zR0+kUBITEfiBeHbZ1rQ== (Sec-WebSocket-Key field). 

Bingo! Here is our socket id stored, now we can feed our Business Service with any file and check if we receive any notifications into our client.

This is the message sent from the Business Service into our Business Operation:

And we can check our client application:

That is our notification! 

Well, that's all. We have configured a simple web socket which receives a connection request from a web application, we have managed a list of open sockets with their identifiers and we have send a notification to those open asynchronous sockets.

If you have any question or suggestion don't hesitate to send a comment!

That's All Folks | Southern Stars | Thats all folks, Parking spot painting,  Folk

Discussion (2)2
Log in or sign up to continue

Hi Luis,

This is awesome, and just what I was currently engaged with Support about! Talk about good timing...

A couple of questions:

In your "broadcast" routine, you have the read of the Websocket in IRIS like:

Set data= ws.Read(, .status, )

The docs say that the format of that call is:

method Read(ByRef len As %Integer = 32656, ByRef sc As %Status, timeout As %Integer = 86400) as %String
 

Won't that mean that if your WebSocket is open (but the user is out to lunch), you will hang for 86400 seconds before returning?

Second, do you know what the licensing impact on IRIS is for this? Say there was 100 notification targets,. Would the system take out 100 License Units in this loop?

Thanks!

Richard

Hi Richard! sorry for the delay, I didn't see your message.

Well, for my experience with the websocket the Read timeout is not a problem when is defined as an empty value, if there is a connection error or the websocket is open your objectscript code will usually receive an inmediate response. In this case Read is working just as a "ping". In my example the client is not writting anything.

About the licenses I will paste an answer from other post of the community:

"This does not hold a license, basically the CSP gateway holds the data in it's pool, and at any time, a process can open the WebSocket via OpenServer and Read/Write the socket. Inside read is a call to ReadAsync if SharedConnection = 1."

https://community.intersystems.com/post/asynchronous-websockets-quick-tu...