Un cuento super cortito acerca de WebSockets y SignalR
Érase una vez Socket.io y fue desbancado por SignalR
Hoy vamos a hacer más que un cuento corto, uno de esos muy breves artículos introductorios a tecnologías específicas y el turno es para SignalR. Vamos a explicar en qué consiste y además ejecutaremos un ejercicio simple para validar la funcionalidad que provee SignalR(Para ti que eres impaciente, aquí el repo para que vayas al código de una, pero te pido que leas hasta el final).
Introduciendo WebSockets
Para los desarrolladores web, este no es un concepto nuevo u oscuro. Los WebSockets son tecnología que se viene apalancando hace muchos años y en esencia se trata de conexiones persistentes entre cliente y servidor.
En una petición http clásica, el cliente hace una solicitud (como una página o recurso como imagen, hoja de estilos, etc.) al servidor y este responde y se “olvida” inmediatamente de dicho cliente. A esto llamamos peticiones sin estado.
Al “olvidar” al cliente, no hay recuerdos o memoria de la solicitud procesada, esto es, no hay conocimiento de solicitudes previas del mismo cliente.
Los WebSockets proveen un mecanismo que permite mantener una comunicación en doble vía (full duplex) entre cliente y servidor, eliminando la sobrecarga de tener que crear conexiones nuevas resultado de cada solicitud de los clientes, ya que tienen un costo elevado en términos de recursos para poder establecer esta nueva conexión además del procesamiento de cabeceras. Con los websockets, la conexión entre cliente y servidor se establece y queda “abierta” para que ambos puedan intercambiar mensajes en forma de eventos y aquí es donde SignaR sobresale.
Juguemos con SignalR
SignalR es una librería opensource, disponible como parte de ASP.NET Core, que permite añadir esa capacidad de comunicación en tiempo real entre cliente y servidor. Es una implementación de funcionalidad de comunicación bidireccional que abstrae toda la complejidad de mantener esas conexiones persistentes de una forma muy sencilla. Si te preguntas en que se parecen SignalR y WebSockets ? en esencia ambas, permiten la comunicación bidireccional (en tiempo real?), solo que SignalR usa WebSockets como capa de transporte, además de Server Sent Events y Long Pooling (estos 2 últimos los podemos tratar en otro post).
En nuestro ejemplo, vamos a usar Insomnia (también podria hacerse con Postman) para poner a prueba esta capacidad, pero en un escenario de la vida real, la comunicación entre cliente y servidor sería algo así como una aplicación SPA hablando con su respectivo backend.
Vamo a ensuciarnos las manos
Empecemos por crear una simple webapi con dotnet cli y vamos a añadir las capacidades de SignalR para ello, entonces en tu consola (terminal, powershell, etc.).
dotnet new webapi -n WebsocketTest
Ingresamos a la carpeta y abrimos con code (. para indicar la carpeta actual una vez accedamos a ella)
code .
Lo primero que debemos hacer es crear/extender un Hub, esto es, una clase que abstrae y maneja toda la comunicación entre cliente y servidor. En esencia en esta clase vamos a crear métodos que puedan ser invocados por el cliente en el servidor. Recuerden que mencionamos el término bidireccional previamente, lo cual significa que desde el servidor también podrían ser invocados metodos en el cliente.
En nuestro ejemplo, el hub va a ofrecer la capacidad de servidor de difusión: todo mensaje que reciba de un cliente será reenviado a todos los demás clientes via el método “DifundirMensaje” que básicamente reenvía el mensaje a todos los clientes conectados.
Paso siguiente: instruir nuesta webapi para que levante la infraestructura de SignalR
Tome especial nota de las siguientes instrucciones :
builder.Services.AddSignalR() : Método de extensión para hacer disponibles los servicios de SignalR al IServiceCollection (el lugar donde se mantiene la colección de descriptores de servicios).
app.MapHub<WSTestHub>(“/ws”) : este método se encarga de mapear las solicitudes entrantes en la ruta especificada (en este caso “/ws”) a nuestra clase WSTestHub, dicho de otra forma, “solicitudes de tipo websocket a la ruta “/ws” serán entregadas a nuestra clase Hub para que se les dé el tratamiento necesario.
Adicionalmente, y para ilustrar cómo se pueden usar las capacidades de SignalR, en la línea
app.MapGet(“/weatherforecast”, async(IHubContext<WSTestHub> hub) =>
Estoy inyectando el IHubcontext<T> que me permite obtener la referencia de mi WSTestHub y de forma programática lanzar eventos y/o invocar métodos en los clientes.
Para poner esto a prueba, vamos a ejecutar nuestra webapi, vamos a lanzar Insomnia y vamos a establecer una conexión de tipo WebSocket a la misma, de esta forma :
Ahora suministramos los datos de conexión, usando el esquema ws://<host:port>/wspath, donde
- host es localhost
- port es el puerto que tu webapi expone
- wspath es la ruta usada para exponer tu Hub, en nuestro ejercicio es “/ws”
Click en Connect y ahora párale bola: para poder empezar este flujo de comunicación bidireccional, el primer mensaje que usted debe enviar al servidor es el siguiente:
{
"protocol": "json",
"version": 1
}
Copiar y pegar completo, hay un caracter invisible al final del mensaje y todos los mensajes enviados al servidor deben incluir dicho caracter al final (Caracter de terminación - ASCII 0x1E )
En este punto puedes poner a prueba la conexión al servidor, lo más fácil sería pegarle al endpoint “/weatherforecast” (consola o swagger) y mirar como llegan los mensajes al cliente conectado en Insomnia:
Ahora, desde el cliente también se pueden hacer llamados al servidor, tomen por ejemplo el siguiente mensaje :
{
“arguments”: [
“Difundir a todos”
],
“target”: “DifundirMensaje”,
“type”: 1
}
“target” es el nombre del método del Hub a invocar, “arguments” son los parametros y “type” es el tipo de mensaje a enviar : 1 es invocación (ver aqui los tipos de mensajes). Al enviar ese mensaje podrás ver cómo lo recibes de vuelta, intenta conectando otro cliente como Postman.
Como pueden ver es muy fácil aprender a jugar con SignalR, en una próxima entrega haremos un ejercicio entre un cliente SPA y un servidor con un ejemplo mas diciente.
Hasta una próxima entrega amiwitos!