/// <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> /// 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="configureRouter">Action to configure the router properties</param> /// <returns><see cref="IApplicationBuilder"/> that includes the Basic auth middleware</returns> public static IApplicationBuilder UseJsonRpc(this IApplicationBuilder app, Action <RpcRouterConfiguration> configureRouter) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (configureRouter == null) { throw new ArgumentNullException(nameof(configureRouter)); } RpcRouterConfiguration configuration = new RpcRouterConfiguration(); configureRouter.Invoke(configuration); if (configuration.Routes.Count < 1) { throw new RpcConfigurationException("At least on class/route must be configured for router to work."); } IRpcInvoker rpcInvoker = app.ApplicationServices.GetRequiredService <IRpcInvoker>(); IRpcParser rpcParser = app.ApplicationServices.GetRequiredService <IRpcParser>(); IRpcCompressor rpcCompressor = app.ApplicationServices.GetRequiredService <IRpcCompressor>(); ILoggerFactory loggerFactory = app.ApplicationServices.GetService <ILoggerFactory>(); ILogger logger = loggerFactory?.CreateLogger <RpcRouter>(); app.UseRouter(new RpcRouter(configuration, rpcInvoker, rpcParser, rpcCompressor, logger)); return(app); }
/// <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)); }
/// <param name="configuration">Configuration data for the router</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> public RpcRouter(RpcRouterConfiguration configuration, IRpcInvoker invoker, IRpcParser parser, IRpcCompressor compressor, ILogger logger) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } if (invoker == null) { throw new ArgumentNullException(nameof(invoker)); } if (parser == null) { throw new ArgumentNullException(nameof(parser)); } if (compressor == null) { throw new ArgumentNullException(nameof(compressor)); } this.configuration = configuration; this.invoker = invoker; this.parser = parser; this.compressor = compressor; this.logger = logger; }
/// <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> /// Takes a route/http contexts and attempts to parse, invoke, respond to an Rpc request /// </summary> /// <param name="context">Route context</param> /// <returns>Task for async routing</returns> public async Task RouteAsync(RouteContext context) { ILogger <RpcRouter> logger = context.HttpContext.RequestServices.GetService <ILogger <RpcRouter> >(); try { RpcPath requestPath; if (!context.HttpContext.Request.Path.HasValue) { requestPath = RpcPath.Default; } else { if (!RpcPath.TryParse(context.HttpContext.Request.Path.Value, out requestPath)) { logger?.LogInformation($"Could not parse the path '{context.HttpContext.Request.Path.Value}' for the " + $"request into an rpc path. Skipping rpc router middleware."); return; } } if (!requestPath.TryRemoveBasePath(this.routeProvider.BaseRequestPath, out requestPath)) { logger?.LogTrace("Request did not match the base request path. Skipping rpc router."); return; } HashSet <RpcPath> availableRoutes = this.routeProvider.GetRoutes(); if (!availableRoutes.Any()) { logger?.LogDebug($"Request matched base request path but no routes."); return; } logger?.LogInformation($"Rpc request route '{requestPath}' matched."); Stream contentStream = context.HttpContext.Request.Body; string jsonString; if (contentStream == null) { jsonString = null; } else { StreamReader streamReader = new StreamReader(contentStream); jsonString = streamReader.ReadToEnd().Trim(); } var requestHandler = context.HttpContext.RequestServices.GetRequiredService <IRpcRequestHandler>(); var routeContext = DefaultRouteContext.FromHttpContext(context.HttpContext, this.routeProvider); string responseJson = await requestHandler.HandleRequestAsync(requestPath, jsonString, routeContext); if (responseJson == null) { //No response required return; } context.HttpContext.Response.ContentType = "application/json"; bool responseSet = false; string acceptEncoding = context.HttpContext.Request.Headers["Accept-Encoding"]; if (!string.IsNullOrWhiteSpace(acceptEncoding)) { IRpcCompressor compressor = context.HttpContext.RequestServices.GetService <IRpcCompressor>(); if (compressor != null) { string[] encodings = acceptEncoding.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (string encoding in encodings) { bool haveType = Enum.TryParse(encoding, true, out CompressionType compressionType); if (!haveType) { continue; } context.HttpContext.Response.Headers.Add("Content-Encoding", new[] { encoding }); compressor.CompressText(context.HttpContext.Response.Body, responseJson, Encoding.UTF8, compressionType); responseSet = true; break; } } } if (!responseSet) { await context.HttpContext.Response.WriteAsync(responseJson); } context.MarkAsHandled(); logger?.LogInformation("Rpc request complete"); } catch (Exception ex) { string errorMessage = "Unknown exception occurred when trying to process Rpc request. Marking route unhandled"; logger?.LogException(ex, errorMessage); context.MarkAsHandled(); } }