Skip to content

Commit b673778

Browse files
authored
Merge pull request #261063 from ShawnJackson/signalr-concept-client-negotiation
[AQ] edit pass: signalr-concept-client-negotiation
2 parents 129ca83 + 96d0b8d commit b673778

File tree

1 file changed

+92
-90
lines changed

1 file changed

+92
-90
lines changed
Lines changed: 92 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
title: Client negotiation in Azure SignalR service
3-
description: This article provides information about client negotiation in Azure SignalR service.
2+
title: Client negotiation in Azure SignalR Service
3+
description: This article provides information about client negotiation in Azure SignalR Service.
44
author: JialinXin
55
ms.author: jixin
66
ms.service: signalr
@@ -10,104 +10,104 @@ ms.date: 12/08/2023
1010

1111
# Client negotiation
1212

13-
The first request between client and server is the negotiation request. When use self-host SignalR, the request is used to establish a connection between the client and the server. And when use Azure SignalR service, clients connect to the service instead of the application server. This article shares the concept about negotiation protocols and ways to customize negotiation endpoint.
13+
The first request between a client and a server is the negotiation request. When you use self-hosted SignalR, you use the request to establish a connection between the client and the server. And when you use Azure SignalR Service, clients connect to the service instead of the application server. This article shares concepts about negotiation protocols and ways to customize a negotiation endpoint.
1414

1515
## What is negotiation?
1616

1717
The response to the `POST [endpoint-base]/negotiate` request contains one of three types of responses:
1818

19-
* A response that contains the `connectionId`, which is used to identify the connection on the server and the list of the transports supported by the server.
19+
* A response that contains `connectionId`, which identifies the connection on the server and the list of transports that the server supports:
2020

21-
```json
22-
{
23-
"connectionId":"807809a5-31bf-470d-9e23-afaee35d8a0d",
24-
"negotiateVersion":0,
25-
"availableTransports":[
26-
{
27-
"transport": "WebSockets",
28-
"transferFormats": [ "Text", "Binary" ]
29-
},
30-
{
31-
"transport": "ServerSentEvents",
32-
"transferFormats": [ "Text" ]
33-
},
34-
{
35-
"transport": "LongPolling",
36-
"transferFormats": [ "Text", "Binary" ]
37-
}
38-
]
39-
}
40-
```
21+
```json
22+
{
23+
"connectionId":"807809a5-31bf-470d-9e23-afaee35d8a0d",
24+
"negotiateVersion":0,
25+
"availableTransports":[
26+
{
27+
"transport": "WebSockets",
28+
"transferFormats": [ "Text", "Binary" ]
29+
},
30+
{
31+
"transport": "ServerSentEvents",
32+
"transferFormats": [ "Text" ]
33+
},
34+
{
35+
"transport": "LongPolling",
36+
"transferFormats": [ "Text", "Binary" ]
37+
}
38+
]
39+
}
40+
```
4141

42-
The payload returned from this endpoint provides the following data:
42+
The payload that this endpoint returns provides the following data:
4343

44-
* The `connectionId` is **required** by the Long Polling and Server-Sent Events transports (in order to correlate sends and receives).
45-
* The `negotiateVersion` is the negotiation protocol version being used between the server and client.
46-
* The `availableTransports` list describes the transports the server supports. For each transport, the name of the transport (`transport`) is listed, as is a list of "transfer formats" supported by the transport (`transferFormats`)
44+
* The `connectionId` value is required by the `LongPolling` and `ServerSentEvents` transports to correlate sending and receiving.
45+
* The `negotiateVersion` value is the negotiation protocol version that you use between the server and the client.
46+
* The `availableTransports` list describes the transports that the server supports. For each transport, the payload lists the name of the transport (`transport`) and a list of transfer formats that the transport supports (`transferFormats`).
4747

