Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SignalR serverless protocol project #24954

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/.docsettings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ known_content_issues:
- ['sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/README.md', '#15423']
- ['sdk/personalizer/README.md','azure-sdk-tools/issues/42']
- ['sdk/personalizer/Azure.AI.Personalizer/README.md','azure-sdk-tools/issues/42']
- ['sdk/signalr/Microsoft.Azure.SignalR.Serverless.Protocols/README.md', 'azure-sdk-tools/issues/404']

# .net climbs upwards. placing these to prevent assigning readmes to the wrong project
package_indexing_exclusion_list:
Expand Down
3 changes: 2 additions & 1 deletion eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,12 @@
<ItemGroup Condition="'$(IsClientLibrary)' == 'true' and $(MSBuildProjectName.StartsWith('Microsoft.'))">
<PackageReference Update="CloudNative.CloudEvents" Version="2.0.0" />
<PackageReference Update="CloudNative.CloudEvents.SystemTextJson" Version="2.0.0" />
<PackageReference Update="MessagePack" Version="1.9.11" />
<PackageReference Update="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="3.0.0" Condition="'$(TargetFramework)' == 'netcoreapp3.1'" />
<PackageReference Update="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="1.1.5" />
<PackageReference Update="Microsoft.Azure.SignalR" Version="1.12.0" />
<PackageReference Update="Microsoft.Azure.SignalR.Management" Version="1.12.0" />
<PackageReference Update="Microsoft.Azure.SignalR.Protocols" Version="1.12.0" />
<PackageReference Update="Microsoft.Azure.SignalR.Serverless.Protocols" Version="1.6.0" />
<PackageReference Update="Microsoft.Azure.WebJobs" Version="3.0.30" />
<PackageReference Update="Microsoft.Azure.WebJobs.Sources" Version="3.0.30" />
<PackageReference Update="Microsoft.Spatial" Version="7.5.3" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Release History

## 1.7.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes

### Bugs Fixed

### Other Changes

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<IsClientLibrary>true</IsClientLibrary>
</PropertyGroup>

<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Azure.SignalR.Serverless.Protocols", "src\Microsoft.Azure.SignalR.Serverless.Protocols.csproj", "{A15F41D6-11A7-45D3-8D5A-02311D329656}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Azure.SignalR.Serverless.Protocols.Tests", "tests\Microsoft.Azure.SignalR.Serverless.Protocols.Tests.csproj", "{D4DB257A-E324-4248-9FD8-7973B503B641}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Debug|x64.ActiveCfg = Debug|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Debug|x64.Build.0 = Debug|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Debug|x86.ActiveCfg = Debug|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Debug|x86.Build.0 = Debug|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Release|Any CPU.Build.0 = Release|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Release|x64.ActiveCfg = Release|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Release|x64.Build.0 = Release|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Release|x86.ActiveCfg = Release|Any CPU
{A15F41D6-11A7-45D3-8D5A-02311D329656}.Release|x86.Build.0 = Release|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Debug|x64.ActiveCfg = Debug|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Debug|x64.Build.0 = Debug|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Debug|x86.ActiveCfg = Debug|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Debug|x86.Build.0 = Debug|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Release|Any CPU.Build.0 = Release|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Release|x64.ActiveCfg = Release|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Release|x64.Build.0 = Release|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Release|x86.ActiveCfg = Release|Any CPU
{D4DB257A-E324-4248-9FD8-7973B503B641}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
123 changes: 123 additions & 0 deletions sdk/signalr/Microsoft.Azure.SignalR.Serverless.Protocols/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Microsoft.Azure.SignalR.Serverless.Protocols

This project implements a protocol to communicate with Azure SignalR Service in serverless scenarios. It's recommended to use [Azure SignalR Function extension](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/signalr/Microsoft.Azure.WebJobs.Extensions.SignalRService/README.md) to handle serverless message, as it enables you to write codes in a more intuitive way. If you want to implement your own Web API server to handle serverless message, you could leverage this library to parse serverless messages.
## Getting started

### Install the package

