From 4bb911a7915c6314593a3ca63df65bb5516767a2 Mon Sep 17 00:00:00 2001 From: Mario Di Vece Date: Mon, 11 Apr 2016 20:58:58 -0500 Subject: [PATCH] improving code quality and routing structure. Renaming some routing strategy code. --- .../RegexRoutesTest.cs | 4 +- .../Unosquare.Labs.EmbedIO.Tests.csproj | 200 +++++++++--------- Unosquare.Labs.EmbedIO/Constants.cs | 8 +- .../Modules/WebApiModule.cs | 134 +++++++----- Unosquare.Labs.EmbedIO/WebServer.cs | 38 +++- 5 files changed, 216 insertions(+), 168 deletions(-) diff --git a/Unosquare.Labs.EmbedIO.Tests/RegexRoutesTest.cs b/Unosquare.Labs.EmbedIO.Tests/RegexRoutesTest.cs index cd85fd3d1..aafaacdf8 100644 --- a/Unosquare.Labs.EmbedIO.Tests/RegexRoutesTest.cs +++ b/Unosquare.Labs.EmbedIO.Tests/RegexRoutesTest.cs @@ -9,7 +9,7 @@ namespace Unosquare.Labs.EmbedIO.Tests { [TestFixture] - public class RegexRoutesTest + public class RegExRoutesTest { protected WebServer WebServer; protected TestConsoleLog Logger = new TestConsoleLog(); @@ -18,7 +18,7 @@ public class RegexRoutesTest public void Init() { WebServer = - new WebServer(Resources.ServerAddress, Logger, RoutingStrategyEnum.Regex) + new WebServer(Resources.ServerAddress, Logger, RoutingStrategy.RegEx) .WithWebApiController(); WebServer.RunAsync(); } diff --git a/Unosquare.Labs.EmbedIO.Tests/Unosquare.Labs.EmbedIO.Tests.csproj b/Unosquare.Labs.EmbedIO.Tests/Unosquare.Labs.EmbedIO.Tests.csproj index 90b316640..8a136034e 100644 --- a/Unosquare.Labs.EmbedIO.Tests/Unosquare.Labs.EmbedIO.Tests.csproj +++ b/Unosquare.Labs.EmbedIO.Tests/Unosquare.Labs.EmbedIO.Tests.csproj @@ -1,107 +1,107 @@ - - - - - Debug - AnyCPU - {2E100548-08CD-4EFD-AD08-AF584081B098} - Library - Properties - Unosquare.Labs.EmbedIO.Tests - Unosquare.Labs.EmbedIO.Tests - v4.5 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\NUnit.2.6.4\lib\nunit.framework.dll - True - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - - - - - {7d7c29b4-9493-4ebd-8f20-6fac1e7161ee} - Unosquare.Labs.EmbedIO - - - - - Always - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - + + + + + Debug + AnyCPU + {2E100548-08CD-4EFD-AD08-AF584081B098} + Library + Properties + Unosquare.Labs.EmbedIO.Tests + Unosquare.Labs.EmbedIO.Tests + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\NUnit.2.6.4\lib\nunit.framework.dll + True + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + + {7d7c29b4-9493-4ebd-8f20-6fac1e7161ee} + Unosquare.Labs.EmbedIO + + + + + Always + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + --> \ No newline at end of file diff --git a/Unosquare.Labs.EmbedIO/Constants.cs b/Unosquare.Labs.EmbedIO/Constants.cs index 37db8a99c..86979ab22 100644 --- a/Unosquare.Labs.EmbedIO/Constants.cs +++ b/Unosquare.Labs.EmbedIO/Constants.cs @@ -723,9 +723,11 @@ public static class Constants } /// - /// Defines the routing strategy enumeration + /// Defines the routing strategy for URL matching + /// This is especially useful for REST service implementations + /// in the WebApi module. /// - public enum RoutingStrategyEnum + public enum RoutingStrategy { /// /// The wildcard strategy, default one @@ -734,6 +736,6 @@ public enum RoutingStrategyEnum /// /// The Regex strategy /// - Regex + RegEx } } \ No newline at end of file diff --git a/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs b/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs index 89935bb9e..0d64604d0 100644 --- a/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs +++ b/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs @@ -15,14 +15,20 @@ /// will be used to respond to web server requests /// public class WebApiModule : WebModuleBase - { + { + #region Inmutable Declarations + private readonly List ControllerTypes = new List(); private readonly Dictionary, MethodInfo>>> DelegateMap - = - new Dictionary, MethodInfo>>>( - StringComparer.InvariantCultureIgnoreCase); - + = new Dictionary, MethodInfo>>>(StringComparer.InvariantCultureIgnoreCase); + + private static readonly Regex RouteParamRegEx = new Regex(@"\{[^\/]*\}", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private const string RegExRouteReplace = "(.*)"; + + #endregion + /// /// Initializes a new instance of the class. /// @@ -32,39 +38,49 @@ public WebApiModule() this.AddHandler(ModuleMap.AnyPath, HttpVerbs.Any, (server, context) => { var verb = context.RequestVerb(); - var routeParams = new Dictionary(); - var path = server.RoutingStrategy == RoutingStrategyEnum.Wildcard - ? GetPathByWildcard(verb, context) - : GetPathByRegex(verb, context, routeParams); + var regExRouteParams = new Dictionary(); + var path = server.RoutingStrategy == RoutingStrategy.Wildcard + ? NormalizeWildcardPath(verb, context) + : NormalizeRegExPath(verb, context, regExRouteParams); + // return a non-math if no handler hold the route if (path == null) return false; var methodPair = DelegateMap[path][verb]; var controller = methodPair.Item1(); - context.NoCache(); + // ensure module does not retun cached responses + context.NoCache(); + + // Log the handler to be used + server.Log.DebugFormat("Handler: {0}.{1}", methodPair.Item2.DeclaringType.FullName, methodPair.Item2.Name); - if (server.RoutingStrategy == RoutingStrategyEnum.Regex) + // Select the routing strategy + if (server.RoutingStrategy == RoutingStrategy.RegEx) { - server.Log.DebugFormat("Handler: {0}.{1}", methodPair.Item2.DeclaringType.FullName, - methodPair.Item2.Name); - - var args = new List() {server, context}; + // Initially, only the server and context objects will be available + var args = new List() { server, context }; + // Parse the arguments to their intended type skipping the first two. foreach (var arg in methodPair.Item2.GetParameters().Skip(2)) { - if (routeParams.ContainsKey(arg.Name) == false) continue; - - var m = arg.ParameterType.GetMethod(nameof(int.Parse), new[] {typeof (string)}); - - args.Add(m != null ? m.Invoke(null, new[] {routeParams[arg.Name]}) : routeParams[arg.Name]); + if (regExRouteParams.ContainsKey(arg.Name) == false) continue; + // get a reference to the parse method + var parseMethod = arg.ParameterType.GetMethod(nameof(int.Parse), new[] { typeof(string) }); + + // add the parsed argument to the argument list if available + args.Add(parseMethod != null ? + parseMethod.Invoke(null, new[] { regExRouteParams[arg.Name] }) : + regExRouteParams[arg.Name]); } - if (methodPair.Item2.ReturnType == typeof (Task)) + // Now, check if the call is handled asynchronously. + if (methodPair.Item2.ReturnType == typeof(Task)) { + // Run the method asynchronously var returnValue = Task.Run(async () => { - var task = await (Task) methodPair.Item2.Invoke(controller, args.ToArray()); + var task = await (Task)methodPair.Item2.Invoke(controller, args.ToArray()); return task; }); @@ -72,23 +88,20 @@ public WebApiModule() } else { - var returnValue = (bool) methodPair.Item2.Invoke(controller, args.ToArray()); - + // If the handler is not asynchronous, simply call the method. + var returnValue = (bool)methodPair.Item2.Invoke(controller, args.ToArray()); return returnValue; } } - else + else if (server.RoutingStrategy == RoutingStrategy.Wildcard) { - if (methodPair.Item2.ReturnType == typeof (Task)) + if (methodPair.Item2.ReturnType == typeof(Task)) { - var method = Delegate.CreateDelegate(typeof (AsyncResponseHandler), controller, methodPair.Item2); - - server.Log.DebugFormat("Handler: {0}.{1}", method.Method.DeclaringType.FullName, - method.Method.Name); - + // Asynchronous handling of wildcard matching strategy + var method = Delegate.CreateDelegate(typeof(AsyncResponseHandler), controller, methodPair.Item2); var returnValue = Task.Run(async () => { - var task = await (Task) method.DynamicInvoke(server, context); + var task = await (Task)method.DynamicInvoke(server, context); return task; }); @@ -96,29 +109,39 @@ public WebApiModule() } else { - var method = Delegate.CreateDelegate(typeof (ResponseHandler), controller, methodPair.Item2); - - server.Log.DebugFormat("Handler: {0}.{1}", method.Method.DeclaringType.FullName, - method.Method.Name); - - var returnValue = (bool) method.DynamicInvoke(server, context); + // Regular handling of wildcard matching strategy + var method = Delegate.CreateDelegate(typeof(ResponseHandler), controller, methodPair.Item2); + var returnValue = (bool)method.DynamicInvoke(server, context); return returnValue; } } + else + { + // Log the handler to be used + server.Log.WarnFormat($"Routing strategy '{server.RoutingStrategy}' is not supported by this module."); + return false; + } }); } - private static readonly Regex RouteParamRegex = new Regex(@"\{[^\/]*\}"); - private const string RegexRouteReplace = "(.*)"; - private string GetPathByRegex(HttpVerbs verb, HttpListenerContext context, + + /// + /// Normalizes a path meant for RegEx matching, extracts the route parameters, and returns the registered + /// path in the internal delegate map. + /// + /// The verb. + /// The context. + /// The route parameters. + /// + private string NormalizeRegExPath(HttpVerbs verb, HttpListenerContext context, Dictionary routeParams) { var path = context.RequestPath(); foreach (var route in DelegateMap.Keys) { - var regex = new Regex(RouteParamRegex.Replace(route, RegexRouteReplace)); + var regex = new Regex(RouteParamRegEx.Replace(route, RegExRouteReplace)); var match = regex.Match(path); if (!match.Success || !DelegateMap[route].Keys.Contains(verb)) continue; @@ -137,7 +160,14 @@ private string GetPathByRegex(HttpVerbs verb, HttpListenerContext context, return null; } - private string GetPathByWildcard(HttpVerbs verb, HttpListenerContext context) + /// + /// Normalizes a URL request path meant for Wildcard matching and returns the registered + /// path in the internal delegate map. + /// + /// The verb. + /// The context. + /// + private string NormalizeWildcardPath(HttpVerbs verb, HttpListenerContext context) { var path = context.RequestPath(); @@ -147,8 +177,8 @@ private string GetPathByWildcard(HttpVerbs verb, HttpListenerContext context) .ToArray(); var wildcardMatch = wildcardPaths.FirstOrDefault(p => // wildcard at the end - path.StartsWith(p.Substring(0, p.Length - ModuleMap.AnyPath.Length)) - // wildcard in the middle so check both start/end + path.StartsWith(p.Substring(0, p.Length - ModuleMap.AnyPath.Length)) + // wildcard in the middle so check both start/end || (path.StartsWith(p.Substring(0, p.IndexOf(ModuleMap.AnyPath, StringComparison.Ordinal))) && path.EndsWith(p.Substring(p.IndexOf(ModuleMap.AnyPath, StringComparison.Ordinal) + 1))) ); @@ -183,7 +213,7 @@ private string GetPathByWildcard(HttpVerbs verb, HttpListenerContext context) public override string Name => "Web API Module"; /// - /// Gets the controllers count + /// Gets the number of controller objects registered in this API /// public int ControllersCount => ControllerTypes.Count; @@ -195,10 +225,10 @@ private string GetPathByWildcard(HttpVerbs verb, HttpListenerContext context) public void RegisterController() where T : WebApiController, new() { - if (ControllerTypes.Contains(typeof (T))) + if (ControllerTypes.Contains(typeof(T))) throw new ArgumentException("Controller types must be unique within the module"); - RegisterController(typeof (T)); + RegisterController(typeof(T)); } /// @@ -210,10 +240,10 @@ public void RegisterController() public void RegisterController(Func controllerFactory) where T : WebApiController { - if (ControllerTypes.Contains(typeof (T))) + if (ControllerTypes.Contains(typeof(T))) throw new ArgumentException("Controller types must be unique within the module"); - RegisterController(typeof (T), controllerFactory); + RegisterController(typeof(T), controllerFactory); } /// @@ -250,7 +280,7 @@ public void RegisterController(Type controllerType, Func controllerFacto foreach (var method in methods) { var attribute = - method.GetCustomAttributes(typeof (WebApiHandlerAttribute), true).FirstOrDefault() as + method.GetCustomAttributes(typeof(WebApiHandlerAttribute), true).FirstOrDefault() as WebApiHandlerAttribute; if (attribute == null) continue; @@ -309,7 +339,7 @@ public WebApiHandlerAttribute(HttpVerbs verb, string path) throw new ArgumentException("The argument 'path' must be specified."); this.Verb = verb; - this.Paths = new string[] {path}; + this.Paths = new string[] { path }; } /// diff --git a/Unosquare.Labs.EmbedIO/WebServer.cs b/Unosquare.Labs.EmbedIO/WebServer.cs index ab4b98221..cf598a4a2 100644 --- a/Unosquare.Labs.EmbedIO/WebServer.cs +++ b/Unosquare.Labs.EmbedIO/WebServer.cs @@ -65,16 +65,17 @@ public ReadOnlyCollection Modules public ILog Log { get; protected set; } /// - /// Gets the RoutingStrategy used in this instance + /// Gets the URL RoutingStrategy used in this instance. + /// By default it is set to Wildcard, but RegEx is the the recommended value. /// - public RoutingStrategyEnum RoutingStrategy { get; protected set; } + public RoutingStrategy RoutingStrategy { get; protected set; } /// /// Initializes a new instance of the class. /// This constructor does not provide any Logging capabilities. /// public WebServer() - : this("http://*/", new NullLog(), RoutingStrategyEnum.Wildcard) + : this("http://*/", new NullLog(), RoutingStrategy.Wildcard) { // placeholder } @@ -96,7 +97,7 @@ public WebServer(int port) /// The port. /// public WebServer(int port, ILog log) - : this("http://*:" + port.ToString() + "/", log, RoutingStrategyEnum.Wildcard) + : this("http://*:" + port.ToString() + "/", log, RoutingStrategy.Wildcard) { // placeholder } @@ -107,7 +108,7 @@ public WebServer(int port, ILog log) /// The port. /// /// The routing strategy - public WebServer(int port, ILog log, RoutingStrategyEnum routingStrategy) + public WebServer(int port, ILog log, RoutingStrategy routingStrategy) : this("http://*:" + port.ToString() + "/", log, routingStrategy) { // placeholder @@ -119,7 +120,7 @@ public WebServer(int port, ILog log, RoutingStrategyEnum routingStrategy) /// /// The URL prefix. public WebServer(string urlPrefix) - : this(urlPrefix, new NullLog(), RoutingStrategyEnum.Wildcard) + : this(urlPrefix, new NullLog(), RoutingStrategy.Wildcard) { // placeholder } @@ -130,7 +131,7 @@ public WebServer(string urlPrefix) /// The URL prefix. /// The log. public WebServer(string urlPrefix, ILog log) - : this(new[] { urlPrefix }, log, RoutingStrategyEnum.Wildcard) + : this(new[] { urlPrefix }, log, RoutingStrategy.Wildcard) { // placeholder } @@ -141,7 +142,7 @@ public WebServer(string urlPrefix, ILog log) /// The URL prefix. /// The log. /// The routing strategy - public WebServer(string urlPrefix, ILog log, RoutingStrategyEnum routingStrategy) + public WebServer(string urlPrefix, ILog log, RoutingStrategy routingStrategy) : this(new[] { urlPrefix }, log, routingStrategy) { // placeholder @@ -153,7 +154,7 @@ public WebServer(string urlPrefix, ILog log, RoutingStrategyEnum routingStrategy /// /// The URL prefixes. public WebServer(string[] urlPrefixes) - : this(urlPrefixes, new NullLog(), RoutingStrategyEnum.Wildcard) + : this(urlPrefixes, new NullLog(), RoutingStrategy.Wildcard) { // placeholder } @@ -168,7 +169,7 @@ public WebServer(string[] urlPrefixes) /// The routing strategy /// The HTTP Listener is not supported in this OS /// Argument urlPrefix must be specified - public WebServer(string[] urlPrefixes, ILog log, RoutingStrategyEnum routingStrategy) + public WebServer(string[] urlPrefixes, ILog log, RoutingStrategy routingStrategy) { if (HttpListener.IsSupported == false) throw new InvalidOperationException("The HTTP Listener is not supported in this OS"); @@ -524,13 +525,28 @@ public static WebServer Create(int port, ILog log = null) } /// - /// Static method to create webser instance with SimpleConsoleLog + /// Static method to create a webserver instance using a simple console output. + /// This method is useful for fluent configuration. /// /// /// The webserver instance. public static WebServer CreateWithConsole(string urlPrefix) { return new WebServer(urlPrefix, new SimpleConsoleLog()); + } + + /// + /// Static method to create a webserver instance using a simple console output. + /// This method is useful for fluent configuration. + /// + /// The URL prefix. + /// The routing strategy. + /// + /// The webserver instance. + /// + public static WebServer CreateWithConsole(string urlPrefix, RoutingStrategy routingStrategy) + { + return new WebServer(urlPrefix, new SimpleConsoleLog(), routingStrategy); } } } \ No newline at end of file