Skip to content

Commit

Permalink
Merge pull request #406 from vicancy/r107
Browse files Browse the repository at this point in the history
Releasing 1.0.7
  • Loading branch information
vicancy authored Feb 25, 2019
2 parents 8f99de4 + 6405f88 commit 57dc13b
Show file tree
Hide file tree
Showing 85 changed files with 2,216 additions and 610 deletions.
7 changes: 7 additions & 0 deletions AzureSignalR.sln
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatSample", "samples\ChatS
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatSample.CSharpClient", "samples\ChatSample\ChatSample.CSharpClient\ChatSample.CSharpClient.csproj", "{C51AA61E-5210-4545-B49E-61255C136B83}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.SignalR.TestsCommon", "test\Microsoft.Azure.SignalR.TestsCommon\Microsoft.Azure.SignalR.TestsCommon.csproj", "{05DA20E1-E2CC-4DD6-A5B1-B3A1DEA3E9DB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -124,6 +126,10 @@ Global
{C51AA61E-5210-4545-B49E-61255C136B83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C51AA61E-5210-4545-B49E-61255C136B83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C51AA61E-5210-4545-B49E-61255C136B83}.Release|Any CPU.Build.0 = Release|Any CPU
{05DA20E1-E2CC-4DD6-A5B1-B3A1DEA3E9DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05DA20E1-E2CC-4DD6-A5B1-B3A1DEA3E9DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05DA20E1-E2CC-4DD6-A5B1-B3A1DEA3E9DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05DA20E1-E2CC-4DD6-A5B1-B3A1DEA3E9DB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -146,6 +152,7 @@ Global
{C965ED06-6A17-4329-B3C6-811830F4F4ED} = {C4BC9889-B49F-41B6-806B-F84941B2549B}
{3F9B3FD8-A54F-4AB4-B9D4-D4D55686A0E9} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
{C51AA61E-5210-4545-B49E-61255C136B83} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
{05DA20E1-E2CC-4DD6-A5B1-B3A1DEA3E9DB} = {2429FBD8-1FCE-4C42-AA28-DF32F7249E77}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The following documents describe more details about Azure SignalR Service.
- [REST API in Azure SignalR Service](./docs/rest-api.md)
- [Internals of the Azure SignalR Service](./docs/internal.md)
- [FAQ](./docs/faq.md)
- [Troubleshooting Guide](./docs/tsg.md)

Contributions are highly welcome. Keep reading if you want to contribute to our repository.

Expand Down
13 changes: 8 additions & 5 deletions build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<PropertyGroup Label="Package Versions">
<!-- Azure ASP.NET Core SignalR -->
<MessagePackPackageVersion>1.7.3.4</MessagePackPackageVersion>
<MicrosoftAspNetCoreHttpConnectionsClientPackageVersion>1.0.0</MicrosoftAspNetCoreHttpConnectionsClientPackageVersion>
<MicrosoftAspNetCoreSignalRPackageVersion>1.0.0</MicrosoftAspNetCoreSignalRPackageVersion>
<MicrosoftAspNetCoreHttpConnectionsClientPackageVersion>1.1.0</MicrosoftAspNetCoreHttpConnectionsClientPackageVersion>
<MicrosoftAspNetCoreSignalRPackageVersion>1.1.0</MicrosoftAspNetCoreSignalRPackageVersion>
<MicrosoftIdentitiyModelClientsActiveDirectoryPackageVersion>3.19.1</MicrosoftIdentitiyModelClientsActiveDirectoryPackageVersion>
<MicrosoftAzureKeyVaultPackageVersion>2.3.2</MicrosoftAzureKeyVaultPackageVersion>
<SystemBuffersPackageVersion>4.5.0</SystemBuffersPackageVersion>
Expand All @@ -15,10 +15,11 @@
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>2.1.0</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsPrimitivesPackageVersion>2.1.0</MicrosoftExtensionsPrimitivesPackageVersion>

<MicrosoftAspNetCoreSignalRClient>1.1.0</MicrosoftAspNetCoreSignalRClient>

<!-- Azure ASP.NET SignalR -->
<MicrosoftAspNetSignalRPackageVersion>2.4.0</MicrosoftAspNetSignalRPackageVersion>
<MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion>2.1.0</MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion>
<MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion>2.2.0</MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingTraceSourcePackageVersion>2.1.0</MicrosoftExtensionsLoggingTraceSourcePackageVersion>
<SystemThreadingChannelsPackageVersion>4.5.0</SystemThreadingChannelsPackageVersion>

Expand All @@ -39,12 +40,14 @@
<OwinPackageVersion>1.0.0</OwinPackageVersion>

<!--Testing -->
<MicrosoftAspNetCoreSignalRProtocolsMessagePackPackageVersion>1.0.0</MicrosoftAspNetCoreSignalRProtocolsMessagePackPackageVersion>
<MicrosoftAspNetCoreSignalRProtocolsMessagePackPackageVersion>1.1.0</MicrosoftAspNetCoreSignalRProtocolsMessagePackPackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion>
<XunitPackageVersion>2.4.0</XunitPackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.4.0</XunitRunnerVisualStudioPackageVersion>
<MicrosoftOwinTestingPackageVersion>4.0.0</MicrosoftOwinTestingPackageVersion>
<MicrosoftAspNetCoreTestingVersion>2.1.0</MicrosoftAspNetCoreTestingVersion>
<E2eTestUserSecretId>E2eTestUserSecret</E2eTestUserSecretId>
</PropertyGroup>
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
</Project>
Binary file added docs/images/chrome_network.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/fiddler_view_network.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/throw_clr_exceptions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/tls_throws.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/uncheck_just_my_code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
159 changes: 159 additions & 0 deletions docs/tsg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Troubleshooting Guide

This guidence is to provide useful troubleshooting guide based on the common issues customers encountered and resolved in the past years.

## Access token too long

### Possible errors:

1. Client side `ERR_CONNECTION_`
2. 414 URI Too Long
3. 413 Payload Too Large

### Root cause:
For HTTP/2, the max length for a single header is **4K**, so if you are using browser to access Azure service, you will encounter this limitation with `ERR_CONNECTION_` error.

For HTTP/1.1, or c# clients, the max URI length is **12K**, max header length is **16K**.

With SDK version **1.0.6** or higher, `/negotiate` will throw `413 Payload Too Large` when the generated access token is larger than **4K**.

### Solution:
By default, claims from `context.User.Claims` are included when generating JWT access token to **ASRS**(**A**zure **S**ignal**R** **S**ervice), so that the claims are preserved and can be passed from **ASRS** to the `Hub` when the client connects to the `Hub`.

In some cases, `context.User.Claims` are leveraged to store lots of information for app server, most of which are not used by `Hub`s but by other components.

The generated access token are passed through network, and for websocket/SSE connections, access tokens are passed through query strings. So as the best practice, we suggest only passing **neccessory** claims from client through **ASRS** to your app server.

There is a `ClaimsProvider` for you to customize the claims passing to **ASRS** inside the access token.

```cs
services.AddSignalR()
.AddAzureSignalR(options =>
{
// pick up neccessory claims
options.ClaimsProvider = context => context.User.Claims.Where(...);
});
```

### Tips:
<a name="view_request"></a>
* How to view the outgoing request from client?
1. From browser:

Take chrome for example, **F12** to open the consile window, and switch to **Netork** tab. You might need to refresh the page using **F5** to capture the network from the very beginning.

![Chrome View Network](./images/chrome_network.gif)

2. From C# client:

You can view local web traffics using [Fiddler](https://www.telerik.com/fiddler). WebSocket traffics are supported since Fiddler 4.5.

![Fiddler View Network](./images/fiddler_view_network.png)

## TLS 1.2 required

### Possible errors:

1. ASP.Net "No server available" error [#279](https://github.com/Azure/azure-signalr/issues/279)
2. ASP.Net "The connection is not active, data cannot be sent to the service." error [#324](https://github.com/Azure/azure-signalr/issues/324)
3. "An error occurred while making the HTTP request to https://<API endpoint>. This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. This could also be caused by a mismatch of the security binding between the client and the server."

### Root cause:
Azure Service only support TLS1.2 for security concerns. With .NET framework, it is possible that TLS1.2 is not the default protocol. As a result, the server connections to ASRS can not be successfully established.

### Troubleshooting Guide
1. If this error can be repro-ed locally, uncheck *Just My Code* and throw all CLR exceptions and debug the app server locally to see what exception throws.
* Uncheck *Just My Code*

![Uncheck Just My Code](./images/uncheck_just_my_code.png)
* Throw CLR exceptions

![Throw CLR exceptions](./images/throw_clr_exceptions.png)
* See the exceptions throw when debugging the app server side code:

![Exception throws](./images/tls_throws.png)

2. For ASP.NET ones, you can also add following code to your `Startup.cs` to enable detailed trace and see the errors from the log.
```cs
app.MapAzureSignalR(this.GetType().FullName);
// Make sure this switch is called after MapAzureSignalR
GlobalHost.TraceManager.Switch.Level = SourceLevels.Information;
```

### Solution:

Add following code to your Startup:
```cs
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
```

## Random 404 returned for client requests

For a SignalR persistent connection, it first `/negotiate` to Azure SignalR service and then establish the real connection to Azure SignalR service. Our load balancer must ensure that the `/negotiate` request and the following connect request goes to the similar instance of the Service otherwise 404 occurs. Our load balancer now relies on the *signature* part of the generated `access_token` to keep the session sticky.

### Troubleshooting Guide
1. Following [How to view outgoing requests](#view_request) to get the request from client the the service.
2. Check if there are multiple `access_token` inside the outgoing request. Our load balancer is not able to handle duplicate `access_token` correctly, as described in [#346](https://github.com/Azure/azure-signalr/issues/346).
3. Another 404 can happen when the connect request is handled more than **5** seconds after `/negotiate` is called. Check the timestamp of client request, and open an issue to us if the request to the service has very slow response.

## 403 returned for client requests
### Root cause
Currently the default value of JWT token's lifetime is 1 hour.

For ASP.NET Core SignalR, when it is using WebSocket transport type, it is OK.

For ASP.NET Core SignalR's other transport type, SSE and longpolling, this means by default the connection can at most persist for 1 hour.

For ASP.NET SignalR, the client sends a `/ping` KeepAlive request to the service from time to time, when the `/ping` fails, the client **aborts** the connection and never reconnect. This means, for ASP.NET SignalR, the default token lifetime makes the connection lasts for **at most** 1 hour for all the transport type.

### Workaround

There is a `AccessTokenLifetime` for you to customize the lifetime of the access token.

```cs
services.AddSignalR()
.AddAzureSignalR(options =>
{
// extend the TTL
options.AccessTokenLifetime = TimeSpan.FromDays(1);
});
```

## ⌛️[TODO]Client side connection drop

### Possible errors:
1. Client side error log: "The remote party closed the WebSocket connection without completing the close handshake"
2. Client side error log: "Service timeout. 30.00ms elapsed without receiving a message from service."

### Root cause:
Possibility 1. App server restarts
Possibility 2. **ASRS**(**A**zure **S**ignal**R** **S**ervice) internal error

### Troubleshooting Guide
1. Open app server side log to see if anything abnormal took place
2. Check app server side event log to see if the app server restarted
3. Create an issue to us providing time frame, and email the resource name to us

## ⌛️[TODO]Server side connection drop

### Possible errors:
1. Server side error log: "[Error]Connection "..." to the service was dropped"
2. Server side error: "The remote party closed the WebSocket connection without completing the close handshake"

### Root cause:
Server-service connection is closed by **ASRS**(**A**zure **S**ignal**R** **S**ervice).

### Troubleshooting Guide
1. Open app server side log to see if anything abnormal took place
2. Check app server side event log to see if the app server restarted
3. Create an issue to us providing time frame, and email the resource name to us

## ⌛️[TODO]Heartbeat failed

### Possible errors:
1. Server side log: "Service timeout. 30.00ms elapsed without receiving a message from service."
2. Client side error log: "Service timeout. 30.00ms elapsed without receiving a message from service."

### Troubleshooting Guide
1. Open app server side log to see if anything abnormal took place
2. Create server side dump file to see if the app server is thread starving
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
<connectionStrings>
<add name="Azure:SignalR:ConnectionString" connectionString=""/>
</connectionStrings>
</configuration>
</configuration>
22 changes: 22 additions & 0 deletions specs/ServiceProtocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,25 @@ MessagePack uses different formats to encode values. Refer to the [MessagePack F
- Payloads - A MessagePack Map containing payloads, with string keys and byte array values. The key is the protocol name of the value.

#### Example: TODO

### UserJoinGroup Message
`UserJoinGroup` messages have the following structure.
```
[16, UserId, GroupName]
```
- 16 - Message Type, indicating this is a `UserJoinGroup` message.
- UserId - A `String` encoding unique Id for the user.
- GroupName - A `String` encoding group name, which the user will join.

#### Example: TODO

### UserLeaveGroup Message
`UserLeaveGroup` messages have the following structure.
```
[17, UserId, GroupName]
```
- 17 - Message Type, indicating this is a `UserLeaveGroup` message.
- UserId - A `String` encoding unique Id for the user.
- GroupName - A `String` encoding group name, which the user will leave.

#### Example: TODO
100 changes: 100 additions & 0 deletions src/Microsoft.Azure.SignalR.AspNet/DispatcherHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.AspNet.SignalR.Messaging;
using Microsoft.AspNet.SignalR.Tracing;
using Microsoft.AspNet.SignalR.Transports;
using Microsoft.Azure.SignalR.Protocol;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Owin.Infrastructure;
using Owin;

namespace Microsoft.Azure.SignalR.AspNet
{
internal class DispatcherHelper
{
internal static ServiceHubDispatcher PrepareAndGetDispatcher(IAppBuilder builder, HubConfiguration configuration, ServiceOptions options, string applicationName, ILoggerFactory loggerFactory)
{
// Ensure we have the conversions for MS.Owin so that
// the app builder respects the OwinMiddleware base class
SignatureConversions.AddConversions(builder);

// ServiceEndpointManager needs the logger
var hubs = GetAvailableHubNames(configuration);

var endpoint = new ServiceEndpointManager(options, loggerFactory);
configuration.Resolver.Register(typeof(IServiceEndpointManager), () => endpoint);

// Get the one from DI or new a default one
var router = configuration.Resolver.Resolve<IEndpointRouter>() ?? new DefaultEndpointRouter();

builder.Use<NegotiateMiddleware>(configuration, applicationName, endpoint, router, options, loggerFactory);

builder.RunSignalR(configuration);

// Fetch the trace manager from DI and add logger provider
var traceManager = configuration.Resolver.Resolve<ITraceManager>();
if (traceManager != null)
{
loggerFactory.AddProvider(new TraceManagerLoggerProvider(traceManager));
}

// TODO: Using IOptions looks wierd, thinking of a way removing it
// share the same object all through
var serviceOptions = Options.Create(options);

// For safety, ALWAYS register abstract classes or interfaces
// Some third-party DI frameworks such as Ninject, implicit self-binding concrete types:
// https://github.com/ninject/ninject/wiki/dependency-injection-with-ninject#skipping-the-type-binding-bit--implicit-self-binding-of-concrete-types
configuration.Resolver.Register(typeof(IOptions<ServiceOptions>), () => serviceOptions);

var serviceProtocol = new ServiceProtocol();
configuration.Resolver.Register(typeof(IServiceProtocol), () => serviceProtocol);

// allow override from tests
var scm = configuration.Resolver.Resolve<IServiceConnectionManager>();
if (scm == null)
{
scm = new ServiceConnectionManager(applicationName, hubs);
configuration.Resolver.Register(typeof(IServiceConnectionManager), () => scm);
}

var ccm = configuration.Resolver.Resolve<IClientConnectionManager>();
if (ccm == null)
{
ccm = new ClientConnectionManager(configuration);
configuration.Resolver.Register(typeof(IClientConnectionManager), () => ccm);
}

var atm = new AzureTransportManager(configuration.Resolver);
configuration.Resolver.Register(typeof(ITransportManager), () => atm);

var parser = new SignalRMessageParser(hubs, configuration.Resolver);
configuration.Resolver.Register(typeof(IMessageParser), () => parser);

var smb = new ServiceMessageBus(configuration.Resolver);
configuration.Resolver.Register(typeof(IMessageBus), () => smb);

if (hubs?.Count > 0)
{
return new ServiceHubDispatcher(hubs, serviceProtocol, scm, ccm, endpoint, router, serviceOptions, loggerFactory);
}
else
{
loggerFactory.CreateLogger<DispatcherHelper>().Log(LogLevel.Warning, "No hubs found.");
return null;
}
}

private static IReadOnlyList<string> GetAvailableHubNames(HubConfiguration configuration)
{
var hubManager = configuration.Resolver.Resolve<IHubManager>();
return hubManager?.GetHubs().Select(s => s.Name).ToList();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,14 @@ private async Task WriteMessage(IServiceConnectionContainer connection, AppMessa
}
break;
case LeaveGroupMessage leaveGroupMessage:
await connection.WriteAsync(leaveGroupMessage.GroupName, leaveGroupMessage);
try
{
await connection.WriteAsync(leaveGroupMessage.GroupName, leaveGroupMessage);
}
finally
{
_ackHandler.TriggerAck(appMessage.RawMessage.CommandId);
}
break;
case GroupBroadcastDataMessage groupBroadcastMessage:
await connection.WriteAsync(groupBroadcastMessage.GroupName, groupBroadcastMessage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private Task ProcessNegotiationRequest(IOwinContext owinContext, HostContext con
IServiceEndpointProvider provider;
try
{
provider = _endpointManager.GetEndpointProvider(_router.GetNegotiateEndpoint(_endpointManager.GetPrimaryEndpoints()));
provider = _endpointManager.GetEndpointProvider(_router.GetNegotiateEndpoint(_endpointManager.Endpoints));
}
catch (AzureSignalRNotConnectedException e)
{
Expand Down
Loading

0 comments on commit 57dc13b

Please sign in to comment.