Install the library with [NuGet](https://www.nuget.org/packages/Microsoft.Azure.SignalR.Serverless.Protocols/):

```dotnetcli
dotnet add package Microsoft.Azure.SignalR.Serverless.Protocols
```

### Prerequisites

- **Azure Subscription:** To use Azure services, including Azure SignalR Service, you'll need a subscription. If you do not have an existing Azure account, you may sign up for a [free trial](https://azure.microsoft.com/free/dotnet/) or use your [Visual Studio Subscription](https://visualstudio.microsoft.com/subscriptions/) benefits when you [create an account](https://account.windowsazure.com/Home/Index).

- **Azure SignalR resource:** To receive Azure SignalR serverless messages you'll also need a Azure SignalR resource. If you are not familiar with creating Azure resources, you may wish to follow the step-by-step guide for creating a SignalR resource using the Azure portal. There, you can also find detailed instructions for using the Azure CLI, Azure PowerShell, or Azure Resource Manager (ARM) templates to create a SignalR resource.

To quickly create the needed SignalR resource in Azure and to receive a connection string for them, you can deploy our sample template by clicking:

[![Deploy to Azure](https://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3a%2f%2fraw.neting.cc%2fAzure%2fazure-quickstart-templates%2fmaster%2fquickstarts%2fmicrosoft.signalrservice%2fsignalr%2fazuredeploy.json)

After the instance is deployed, open it in the portal and locate its Settings page. Change the Service Mode setting to *Serverless*.

![SignalR Service mode setting](images/signalr-service-mode.png)

## Key concepts

### Upstream settings

Please see [doc](https://docs.microsoft.com/azure/azure-signalr/concept-upstream#serverless-protocols) here.

### JSON serverless protocol

Class `JsonServerlessProtocol` helps to parse serverless message with a HTTP header `Content-Type=application/json`. All the connection messages (including connected and disconnected events) should be parsed with JSON serverless protocol. Invocation messages should be parsed with JSON protocol when a HTTP header `Content-Type=application/json` is present.

### MessagePack serverless protocol

Class `MessagePackServerlessProtocol` helps to parse serverless **invocation message** with a HTTP header `Content-Type=application/x-msgpack`. Please note that connection messages won't be sent in MessagePack format.

## Examples

### Handle `OpenConnectionMessage`
This example demonstrates how to parse a `OpenConnectionMessage` from an `HttpRequest` and get useful information such as the connection ID and the user ID.

```C# Snippet:HandleConnectedMessage
public void HandleConnectedMessage(HttpRequest httpRequest, ILogger logger)
{
var connectionId = httpRequest.Headers["X-ASRS-Connection-Id"];
var userId = httpRequest.Headers["X-ASRS-User-Id"];

using var memoryStream = new MemoryStream();
httpRequest.Body.CopyTo(memoryStream);
var bytes = new ReadOnlySequence<byte>(memoryStream.ToArray());
var protocol = new JsonServerlessProtocol();
if (protocol.TryParseMessage(ref bytes, out var message))
{
if (message is OpenConnectionMessage connectedMessage)
{
logger.LogInformation($"Connection {connectionId} is connected. User name is {userId}.");
}
}
}
```

### Handle `CloseConnectionMessage`
This example demonstrates how to parse a `CloseConnectionMessage` from an `HttpRequest` and get useful information such as the error why the connection is closed.

```C# Snippet:HandleDisconnectedMessage
public void HandleDisconnectedMessage(HttpRequest httpRequest, ILogger logger)
{
var connectionId = httpRequest.Headers["X-ASRS-Connection-Id"];
var userId = httpRequest.Headers["X-ASRS-User-Id"];

using var memoryStream = new MemoryStream();
httpRequest.Body.CopyTo(memoryStream);
var bytes = new ReadOnlySequence<byte>(memoryStream.ToArray());
var protocol = new JsonServerlessProtocol();
if (protocol.TryParseMessage(ref bytes, out var message))
{
if (message is CloseConnectionMessage disconnectedMessage)
{
logger.LogInformation($"Connection {connectionId} is disconnected. User name is {userId}. Reason is {disconnectedMessage.Error}");
}
}
}
```
### Handle `InvocationMessage`
This example demonstrates how to parse a `InvocationMessage` from an `HttpRequest` according to its content-type header and get the invocation target and arguments.

```C# Snippet:HandleInvocationMessage
public async Task HandleInvocation(HttpRequest httpRequest, ILogger logger)
{
var contentType = httpRequest.Headers["Content-Type"];
IServerlessProtocol protocol = (string)contentType switch
{
"application/json" => new JsonServerlessProtocol(),
"application/x-msgpack" => new MessagePackServerlessProtocol(),
_ => throw new NotSupportedException(),
};

using var memoryStream = new MemoryStream();
await httpRequest.Body.CopyToAsync(memoryStream);
var bytes = new ReadOnlySequence<byte>(memoryStream.ToArray());
if (protocol.TryParseMessage(ref bytes, out var message))
{
if (message is InvocationMessage invocationMessage)
{
var target = invocationMessage.Target;
var arguments = invocationMessage.Arguments;
logger.LogInformation($"{target},{arguments[0]}");
}
}
}

public Task Broadcast(object message)
{
//do something here.
return Task.CompletedTask;
}
```

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Buffers;

namespace Microsoft.Azure.SignalR.Serverless.Protocols
{
/// <summary>
/// A protocol abstraction for communicating with Azure SignalR Service in serverless scenarios.
/// </summary>
public interface IServerlessProtocol
{
// TODO: Have a discussion about how to handle version change.
/// <summary>
/// Gets the version of the protocol.
/// </summary>
int Version { get; }

/// <summary>
/// Creates a new <see cref="ServerlessMessage"/> from the specified serialized representation.
/// </summary>
/// <param name="input">The serialized representation of the message.</param>
/// <param name="message">When this method returns <c>true</c>, contains the parsed message.</param>
/// <returns>A value that is <c>true</c> if the <see cref="ServerlessMessage"/> was successfully parsed; otherwise, <c>false</c>.</returns>
bool TryParseMessage(ref ReadOnlySequence<byte> input, out ServerlessMessage message);
}
}
Loading