Build Real-time Serverless Java Apps with Azure SignalR Service
Wednesday, March 6, 2019
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. We'll be writing our functions in Java, but the SignalR Service bindings also work with other languages supported by Azure Functions.
Overview
There are three major components of the app: the drawing canvas, the Azure Function app, and Azure SignalR Service.
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".
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.
When lines are drawn on the canvas, the app sends a series of strokes to an Azure Function named "draw".
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:
Check out the GitHub repo for the source code of the function app in Java, JavaScript, and C#.
Resources
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. We'll be writing our functions in Java, but the SignalR Service bindings also work with other languages supported by Azure Functions.
Overview
There are three major components of the app: the drawing canvas, the Azure Function app, and Azure SignalR Service.
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".
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.
When lines are drawn on the canvas, the app sends a series of strokes to an Azure Function named "draw".
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:
Check out the GitHub repo for the source code of the function app in Java, JavaScript, and C#.