Cómo empezar a utilizar SignalR en Azure con JavaScript

El otro día, algunos buenos desarrolladores de mi empresa se estaban preparando para lanzar una página de actualización de estado. Lo habíamos probado ampliamente, pero ahora estábamos a punto de ponerlo a escala.

Estaba preocupado por su dependencia de un servidor API que había estado actuando recientemente. No hemos determinado la causa raíz de nuestros problemas en el lado de la API, y esta aplicación utiliza sondeos, es decir, constantemente solicita nuevos datos a la API. Si esa API falla, se lleva nuestra aplicación consigo y el aumento de carga de nuestra aplicación podría exacerbar los problemas que estamos viendo.

Turistas en Irlanda, quizás esperando un mensaje

Una forma de alejarse del sondeo es integrar SignalR, una herramienta de conexión persistente que utiliza websockets y tecnologías relacionadas para permitir que los servidores envíen actualizaciones a los clientes.

La tecnología está escrita en .NET y la mayor parte de la documentación que encontrará en la web utiliza C #. Este tutorial cubrirá una implementación básica de JavaScript.

¿Qué hace?

SignalR de código abierto crea una conexión persistente entre un cliente y un servidor. Primero utiliza websockets, luego longpolling y otras tecnologías cuando los websockets no están disponibles.

Una vez que el cliente y el servidor han creado una conexión, SignalR se puede utilizar para "transmitir" mensajes al cliente. Cuando el cliente recibe esos mensajes, puede realizar funciones como actualizar una tienda.

El ejemplo más común dado para websockets es una aplicación de chat: se deben mostrar nuevos datos al usuario sin necesidad de actualizar la página. Pero si su servidor recibe actualizaciones sobre cambios de datos que necesita mostrar a un cliente, este podría ser el servicio para usted.

SignalR en la plataforma Azure

Quizás porque fue desarrollado por Microsoft, SignalR tiene una integración muy limpia en la plataforma en la nube de Azure. Al igual que otras aplicaciones de funciones, creará un activador "de entrada" y un enlace de "salida" para transmitir mensajes.

Costos

Debido a que fui el primero en considerar esta tecnología a escala en mi empresa, tuve que indagar un poco sobre los costos de este servicio. Azure cobra alrededor de 50 dólares al mes por una "unidad" de servicio de SignalR: 1000 conexiones simultáneas y un millón de mensajes al día. También hay un servicio gratuito para los que juegan o para las pequeñas empresas.

Fue realmente bueno que indagué en esos números, como verán un poco más abajo.

Crear un centro de SignalR

Empecemos. Necesitaremos un concentrador SignalR, dos aplicaciones de funciones y un código de cliente para agregar a nuestra aplicación web.

Vaya a SignalR -> Agregar y complete sus datos. El trabajador tarda un segundo en crear su servicio. Asegúrese de darle al servicio un nombre de recurso decente, ya que lo usará con el resto de sus aplicaciones. También tome Keys -> Connection String para usar en nuestro enlace.

Configuración de SignalR en Azure

Cree su aplicación de función para enviar mensajes SignalR

Debido a que estamos trabajando con Azure, crearemos aplicaciones de funciones para interactuar con SignalR. Escribí una publicación de blog de introducción sobre las aplicaciones de funciones de Azure hace un tiempo.

Este tutorial asume que ya sabe cómo trabajar con aplicaciones de funciones. Naturalmente, puede trabajar con estas bibliotecas sin la magia de vinculación, ¡pero tendrá que hacer su propia traducción del código .NET!

La aplicación de conexión

Lo primero que necesitamos es una forma para que los clientes soliciten permiso para conectarse a nuestro servicio SignalR. El código para esta función no podría ser más básico:

module.exports = function (context, _req, connectionInfo) { context.res = { body: connectionInfo } context.done() } 

Toda la magia sucede en los enlaces, donde ingresamos nuestro servicio SignalR. El disparador es una solicitud HTTP a la que nuestro cliente puede llamar.

{ "bindings": [ { "authLevel": "function", "type": "httpTrigger", "direction": "in", "name": "req", "methods": ["get"] }, { "type": "signalRConnectionInfo", "name": "connectionInfo", "hubName": "your-signalr-service-name", "connectionStringSetting": "connection-string", "direction": "in" } ] } 

El código de cliente

Para acceder a este método, nuestro cliente llamará:

import * as signalR from '@microsoft/signalr' const { url: connectionUrl, accessToken } = await axios .get(url-to-your-connection-app) .then(({ data }) => data) .catch(console.error) 

Nuestra aplicación de función devolverá un urly accessToken, que luego podemos usar para conectarnos a nuestro servicio SignalR. Tenga en cuenta que creamos el enlace con el hubNamede nuestro servicio SignalR, lo que significa que podría tener múltiples conexiones a diferentes hubs en un cliente.

