// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hosting;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.SignalR.Json;
namespace Microsoft.AspNet.SignalR.Hubs
{
///
/// Handles all communication over the hubs persistent connection.
///
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This dispatcher makes use of many interfaces.")]
public class HubDispatcher : PersistentConnection
{
private const string HubsSuffix = "/hubs";
private readonly List _hubs = new List();
private readonly bool _enableJavaScriptProxies;
private readonly bool _enableDetailedErrors;
private IJavaScriptProxyGenerator _proxyGenerator;
private IHubManager _manager;
private IHubRequestParser _requestParser;
private IParameterResolver _binder;
private IHubPipelineInvoker _pipelineInvoker;
private IPerformanceCounterManager _counters;
private bool _isDebuggingEnabled;
private static readonly MethodInfo _continueWithMethod = typeof(HubDispatcher).GetMethod("ContinueWith", BindingFlags.NonPublic | BindingFlags.Static);
///
/// Initializes an instance of the class.
///
/// Configuration settings determining whether to enable JS proxies and provide clients with detailed hub errors.
public HubDispatcher(HubConfiguration configuration)
{
if (configuration == null)
{
throw new ArgumentNullException("configuration");
}
_enableJavaScriptProxies = configuration.EnableJavaScriptProxies;
_enableDetailedErrors = configuration.EnableDetailedErrors;
}
protected override TraceSource Trace
{
get
{
return TraceManager["SignalR.HubDispatcher"];
}
}
internal override string GroupPrefix
{
get
{
return PrefixHelper.HubGroupPrefix;
}
}
public override void Initialize(IDependencyResolver resolver, HostContext context)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver");
}
if (context == null)
{
throw new ArgumentNullException("context");
}
_proxyGenerator = _enableJavaScriptProxies ? resolver.Resolve()
: new EmptyJavaScriptProxyGenerator();
_manager = resolver.Resolve();
_binder = resolver.Resolve();
_requestParser = resolver.Resolve();
_pipelineInvoker = resolver.Resolve();
_counters = resolver.Resolve();
base.Initialize(resolver, context);
}
protected override bool AuthorizeRequest(IRequest request)
{
// Populate _hubs
string data = request.QueryStringOrForm("connectionData");
if (!String.IsNullOrEmpty(data))
{
var clientHubInfo = JsonSerializer.Parse>(data);
// If there's any hubs then perform the auth check
if (clientHubInfo != null && clientHubInfo.Any())
{
var hubCache = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var hubInfo in clientHubInfo)
{
if (hubCache.ContainsKey(hubInfo.Name))
{
throw new InvalidOperationException(Resources.Error_DuplicateHubs);
}
// Try to find the associated hub type
HubDescriptor hubDescriptor = _manager.EnsureHub(hubInfo.Name,
_counters.ErrorsHubResolutionTotal,
_counters.ErrorsHubResolutionPerSec,
_counters.ErrorsAllTotal,
_counters.ErrorsAllPerSec);
if (_pipelineInvoker.AuthorizeConnect(hubDescriptor, request))
{
// Add this to the list of hub descriptors this connection is interested in
hubCache.Add(hubDescriptor.Name, hubDescriptor);
}
}
_hubs.AddRange(hubCache.Values);
// If we have any hubs in the list then we're authorized
return _hubs.Count > 0;
}
}
return base.AuthorizeRequest(request);
}
///
/// Processes the hub's incoming method calls.
///
protected override Task OnReceived(IRequest request, string connectionId, string data)
{
HubRequest hubRequest = _requestParser.Parse(data);
// Create the hub
HubDescriptor descriptor = _manager.EnsureHub(hubRequest.Hub,
_counters.ErrorsHubInvocationTotal,
_counters.ErrorsHubInvocationPerSec,
_counters.ErrorsAllTotal,
_counters.ErrorsAllPerSec);
IJsonValue[] parameterValues = hubRequest.ParameterValues;
// Resolve the method
MethodDescriptor methodDescriptor = _manager.GetHubMethod(descriptor.Name, hubRequest.Method, parameterValues);
if (methodDescriptor == null)
{
_counters.ErrorsHubInvocationTotal.Increment();
_counters.ErrorsHubInvocationPerSec.Increment();
// Empty (noop) method descriptor
// Use: Forces the hub pipeline module to throw an error. This error is encapsulated in the HubDispatcher.
// Encapsulating it in the HubDispatcher prevents the error from bubbling up to the transport level.
// Specifically this allows us to return a faulted task (call .fail on client) and to not cause the
// transport to unintentionally fail.
methodDescriptor = new NullMethodDescriptor(hubRequest.Method);
}
// Resolving the actual state object
var tracker = new StateChangeTracker(hubRequest.State);
var hub = CreateHub(request, descriptor, connectionId, tracker, throwIfFailedToCreate: true);
return InvokeHubPipeline(hub, parameterValues, methodDescriptor, hubRequest, tracker)
.ContinueWith(task => hub.Dispose(), TaskContinuationOptions.ExecuteSynchronously);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are flown to the caller.")]
private Task InvokeHubPipeline(IHub hub,
IJsonValue[] parameterValues,
MethodDescriptor methodDescriptor,
HubRequest hubRequest,
StateChangeTracker tracker)
{
Task