loading...
Cover image for Build Real-time Serverless Apps with Azure SignalR Service
Microsoft Azure

Build Real-time Serverless Apps with Azure SignalR Service

anthony profile image Anthony Chu Originally published at anthonychu.ca Updated on ・5 min read

Today, Microsoft announced the general availability of the Azure SignalR Service bindings for Azure Functions. The bindings allow Azure Functions to integrate seamlessly with SignalR Service to broadcast real-time messages at large scale over WebSockets.

We'll take a look at how to build a browser-based, collaborative drawing app using HTML and JavaScript. Multiple people can open the app in their own browser and draw on the canvas. Changes are synchronized in real-time using Azure Functions and SignalR Service.

Java language support in Azure Functions has also recently become generally available. In this article, we'll be writing our functions in Java, but the SignalR Service bindings also work with other languages supported by Azure Functions. Check out the GitHub repo for source code in Java, C#, and JavaScript.

Overview

There are three major components of the app: the drawing canvas, the Azure Function app, and Azure SignalR Service.

Architecture

  1. The canvas is a simple JavaScript app that runs in the browser. When the app starts up, it retrieves the SignalR Service connection endpoint and access token from an Azure Function named "negotiate".

  2. The app connects to SignalR Service and a real-time channel is established. It's usually a WebSocket connection, but SignalR will automatically fallback to other transports if WebSocket is not available for a given connection.

  3. When lines are drawn on the canvas, the app sends a series of strokes to an Azure Function named "draw".

  4. The draw Azure Function uses SignalR Service to broadcast the series of strokes to all connected clients.

Create an Azure SignalR Service instance

Azure SignalR Service is a fully managed real-time messaging platform that our app will use to broadcast messages. We can create a free instance of the service in the Azure portal, or we can run the following Azure CLI command.

az signalr create -n $SIGNALR_NAME -g $GROUP_NAME --sku Free_DS2 -l westus

After the instance is created, we'll need to get the connection string that will be used by the function app.

az signalr key list -n $SIGNALR_NAME -g $GROUP_NAME

Build the Azure Function app

The Azure Function app consists of two key functions. The first is a negotiate function that returns the SignalR Service connection information. The second is a draw function that is called whenever lines are drawn on any canvas; it will broadcast the lines to the other canvases.

If you haven't worked with Azure Functions before, check out their quickstarts.

negotiate function

This HTTP triggered function uses the SignalRConnectionInfoInput binding to generate the access token and endpoint information for the client. The binding generates this information using the connection string.

@FunctionName("negotiate")
public SignalRConnectionInfo negotiate(
    @HttpTrigger(
      name = "req",
      methods = { HttpMethod.POST },
      authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> req,
    @SignalRConnectionInfoInput(
      name = "connectionInfo",
      hubName = "serverlessdraw") SignalRConnectionInfo connectionInfo) {
  return connectionInfo;
}

draw function

The draw HTTP triggered function uses the SignalROutput binding to broadcast the strokes that were drawn on a canvas to all canvases that are currently connected to SignalR Service.

It simply relays the contents of the HTTP request body to SignalR Service.

@FunctionName("draw")
public void draw(
    @HttpTrigger(
      name = "req",
      methods = { HttpMethod.POST },
      authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<StrokeCollection> req,
    @SignalROutput(
      name = "signalRMessage",
      hubName = "serverlessdraw") OutputBinding<SignalRMessage> signalRMessage) {

  StrokeCollection strokeCollection = req.getBody();
  SignalRMessage msg = new SignalRMessage("newStrokes", strokeCollection);
  signalRMessage.setValue(msg);
}

Create the drawing canvas

The canvas is a surprisingly simple browser-based JavaScript app that uses an HTML canvas. You can watch me build the first version of it on my Twitch stream.

SignalR Service connection

The first thing we do when the canvas app starts is connect to SignalR Service. We can reference the SignalR JavaScript client using a CDN.

<script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.2/dist/browser/signalr.js"></script>

Then we can use the client to create a connection to SignalR Service. By convention, the SignalR client will append /negotiate at the end of the URL to discover the connection negotiation endpoint, so we'll leave that part off of our function's URL when we initialize the HubConnectionBuilder.

var connection = new signalR.HubConnectionBuilder()
  .withUrl(`${apiBaseUrl}/api`) // function URL minus /negotiate
  .build()

// set up event listeners
connection.on('newStrokes', drawStrokes)
connection.on('clearCanvas', clearCanvas)

connection.start()
  .then(() => console.log('connected'))

Between configuring the connection and starting it, we set up a couple of event listeners. When an event arrives from SignalR Service that matches one of those names, the event handler will be called. Here's what the clearCanvas function looks like:

function clearCanvas() {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
}

The drawing canvas

The main canvas is (you guessed it) a single <canvas> tag:

<canvas id="draw-canvas" height="500" width="800"></canvas>

The main JavaScript function is named mouseMove. It is called when each stroke is drawn. We figure out the start and end of the line, and draw it on the canvas. Then we add the stroke to an array so that we can collect a few strokes together and send them off in a batch.

function mouseMove(ev) {

  // 🌟 math happens here

  drawStroke(start, end, colorButton.value)
  unsentStrokes.push({
    start: start,
    end: end,
    color: colorButton.value
  })
}

Every 250 milliseconds or so, we post our batch of strokes to our draw Azure Function.

setInterval(function () {
  if (unsentStrokes.length) {
    axios.post(`${apiBaseUrl}/api/draw`, {
      sender: clientId,
      strokes: unsentStrokes
    })
    unsentStrokes = []
  }
}, 250)

The draw function will use SignalR Service to raise the newStrokes event in each running instance of our app. This invokes a function named drawStrokes to draw the stroke.

Here's what the finished product looks like:

Serverless draw app

Check out the GitHub repo for the source code of the function app in Java, JavaScript, and C#.

Resources

Discussion

pic
Editor guide