48-
> [!NOTE]
49-
> Now Azure SignalR service supports negotiate `Version 0` only. And client with the `negotiateVersion` greater than zero will get a response with `negotiateVersion=0` by design. Please check [TransportProtocols](https://github.com/dotnet/aspnetcore/blob/main/src/SignalR/docs/specs/TransportProtocols.md) for protocol details.
48+
> [!NOTE]
49+
> Azure SignalR Service supports only `Version 0` for the negotiation protocol. A client that has a `negotiateVersion` value greater than zero will get a response with `negotiateVersion=0` by design. For protocol details, see [Transport Protocols](https://github.com/dotnet/aspnetcore/blob/main/src/SignalR/docs/specs/TransportProtocols.md).
5050
51-
* A redirect response tells the client the URL and optionally access token to use as a result.
51+
* A redirect response that tells the client which URL and (optionally) access token to use as a result:
5252

53-
```json
54-
{
55-
"url": "https://<Server endpoint>/<Hub name>",
56-
"accessToken": "<accessToken>"
57-
}
58-
```
53+
```json
54+
{
55+
"url": "https://<Server endpoint>/<Hub name>",
56+
"accessToken": "<accessToken>"
57+
}
58+
```
5959

60-
The payload returned from this endpoint provides the following data:
61-
62-
* The `url` is the URL the client should connect to.
63-
* The `accessToken` is an optional bearer token for accessing the specified url.
60+
The payload that this endpoint returns provides the following data:
6461

65-
* A response that contains an `error` that should stop the connection attempt.
62+
* The `url` value is the URL that the client should connect to.
63+
* The `accessToken` value is an optional bearer token for accessing the specified URL.
6664

67-
```json
68-
{
69-
"error": "This connection is not allowed."
70-
}
71-
```
65+
* A response that contains an `error` entry that should stop the connection attempt:
7266

73-
The payload returned from this endpoint provides the following data:
67+
```json
68+
{
69+
"error": "This connection is not allowed."
70+
}
71+
```
7472

75-
* The `error` that gives details about why the negotiation failed.
73+
The payload that this endpoint returns provides the following data:
7674

77-
When you use the Azure SignalR service, clients connect to the service instead of the application server. There are three steps to establish persistent connections between the client and the SignalR Service.
75+
* The `error` string gives details about why the negotiation failed.
7876

79-
1. A client sends a negotiate request to the application server.
77+
When you use Azure SignalR Service, clients connect to the service instead of the app server. There are three steps to establish persistent connections between the client and Azure SignalR Service:
8078

81-
1. The application server uses Azure SignalR Service SDK to return a redirect response containing the Azure SignalR service URL and access token.
79+
1. A client sends a negotiation request to the app server.
8280

83-
For ASP.NET Core SignalR, a typical redirect response looks like:
81+
1. The app server uses the Azure SignalR Service SDK to return a redirect response that contains the Azure SignalR Service URL and access token.
8482

85-
```cs
86-
{
87-
"url":"https://<SignalR name>.service.signalr.net/client/?hub=<Hub name>&...",
88-
"accessToken":"<accessToken>"
89-
}
90-
```
83+
For ASP.NET Core SignalR, a typical redirect response looks like this example:
9184

92-
1. After the client receives the redirect response, it uses the URL and access token to connect to SignalR Service, then service routes client to app server.
85+
```cs
86+
{
87+
"url":"https://<SignalR name>.service.signalr.net/client/?hub=<Hub name>&...",
88+
"accessToken":"<accessToken>"
89+
}
90+
```
91+
92+
1. After the client receives the redirect response, it uses the URL and access token to connect to SignalR Service. The service then routes the client to the app server.
9393

9494
> [!IMPORTANT]
95-
> In self-host SignalR, some user would choose to skip client negotiation when clients only support WebSocket and save the roundtrip for negotiation. However, when working with Azure SignalR service, clients should always ask a trusted server or a trusted authntication center to build the access token. So __DO NOT__ set `SkipNegotiation` to `true` in client side. `SkipNegotiation` means clients need to build the accessToken themselves. This brings security risks that client could do anything to the service endpoint.
95+
> In self-hosted SignalR, some users might choose to skip client negotiation when clients support only WebSocket and save the round trip for negotiation. However, when you're working with Azure SignalR Service, clients should always ask a trusted server or a trusted authentication center to build the access token. So _don't_ set `SkipNegotiation` to `true` on the client side. `SkipNegotiation` means clients need to build the access token themselves. This setting brings a security risk that the client could do anything to the service endpoint.
9696
97-
## What can be done during negotiation?
97+
## What can you do during negotiation?
9898

9999
### Custom settings for client connections
100100

101-
Customer can gate the client connection to customize settings for security or business needs. For example:
101+
You can gate the client connection to customize settings for security or business needs. For example:
102102

103-
* Use a short `AccessTokenLifetime` for security
104-
* Only pass necessary info of client claims
105-
* Add custom claims for business needs
103+
* Use a short `AccessTokenLifetime` value for security.
104+
* Pass only necessary information from client claims.
105+
* Add custom claims for business needs.
106106

107107
```cs
108108
services.AddSignalR().AddAzureSignalR(options =>
109109
{
110-
// Only pass necessary info in negotiation step
110+
// Pass only necessary information in the negotiation step
111111
options.ClaimsProvider = context => new[]
112112
{
113113
new Claim(ClaimTypes.NameIdentifier, context.Request.Query["username"]),
@@ -119,7 +119,9 @@ services.AddSignalR().AddAzureSignalR(options =>
119119

120120
### Server stickiness
121121

122-
When you have multiple app servers, by default there's no guarantee that two servers (the one who does negotiation and the one who gets the hub invocation) are the same one. In some cases, customers may want to have client state information maintained locally on the app server. For example, when using server-side Blazor, UI state is maintained at server side so you want all client requests go to the same server including the SignalR connection. Then you would need to enable server sticky mode to `Required` during negotiation.
122+
When you have multiple app servers, there's no guarantee (by default) that the server that does negotiation and the server that gets the hub invocation are the same. In some cases, you might want to have client state information maintained locally on the app server.
123+
124+
For example, when you're using server-side Blazor, the UI state is maintained at the server side. So you want all client requests to go to the same server, including the SignalR connection. Then you need to enable server sticky mode to `Required` during negotiation:
123125

124126
```cs
125127
services.AddSignalR().AddAzureSignalR(options => {
@@ -129,15 +131,15 @@ services.AddSignalR().AddAzureSignalR(options => {
129131

130132
### Custom routing in multiple endpoints
131133

132-
Another case customer would customize negotiation is in multiple endpoints cases. Since app server provides the service URL as the negotiation response, app server can determine which endpoint to return clients for load balancing and communication efficiency, that is let client connect to the nearest service endpoint to save traffic cost.
134+
Another way that you can customize negotiation is in multiple endpoints. Because the app server provides the service URL as the negotiation response, the app server can determine which endpoint to return to clients for load balancing and communication efficiency. That is, you can let the client connect to the nearest service endpoint to save traffic costs.
133135

134136
```cs
135-
// Sample of custom router
137+
// Sample of a custom router
136138
private class CustomRouter : EndpointRouterDecorator
137139
{
138140
public override ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable<ServiceEndpoint> endpoints)
139141
{
140-
// Override the negotiate behavior to get the endpoint from query string
142+
// Override the negotiation behavior to get the endpoint from the query string
141143
var endpointName = context.Request.Query["endpoint"];
142144
if (endpointName.Count == 0)
143145
{
@@ -147,15 +149,16 @@ private class CustomRouter : EndpointRouterDecorator
147149
return null;
148150
}
149151

150-
return endpoints.FirstOrDefault(s => s.Name == endpointName && s.Online) // Get the endpoint with name matching the incoming request
151-
?? base.GetNegotiateEndpoint(context, endpoints); // Or fallback to the default behavior to randomly select one from primary endpoints, or fallback to secondary when no primary ones are online
152+
return endpoints.FirstOrDefault(s => s.Name == endpointName && s.Online) // Get the endpoint with the name that matches the incoming request
153+
?? base.GetNegotiateEndpoint(context, endpoints); // Fall back to the default behavior to randomly select one from primary endpoints, or fall back to secondary when no primary ones are online
152154
}
153155
}
154156
```
155157

156-
Besides, also register the router to DI using:
158+
Also register the router to dependency injection:
159+
157160
```cs
158-
// Sample of configure multiple endpoints and DI CustomRouter.
161+
// Sample of configuring multiple endpoints and dependency injection
159162
services.AddSingleton(typeof(IEndpointRouter), typeof(CustomRouter));
160163
services.AddSignalR().AddAzureSignalR(
161164
options =>
@@ -170,34 +173,34 @@ services.AddSignalR().AddAzureSignalR(
170173

171174
```
172175

173-
## How to add a client negotiation endpoint in `Serverless` mode?
176+
## How can you add a client negotiation endpoint in serverless mode?
174177

175-
Under `Serverless` mode, there's is no server accepts SignalR clients. To protect your connection string, you need to redirect SignalR clients from the negotiation endpoint to Azure SignalR Service instead of giving your connection string to all the SignalR clients.
178+
In serverless (`Serverless`) mode, no server accepts SignalR clients. To help protect your connection string, you need to redirect SignalR clients from the negotiation endpoint to Azure SignalR Service instead of giving your connection string to all the SignalR clients.
176179

177-
The best practice is to host a negotiation endpoint and then you can use SignalR clients to this endpoint and fetch service url and access token.
180+
The best practice is to host a negotiation endpoint. Then you can use SignalR clients to this endpoint and fetch the service URL and access token.
178181

179-
### Azure SignalR service Management SDK
182+
### Azure SignalR Service Management SDK
180183

181-
Negotiation can be approached by working with [Management SDK](https://github.com/Azure/azure-signalr/blob/dev/docs/management-sdk-guide.md).
184+
You can approach negotiation by working with the [Management SDK](https://github.com/Azure/azure-signalr/blob/dev/docs/management-sdk-guide.md).
182185

183-
You can use the instance of `ServiceHubContext` to generate the endpoint url and corresponding access token for SignalR clients to connect to your Azure SignalR Service.
186+
You can use the instance of `ServiceHubContext` to generate the endpoint URL and corresponding access token for SignalR clients to connect to Azure SignalR Service:
184187

185188
```cs
186189
var negotiationResponse = await serviceHubContext.NegotiateAsync(new (){ UserId = "<Your User Id>" });
187190
```
188191

189-
Suppose your hub endpoint is `http://<Your Host Name>/<Your Hub Name>`, then your negotiation endpoint is `http://<Your Host Name>/<Your Hub Name>/negotiate`. Once you host the negotiation endpoint, you can use the SignalR clients to connect to your hub like this:
192+
Suppose your hub endpoint is `http://<Your Host Name>/<Your Hub Name>`. Then your negotiation endpoint is `http://<Your Host Name>/<Your Hub Name>/negotiate`. After you host the negotiation endpoint, you can use the SignalR clients to connect to your hub:
190193

191194
```cs
192195
var connection = new HubConnectionBuilder().WithUrl("http://<Your Host Name>/<Your Hub Name>").Build();
193196
await connection.StartAsync();
194197
```
195198

196-
A full sample on how to use Management SDK to redirect SignalR clients to Azure SignalR Service can be found [here](https://github.com/aspnet/AzureSignalR-samples/tree/main/samples/Management).
199+
You can find a full sample on how to use the Management SDK to redirect SignalR clients to Azure SignalR Service on [GitHub](https://github.com/aspnet/AzureSignalR-samples/tree/main/samples/Management).
197200

198-
### Azure SignalR service functions extension
201+
### Azure SignalR Service function extension
199202

200-
When you use Azure Function App, typically, you can work with the Function Extension. Here's a sample using `SignalRConnectionInfo` to help you build the negotiation response.
203+
When you use an Azure function app, you can work with the function extension. Here's a sample of using `SignalRConnectionInfo` to help you build the negotiation response:
201204

202205
```cs
203206
[FunctionName("negotiate")]
@@ -211,12 +214,11 @@ public SignalRConnectionInfo Negotiate([HttpTrigger(AuthorizationLevel.Anonymous
211214
}
212215
```
213216

214-
Then your clients can request to this function endpoint `https://<Your Function App Name>.azurewebsites.net/api/negotiate` to get the service url and accessToken. A full sample can be found [here](https://github.com/aspnet/AzureSignalR-samples/tree/main/samples/BidirectionChat).
217+
Then your clients can request the function endpoint `https://<Your Function App Name>.azurewebsites.net/api/negotiate` to get the service URL and access token. You can find a full sample on [GitHub](https://github.com/aspnet/AzureSignalR-samples/tree/main/samples/BidirectionChat).
215218

216219
## Next steps
217220

218-
See the following articles to learn more about how to use Default and Serverless modes.
219-
220-
- [Azure SignalR Service internals](signalr-concept-internals.md)
221+
To learn more about how to use default and serverless modes, see the following articles:
221222

222-
- [Azure Functions development and configuration with Azure SignalR Service](signalr-concept-serverless-development-config.md)
223+
* [Azure SignalR Service internals](signalr-concept-internals.md)
224+
* [Azure Functions development and configuration with Azure SignalR Service](signalr-concept-serverless-development-config.md)

0 commit comments

Comments
 (0)