From a33634a29de3fbccefc7d4a914be0ca600059091 Mon Sep 17 00:00:00 2001 From: Marcin Torba Date: Sat, 20 Jan 2024 18:43:08 +0100 Subject: [PATCH 1/4] Refactor WebServer class for UnitTests. --- .../nanoFramework.WebServer.FileSystem.nfproj | 7 +- .../packages.config | 2 +- .../packages.lock.json | 6 +- nanoFramework.WebServer.sln | 8 + nanoFramework.WebServer/MethodAttribute.cs | 4 +- .../Properties/AssemblyInfo.cs | 1 - nanoFramework.WebServer/WebServer.cs | 305 +++++++++--------- .../nanoFramework.WebServer.nfproj | 5 +- nanoFramework.WebServer/packages.config | 2 +- nanoFramework.WebServer/packages.lock.json | 6 +- .../Properties/AssemblyInfo.cs | 31 ++ .../WebServerTests.cs | 79 +++++ .../nano.runsettings | 17 + .../nanoFramework.WebServer.Tests.nfproj | 73 +++++ .../packages.config | 12 + 15 files changed, 394 insertions(+), 164 deletions(-) create mode 100644 tests/nanoFramework.WebServer.Tests/Properties/AssemblyInfo.cs create mode 100644 tests/nanoFramework.WebServer.Tests/WebServerTests.cs create mode 100644 tests/nanoFramework.WebServer.Tests/nano.runsettings create mode 100644 tests/nanoFramework.WebServer.Tests/nanoFramework.WebServer.Tests.nfproj create mode 100644 tests/nanoFramework.WebServer.Tests/packages.config diff --git a/nanoFramework.WebServer.FileSystem/nanoFramework.WebServer.FileSystem.nfproj b/nanoFramework.WebServer.FileSystem/nanoFramework.WebServer.FileSystem.nfproj index a11dd06..59277d1 100644 --- a/nanoFramework.WebServer.FileSystem/nanoFramework.WebServer.FileSystem.nfproj +++ b/nanoFramework.WebServer.FileSystem/nanoFramework.WebServer.FileSystem.nfproj @@ -99,14 +99,13 @@ ..\packages\nanoFramework.System.IO.Streams.1.1.52\lib\System.IO.Streams.dll True - - ..\packages\nanoFramework.System.Net.1.10.64\lib\System.Net.dll - True - ..\packages\nanoFramework.System.IO.FileSystem.1.1.23\lib\System.IO.FileSystem.dll True + + ..\packages\nanoFramework.System.Net.1.10.68\lib\System.Net.dll + ..\packages\nanoFramework.System.Net.Http.Server.1.5.113\lib\System.Net.Http.dll True diff --git a/nanoFramework.WebServer.FileSystem/packages.config b/nanoFramework.WebServer.FileSystem/packages.config index cc8cd99..a67602b 100644 --- a/nanoFramework.WebServer.FileSystem/packages.config +++ b/nanoFramework.WebServer.FileSystem/packages.config @@ -5,7 +5,7 @@ - + diff --git a/nanoFramework.WebServer.FileSystem/packages.lock.json b/nanoFramework.WebServer.FileSystem/packages.lock.json index 976ba7d..da1f598 100644 --- a/nanoFramework.WebServer.FileSystem/packages.lock.json +++ b/nanoFramework.WebServer.FileSystem/packages.lock.json @@ -34,9 +34,9 @@ }, "nanoFramework.System.Net": { "type": "Direct", - "requested": "[1.10.64, 1.10.64]", - "resolved": "1.10.64", - "contentHash": "BM/+UzIrPqseGTRO1biYDU5zT5Mnt9Rb2IDa0milqbulAKJtZn0Sv+dwxSrT+FI27q8mgu78azhDF+fCqSmGhQ==" + "requested": "[1.10.68, 1.10.68]", + "resolved": "1.10.68", + "contentHash": "PoM4NadhtMBnuGiOPqi+TJZD8xicz0v72hG5gPGDvWXeKDCMYCMqiCqY+di2pRL3qGHkF+hh8kLOf+wBUJBWNQ==" }, "nanoFramework.System.Net.Http.Server": { "type": "Direct", diff --git a/nanoFramework.WebServer.sln b/nanoFramework.WebServer.sln index ce816cf..f8a5431 100644 --- a/nanoFramework.WebServer.sln +++ b/nanoFramework.WebServer.sln @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.WebServer.FileSystem", "nanoFramework.WebServer.FileSystem\nanoFramework.WebServer.FileSystem.nfproj", "{9D8A2D18-8036-4880-B46B-D5218247257D}" EndProject +Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.WebServer.Tests", "tests\nanoFramework.WebServer.Tests\nanoFramework.WebServer.Tests.nfproj", "{2C2B4750-2A48-4D19-9404-178AAB946482}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +33,12 @@ Global {9D8A2D18-8036-4880-B46B-D5218247257D}.Release|Any CPU.ActiveCfg = Release|Any CPU {9D8A2D18-8036-4880-B46B-D5218247257D}.Release|Any CPU.Build.0 = Release|Any CPU {9D8A2D18-8036-4880-B46B-D5218247257D}.Release|Any CPU.Deploy.0 = Release|Any CPU + {2C2B4750-2A48-4D19-9404-178AAB946482}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C2B4750-2A48-4D19-9404-178AAB946482}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C2B4750-2A48-4D19-9404-178AAB946482}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {2C2B4750-2A48-4D19-9404-178AAB946482}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C2B4750-2A48-4D19-9404-178AAB946482}.Release|Any CPU.Build.0 = Release|Any CPU + {2C2B4750-2A48-4D19-9404-178AAB946482}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/nanoFramework.WebServer/MethodAttribute.cs b/nanoFramework.WebServer/MethodAttribute.cs index cd08f0a..527a056 100644 --- a/nanoFramework.WebServer/MethodAttribute.cs +++ b/nanoFramework.WebServer/MethodAttribute.cs @@ -18,9 +18,9 @@ namespace nanoFramework.WebServer public class MethodAttribute : Attribute { /// - /// Gets or sets the method. + /// Gets the method. /// - public string Method { get; set; } + public string Method { get; } /// /// Creates a method attribute. diff --git a/nanoFramework.WebServer/Properties/AssemblyInfo.cs b/nanoFramework.WebServer/Properties/AssemblyInfo.cs index adc9caf..7bae945 100644 --- a/nanoFramework.WebServer/Properties/AssemblyInfo.cs +++ b/nanoFramework.WebServer/Properties/AssemblyInfo.cs @@ -15,4 +15,3 @@ // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - diff --git a/nanoFramework.WebServer/WebServer.cs b/nanoFramework.WebServer/WebServer.cs index 75538d5..1a513a7 100644 --- a/nanoFramework.WebServer/WebServer.cs +++ b/nanoFramework.WebServer/WebServer.cs @@ -177,76 +177,77 @@ public WebServer(int port, HttpProtocol protocol) : this(port, protocol, null) public WebServer(int port, HttpProtocol protocol, Type[] controllers) { _callbackRoutes = new ArrayList(); + RegisterControllers(controllers); + Protocol = protocol; + Port = port; + string prefix = Protocol == HttpProtocol.Http ? "http" : "https"; + _listener = new HttpListener(prefix, port); + } - if (controllers != null) + private void RegisterControllers(Type[] controllers) + { + if (controllers == null) { - foreach (var controller in controllers) + return; + } + + foreach (var controller in controllers) + { + var controlAttribs = controller.GetCustomAttributes(true); + Authentication authentication = null; + foreach (var ctrlAttrib in controlAttribs) { - var controlAttribs = controller.GetCustomAttributes(true); - Authentication authentication = null; - foreach (var ctrlAttrib in controlAttribs) + if (typeof(AuthenticationAttribute) == ctrlAttrib.GetType()) { - if (typeof(AuthenticationAttribute) == ctrlAttrib.GetType()) - { - var strAuth = ((AuthenticationAttribute)ctrlAttrib).AuthenticationMethod; - // We do support only None, Basic and ApiKey, raising an exception if this doesn't start by any - authentication = ExtractAuthentication(strAuth); - } + var strAuth = ((AuthenticationAttribute)ctrlAttrib).AuthenticationMethod; + // We do support only None, Basic and ApiKey, raising an exception if this doesn't start by any + authentication = ExtractAuthentication(strAuth); } + } - var functions = controller.GetMethods(); - foreach (var func in functions) + var functions = controller.GetMethods(); + foreach (var func in functions) + { + var attributes = func.GetCustomAttributes(true); + foreach (var attrib in attributes) { - var attributes = func.GetCustomAttributes(true); - CallbackRoutes callbackRoutes = null; - foreach (var attrib in attributes) + if (typeof(RouteAttribute) != attrib.GetType()) + { + continue; + } + + var callbackRoutes = new CallbackRoutes { - if (typeof(RouteAttribute) == attrib.GetType()) + Route = ((RouteAttribute)attrib).Route, + CaseSensitive = false, + Method = string.Empty, + Authentication = authentication, + Callback = func + }; + + foreach (var attribute in attributes) + { + if (typeof(MethodAttribute) == attribute.GetType()) + { + callbackRoutes.Method = ((MethodAttribute)attribute).Method; + } + else if (typeof(CaseSensitiveAttribute) == attribute.GetType()) + { + callbackRoutes.CaseSensitive = true; + } + else if (typeof(AuthenticationAttribute) == attribute.GetType()) { - callbackRoutes = new CallbackRoutes(); - callbackRoutes.Route = ((RouteAttribute)attrib).Route; - callbackRoutes.CaseSensitive = false; - callbackRoutes.Method = string.Empty; - callbackRoutes.Authentication = authentication; - - callbackRoutes.Callback = func; - foreach (var otherattrib in attributes) - { - if (typeof(MethodAttribute) == otherattrib.GetType()) - { - callbackRoutes.Method = ((MethodAttribute)otherattrib).Method; - } - else if (typeof(CaseSensitiveAttribute) == otherattrib.GetType()) - { - callbackRoutes.CaseSensitive = true; - } - else if (typeof(AuthenticationAttribute) == otherattrib.GetType()) - { - var strAuth = ((AuthenticationAttribute)otherattrib).AuthenticationMethod; - // A method can have a different authentication than the main class, so we override if any - callbackRoutes.Authentication = ExtractAuthentication(strAuth); - } - } - - _callbackRoutes.Add(callbackRoutes); + var strAuth = ((AuthenticationAttribute)attribute).AuthenticationMethod; + // A method can have a different authentication than the main class, so we override if any + callbackRoutes.Authentication = ExtractAuthentication(strAuth); } } - } + _callbackRoutes.Add(callbackRoutes); + Log($"{callbackRoutes.Callback.Name}, {callbackRoutes.Route}, {callbackRoutes.Method}, {callbackRoutes.CaseSensitive}"); + } } } - - foreach (var callback in _callbackRoutes) - { - var cb = (CallbackRoutes)callback; - Debug.WriteLine($"{cb.Callback.Name}, {cb.Route}, {cb.Method}, {cb.CaseSensitive}"); - } - - Protocol = protocol; - Port = port; - string prefix = Protocol == HttpProtocol.Http ? "http" : "https"; - _listener = new HttpListener(prefix, port); - Debug.WriteLine("Web server started on port " + port.ToString()); } private Authentication ExtractAuthentication(string strAuth) @@ -358,7 +359,8 @@ public bool Start() { _cancel = false; _serverThread.Start(); - Debug.WriteLine("Started server in thread " + _serverThread.GetHashCode().ToString()); + Log("Web server started on port " + Port); + Log("Started server in thread " + _serverThread.GetHashCode()); } catch { //if there is a problem, maybe due to the fact we did not wait enough @@ -368,15 +370,6 @@ public bool Start() return bStarted; } - /// - /// Restart the server. - /// - private bool Restart() - { - Stop(); - return Start(); - } - /// /// Stop the multi threaded server. /// @@ -504,54 +497,24 @@ private void StartListener() new Thread(() => { - bool isRoute = false; - string rawUrl = context.Request.RawUrl; - //This is for handling with transitory or bad requests - if (rawUrl == null) + if (context.Request.RawUrl == null) { return; } - int urlParam = rawUrl.IndexOf(ParamStart); - // Variables used only within the "for". They are here for performance reasons - bool isFound; - string routeStr; - int incForSlash; - string toCompare; bool mustAuthenticate; bool isAuthOk; - // + bool isRoute = false; - foreach (var rt in _callbackRoutes) + foreach (CallbackRoutes route in _callbackRoutes) { - CallbackRoutes route = (CallbackRoutes)rt; - - routeStr = route.Route; - incForSlash = routeStr.IndexOf('/') == 0 ? 0 : 1; - toCompare = route.CaseSensitive ? rawUrl : rawUrl.ToLower(); - - if (urlParam > 0) - { - isFound = urlParam == routeStr.Length + incForSlash; - } - else - { - isFound = toCompare.Length == routeStr.Length + incForSlash; - } - - // Matching the route name - // Matching the method type - if (!isFound || - (toCompare.IndexOf(routeStr) != incForSlash) || - (route.Method != string.Empty && context.Request.HttpMethod != route.Method) - ) + if (!IsRouteMatch(route, context.Request.HttpMethod, context.Request.RawUrl)) { continue; } - // Starting a new thread to be able to handle a new request in parallel isRoute = true; // Check auth first @@ -579,31 +542,23 @@ private void StartListener() } } - if (isAuthOk) - { - InvokeRoute(route, context); - } - else + if (!isAuthOk) { - if (route.Authentication != null && route.Authentication.AuthenticationType == AuthenticationType.Basic) + if (route.Authentication != null && + route.Authentication.AuthenticationType == AuthenticationType.Basic) { - context.Response.Headers.Add("WWW-Authenticate", $"Basic realm=\"Access to {routeStr}\""); + context.Response.Headers.Add("WWW-Authenticate", + $"Basic realm=\"Access to {route.Route}\""); } context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; context.Response.ContentLength64 = 0; - } - // When context has been handed over to WebsocketServer, it will be null at this point - if (context.Response == null) - { - //do nothing this is a websocket that is managed by a websocketserver that is responsible for the context now. - } - else - { - context.Response.Close(); - context.Close(); + HandleContextResponse(context); + return; } + + InvokeRoute(route, context); } if (!isRoute) @@ -615,36 +570,11 @@ private void StartListener() } else { - context.Response.StatusCode = 404; + context.Response.StatusCode = (int)HttpStatusCode.NotFound; context.Response.ContentLength64 = 0; } - // When context has been handed over to WebsocketServer, it will be null at this point - if (context.Response == null) - { - //do nothing this is a websocket that is managed by a websocketserver that is responsible for the context now. - } - else - { - try - { - context.Response.Close(); - } - catch - { - // Nothing on purpose - } - - try - { - context.Close(); - } - catch - { - // Nothing on purpose - } - - } + HandleContextResponse(context); } }).Start(); @@ -654,6 +584,52 @@ private void StartListener() _listener.Stop(); } } + + /// + /// Checks if route matches called resource. + /// For internal use only. + /// + /// Route to check. + /// Invoked resource method. + /// Invoked resource URL. + /// + public static bool IsRouteMatch(CallbackRoutes route, string method, string rawUrl) + { + if (route.Method == string.Empty) + { + return false; + } + + if (method != route.Method) + { + return false; + } + + var urlParam = rawUrl.IndexOf(ParamStart); + var incForSlash = route.Route.IndexOf('/') == 0 ? 0 : 1; + var rawUrlToCompare = route.CaseSensitive ? rawUrl : rawUrl.ToLower(); + var routeToCompare = route.CaseSensitive ? route.Route : route.Route.ToLower(); + bool isFound; + + if (urlParam > 0 ) + { + isFound = urlParam == routeToCompare.Length + incForSlash; + } + else + { + isFound = rawUrlToCompare.Length == routeToCompare.Length + incForSlash; + } + + // Matching the route name + // Matching the method type + if (!isFound || + (rawUrlToCompare.IndexOf(routeToCompare) != incForSlash)) + { + return false; + } + + return true; + } /// /// Method which invokes route. Can be overriden to inject custom logic. @@ -665,6 +641,43 @@ protected virtual void InvokeRoute(CallbackRoutes route, HttpListenerContext con route.Callback.Invoke(null, new object[] { new WebServerEventArgs(context) }); } + /// + /// Logs a message. + /// + /// The message to be logged. + protected virtual void Log(string message) + { + Debug.WriteLine(message); + } + + private static void HandleContextResponse(HttpListenerContext context) + { + // When context has been handed over to WebsocketServer, it will be null at this point + // Do nothing this is a websocket that is managed by a websocketserver that is responsible for the context now. + if (context.Response == null) + { + return; + } + + try + { + context.Response.Close(); + } + catch + { + // Nothing on purpose + } + + try + { + context.Close(); + } + catch + { + // Nothing on purpose + } + } + private string GetApiKeyFromHeaders(WebHeaderCollection headers) { var sec = headers.GetValues("ApiKey"); @@ -683,10 +696,10 @@ private string GetApiKeyFromHeaders(WebHeaderCollection headers) private void ListInterfaces() { NetworkInterface[] ifaces = NetworkInterface.GetAllNetworkInterfaces(); - Debug.WriteLine("Number of Interfaces: " + ifaces.Length.ToString()); + Log("Number of Interfaces: " + ifaces.Length); foreach (NetworkInterface iface in ifaces) { - Debug.WriteLine("IP: " + iface.IPv4Address + "/" + iface.IPv4SubnetMask); + Log("IP: " + iface.IPv4Address + "/" + iface.IPv4SubnetMask); } } diff --git a/nanoFramework.WebServer/nanoFramework.WebServer.nfproj b/nanoFramework.WebServer/nanoFramework.WebServer.nfproj index e583d7c..d681b66 100644 --- a/nanoFramework.WebServer/nanoFramework.WebServer.nfproj +++ b/nanoFramework.WebServer/nanoFramework.WebServer.nfproj @@ -67,9 +67,8 @@ ..\packages\nanoFramework.System.IO.Streams.1.1.52\lib\System.IO.Streams.dll True - - ..\packages\nanoFramework.System.Net.1.10.64\lib\System.Net.dll - True + + ..\packages\nanoFramework.System.Net.1.10.68\lib\System.Net.dll ..\packages\nanoFramework.System.Net.Http.Server.1.5.113\lib\System.Net.Http.dll diff --git a/nanoFramework.WebServer/packages.config b/nanoFramework.WebServer/packages.config index ababd67..9d71a71 100644 --- a/nanoFramework.WebServer/packages.config +++ b/nanoFramework.WebServer/packages.config @@ -4,7 +4,7 @@ - + diff --git a/nanoFramework.WebServer/packages.lock.json b/nanoFramework.WebServer/packages.lock.json index 7496cf8..c51a4c8 100644 --- a/nanoFramework.WebServer/packages.lock.json +++ b/nanoFramework.WebServer/packages.lock.json @@ -28,9 +28,9 @@ }, "nanoFramework.System.Net": { "type": "Direct", - "requested": "[1.10.64, 1.10.64]", - "resolved": "1.10.64", - "contentHash": "BM/+UzIrPqseGTRO1biYDU5zT5Mnt9Rb2IDa0milqbulAKJtZn0Sv+dwxSrT+FI27q8mgu78azhDF+fCqSmGhQ==" + "requested": "[1.10.68, 1.10.68]", + "resolved": "1.10.68", + "contentHash": "PoM4NadhtMBnuGiOPqi+TJZD8xicz0v72hG5gPGDvWXeKDCMYCMqiCqY+di2pRL3qGHkF+hh8kLOf+wBUJBWNQ==" }, "nanoFramework.System.Net.Http.Server": { "type": "Direct", diff --git a/tests/nanoFramework.WebServer.Tests/Properties/AssemblyInfo.cs b/tests/nanoFramework.WebServer.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a3735af --- /dev/null +++ b/tests/nanoFramework.WebServer.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyCopyright("Copyright (c) 2021 nanoFramework contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/nanoFramework.WebServer.Tests/WebServerTests.cs b/tests/nanoFramework.WebServer.Tests/WebServerTests.cs new file mode 100644 index 0000000..0f6b84b --- /dev/null +++ b/tests/nanoFramework.WebServer.Tests/WebServerTests.cs @@ -0,0 +1,79 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.WebServer.Tests +{ + [TestClass] + public class WebServerTests + { + // IsRouteMatch GET, POST, PUT, DELETE, PATCH + // IsRouteMatch CaseSensitive true/false + + [TestMethod] + public void IsRouteMatch_Should_ReturnFalseForEmptyMethod() + { + // Arrange + var route = new CallbackRoutes() + { + Method = "GET", + Route = "/api/test" + }; + + // Act + var result = WebServer.IsRouteMatch(route, "", "/api/test"); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void IsRouteMatch_Should_ReturnFalseForNotMatchingMethod() + { + // Arrange + var route = new CallbackRoutes() + { + Method = "GET", + Route = "/api/test" + }; + + // Act + var result = WebServer.IsRouteMatch(route, "POST", "/api/test"); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + [DataRow("GET", "/api/test", "/api/test")] + [DataRow("POST", "/api/test", "/api/test")] + [DataRow("PUT", "/api/test", "/api/test")] + [DataRow("PATCH", "/api/test", "/api/test")] + [DataRow("DELETE", "/api/test", "/api/test")] + [DataRow("GET", "/API/TEST", "/api/test")] + [DataRow("POST", "/API/TEST", "/api/test")] + [DataRow("PUT", "/API/TEST", "/api/test")] + [DataRow("PATCH", "/API/TEST", "/api/test")] + [DataRow("DELETE", "/API/TEST", "/api/test")] + [DataRow("GET", "/api/test", "/API/TEST")] + [DataRow("POST", "/api/test", "/api/test")] + [DataRow("PUT", "/api/test", "/API/TEST")] + [DataRow("PATCH", "/api/test", "/API/TEST")] + [DataRow("DELETE", "/api/test", "/API/TEST")] + public void IsRouteMatch_Should_ReturnTrueForMatchingMethodAndRoute(string method, string url, string invokedUrl) + { + // Arrange + var route = new CallbackRoutes() + { + Method = method, + Route = url, + CaseSensitive = false + }; + + // Act + var result = WebServer.IsRouteMatch(route, method, invokedUrl); + + // Assert + Assert.IsTrue(result); + } + } +} diff --git a/tests/nanoFramework.WebServer.Tests/nano.runsettings b/tests/nanoFramework.WebServer.Tests/nano.runsettings new file mode 100644 index 0000000..93ce85e --- /dev/null +++ b/tests/nanoFramework.WebServer.Tests/nano.runsettings @@ -0,0 +1,17 @@ + + + + + .\TestResults + 120000 + net48 + x64 + + + None + False + COM3 + + + + \ No newline at end of file diff --git a/tests/nanoFramework.WebServer.Tests/nanoFramework.WebServer.Tests.nfproj b/tests/nanoFramework.WebServer.Tests/nanoFramework.WebServer.Tests.nfproj new file mode 100644 index 0000000..2fecdae --- /dev/null +++ b/tests/nanoFramework.WebServer.Tests/nanoFramework.WebServer.Tests.nfproj @@ -0,0 +1,73 @@ + + + + $(MSBuildExtensionsPath)\nanoFramework\v1.0\ + + + + + + + Debug + AnyCPU + {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 2c2b4750-2a48-4d19-9404-178aab946482 + Library + Properties + 512 + nanoFramework.WebServer.Tests + NFUnitTest + False + true + UnitTest + v1.0 + + + + $(MSBuildProjectDirectory)\nano.runsettings + + + + + + + + ..\..\packages\nanoFramework.CoreLibrary.1.15.5\lib\mscorlib.dll + + + ..\..\packages\nanoFramework.Runtime.Events.1.11.15\lib\nanoFramework.Runtime.Events.dll + + + ..\..\packages\nanoFramework.System.Collections.1.5.31\lib\nanoFramework.System.Collections.dll + + + ..\..\packages\nanoFramework.System.Text.1.2.54\lib\nanoFramework.System.Text.dll + + + ..\..\packages\nanoFramework.TestFramework.2.1.87\lib\nanoFramework.TestFramework.dll + + + ..\..\packages\nanoFramework.TestFramework.2.1.87\lib\nanoFramework.UnitTestLauncher.exe + + + ..\..\packages\nanoFramework.System.IO.Streams.1.1.52\lib\System.IO.Streams.dll + + + ..\..\packages\nanoFramework.System.Net.1.10.68\lib\System.Net.dll + + + ..\..\packages\nanoFramework.System.Net.Http.Server.1.5.113\lib\System.Net.Http.dll + + + ..\..\packages\nanoFramework.System.Threading.1.1.32\lib\System.Threading.dll + + + + + + + + + + + \ No newline at end of file diff --git a/tests/nanoFramework.WebServer.Tests/packages.config b/tests/nanoFramework.WebServer.Tests/packages.config new file mode 100644 index 0000000..12270f3 --- /dev/null +++ b/tests/nanoFramework.WebServer.Tests/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file From fcef8442ee75bc0a0aa4ec05e38f9990ca30b9f2 Mon Sep 17 00:00:00 2001 From: Marcin Torba Date: Sun, 21 Jan 2024 14:50:44 +0100 Subject: [PATCH 2/4] Add packages.lock --- .../packages.lock.json | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/nanoFramework.WebServer.Tests/packages.lock.json diff --git a/tests/nanoFramework.WebServer.Tests/packages.lock.json b/tests/nanoFramework.WebServer.Tests/packages.lock.json new file mode 100644 index 0000000..a021e01 --- /dev/null +++ b/tests/nanoFramework.WebServer.Tests/packages.lock.json @@ -0,0 +1,61 @@ +{ + "version": 1, + "dependencies": { + ".NETnanoFramework,Version=v1.0": { + "nanoFramework.CoreLibrary": { + "type": "Direct", + "requested": "[1.15.5, 1.15.5]", + "resolved": "1.15.5", + "contentHash": "u2+GvAp1uxLrGdILACAZy+EVKOs28EQ52j8Lz7599egXZ3GBGejjnR2ofhjMQwzrJLlgtyrsx8nSLngDfJNsAg==" + }, + "nanoFramework.Runtime.Events": { + "type": "Direct", + "requested": "[1.11.15, 1.11.15]", + "resolved": "1.11.15", + "contentHash": "3uDNSTfiaewDAyi6fOMWYru0JCn/gr8DEv+Ro/V12SzojU9Dyxl5nSVOBtBXts7vErfIthB6SPiK180AMnrI8A==" + }, + "nanoFramework.System.Collections": { + "type": "Direct", + "requested": "[1.5.31, 1.5.31]", + "resolved": "1.5.31", + "contentHash": "q7G0BHkbhUzpUJiOQNlZZDSMcZEU2/QJBDiSEQAF23wOya4EBaCXS74jAVcEfkHBgOkF413jKZq5vldpjqUfUw==" + }, + "nanoFramework.System.IO.Streams": { + "type": "Direct", + "requested": "[1.1.52, 1.1.52]", + "resolved": "1.1.52", + "contentHash": "gdExWfWNSl4dgaIoVHHFmhLiRSKAabHA8ueHuErGAWd97qaoN2wSHCtvKqfOu1zuzyccbFpm4HBxVsh6bWMyXw==" + }, + "nanoFramework.System.Net": { + "type": "Direct", + "requested": "[1.10.68, 1.10.68]", + "resolved": "1.10.68", + "contentHash": "PoM4NadhtMBnuGiOPqi+TJZD8xicz0v72hG5gPGDvWXeKDCMYCMqiCqY+di2pRL3qGHkF+hh8kLOf+wBUJBWNQ==" + }, + "nanoFramework.System.Net.Http.Server": { + "type": "Direct", + "requested": "[1.5.113, 1.5.113]", + "resolved": "1.5.113", + "contentHash": "gaCPn1h0VGWRA5gG6FoWlm8W9PgoBMekOsgOKKJEhl90TEMJSI8VM/4Xlgzg8KY6tEKvUkn9uXBoVHa0f2vGLg==" + }, + "nanoFramework.System.Text": { + "type": "Direct", + "requested": "[1.2.54, 1.2.54]", + "resolved": "1.2.54", + "contentHash": "k3OutSNRMs9di42LQ+5GbpHBY07aMEZWGkaS3Mj3ZU4cWqJc4deFGzRd+LBFQl1mRGdQaM5sl/euTZdcg8R9Zg==" + }, + "nanoFramework.System.Threading": { + "type": "Direct", + "requested": "[1.1.32, 1.1.32]", + "resolved": "1.1.32", + "contentHash": "6o7Y4gH15FLuo2FWGLecABiCD57V5QMf5g/hEneV64VmhoXI8Bk7r6BDBPTfAePs738xbc1ECpA5dJmbSmtilg==" + }, + "nanoFramework.TestFramework": { + "type": "Direct", + "requested": "[2.1.87, 2.1.87]", + "resolved": "2.1.87", + "contentHash": "58nW0UByML7fciTfvLfaEwysf6319SQWmah9v6N3xF9E7jWiHnh3FfEN/wtP/E2wwufmiFZAO2CidbiuEEJmKA==" + } + } + } +} \ No newline at end of file From a4735094c401aea134d03261faef239f56585ed7 Mon Sep 17 00:00:00 2001 From: Marcin Torba Date: Sun, 21 Jan 2024 15:09:20 +0100 Subject: [PATCH 3/4] Add few more tests for route matching. --- .../WebServerTests.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/nanoFramework.WebServer.Tests/WebServerTests.cs b/tests/nanoFramework.WebServer.Tests/WebServerTests.cs index 0f6b84b..3b4cbf0 100644 --- a/tests/nanoFramework.WebServer.Tests/WebServerTests.cs +++ b/tests/nanoFramework.WebServer.Tests/WebServerTests.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) 2020 Laurent Ellerbach and the project contributors +// See LICENSE file in the project root for full license information. +// + using nanoFramework.TestFramework; using System; @@ -6,9 +11,6 @@ namespace nanoFramework.WebServer.Tests [TestClass] public class WebServerTests { - // IsRouteMatch GET, POST, PUT, DELETE, PATCH - // IsRouteMatch CaseSensitive true/false - [TestMethod] public void IsRouteMatch_Should_ReturnFalseForEmptyMethod() { @@ -59,8 +61,16 @@ public void IsRouteMatch_Should_ReturnFalseForNotMatchingMethod() [DataRow("PUT", "/api/test", "/API/TEST")] [DataRow("PATCH", "/api/test", "/API/TEST")] [DataRow("DELETE", "/api/test", "/API/TEST")] + [DataRow("GET", "/api/test", "/api/test?id=1234")] + [DataRow("GET", "/api/test", "/api/test?id=")] + [DataRow("GET", "/api/test/resource/name", "/api/test/resource/name")] + [DataRow("GET", "/api/test/resource/name", "/api/test/resource/name?id=1234")] + [DataRow("GET", "/api/test/resource/name", "/api/test/resource/name?test=")] + [DataRow("GET", "/api/test/resource/name", "/api/test/resource/name?")] + [DataRow("GET", "/api/test/resource/name", "/api/test/resource/name?test=&id=123&app=something")] public void IsRouteMatch_Should_ReturnTrueForMatchingMethodAndRoute(string method, string url, string invokedUrl) { + Console.WriteLine(invokedUrl); // Arrange var route = new CallbackRoutes() { @@ -75,5 +85,7 @@ public void IsRouteMatch_Should_ReturnTrueForMatchingMethodAndRoute(string metho // Assert Assert.IsTrue(result); } + + // TODO: Case sensitive tests } } From 0c4e97eceed1faeae7205adef1a64be993021cd6 Mon Sep 17 00:00:00 2001 From: Marcin Torba Date: Sun, 21 Jan 2024 15:17:56 +0100 Subject: [PATCH 4/4] Revert to Debug.WriteLine --- nanoFramework.WebServer/WebServer.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/nanoFramework.WebServer/WebServer.cs b/nanoFramework.WebServer/WebServer.cs index 1a513a7..f32ba94 100644 --- a/nanoFramework.WebServer/WebServer.cs +++ b/nanoFramework.WebServer/WebServer.cs @@ -244,7 +244,7 @@ private void RegisterControllers(Type[] controllers) } _callbackRoutes.Add(callbackRoutes); - Log($"{callbackRoutes.Callback.Name}, {callbackRoutes.Route}, {callbackRoutes.Method}, {callbackRoutes.CaseSensitive}"); + Debug.WriteLine($"{callbackRoutes.Callback.Name}, {callbackRoutes.Route}, {callbackRoutes.Method}, {callbackRoutes.CaseSensitive}"); } } } @@ -359,8 +359,8 @@ public bool Start() { _cancel = false; _serverThread.Start(); - Log("Web server started on port " + Port); - Log("Started server in thread " + _serverThread.GetHashCode()); + Debug.WriteLine("Web server started on port " + Port); + Debug.WriteLine("Started server in thread " + _serverThread.GetHashCode()); } catch { //if there is a problem, maybe due to the fact we did not wait enough @@ -641,15 +641,6 @@ protected virtual void InvokeRoute(CallbackRoutes route, HttpListenerContext con route.Callback.Invoke(null, new object[] { new WebServerEventArgs(context) }); } - /// - /// Logs a message. - /// - /// The message to be logged. - protected virtual void Log(string message) - { - Debug.WriteLine(message); - } - private static void HandleContextResponse(HttpListenerContext context) { // When context has been handed over to WebsocketServer, it will be null at this point @@ -696,10 +687,10 @@ private string GetApiKeyFromHeaders(WebHeaderCollection headers) private void ListInterfaces() { NetworkInterface[] ifaces = NetworkInterface.GetAllNetworkInterfaces(); - Log("Number of Interfaces: " + ifaces.Length); + Debug.WriteLine("Number of Interfaces: " + ifaces.Length); foreach (NetworkInterface iface in ifaces) { - Log("IP: " + iface.IPv4Address + "/" + iface.IPv4SubnetMask); + Debug.WriteLine("IP: " + iface.IPv4Address + "/" + iface.IPv4SubnetMask); } }