├── .github └── workflows │ ├── osx.yml │ └── ubuntu.yml ├── README.md ├── SECURITY.md ├── docs └── sharding.md └── images └── icon.png /.github/workflows/osx.yml: -------------------------------------------------------------------------------- 1 | name: Gated-OSX 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | - master 8 | pull_request: 9 | branches: 10 | - dev 11 | - master 12 | 13 | jobs: 14 | build_osx: 15 | 16 | runs-on: macos-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Setup .NET 2.1 21 | uses: actions/setup-dotnet@v1 22 | with: 23 | dotnet-version: '2.1.x' 24 | - name: Setup .NET 3.1 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: '3.1.x' 28 | - name: Setup .NET 5.0 29 | uses: actions/setup-dotnet@v1 30 | with: 31 | dotnet-version: '5.0.x' 32 | - name: Restore dependencies 33 | run: dotnet restore 34 | - name: Install packages 35 | run: brew install maven 36 | - name: Build 37 | run: | 38 | ./build.sh --ci 39 | mvn clean package -f ./binding-library/java/pom.xml 40 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Gated-Ubuntu 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | - master 8 | pull_request: 9 | branches: 10 | - dev 11 | - master 12 | 13 | jobs: 14 | build_ubuntu: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Setup .NET 2.1 21 | uses: actions/setup-dotnet@v1 22 | with: 23 | dotnet-version: '2.1.x' 24 | - name: Setup .NET 3.1 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: '3.1.x' 28 | - name: Setup .NET 5.0 29 | uses: actions/setup-dotnet@v1 30 | with: 31 | dotnet-version: '5.0.x' 32 | - name: Restore dependencies 33 | run: dotnet restore 34 | - name: Install packages 35 | run: sudo apt -y install libunwind8 maven 36 | - name: Build 37 | run: | 38 | ./build.sh --ci 39 | mvn clean package -f ./binding-library/java/pom.xml 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Functions Bindings for Azure SignalR Service 2 | **Project moved to [azure-sdk-for-net](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/signalr/Microsoft.Azure.WebJobs.Extensions.SignalRService) repo.** 3 | 4 | The code in `dev` branch is moved to [`archived-dev`](https://github.com/Azure/azure-functions-signalrservice-extension/tree/archived-dev) branch. 5 | 6 | ## Build Status 7 | 8 | Travis: [![travis](https://travis-ci.org/Azure/azure-functions-signalrservice-extension.svg?branch=dev)](https://travis-ci.org/Azure/azure-functions-signalrservice-extension) 9 | 10 | ## NuGet Packages 11 | 12 | Package Name | Target Framework | NuGet 13 | ---|---|--- 14 | Microsoft.Azure.WebJobs.Extensions.SignalRService | .NET Core App 2.1
.NET Core App 3.1 | [![NuGet](https://img.shields.io/nuget/v/Microsoft.Azure.WebJobs.Extensions.SignalRService.svg)](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.SignalRService) 15 | 16 | 17 | ## Intro 18 | 19 | These bindings allow Azure Functions to integrate with [Azure SignalR Service](http://aka.ms/signalr_service). 20 | 21 | ### Supported scenarios 22 | 23 | - Allow clients to serverlessly connect to a SignalR Service hub without requiring an ASP.NET Core backend 24 | - Use Azure Functions (any language supported by V2) to broadcast messages to all clients connected to a SignalR Service hub. 25 | - Use Azure Functions (any language supported by V2) to send messages to a single user, or all the users in a group. 26 | - Use Azure Functions (any language supported by V2) to manage group users like add/remove a single user in a group. 27 | - Example scenarios include: broadcast messages to a SignalR Service hub on HTTP requests and events from Cosmos DB change feed, Event Hub, Event Grid, etc 28 | - Use multiple Azure SignalR Service instances for resiliency and disaster recovery in Azure Functions. See details in [Multiple SignalR service endpoint support](./docs/sharding.md). 29 | 30 | ### Bindings 31 | 32 | `SignalRConnectionInfo` input binding makes it easy to generate the token required for clients to initiate a connection to Azure SignalR Service. 33 | 34 | `SignalR` output binding allows messages to be broadcast to an Azure SignalR Service hub. 35 | 36 | ## Prerequisites 37 | 38 | - [Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools) (V2 or V3) 39 | 40 | ## Usage 41 | 42 | ### Create Azure SignalR Service instance 43 | 44 | 1. Create Azure SignalR Service instances in the Azure Portal. Note the connection strings, you'll need them later. 45 | 46 | ### Create Function App with extension 47 | 48 | 1. In a new folder, create a new Azure Functions app. 49 | - `func init` 50 | 1. Install this Functions extension. 51 | - `func extensions install -p Microsoft.Azure.WebJobs.Extensions.SignalRService -v 1.0.0` 52 | 53 | ### Add application setting for SignalR connection string 54 | 55 | 1. Create an app setting called `AzureSignalRConnectionString` with the SignalR connection string. 56 | - On localhost, use `local.settings.json` 57 | - In Azure, use App Settings 58 | 59 | ### Using the SignalRConnectionInfo input binding 60 | 61 | In order for a client to connect to SignalR, it needs to obtain the SignalR Service client hub URL and an access token. 62 | 63 | 1. Create a new function named `negotiate` and use the `SignalRConnectionInfo` input binding to obtain the connection information and return it. Take a look at this [sample](samples/simple-chat/js/functionapp/negotiate/). 64 | 1. Client connects to the `negotiate` function as it's a normal SignalR hub. See [this file](samples/simple-chat/content/index.html) for a sample usage. 65 | 66 | Binding schema: 67 | 68 | ```javascript 69 | { 70 | "type": "signalRConnectionInfo", 71 | "name": "connectionInfo", 72 | "hubName": "", 73 | "connectionStringSetting": "", // Defaults to AzureSignalRConnectionString 74 | "direction": "in" 75 | } 76 | ``` 77 | 78 | ### Using the SignalR output binding 79 | 80 | The `SignalR` output binding can be used to broadcast messages to all clients connected a hub. Take a look at this sample: 81 | 82 | - [HttpTrigger function to send messages](samples/simple-chat/js/functionapp/messages/) 83 | - [Simple chat app](samples/simple-chat/content/index.html) 84 | - Calls negotiate endpoint to fetch connection information 85 | - Connects to SignalR Service 86 | - Sends messages to HttpTrigger function, which then broadcasts the messages to all clients 87 | 88 | Binding schema: 89 | 90 | ```javascript 91 | { 92 | "type": "signalR", 93 | "name": "signalRMessages", // name of the output binding 94 | "hubName": "", 95 | "connectionStringSetting": "", // Defaults to AzureSignalRConnectionString 96 | "direction": "out" 97 | } 98 | ``` 99 | 100 | To send one or more messages, set the output binding to an array of objects: 101 | 102 | ```javascript 103 | module.exports = function (context, req) { 104 | context.bindings.signalRMessages = [{ 105 | "target": "newMessage", // name of the client method to invoke 106 | "arguments": [ 107 | req.body // arguments to pass to client method 108 | ] 109 | }]; 110 | context.done(); 111 | }; 112 | ``` 113 | 114 | ## Contributing 115 | 116 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 117 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 118 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 119 | 120 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 121 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 122 | provided by the bot. You will only need to do this once across all repos using our CLA. 123 | 124 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 125 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 126 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 127 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/sharding.md: -------------------------------------------------------------------------------- 1 | # Multiple Azure SignalR Service Instances Support in Azure Functions 2 | Currently we add support for configuring multiple SignalR Service instances. You can distribute your clients to multiple SignalR service instances and send messages to multiple instances as if to one instance. 3 | 4 | 5 | 6 | - [Multiple Azure SignalR Service Instances Support in Azure Functions](#multiple-azure-signalr-service-instances-support-in-azure-functions) 7 | - [Usage scenarios](#usage-scenarios) 8 | - [Limitations](#limitations) 9 | - [Configuration](#configuration) 10 | - [Routing](#routing) 11 | - [Default behavior](#default-behavior) 12 | - [Customization](#customization) 13 | - [CSharp](#csharp) 14 | - [Other languages](#other-languages) 15 | - [Client routing](#client-routing) 16 | - [Messages routing](#messages-routing) 17 | 18 | 19 | 20 | ## Usage scenarios 21 | Routing logic is the way to decide to which SignalR Service instance among multiple instances your clients connect and your messages send. By applying different routing logic, this feature can be used in different scenarios. 22 | * Scaling. Randomly route each client to one SignalR Service instance, send messages to all the SignalR Service instances so that you can scale the concurrent connections. 23 | * Cross-geo scenario. Cross-geo networks can be comparatively unstable. Route your clients to a SignalR Service instance in the same region can reduce cross-geo connections. 24 | * High availability and disaster recovery scenarios. Set up multiple service instances in different regions, so when one region is down, the others can be used as backup. Configure service instances as two roles, **primary** and **secondary**. By default, clients will be routed to a primary online instance. When SDK detects all the primary instances are down, it will route clients to secondary instances. Clients connected before will experience connection drops when there is a disaster and failover take place. You'll need to handle such cases at client side to make it transparent to your end customers. For example, do reconnect after a connection is closed. 25 | 26 | ## Limitations 27 | Currently multiple-endpoint feature is only supported on `Persistent` transport type. 28 | 29 | ## Configuration 30 | 31 | To enable multiple SignalR Service instances, you should: 32 | 33 | 1. Use `Persistent` transport type. 34 | 35 | The default transport type is `Transient` mode. You should add the following entry to your `local.settings.json` file or the application setting on Azure. 36 | 37 | ```json 38 | { 39 | "AzureSignalRServiceTransportType":"Persistent" 40 | } 41 | ``` 42 | >Notes for switching from `Transient` mode to `Persistent` mode on **Azure Functions runtime V3** : 43 | > 44 | > Under `Transient` mode, `Newtonsoft.Json` library is used to serialize arguments of hub methods, however, under `Persistent` mode, `System.Text.Json` library is used as default on Azure Functions runtime V3. `System.Text.Json` has some key differences in default behavior with `Newtonsoft.Json`. If you want to use `Newtonsoft.Json` under `Persistent` mode, you can add a configuration item: `"Azure:SignalR:HubProtocol":"NewtonsoftJson"` in `local.settings.json` file or `Azure__SignalR__HubProtocol=NewtonsoftJson` on Azure portal. 45 | 46 | 47 | 2. Configure multiple SignalR Service endpoints entries in your configuration. 48 | 49 | We use a [`ServiceEndpoint`](https://github.com/Azure/azure-signalr/blob/dev/src/Microsoft.Azure.SignalR.Common/Endpoints/ServiceEndpoint.cs) object to represent a SignalR Service instance. You can define an service endpoint with its `` and `` in the entry key, and the connection string in the entry value. The keys are in the following format : 50 | 51 | ``` 52 | Azure:SignalR:Endpoints:: 53 | ``` 54 | 55 | `` is optional and defaults to `primary`. See samples below: 56 | 57 | ```json 58 | { 59 | "Azure:SignalR:Endpoints:EastUs":"", 60 | 61 | "Azure:SignalR:Endpoints:EastUs2:Secondary":"", 62 | 63 | "Azure:SignalR:Endpoints:WestUs:Primary":"" 64 | } 65 | ``` 66 | 67 | > * When you configure Azure SignalR endpoints in the App Service on Azure portal, don't forget to replace `":"` with `"__"`, the double underscore in the keys. For reasons, see [Environment variables](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#environment-variables). 68 | > 69 | > * Connection string configured with the key `{ConnectionStringSetting}` (defaults to "AzureSignalRConnectionString") is also recognized as a primary service endpoint with empty name. But this configuration style is not recommended for multiple endpoints. 70 | ## Routing 71 | 72 | ### Default behavior 73 | By default, the SDK uses the [DefaultEndpointRouter](https://github.com/Azure/azure-signalr/blob/dev/src/Microsoft.Azure.SignalR/EndpointRouters/DefaultEndpointRouter.cs) to pick up endpoints. 74 | 75 | * Client routing: Randomly select one endpoint from **primary online** endpoints. If all the primary endpoints are offline, then randomly select one **secondary online** endpoint. If the selection fails again, then exception is thrown. 76 | 77 | * Server message routing: All service endpoints are returned. 78 | 79 | ### Customization 80 | #### CSharp 81 | 82 | Here are the steps: 83 | * Implement a customized router. You can leverage information provided from [`ServiceEndpoint`](https://github.com/Azure/azure-signalr/blob/dev/src/Microsoft.Azure.SignalR.Common/Endpoints/ServiceEndpoint.cs) to make routing decision. See guide here: [customize-route-algorithm](https://github.com/Azure/azure-signalr/blob/dev/docs/sharding.md#customize-route-algorithm). **Please note that Http trigger is required in the negotiation function when you need `HttpContext` in custom negotiation method.** 84 | 85 | * Register the router to DI container. 86 | ```cs 87 | using Microsoft.Azure.Functions.Extensions.DependencyInjection; 88 | using Microsoft.Azure.SignalR; 89 | using Microsoft.Extensions.DependencyInjection; 90 | 91 | [assembly: FunctionsStartup(typeof(SimpleChatV3.Startup))] 92 | namespace SimpleChatV3 93 | { 94 | public class Startup : FunctionsStartup 95 | { 96 | public override void Configure(IFunctionsHostBuilder builder) 97 | { 98 | builder.Services.AddSingleton(); 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | #### Other languages 105 | 106 | For languages other than C#, we support specifying target endpoints in each request. You will use new binding types to get endpoint information. 107 | 108 | ##### Client routing 109 | The `SignalRConnectionInfo` binding selects one endpoint according the default routing rule. If you want to customize routing rule, you should use `SignalRNegotiation` binding instead of `SignalRConnectionInfo` binding. 110 | 111 | `SignalRNegotiation` binding configuration properties are the same as `SignalRConnectionInfo`. Here's a `function.json` file sample: 112 | ```json 113 | { 114 | "type": "signalRNegotiation", 115 | "name": "negotiationContext", 116 | "hubName": "", 117 | "direction": "in" 118 | } 119 | ``` 120 | 121 | You could also add other binding data such as `userId`, `idToken` and `claimTypeList` just like `SignalRConnectionInfo`. 122 | 123 | The object you get from `SignalRNegotiation` binding is in the following format: 124 | ```json 125 | { 126 | "endpoints": [ 127 | { 128 | "endpointType": "Primary", 129 | "name": "", 130 | "endpoint": "https://****.service.signalr.net", 131 | "online": true, 132 | "connectionInfo": { 133 | "url": "", 134 | "accessToken": "" 135 | } 136 | }, 137 | { 138 | "...": "..." 139 | } 140 | ] 141 | } 142 | ``` 143 | 144 | Here's a Javascript usage sample of `SignalRNegotiation` binding: 145 | ```js 146 | module.exports = function (context, req, negotiationContext) { 147 | var userId = req.query.userId; 148 | if (userId.startsWith("east-")) { 149 | //return the first endpoint whose name starts with "east-" and status is online. 150 | context.res.body = negotiationContext.endpoints.find(endpoint => endpoint.name.startsWith("east-") && endpoint.online).connectionInfo; 151 | } 152 | else { 153 | //return the first online endpoint 154 | context.res.body = negotiationContext.endpoints.filter(endpoint => endpoint.online)[0].connectionInfo; 155 | } 156 | } 157 | ``` 158 | 159 | ##### Messages routing 160 | Messages or actions routing needs two binding types to cooperate. In general, firstly you need a new input binding type `SignalREndpoints` to get all the available endpoint information. Then you filter the endpoints and get an array containing all the endpoints that you want to send to. Lastly you specify the target endpoints in the `SignalR` output binding. 161 | 162 | Here's the `SignalREndpoints` binding configuration properties in `functions.json` file: 163 | ```json 164 | { 165 | "type": "signalREndpoints", 166 | "direction": "in", 167 | "name": "endpoints", 168 | "hubName": "" 169 | } 170 | ``` 171 | 172 | The object you get from `SignalREndpoints` is an array of endpoints each of which is represented as a JSON object with the following schema: 173 | 174 | ```json 175 | { 176 | "endpointType": "", 177 | "name": "", 178 | "endpoint": "https://****.service.signalr.net", 179 | "online": true 180 | } 181 | ``` 182 | 183 | 184 | After you get the target endpoint array, add an `endpoints` property to the output binding object. This is a Javascript example: 185 | ```js 186 | module.exports = function (context, req, endpoints) { 187 | var targetEndpoints = endpoints.filter(endpoint => endpoint.name.startsWith("east-")); 188 | context.bindings.signalRMessages = [{ 189 | "target": "chat", 190 | "arguments": ["hello-world"], 191 | "endpoints": targetEndpoints, 192 | }]; 193 | context.done(); 194 | } 195 | ``` 196 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/azure-functions-signalrservice-extension/3fcc65f19c57686688f5e064da49e847b07cd614/images/icon.png --------------------------------------------------------------------------------