El servicio de radiodifusión

Ahora estamos listos para comenzar a enviar mensajes. Nuevamente comenzaremos con la aplicación de funciones. Toma un disparador y emite un mensaje SignalR.

Un desencadenador podría ser otro que utilice la publicación de un mensaje, un evento de un centro de eventos o cualquier otro desencadenador que admita Azure. Necesito activar los cambios en la base de datos.

{ "bindings": [ { "type": "cosmosDBTrigger", "name": "documents", "direction": "in", [...] }, { "type": "signalR", "name": "signalRMessages", "hubName": "your-signalr-service-name", "connectionStringSetting": "connection-string", "direction": "out" } ] } 

Y el código. De nuevo, absolutamente simple.

module.exports = async function (context, documents) { const messages = documents.map(update => { return { target: 'statusUpdates', arguments: [update] } }) context.bindings.signalRMessages = messages } 

Los mensajes de SignalR toman un objeto targety arguments. Una vez que sus disparadores comienzan a dispararse, ¡eso es todo lo que necesita para comenzar con SignalR en el servidor! Microsoft nos ha facilitado todo esto.

El código de cliente

Por el lado del cliente, las cosas son un poco más complejas, pero no inmanejables. Aquí está el resto del código del cliente:

const connection = new signalR.HubConnectionBuilder() .withUrl(connectionUrl, { accessTokenFactory: () => accessToken }) // .configureLogging(signalR.LogLevel.Trace) .withAutomaticReconnect() .build() connection.on('statusUpdates', data => { // do something with the data you get from SignalR }) connection.onclose(function() { console.log('signalr disconnected') }) connection.onreconnecting(err => console.log('err reconnecting ', err) ) connection .start() .then(res => // Potential to do something on initial load) .catch(console.error) 

Consumimos connectionUrly accessTokenrecibimos de la función de conexión anteriormente, luego construimos nuestra conexión usando esos valores.

Luego escuchamos los mensajes con la clave compartida (para mí, es statusUpdates) y proporcionamos controladores para las funciones de cierre y reconexión.

Finalmente, iniciamos la conexión. Aquí podemos proporcionar una función de carga inicial. Necesitaba uno para obtener datos iniciales para mostrar el estado actual. Si está creando una aplicación de chat, es posible que deba buscar los mensajes iniciales aquí.

¡Esto es (casi, tal vez) todo lo que necesita para comenzar a usar JavaScript con SignalR en Azure!

Alcance por usuario

Pero tal vez tú, como yo, necesites enviar muchos mensajes a muchos usuarios.

Cuando puse esto en producción por primera vez, en un subconjunto de usuarios, estaba explotando cada conexión con cada actualización. Debido a que el código del cliente puede abarcar los mensajes que escucha, utilicé algo como statusUpdates-${userId}para que el cliente solo vea sus propias actualizaciones.

That could work just fine if you have very low volume, and the more general one is great if everybody in your system needs the same message. But the status I work with is particular to an individual.

800.000 mensajes de SignalR enviados desde la plataforma Azure

Remember how Azure charges per "unit" and each unit has one million messages? I hit that during a few hours of testing this during a not-busy time.

Azure counts each message SignalR has to send as one message. That is, if five connections are hooked up to your hub and you send ten messages, that counts as 50, not 10. This was a surprise to me, and also required a couple more hours of research.

We can scope our SignalR function code to send only to certain users. First, we update the connection app to accept userId as a query param:

 { "type": "signalRConnectionInfo", "name": "connectionInfo", "userId": "{userId}", "hubName": "your-signalr-service-name", "connectionStringSetting": "connection-string", "direction": "in" } 

Then we update the broadcasting function to send only to that user:

const messages = documents.map(update => { return { target: 'statusUpdates', userId: update.user.id, arguments: [update] } }) 

The broadcasting service won't know who has connected, so you'll need to trigger it with something that has access to a unique ID that the client will also have access to.

The client code simply passes in the userId as a query param:

const { url: connectionUrl, accessToken } = await axios .get(`${url-to-your-connection-app}&userId=${userId}`) .then(({ data }) => data) .catch(console.error) 

I swear to you, the only place on the entire internet I found to let me know how to request a connection using the userId was an answer on this Stack Overflow question.

The internet is amazing, and JavaScript Azure docs are hard to come by.

Resources

  • SignalR Javascript client docs from Microsoft
  • Configuring Users and Groups when sending SignalR messages -

    examples in C# but you can maybe figure out how the JavaScript client is going to behave and make some educated guesses.

  • SignalR Service bindings for Azure Functions
  • Client API
  • Working with Groups in SignalR
  • Tutorial: autenticación del servicio Azure SignalR con Azure Functions

Esta publicación apareció originalmente en wilkie.tech.