PageRenderTime 42ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/SignalR/Hubs/HubDispatcher.cs

https://github.com/kpmrafeeq/SignalR
C# | 312 lines | 234 code | 53 blank | 25 comment | 23 complexity | 8e30ad74af01c4b7b33f62983cf1399d MD5 | raw file
Possible License(s): MIT
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using System.Reflection;
  8. using System.Threading.Tasks;
  9. namespace SignalR.Hubs
  10. {
  11. /// <summary>
  12. /// Handles all communication over the hubs persistent connection.
  13. /// </summary>
  14. public class HubDispatcher : PersistentConnection
  15. {
  16. private IJavaScriptProxyGenerator _proxyGenerator;
  17. private IHubManager _manager;
  18. private IHubRequestParser _requestParser;
  19. private IParameterResolver _binder;
  20. private readonly List<HubDescriptor> _hubs = new List<HubDescriptor>();
  21. private bool _isDebuggingEnabled;
  22. private readonly string _url;
  23. /// <summary>
  24. /// Initializes an instance of the <see cref="HubDispatcher"/> class.
  25. /// </summary>
  26. /// <param name="url">The base url of the connection url.</param>
  27. public HubDispatcher(string url)
  28. {
  29. _url = url;
  30. }
  31. protected override TraceSource Trace
  32. {
  33. get
  34. {
  35. return _trace["SignalR.HubDispatcher"];
  36. }
  37. }
  38. public override void Initialize(IDependencyResolver resolver)
  39. {
  40. _proxyGenerator = resolver.Resolve<IJavaScriptProxyGenerator>();
  41. _manager = resolver.Resolve<IHubManager>();
  42. _binder = resolver.Resolve<IParameterResolver>();
  43. _requestParser = resolver.Resolve<IHubRequestParser>();
  44. base.Initialize(resolver);
  45. }
  46. /// <summary>
  47. /// Processes the hub's incoming method calls.
  48. /// </summary>
  49. protected override Task OnReceivedAsync(IRequest request, string connectionId, string data)
  50. {
  51. HubRequest hubRequest = _requestParser.Parse(data);
  52. // Create the hub
  53. HubDescriptor descriptor = _manager.EnsureHub(hubRequest.Hub);
  54. IJsonValue[] parameterValues = hubRequest.ParameterValues;
  55. // Resolve the method
  56. MethodDescriptor methodDescriptor = _manager.GetHubMethod(descriptor.Name, hubRequest.Method, parameterValues);
  57. if (methodDescriptor == null)
  58. {
  59. throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, "'{0}' method could not be resolved.", hubRequest.Method));
  60. }
  61. // Resolving the actual state object
  62. var state = new TrackingDictionary(hubRequest.State);
  63. var hub = CreateHub(request, descriptor, connectionId, state, throwIfFailedToCreate: true);
  64. Task resultTask;
  65. try
  66. {
  67. // Invoke the method
  68. object result = methodDescriptor.Invoker.Invoke(hub, _binder.ResolveMethodParameters(methodDescriptor, parameterValues));
  69. Type returnType = result != null ? result.GetType() : methodDescriptor.ReturnType;
  70. if (typeof(Task).IsAssignableFrom(returnType))
  71. {
  72. var task = (Task)result;
  73. if (!returnType.IsGenericType)
  74. {
  75. return task.ContinueWith(t => ProcessResponse(state, null, hubRequest, t.Exception))
  76. .FastUnwrap();
  77. }
  78. else
  79. {
  80. // Get the <T> in Task<T>
  81. Type resultType = returnType.GetGenericArguments().Single();
  82. // Get the correct ContinueWith overload
  83. var continueWith = TaskAsyncHelper.GetContinueWith(task.GetType());
  84. var taskParameter = Expression.Parameter(continueWith.Type);
  85. var processResultMethod = typeof(HubDispatcher).GetMethod("ProcessTaskResult", BindingFlags.NonPublic | BindingFlags.Instance).MakeGenericMethod(resultType);
  86. var body = Expression.Call(Expression.Constant(this),
  87. processResultMethod,
  88. Expression.Constant(state),
  89. Expression.Constant(hubRequest),
  90. taskParameter);
  91. var lambda = Expression.Lambda(body, taskParameter);
  92. var call = Expression.Call(Expression.Constant(task, continueWith.Type), continueWith.Method, lambda);
  93. Func<Task<Task>> continueWithMethod = Expression.Lambda<Func<Task<Task>>>(call).Compile();
  94. return continueWithMethod.Invoke().FastUnwrap();
  95. }
  96. }
  97. else
  98. {
  99. resultTask = ProcessResponse(state, result, hubRequest, null);
  100. }
  101. }
  102. catch (TargetInvocationException e)
  103. {
  104. resultTask = ProcessResponse(state, null, hubRequest, e);
  105. }
  106. return resultTask.Then(() => base.OnReceivedAsync(request, connectionId, data))
  107. .Catch();
  108. }
  109. public override Task ProcessRequestAsync(HostContext context)
  110. {
  111. // Generate the proxy
  112. if (context.Request.Url.LocalPath.EndsWith("/hubs", StringComparison.OrdinalIgnoreCase))
  113. {
  114. context.Response.ContentType = "application/x-javascript";
  115. return context.Response.EndAsync(_proxyGenerator.GenerateProxy(_url));
  116. }
  117. _isDebuggingEnabled = context.IsDebuggingEnabled();
  118. return base.ProcessRequestAsync(context);
  119. }
  120. protected override Task OnConnectedAsync(IRequest request, string connectionId)
  121. {
  122. return ExecuteHubEventAsync<IConnected>(request, connectionId, hub => hub.Connect());
  123. }
  124. protected override Task OnReconnectedAsync(IRequest request, IEnumerable<string> groups, string connectionId)
  125. {
  126. return ExecuteHubEventAsync<IConnected>(request, connectionId, hub => hub.Reconnect(groups));
  127. }
  128. protected override Task OnDisconnectAsync(string connectionId)
  129. {
  130. return ExecuteHubEventAsync<IDisconnect>(request: null, connectionId: connectionId, action: hub => hub.Disconnect());
  131. }
  132. private Task ExecuteHubEventAsync<T>(IRequest request, string connectionId, Func<T, Task> action) where T : class
  133. {
  134. var operations = GetHubsImplementingInterface(typeof(T))
  135. .Select(hub => CreateHub(request, hub, connectionId))
  136. .OfType<T>()
  137. .Select(instance => action(instance).Catch() ?? TaskAsyncHelper.Empty)
  138. .ToList();
  139. if (operations.Count == 0)
  140. {
  141. return TaskAsyncHelper.Empty;
  142. }
  143. var tcs = new TaskCompletionSource<object>();
  144. Task.Factory.ContinueWhenAll(operations.ToArray(), tasks =>
  145. {
  146. var faulted = tasks.FirstOrDefault(t => t.IsFaulted);
  147. if (faulted != null)
  148. {
  149. tcs.SetException(faulted.Exception);
  150. }
  151. else if (tasks.Any(t => t.IsCanceled))
  152. {
  153. tcs.SetCanceled();
  154. }
  155. else
  156. {
  157. tcs.SetResult(null);
  158. }
  159. });
  160. return tcs.Task;
  161. }
  162. private IHub CreateHub(IRequest request, HubDescriptor descriptor, string connectionId, TrackingDictionary state = null, bool throwIfFailedToCreate = false)
  163. {
  164. try
  165. {
  166. var hub = _manager.ResolveHub(descriptor.Name);
  167. if (hub != null)
  168. {
  169. state = state ?? new TrackingDictionary();
  170. hub.Context = new HubCallerContext(request, connectionId);
  171. hub.Caller = new StatefulSignalProxy(Connection, connectionId, descriptor.Name, state);
  172. hub.Clients = new ClientProxy(Connection, descriptor.Name);
  173. hub.Groups = new GroupManager(Connection, descriptor.Name);
  174. }
  175. return hub;
  176. }
  177. catch (Exception ex)
  178. {
  179. Trace.TraceInformation("Error creating hub {0}. " + ex.Message, descriptor.Name);
  180. if (throwIfFailedToCreate)
  181. {
  182. throw;
  183. }
  184. return null;
  185. }
  186. }
  187. private IEnumerable<HubDescriptor> GetHubsImplementingInterface(Type interfaceType)
  188. {
  189. // Get hubs that implement the specified interface
  190. return _hubs.Where(hub => interfaceType.IsAssignableFrom(hub.Type));
  191. }
  192. private Task ProcessTaskResult<T>(TrackingDictionary state, HubRequest request, Task<T> task)
  193. {
  194. if (task.IsFaulted)
  195. {
  196. return ProcessResponse(state, null, request, task.Exception);
  197. }
  198. return ProcessResponse(state, task.Result, request, null);
  199. }
  200. private Task ProcessResponse(TrackingDictionary state, object result, HubRequest request, Exception error)
  201. {
  202. var exception = error.Unwrap();
  203. string stackTrace = (exception != null && _isDebuggingEnabled) ? exception.StackTrace : null;
  204. string errorMessage = exception != null ? exception.Message : null;
  205. var hubResult = new HubResponse
  206. {
  207. State = state.GetChanges(),
  208. Result = result,
  209. Id = request.Id,
  210. Error = errorMessage,
  211. StackTrace = stackTrace
  212. };
  213. return _transport.Send(hubResult);
  214. }
  215. protected override Connection CreateConnection(string connectionId, IEnumerable<string> groups, IRequest request)
  216. {
  217. string data = request.QueryStringOrForm("connectionData");
  218. if (String.IsNullOrEmpty(data))
  219. {
  220. return base.CreateConnection(connectionId, groups, request);
  221. }
  222. var clientHubInfo = _jsonSerializer.Parse<IEnumerable<ClientHubInfo>>(data);
  223. if (clientHubInfo == null || !clientHubInfo.Any())
  224. {
  225. return base.CreateConnection(connectionId, groups, request);
  226. }
  227. IEnumerable<string> hubSignals = clientHubInfo.SelectMany(info => GetSignals(info, connectionId))
  228. .Concat(GetDefaultSignals(connectionId));
  229. return new Connection(_messageBus, _jsonSerializer, null, connectionId, hubSignals, groups, _trace);
  230. }
  231. private IEnumerable<string> GetSignals(ClientHubInfo hubInfo, string connectionId)
  232. {
  233. // Try to find the associated hub type
  234. HubDescriptor hubDescriptor = _manager.EnsureHub(hubInfo.Name);
  235. // Add this to the list of hub desciptors this connection is interested in
  236. _hubs.Add(hubDescriptor);
  237. // Update the name (Issue #344)
  238. hubInfo.Name = hubDescriptor.Name;
  239. // Create the signals for hubs
  240. // 1. The hub name e.g. MyHub
  241. // 2. The connection id for this hub e.g. MyHub.{guid}
  242. // 3. The command signal for this connection
  243. var clientSignals = new[] {
  244. hubInfo.Name,
  245. hubInfo.CreateQualifiedName(connectionId),
  246. SignalCommand.AddCommandSuffix(hubInfo.CreateQualifiedName(connectionId))
  247. };
  248. return clientSignals;
  249. }
  250. private class ClientHubInfo
  251. {
  252. public string Name { get; set; }
  253. public string CreateQualifiedName(string unqualifiedName)
  254. {
  255. return Name + "." + unqualifiedName;
  256. }
  257. }
  258. }
  259. }