/// <param name="serverConfig">Configuration data for the server</param> /// <param name="invoker">Component that invokes Rpc requests target methods and returns a response</param> /// <param name="parser">Component that parses Http requests into Rpc requests</param> /// <param name="compressor">Component that compresses Rpc responses</param> /// <param name="logger">Component that logs actions from the router</param> /// <param name="routeProvider">Provider that allows the retrieval of all configured routes</param> public RpcRouter(IOptions <RpcServerConfiguration> serverConfig, IRpcInvoker invoker, IRpcParser parser, IRpcCompressor compressor, ILogger <RpcRouter> logger, IRpcRouteProvider routeProvider) { if (serverConfig == null) { throw new ArgumentNullException(nameof(serverConfig)); } if (invoker == null) { throw new ArgumentNullException(nameof(invoker)); } if (parser == null) { throw new ArgumentNullException(nameof(parser)); } if (compressor == null) { throw new ArgumentNullException(nameof(compressor)); } if (routeProvider == null) { throw new ArgumentNullException(nameof(routeProvider)); } this.serverConfig = serverConfig; this.invoker = invoker; this.parser = parser; this.compressor = compressor; this.logger = logger; this.routeProvider = routeProvider; }
/// <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); }
/// <param name="serverConfig">Configuration data for the server</param> /// <param name="invoker">Component that invokes Rpc requests target methods and returns a response</param> /// <param name="parser">Component that parses Http requests into Rpc requests</param> /// <param name="compressor">Component that compresses Rpc responses</param> /// <param name="logger">Component that logs actions from the router</param> /// <param name="routeProvider">Provider that allows the retrieval of all configured routes</param> public RpcRouter(IOptions<RpcServerConfiguration> serverConfig, IRpcInvoker invoker, IRpcParser parser, IRpcCompressor compressor, ILogger<RpcRouter> logger, IRpcRouteProvider routeProvider) { if (serverConfig == null) { throw new ArgumentNullException(nameof(serverConfig)); } if (invoker == null) { throw new ArgumentNullException(nameof(invoker)); } if (parser == null) { throw new ArgumentNullException(nameof(parser)); } if (compressor == null) { throw new ArgumentNullException(nameof(compressor)); } if (routeProvider == null) { throw new ArgumentNullException(nameof(routeProvider)); } this.serverConfig = serverConfig; this.invoker = invoker; this.parser = parser; this.compressor = compressor; this.logger = logger; this.routeProvider = routeProvider; }
/// <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; }
/// <param name="compressor">Component that compresses Rpc responses</param> /// <param name="logger">Component that logs actions from the router</param> /// <param name="routeProvider">Provider that allows the retrieval of all configured routes</param> public RpcRouter(ILogger <RpcRouter> logger, IRpcCompressor compressor, IRpcRouteProvider routeProvider, IRpcRequestHandler routeHandler) { this.logger = logger; this.compressor = compressor ?? throw new ArgumentNullException(nameof(compressor)); this.routeProvider = routeProvider ?? throw new ArgumentNullException(nameof(routeProvider)); this.routeHandler = routeHandler ?? throw new ArgumentNullException(nameof(routeHandler)); }
/// <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); }
/// <summary> /// Extension method to use the JsonRpc router in the Asp.Net pipeline /// </summary> /// <param name="app"><see cref="IApplicationBuilder"/> that is supplied by Asp.Net</param> /// <param name="routeProvider">Action to configure route provider</param> /// <returns><see cref="IApplicationBuilder"/> that includes the Basic auth middleware</returns> public static IApplicationBuilder UseJsonRpc(this IApplicationBuilder app, IRpcRouteProvider routeProvider) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if(routeProvider == null) { throw new ArgumentNullException(nameof(routeProvider)); } RpcRouter router = ActivatorUtilities.CreateInstance<RpcRouter>(app.ApplicationServices, routeProvider); return app.UseRouter(router); }
/// <summary> /// Extension method to use the JsonRpc router in the Asp.Net pipeline /// </summary> /// <param name="app"><see cref="IApplicationBuilder"/> that is supplied by Asp.Net</param> /// <param name="routeProvider">Action to configure route provider</param> /// <returns><see cref="IApplicationBuilder"/> that includes the Basic auth middleware</returns> public static IApplicationBuilder UseJsonRpc(this IApplicationBuilder app, IRpcRouteProvider routeProvider) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (routeProvider == null) { throw new ArgumentNullException(nameof(routeProvider)); } RpcRouter router = ActivatorUtilities.CreateInstance <RpcRouter>(app.ApplicationServices, routeProvider); return(app.UseRouter(router)); }
/// <summary> /// Extension method to use the JsonRpc router in the Asp.Net pipeline /// </summary> /// <param name="app"><see cref="IApplicationBuilder"/> that is supplied by Asp.Net</param> /// <param name="options">Auto routing configuration</param> /// <returns><see cref="IApplicationBuilder"/> that includes the Basic auth middleware</returns> public static IApplicationBuilder UseJsonRpc(this IApplicationBuilder app, IRpcRouteProvider routeProvider) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (routeProvider == null) { throw new ArgumentNullException(nameof(routeProvider)); } if (app.ApplicationServices.GetService <RpcServicesMarker>() == null) { throw new InvalidOperationException("AddJsonRpc() needs to be called in the ConfigureServices method."); } var router = new RpcHttpRouter(routeProvider); return(app.UseRouter(router)); }
public static void RegisterTypeRoute <T>(this IRpcRouteProvider routeProvider, string name = null) { routeProvider.RegisterRoute(new List <RouteCriteria> { new RouteCriteria(typeof(T)) }, name); }
public static void RegisterRoute(this IRpcRouteProvider routeProvider, RouteCriteria criteria, string name = null) { routeProvider.RegisterRoute(new List <RouteCriteria> { criteria }, name); }
/// <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); }
/// <param name="compressor">Component that compresses Rpc responses</param> /// <param name="logger">Component that logs actions from the router</param> /// <param name="routeProvider">Provider that allows the retrieval of all configured routes</param> public RpcRouter(IRpcRouteProvider routeProvider) { this.routeProvider = routeProvider ?? throw new ArgumentNullException(nameof(routeProvider)); }
public static IRouteContext FromHttpContext(HttpContext httpContext, IRpcRouteProvider routeProvider) { return(new DefaultRouteContext(httpContext.RequestServices, httpContext.User, routeProvider)); }
public DefaultRouteContext(IServiceProvider serviceProvider, ClaimsPrincipal user, IRpcRouteProvider routeProvider) { this.RequestServices = serviceProvider; this.User = user; this.RouteProvider = routeProvider; }
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 ); } }