public bool MatchesRpcRoute(RpcRouteCollection routes, string requestUrl, out RpcRoute route) { if (routes == null) { throw new ArgumentNullException(nameof(routes)); } if (requestUrl == null) { throw new ArgumentNullException(nameof(requestUrl)); } this.Logger?.LogVerbose($"Attempting to match Rpc route for the request url '{requestUrl}'"); RpcPath requestPath = RpcPath.Parse(requestUrl); RpcPath routePrefix = RpcPath.Parse(routes.RoutePrefix); foreach (RpcRoute rpcRoute in routes) { RpcPath routePath = RpcPath.Parse(rpcRoute.Name); routePath = routePrefix.Add(routePath); if (requestPath == routePath) { this.Logger?.LogVerbose($"Matched the request url '{requestUrl}' to the route '{rpcRoute.Name}'"); route = rpcRoute; return(true); } } this.Logger?.LogVerbose($"Failed to match the request url '{requestUrl}' to a route"); route = null; return(false); }
public void AddPath_Same_Match() { RpcPath fullPath = RpcPath.Parse("/Base/Test"); RpcPath otherPath = RpcPath.Parse("/Base").Add(RpcPath.Parse("/Test")); Assert.Equal(fullPath, otherPath); }
/// <summary> /// Indicates if the incoming request matches any predefined routes /// </summary> /// <param name="requestUrl">The current request url</param> /// <param name="route">The matching route corresponding to the request url if found, otherwise it is null</param> /// <param name="routeProvider">Provider that allows the retrieval of all configured routes</param> /// <returns>True if the request url matches any Rpc routes, otherwise False</returns> public bool MatchesRpcRoute(IRpcRouteProvider routeProvider, string requestUrl, out RpcRoute route) { if (requestUrl == null) { throw new ArgumentNullException(nameof(requestUrl)); } this.logger?.LogDebug($"Attempting to match Rpc route for the request url '{requestUrl}'"); RpcPath requestPath = RpcPath.Parse(requestUrl); this.logger?.LogTrace($"Request path: {requestPath}"); foreach (RpcRoute rpcRoute in routeProvider.GetRoutes()) { RpcPath routePath = RpcPath.Parse(rpcRoute.Name); this.logger?.LogTrace($"Trying to match against route - Name: {rpcRoute.Name}, Path: {routePath}"); if (requestPath == routePath) { this.logger?.LogDebug($"Matched the request url '{requestUrl}' to the route '{rpcRoute.Name}'"); route = rpcRoute; return(true); } } this.logger?.LogDebug($"Failed to match the request url '{requestUrl}' to a route"); route = null; return(false); }
public void AddPath_Different_NoMatch() { RpcPath fullPath = RpcPath.Parse("/Base/Test"); RpcPath otherPath = fullPath.Add(RpcPath.Parse("/Test")); Assert.NotEqual(fullPath, otherPath); }
/// <summary> /// Gets all the method providers for the specified path /// </summary> /// <param name="path">Path to the methods</param> /// <returns>All method providers for the specified path</returns> public List <IRpcMethodProvider> GetMethodsByPath(RpcPath path) { if (this.Options.Value.Routes == null || !this.Options.Value.Routes.TryGetValue(path, out List <IRpcMethodProvider> methods)) { return(new List <IRpcMethodProvider>()); } return(methods); }
public void RegisterMethods(RpcPath path, IRpcMethodProvider methodProvider) { if (!this.Routes.TryGetValue(path, out List <IRpcMethodProvider> methodProviders)) { methodProviders = new List <IRpcMethodProvider>(); this.Routes[path] = methodProviders; } methodProviders.Add(methodProvider); }
/// <summary> /// Gets all the method providers for the specified path /// </summary> /// <param name="path">Path to the methods</param> /// <returns>All method providers for the specified path</returns> public List <IRpcMethodProvider> GetMethodsByPath(RpcPath path) { Dictionary <RpcPath, List <IRpcMethodProvider> > routes = this.GetAllRoutes(); if (!routes.TryGetValue(path, out List <IRpcMethodProvider> methods)) { return(new List <IRpcMethodProvider>()); } return(methods); }
public List <IRpcMethodProvider> GetMethodsByPath(RpcPath path) { Dictionary <RpcPath, List <IRpcMethodProvider> > routes = GetAllRoutes(); foreach (KeyValuePair <RpcPath, List <IRpcMethodProvider> > kvp in routes) { if (path.StartsWith(kvp.Key)) { return(kvp.Value); } } return(new List <IRpcMethodProvider>()); }
/// <summary> /// Gets all the predefined Rpc methods for a Rpc route /// </summary> /// <param name="path">The route to get Rpc methods for</param> /// <param name="serviceProvider">(Optional) IoC Container for rpc method controllers</param> /// <returns>List of Rpc methods for the specified Rpc route</returns> private List <MethodInfo> GetRpcMethods(RpcPath path, IRpcRouteProvider routeProvider) { var methods = new List <MethodInfo>(); foreach (IRpcMethodProvider methodProvider in routeProvider.GetMethodsByPath(path)) { foreach (MethodInfo methodInfo in methodProvider.GetRouteMethods()) { methods.Add(methodInfo); } } return(methods); }
public void ParsePath_DifferentPaths_Valid(string requestUrl, string availableRouteName, bool shouldMatch) { RpcPath requestPath = RpcPath.Parse(requestUrl); RpcPath routePath = RpcPath.Parse(availableRouteName); if (shouldMatch) { Assert.Equal(routePath, requestPath); } else { Assert.NotEqual(routePath, requestPath); } }
public void TryRemoveBasePath_1Part_NullOutput() { RpcPath basePath = RpcPath.Parse("/Base"); RpcPath fullPath = RpcPath.Parse("/Base/"); bool removed = fullPath.TryRemoveBasePath(basePath, out RpcPath? path); Assert.True(removed); Assert.Null(path); //Also check the Remove is the same RpcPath?path2 = fullPath.RemoveBasePath(basePath); Assert.Equal(path, path2); }
/// <summary> /// Invokes the method with the specified parameters, returns the result of the method /// </summary> /// <exception cref="RpcInvalidParametersException">Thrown when conversion of parameters fails or when invoking the method is not compatible with the parameters</exception> /// <param name="parameters">List of parameters to invoke the method with</param> /// <returns>The result of the invoked method</returns> private async Task <object> InvokeAsync(RpcMethodInfo methodInfo, RpcPath path) { object obj = null; if (this.serviceProvider != null) { //Use service provider (if exists) to create instance var objectFactory = ActivatorUtilities.CreateFactory(methodInfo.Method.DeclaringType, new Type[0]); obj = objectFactory(this.serviceProvider, null); } if (obj == null) { //Use reflection to create instance if service provider failed or is null obj = Activator.CreateInstance(methodInfo.Method.DeclaringType); } try { object returnObj = methodInfo.Method.Invoke(obj, methodInfo.ConvertedParameters); returnObj = await DefaultRpcInvoker.HandleAsyncResponses(returnObj); return(returnObj); } catch (TargetInvocationException ex) { var routeInfo = new RpcRouteInfo(methodInfo, path, this.serviceProvider); //Controller error handling RpcErrorFilterAttribute errorFilter = methodInfo.Method.DeclaringType.GetTypeInfo().GetCustomAttribute <RpcErrorFilterAttribute>(); if (errorFilter != null) { OnExceptionResult result = errorFilter.OnException(routeInfo, ex.InnerException); if (!result.ThrowException) { return(result.ResponseObject); } if (result.ResponseObject is Exception rEx) { throw rEx; } } throw new RpcUnknownException("Exception occurred from target method execution.", ex); } catch (Exception ex) { throw new RpcInvalidParametersException("Exception from attempting to invoke method. Possibly invalid parameters for method.", ex); } }
private Dictionary <RpcPath, List <IRpcMethodProvider> > GetAllRoutes() { if (this._routeCache == null) { List <TypeInfo> controllerTypes = new[] { Assembly.GetEntryAssembly(), Assembly.GetExecutingAssembly() } .SelectMany(a => a.DefinedTypes) .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(RpcControllerBase))) .ToList(); var controllerRoutes = new Dictionary <RpcPath, List <IRpcMethodProvider> >(); foreach (TypeInfo controllerType in controllerTypes) { var attribute = controllerType.GetCustomAttribute <RpcRouteAttribute>(true); string routePathString; if (attribute == null || attribute.RouteName == null) { if (controllerType.Name.EndsWith("RpcController")) { routePathString = controllerType.Name.Substring(0, controllerType.Name.IndexOf("RpcController")); } else { routePathString = controllerType.Name; } routePathString = routePathString.Dasherize(); } else { routePathString = attribute.RouteName; } RpcPath routePath = RpcPath.Parse(routePathString); if (!controllerRoutes.TryGetValue(routePath, out List <IRpcMethodProvider> methodProviders)) { methodProviders = new List <IRpcMethodProvider>(); controllerRoutes[routePath] = methodProviders; } methodProviders.Add(new ControllerPublicMethodProvider(controllerType.AsType())); } this._routeCache = controllerRoutes; } return(this._routeCache); }
private Dictionary <RpcPath, List <IRpcMethodProvider> > GetAllRoutes() { if (this.routeCache == null) { //TODO will entry assembly be good enough List <TypeInfo> controllerTypes = Assembly.GetEntryAssembly().DefinedTypes .Where(t => !t.IsAbstract && t.IsSubclassOf(this.Options.BaseControllerType)) .ToList(); var controllerRoutes = new Dictionary <RpcPath, List <IRpcMethodProvider> >(); foreach (TypeInfo controllerType in controllerTypes) { var attribute = controllerType.GetCustomAttribute <RpcRouteAttribute>(true); string routePathString; if (attribute == null || string.IsNullOrWhiteSpace(attribute.RouteName)) { if (controllerType.Name.EndsWith("Controller")) { routePathString = controllerType.Name.Substring(0, controllerType.Name.IndexOf("Controller")); } else { routePathString = controllerType.Name; } } else { routePathString = attribute.RouteName; } RpcPath routePath = RpcPath.Parse(routePathString); if (!controllerRoutes.TryGetValue(routePath, out List <IRpcMethodProvider> methodProviders)) { methodProviders = new List <IRpcMethodProvider>(); controllerRoutes[routePath] = methodProviders; } methodProviders.Add(new ControllerPublicMethodProvider(controllerType.AsType())); } this.routeCache = controllerRoutes; } return(this.routeCache); }
public static async Task HandleJsonRpcWebSocketRequest(this WebSocket webSocket, HttpContext context, IRpcRequestHandler rpcRequestHandler, IRpcRouteProvider rpcRouteProvider) { byte[] buffer = new byte[1024 * 4]; IRouteContext routeContext = DefaultRouteContext.FromHttpContext(context, rpcRouteProvider); while (webSocket.State == WebSocketState.Open) { WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment <byte>(buffer), CancellationToken.None); if (result.MessageType != WebSocketMessageType.Text) { continue; } string requestBody = Encoding.ASCII.GetString(buffer); string response = await rpcRequestHandler.HandleRequestAsync(RpcPath.Parse(context.Request.Path), requestBody, routeContext); await webSocket.SendAsync( new ArraySegment <byte>(Encoding.ASCII.GetBytes(response), 0, response.Length), WebSocketMessageType.Text, true, CancellationToken.None ); } }
public void ToString_LowerCaseAndTrimSlash(string path, string expected) { RpcPath fullPath = RpcPath.Parse(path); Assert.Equal(expected, fullPath?.ToString()); }
/// <summary> /// Gets all the method providers for the specified path /// </summary> /// <param name="path">Path to the methods</param> /// <returns>All method providers for the specified path</returns> public List <IRpcMethodProvider> GetMethodsByPath(RpcPath path) => this.Options.Value?.MethodProviders ?? new List <IRpcMethodProvider>();
public RpcSingleRouteProvider(IOptions <SingleRouteOptions> options) { this.Options = options ?? throw new ArgumentNullException(nameof(options)); this.BaseRequestPath = this.Options.Value?.BaseRequestPath ?? RpcPath.Default; }
/// <summary> /// Call the incoming Rpc requests methods and gives the appropriate respones /// </summary> /// <param name="requests">List of Rpc requests</param> /// <param name="path">Rpc path that applies to the current request</param> /// <param name="httpContext">The context of the current http request</param> /// <returns>List of Rpc responses for the requests</returns> public async Task <List <RpcResponse> > InvokeBatchRequestAsync(List <RpcRequest> requests, RpcPath path, IRouteContext routeContext) { this.logger?.LogDebug($"Invoking '{requests.Count}' batch requests"); var invokingTasks = new List <Task <RpcResponse> >(); foreach (RpcRequest request in requests) { Task <RpcResponse> invokingTask = Task.Run(async() => await this.InvokeRequestAsync(request, path, routeContext)); if (request.Id.HasValue) { //Only wait for non-notification requests invokingTasks.Add(invokingTask); } } await Task.WhenAll(invokingTasks.ToArray()); List <RpcResponse> responses = invokingTasks .Select(t => t.Result) .Where(r => r != null) .ToList(); this.logger?.LogDebug($"Finished '{requests.Count}' batch requests"); return(responses); }
/// <summary> /// Call the incoming Rpc requests methods and gives the appropriate respones /// </summary> /// <param name="requests">List of Rpc requests</param> /// <param name="path">Rpc path that applies to the current request</param> /// <param name="httpContext">The context of the current http request</param> /// <returns>List of Rpc responses for the requests</returns> public async Task <List <RpcResponse> > InvokeBatchRequestAsync(List <RpcRequest> requests, RpcPath path, IRouteContext routeContext) { this.logger?.LogDebug($"Invoking '{requests.Count}' batch requests"); var invokingTasks = new List <Task <RpcResponse> >(); foreach (RpcRequest request in requests) { Task <RpcResponse> invokingTask = Task.Run(async() => await this.InvokeRequestAsync(request, path, routeContext).ConfigureAwait(false));; invokingTasks.Add(invokingTask); } await Task.WhenAll(invokingTasks.ToArray()); List <RpcResponse> responses = invokingTasks .Select(t => t.Result) .Where(r => r != null && ((r.Id != null) && ((RpcId)r.Id).HasValue || r.HasError)) .ToList(); this.logger?.LogDebug($"Finished '{requests.Count}' batch requests"); return(responses); }
public void RegisterController <T>(RpcPath path = default(RpcPath)) { this.RegisterMethods(path, new ControllerPublicMethodProvider(typeof(T))); }
/// <summary> /// Call the incoming Rpc request method and gives the appropriate response /// </summary> /// <param name="request">Rpc request</param> /// <param name="path">Rpc path that applies to the current request</param> /// <param name="httpContext">The context of the current http request</param> /// <returns>An Rpc response for the request</returns> public async Task <RpcResponse> InvokeRequestAsync(RpcRequest request, RpcPath path, IRouteContext routeContext) { try { if (request == null) { throw new ArgumentNullException(nameof(request)); } } catch (ArgumentNullException ex) // Dont want to throw any exceptions when doing async requests { return(this.GetUnknownExceptionReponse(request, ex)); } this.logger?.LogDebug($"Invoking request with id '{request.Id}'"); RpcResponse rpcResponse; try { if (!string.Equals(request.JsonRpcVersion, JsonRpcContants.JsonRpcVersion)) { throw new RpcInvalidRequestException($"Request must be jsonrpc version '{JsonRpcContants.JsonRpcVersion}'"); } RpcMethodInfo rpcMethod = this.GetMatchingMethod(path, request, routeContext.RouteProvider, routeContext.RequestServices); bool isAuthorized = await this.IsAuthorizedAsync(rpcMethod.Method, routeContext); if (isAuthorized) { object result = null; if (request.Id.HasValue) { this.logger?.LogDebug($"Attempting to invoke method '{request.Method}'"); result = await this.InvokeAsync(rpcMethod, path); this.logger?.LogDebug($"Finished invoking method '{request.Method}'"); } else { this.logger?.LogDebug($"Attempting to invoke notification '{request.Method}'"); this.FireAndForget(async() => await this.InvokeAsync(rpcMethod, path)); } JsonSerializer jsonSerializer = this.GetJsonSerializer(); if (result is IRpcMethodResult) { this.logger?.LogTrace($"Result is {nameof(IRpcMethodResult)}."); rpcResponse = ((IRpcMethodResult)result).ToRpcResponse(request.Id, obj => JToken.FromObject(obj, jsonSerializer)); } else { this.logger?.LogTrace($"Result is plain object."); JToken resultJToken = result != null?JToken.FromObject(result, jsonSerializer) : null; rpcResponse = new RpcResponse(request.Id, resultJToken); } } else { var authError = new RpcError(RpcErrorCode.InvalidRequest, "Unauthorized"); rpcResponse = new RpcResponse(request.Id, authError); } } catch (RpcException ex) { this.logger?.LogException(ex, "An Rpc error occurred. Returning an Rpc error response"); RpcError error = new RpcError(ex, this.serverConfig.Value.ShowServerExceptions); rpcResponse = new RpcResponse(request.Id, error); } catch (Exception ex) { rpcResponse = this.GetUnknownExceptionReponse(request, ex); } if (request.Id.HasValue) { this.logger?.LogDebug($"Finished request with id: {request.Id}"); //Only give a response if there is an id return(rpcResponse); } this.logger?.LogDebug($"Finished request with no id."); return(rpcResponse); }
/// <summary> /// Finds the matching Rpc method for the current request /// </summary> /// <param name="path">Rpc route for the current request</param> /// <param name="request">Current Rpc request</param> /// <param name="parameterList">Parameter list parsed from the request</param> /// <param name="serviceProvider">(Optional)IoC Container for rpc method controllers</param> /// <returns>The matching Rpc method to the current request</returns> private RpcMethodInfo GetMatchingMethod(RpcPath path, RpcRequest request, IRpcRouteProvider routeProvider, IServiceProvider serviceProvider) { if (request == null) { throw new ArgumentNullException(nameof(request)); } this.logger?.LogDebug($"Attempting to match Rpc request to a method '{request.Method}'"); List <MethodInfo> allMethods = this.GetRpcMethods(path, routeProvider); //Case insenstive check for hybrid approach. Will check for case sensitive if there is ambiguity var requestMethodName = this.convertSnakeCaseToCamelCase ? this.ConvertSnakeCaseToCamelCase(request.Method) : request.Method; List <MethodInfo> methodsWithSameName = allMethods .Where(m => string.Equals(m.Name, requestMethodName, StringComparison.OrdinalIgnoreCase)) .ToList(); var potentialMatches = new List <RpcMethodInfo>(); foreach (MethodInfo method in methodsWithSameName) { (bool isMatch, RpcMethodInfo methodInfo) = this.HasParameterSignature(method, request); if (isMatch) { potentialMatches.Add(methodInfo); } } if (potentialMatches.Count > 1) { //Try to remove ambiguity with 'perfect matching' (usually optional params and types) List <RpcMethodInfo> exactMatches = potentialMatches .Where(p => p.HasExactParameterMatch()) .ToList(); if (exactMatches.Any()) { potentialMatches = exactMatches; } if (potentialMatches.Count > 1) { //Try to remove ambiguity with case sensitive check potentialMatches = potentialMatches .Where(m => string.Equals(m.Method.Name, requestMethodName, StringComparison.Ordinal)) .ToList(); if (potentialMatches.Count != 1) { this.logger?.LogError("More than one method matched the rpc request. Unable to invoke due to ambiguity."); throw new RpcMethodNotFoundException(); } } } RpcMethodInfo rpcMethod = null; if (potentialMatches.Count == 1) { rpcMethod = potentialMatches.First(); } if (rpcMethod == null) { //Log diagnostics string methodsString = string.Join(", ", allMethods.Select(m => m.Name)); this.logger?.LogTrace("Methods in route: " + methodsString); var methodInfoList = new List <string>(); foreach (MethodInfo matchedMethod in methodsWithSameName) { var parameterTypeList = new List <string>(); foreach (ParameterInfo parameterInfo in matchedMethod.GetParameters()) { string parameterType = parameterInfo.Name + ": " + parameterInfo.ParameterType.Name; if (parameterInfo.IsOptional) { parameterType += "(Optional)"; } parameterTypeList.Add(parameterType); } string parameterString = string.Join(", ", parameterTypeList); methodInfoList.Add($"{{Name: '{matchedMethod.Name}', Parameters: [{parameterString}]}}"); } this.logger?.LogTrace("Methods that matched the same name: " + string.Join(", ", methodInfoList)); this.logger?.LogError("No methods matched request."); throw new RpcMethodNotFoundException(); } this.logger?.LogDebug("Request was matched to a method"); return(rpcMethod); }