Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
0
        public void AddPath_Same_Match()
        {
            RpcPath fullPath  = RpcPath.Parse("/Base/Test");
            RpcPath otherPath = RpcPath.Parse("/Base").Add(RpcPath.Parse("/Test"));

            Assert.Equal(fullPath, otherPath);
        }
Exemplo n.º 3
0
        /// <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);
        }
Exemplo n.º 4
0
        public void AddPath_Different_NoMatch()
        {
            RpcPath fullPath  = RpcPath.Parse("/Base/Test");
            RpcPath otherPath = fullPath.Add(RpcPath.Parse("/Test"));

            Assert.NotEqual(fullPath, otherPath);
        }
Exemplo n.º 5
0
 /// <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);
 }
Exemplo n.º 6
0
 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);
 }
Exemplo n.º 7
0
        /// <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);
        }
Exemplo n.º 8
0
        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>());
        }
Exemplo n.º 9
0
        /// <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);
        }
Exemplo n.º 10
0
        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);
            }
        }
Exemplo n.º 11
0
        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);
        }
Exemplo n.º 12
0
        /// <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);
            }
        }
Exemplo n.º 13
0
        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);
        }
Exemplo n.º 14
0
        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
                    );
            }
        }
Exemplo n.º 16
0
        public void ToString_LowerCaseAndTrimSlash(string path, string expected)
        {
            RpcPath fullPath = RpcPath.Parse(path);

            Assert.Equal(expected, fullPath?.ToString());
        }
Exemplo n.º 17
0
 /// <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>();
Exemplo n.º 18
0
 public RpcSingleRouteProvider(IOptions <SingleRouteOptions> options)
 {
     this.Options         = options ?? throw new ArgumentNullException(nameof(options));
     this.BaseRequestPath = this.Options.Value?.BaseRequestPath ?? RpcPath.Default;
 }
Exemplo n.º 19
0
        /// <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);
        }
Exemplo n.º 20
0
        /// <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);
        }
Exemplo n.º 21
0
 public void RegisterController <T>(RpcPath path = default(RpcPath))
 {
     this.RegisterMethods(path, new ControllerPublicMethodProvider(typeof(T)));
 }
Exemplo n.º 22
0
        /// <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);
        }
Exemplo n.º 23
0
        /// <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);
        }