Artículo
· 2 mar, 2023 Lectura de 6 min

Socket asíncrono en IRIS y conexión desde aplicación web con JavaScript

Recientemente he estado trasteando con la utilización de IRIS como servidor para una conexión mediante web socket desde el frontend de una aplicación en NodeJS.

En esta URL tendréis la información relativa a las conexiones de web sockets tanto en modo cliente como en modo servidor: https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

Para este ejemplo vamos a utilizar la configuración de un servidor asíncrono, configuración que nos vendrá muy bien de cara a crear un gestor de suscripciones ad-hoc para una de nuestras producciones.

Primeramente hemos creado un pequeño objeto persistente donde almacenaremos el identificador de la sesión de nuestro web socket. Dicho número es asignado automáticamente por el cliente al remitir la solicitud.

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

Como podéis ver, es extremadamente simple y lo ubicaremos en nuestro namespace USER. Este objeto nos permitirá posteriormente recuperar los sockets abiertos y enviar notificaciones a las aplicaciones que se suscribieron a los mismos. En el caso de que queramos gestionar más conexiones vía socket podríamos ampliar dicho objeto para indicar el objetivo de dicha suscripción.

Una vez implementado el objeto con el que gestionaremos las conexiones a nuestro socket procederemos a implementar la clase de nuestro socket:

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
}

}

Como podéis observar es extremadamente sencillo. Nuestra clase extiende la clase %CSP.WebSocket de la cual sólo implementaremos para nuestro caso el método OnPreServer(), dicho método se ejecutará al recibir la solicitud de conexión desde nuestro cliente justo antes de establecer la conexión. En el código de dicho método crearemos una instancia de nuestra clase WebSocketSession y guardaremos en ella el identificador del web socket (WebSocketID). Si quisieramos incluir algún tipo de validación de acceso deberíamos implementarlo en el método Server().

Veréis también que estamos configurando una propiedad llamada SharedConnection con valor a 1, pues bien, este es el parámetro que nos permitirá configurar nuestro web socket en modo asíncrono, es decir, desde cualquier parte de nuestras producciones podremos acceder a dicho web socket y enviar notificaciones al cliente conectado.

Para invocar nuestro web socket usaremos la siguiente URL: ws(s)://{IRIS_IP}:{IRIS_PORT}/csp/{NAMESPACE}/{SOCKET_CLASS}

En nuestro caso la URL será ws://localhost:52774/csp/user/User.WebSocketServer.cls  y las llamadas desde nuestra aplicación cliente mediante javascript quedarían de la siguiente manera:

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();
}

Hemos incluido un pequeño mensaje notificando al socket el usuario y la contraseña por si necesitáramos controlar el acceso vía socket a nuestro sistema, en este caso no profundizaremos mucho más al respecto, pero se recomienda enviar un mensaje a nuestro socket para confirmar su correcta conexión.

Como veis, no hay ningún misterio. Tenemos nuestra clase de ObjectScript que nos proporciona un URL para ser invocada directamente desde nuestro cliente. A continuación montaremos una pequeña producción en nuestro IRIS para ver como funcionaría un sistema de notificaciones.

Aquí está nuestra producción estándar que se ha generado al definir una nueva y podéis observar que hemos añadido un nuevo Business Process llamado User.SocketInvocation, este Business Process recibirá un mensaje de HL7 cada vez que nuestro Business Service HL7FileService encuentre un fichero con mensajes en una ruta de nuestro equipo.

Si entramos en la definición del BPL User.SocketInvocation veremos que símplemente consta de un elemento con código ObjectScript.

Ahora abramos el código que hemos escrito:

  // 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)
     }
  }

En este fragmento estamos realizando las siguientes operaciones:

  1. Obtener el listado de sockets abiertos.
  2. Comprobar el estado de cada socket.
    1. Si está cerrado eliminamos el socket de la tabla.
    2. Si está abierto enviamos una notificación.

Ahora comprobemos el funcionamiento abriendo una conexión del socket desde nuestra aplicación cliente:

Si todo ha funcionado bien en nuestro socket deberemos ver en nuestra tabla de WebSocketServer un registro con un identificador de socket K5zR0+kUBITEfiBeHbZ1rQ== (campo Sec-WebSocket-Key). 

Efectivamente, aquí tenemos dado de alta a nuestro socket, ahora alimentemos a nuestro business service con un fichero con su respectivo mensaje de HL7 y comprobemos si enviamos notificación a nuestro cliente.

Aquí tenemos nuestro mensaje enviado por el Business Service a nuestro Business Operation:

Y si comprobamos nuestra aplicación:

¡Aquí tenemos nuestro mensaje de vuelta! 

Pues bien, eso sería todo. Hemos configurado un sencillo web socket que recibe una solicitud de conexión desde una aplicación web, hemos gestionado la lista de sockets abiertos a los que enviar la notificación cuando algo en nuestra producción cambie y hemos enviado al socket asíncrono dicha notificación.

Si tenéis cualquier comentario o sugerencia al respecto no dudéis en escribirla en los comentarios. 

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

Comentarios (0)1
Inicie sesión o regístrese para continuar