From cbf70098318e7936ad01d91fba93697b57b4dc75 Mon Sep 17 00:00:00 2001 From: cypherpotato Date: Sat, 6 Jul 2024 02:17:34 -0300 Subject: [PATCH] wip --- src/{Ext => Entity}/CircularBuffer.cs | 20 +- src/Entity/MultipartFormCollection.cs | 2 +- src/Entity/MultipartObject.cs | 91 +++++---- src/Entity/StringValue.cs | 6 +- src/Http/Handlers/HttpServerHandler.cs | 18 +- .../Hosting}/ConfigurationContext.cs | 5 +- src/Http/Hosting/HttpServerHostContext.cs | 8 +- .../Hosting/HttpServerHostContextBuilder.cs | 6 +- src/Http/Hosting/IConfigurationReader.cs | 2 - .../InitializationParameterCollection.cs | 2 +- src/Http/HtmlContent.cs | 13 +- src/Http/HttpContext.cs | 8 +- src/Http/HttpContextBagRepository.cs | 2 +- src/Http/HttpRequest.cs | 23 ++- src/Http/HttpResponse.cs | 2 + src/Http/HttpServer.cs | 2 +- src/Http/HttpServerConfiguration.cs | 2 +- src/Http/HttpServerExecutionResult.cs | 2 +- src/Http/HttpServerFlags.cs | 3 +- src/Http/HttpServer__Core.cs | 4 +- src/Http/HttpStatusInformation.cs | 52 +++--- src/Http/ListeningHost.cs | 2 +- src/Http/ListeningHostRepository.cs | 4 +- src/Http/ListeningPort.cs | 12 +- src/Http/LogStream.cs | 173 +++++++++--------- src/Http/RotatingLogPolicy.cs | 109 +++++++---- src/Http/Streams/HttpRequestEventSource.cs | 4 +- src/Http/Streams/HttpWebSocket.cs | 2 +- src/Internal/SR.cs | 2 +- src/Routing/Route.cs | 2 +- src/Routing/RouteDefinition.cs | 1 + src/Routing/Router.cs | 18 +- src/Routing/Router__CoreInvoker.cs | 10 +- src/Routing/Router__CoreSetters.cs | 129 +++++++------ src/Routing/ValueResult.cs | 2 +- 35 files changed, 401 insertions(+), 342 deletions(-) rename src/{Ext => Entity}/CircularBuffer.cs (80%) rename src/{Internal/ServiceProvider => Http/Hosting}/ConfigurationContext.cs (93%) diff --git a/src/Ext/CircularBuffer.cs b/src/Entity/CircularBuffer.cs similarity index 80% rename from src/Ext/CircularBuffer.cs rename to src/Entity/CircularBuffer.cs index 58097e1..66e398d 100644 --- a/src/Ext/CircularBuffer.cs +++ b/src/Entity/CircularBuffer.cs @@ -16,11 +16,12 @@ namespace Sisk.Core.Entity; /// /// The type of elements stored in the buffer. /// -public class CircularBuffer : IEnumerable +public class CircularBuffer : IEnumerable, IReadOnlyList { - T[] items; + private T[] items; - int capacity = 0; + int capacity = 0, + addedItems = 0; /// /// Creates an new instance of the with the specified @@ -29,6 +30,7 @@ public class CircularBuffer : IEnumerable /// The circular buffer capacity. public CircularBuffer(int capacity) { + if (capacity <= 0) throw new ArgumentOutOfRangeException("Capacity cannot be less or equals to zero."); this.capacity = capacity; items = new T[capacity]; } @@ -47,6 +49,7 @@ public void Add(T item) items[i] = items[i - 1]; } + addedItems = Math.Min(Capacity, addedItems + 1); items[0] = item; } } @@ -57,6 +60,7 @@ public void Add(T item) public void Clear() { items = new T[capacity]; + addedItems = 0; } /// @@ -65,7 +69,7 @@ public void Clear() /// The new size for this circular buffer. public void Resize(int capacity) { - if (capacity <= 0) throw new ArgumentException("Capacity cannot be less or equals to zero."); + if (capacity <= 0) throw new ArgumentOutOfRangeException("Capacity cannot be less or equals to zero."); Array.Resize(ref items, capacity); this.capacity = capacity; } @@ -86,6 +90,14 @@ public void Resize(int capacity) /// public int Capacity { get => capacity; } + /// + /// Gets the amount of added items in this circular buffer. + /// + public int Count => addedItems; + + /// + public T this[int index] => items[index]; + /// /// public IEnumerator GetEnumerator() diff --git a/src/Entity/MultipartFormCollection.cs b/src/Entity/MultipartFormCollection.cs index de09bb2..10f8e40 100644 --- a/src/Entity/MultipartFormCollection.cs +++ b/src/Entity/MultipartFormCollection.cs @@ -16,7 +16,7 @@ namespace Sisk.Core.Entity; /// /// Represents an class which hosts an multipart form data contents. /// -public class MultipartFormCollection : IEnumerable, IReadOnlyList, IReadOnlyCollection +public sealed class MultipartFormCollection : IEnumerable, IReadOnlyList, IReadOnlyCollection { private readonly IList _items; diff --git a/src/Entity/MultipartObject.cs b/src/Entity/MultipartObject.cs index 5daf22f..28cb30c 100644 --- a/src/Entity/MultipartObject.cs +++ b/src/Entity/MultipartObject.cs @@ -8,6 +8,7 @@ // Repository: https://github.com/sisk-http/core using Sisk.Core.Http; +using Sisk.Core.Internal; using System.Collections.Specialized; using System.Text; @@ -16,7 +17,7 @@ namespace Sisk.Core.Entity /// /// Represents an multipart/form-data object. /// - public class MultipartObject + public sealed class MultipartObject { private readonly Encoding _baseEncoding; @@ -56,7 +57,7 @@ public class MultipartObject } /// - /// Reads the content bytes using the Http request content-encoding. + /// Reads the content bytes using the HTTP request content-encoding. /// public string? ReadContentAsString() { @@ -68,46 +69,57 @@ public class MultipartObject /// public MultipartObjectCommonFormat GetCommonFileFormat() { - IEnumerable len8 = ContentBytes.Take(8); - IEnumerable len4 = ContentBytes.Take(4); - IEnumerable len3 = ContentBytes.Take(3); + int byteLen = ContentBytes.Length; - if (len8.SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 })) + if (byteLen >= 8) { - return MultipartObjectCommonFormat.PNG; - } - else if (len4.SequenceEqual(new byte[] { (byte)'R', (byte)'I', (byte)'F', (byte)'F' })) - { - return MultipartObjectCommonFormat.WEBP; - } - else if (len4.SequenceEqual(new byte[] { 0x25, 0x50, 0x44, 0x46 })) - { - return MultipartObjectCommonFormat.PDF; - } - else if (len3.SequenceEqual(new byte[] { 0xFF, 0xD8, 0xFF })) - { - return MultipartObjectCommonFormat.JPEG; - } - else if (len3.SequenceEqual(new byte[] { 73, 73, 42 })) - { - return MultipartObjectCommonFormat.TIFF; - } - else if (len3.SequenceEqual(new byte[] { 77, 77, 42 })) - { - return MultipartObjectCommonFormat.TIFF; - } - else if (len3.SequenceEqual(new byte[] { 0x42, 0x4D })) - { - return MultipartObjectCommonFormat.BMP; - } - else if (len3.SequenceEqual(new byte[] { 0x47, 0x46, 0x49 })) + Span len8 = ContentBytes.AsSpan(0, 8); + + if (len8.SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 })) + { + return MultipartObjectCommonFormat.PNG; + } + } + if (byteLen >= 4) { - return MultipartObjectCommonFormat.GIF; + Span len4 = ContentBytes.AsSpan(0, 4); + + if (len4.SequenceEqual(new byte[] { (byte)'R', (byte)'I', (byte)'F', (byte)'F' })) + { + return MultipartObjectCommonFormat.WEBP; + } + else if (len4.SequenceEqual(new byte[] { 0x25, 0x50, 0x44, 0x46 })) + { + return MultipartObjectCommonFormat.PDF; + } } - else + if (byteLen >= 3) { - return MultipartObjectCommonFormat.Unknown; + Span len3 = ContentBytes.AsSpan(0, 3); + + if (len3.SequenceEqual(new byte[] { 0xFF, 0xD8, 0xFF })) + { + return MultipartObjectCommonFormat.JPEG; + } + else if (len3.SequenceEqual(new byte[] { 73, 73, 42 })) + { + return MultipartObjectCommonFormat.TIFF; + } + else if (len3.SequenceEqual(new byte[] { 77, 77, 42 })) + { + return MultipartObjectCommonFormat.TIFF; + } + else if (len3.SequenceEqual(new byte[] { 0x42, 0x4D })) + { + return MultipartObjectCommonFormat.BMP; + } + else if (len3.SequenceEqual(new byte[] { 0x47, 0x46, 0x49 })) + { + return MultipartObjectCommonFormat.GIF; + } } + + return MultipartObjectCommonFormat.Unknown; } internal MultipartObject(NameValueCollection headers, string? filename, string name, byte[]? body, Encoding encoding) @@ -120,9 +132,12 @@ internal MultipartObject(NameValueCollection headers, string? filename, string n _baseEncoding = encoding; } + // + // we should rewrite it using Spans<>, but there are so many code and it would take + // days... internal static MultipartFormCollection ParseMultipartObjects(HttpRequest req) { - string? contentType = req.Headers["Content-Type"]; + string? contentType = req.Headers[HttpKnownHeaderNames.ContentType]; if (contentType is null) { throw new InvalidOperationException(SR.MultipartObject_ContentTypeMissing); @@ -252,7 +267,7 @@ bool Equals(byte[] source, byte[] separator, int index) } // parse field name - string[] val = headers["Content-Disposition"]?.Split(';') ?? Array.Empty(); + string[] val = headers[HttpKnownHeaderNames.ContentDisposition]?.Split(';') ?? Array.Empty(); string? fieldName = null; string? fieldFilename = null; diff --git a/src/Entity/StringValue.cs b/src/Entity/StringValue.cs index d6cbd79..fff4bc1 100644 --- a/src/Entity/StringValue.cs +++ b/src/Entity/StringValue.cs @@ -12,9 +12,9 @@ namespace Sisk.Core.Entity; /// -/// Represents an instance that hosts a string value and allows conversion to common types. +/// Represents an option/monad item that wraps an string value and allows conversion to most common types. /// -public struct StringValue : ICloneable, IEquatable, IComparable +public readonly struct StringValue : ICloneable, IEquatable, IComparable { private readonly string? _ref; private readonly string argName; @@ -33,7 +33,7 @@ internal StringValue(string name, string type, string? data) public string Name { get => argName; } /// - /// Gets the value slot of this . + /// Gets the value of the current string if it has been assigned a valid underlying value. /// public string? Value { get => _ref; } diff --git a/src/Http/Handlers/HttpServerHandler.cs b/src/Http/Handlers/HttpServerHandler.cs index 176db09..9116575 100644 --- a/src/Http/Handlers/HttpServerHandler.cs +++ b/src/Http/Handlers/HttpServerHandler.cs @@ -19,7 +19,7 @@ public abstract class HttpServerHandler /// /// Method that is called immediately before starting the . /// - /// The Http server entity which is starting. + /// The HTTP server entity which is starting. protected virtual void OnServerStarting(HttpServer server) { } internal void InvokeOnServerStarting(HttpServer server) => OnServerStarting(server); @@ -27,7 +27,7 @@ protected virtual void OnServerStarting(HttpServer server) { } /// Method that is called immediately after starting the , when it's /// ready and listening. /// - /// The Http server entity which is ready. + /// The HTTP server entity which is ready. protected virtual void OnServerStarted(HttpServer server) { } internal void InvokeOnServerStarted(HttpServer server) => OnServerStarted(server); @@ -35,7 +35,7 @@ protected virtual void OnServerStarted(HttpServer server) { } /// Method that is called before the stop, when it is /// stopping from listening requests. /// - /// The Http server entity which is stopping. + /// The HTTP server entity which is stopping. protected virtual void OnServerStopping(HttpServer server) { } internal void InvokeOnServerStopping(HttpServer server) => OnServerStopping(server); @@ -43,12 +43,12 @@ protected virtual void OnServerStopping(HttpServer server) { } /// Method that is called after the is stopped, meaning /// it has stopped from listening to requests. /// - /// The Http server entity which has stopped. + /// The HTTP server entity which has stopped. protected virtual void OnServerStopped(HttpServer server) { } internal void InvokeOnServerStopped(HttpServer server) => OnServerStopped(server); /// - /// Method that is called when an is binded to the Http server. + /// Method that is called when an is binded to the HTTP server. /// /// The router entity which is binded. protected virtual void OnSetupRouter(Router router) { } @@ -65,22 +65,22 @@ protected virtual void OnContextBagCreated(HttpContextBagRepository contextBag) /// /// Method that is called when an is received in the - /// Http server. + /// HTTP server. /// - /// The connecting Http request entity. + /// The connecting HTTP request entity. protected virtual void OnHttpRequestOpen(HttpRequest request) { } internal void InvokeOnHttpRequestOpen(HttpRequest request) => OnHttpRequestOpen(request); /// /// Method that is called when an is closed in the - /// Http server. + /// HTTP server. /// /// The result of the execution of the request. protected virtual void OnHttpRequestClose(HttpServerExecutionResult result) { } internal void InvokeOnHttpRequestClose(HttpServerExecutionResult result) => OnHttpRequestClose(result); /// - /// Method that is called when an exception is caught in the Http server. This method is called + /// Method that is called when an exception is caught in the HTTP server. This method is called /// regardless of whether is enabled or not. /// /// The exception object. diff --git a/src/Internal/ServiceProvider/ConfigurationContext.cs b/src/Http/Hosting/ConfigurationContext.cs similarity index 93% rename from src/Internal/ServiceProvider/ConfigurationContext.cs rename to src/Http/Hosting/ConfigurationContext.cs index 4d60106..1193e04 100644 --- a/src/Internal/ServiceProvider/ConfigurationContext.cs +++ b/src/Http/Hosting/ConfigurationContext.cs @@ -7,10 +7,7 @@ // File name: ConfigurationContext.cs // Repository: https://github.com/sisk-http/core -using Sisk.Core.Http; -using Sisk.Core.Http.Hosting; - -namespace Sisk.Core.Internal.ServiceProvider; +namespace Sisk.Core.Http.Hosting; /// /// Represents a reading context for a portable configuration file. diff --git a/src/Http/Hosting/HttpServerHostContext.cs b/src/Http/Hosting/HttpServerHostContext.cs index 224b4de..a10a5b2 100644 --- a/src/Http/Hosting/HttpServerHostContext.cs +++ b/src/Http/Hosting/HttpServerHostContext.cs @@ -15,7 +15,7 @@ namespace Sisk.Core.Http.Hosting; /// /// Represents the class that hosts most of the components needed to run a Sisk application. /// -public class HttpServerHostContext : IDisposable +public sealed class HttpServerHostContext : IDisposable { /// /// Gets the initialization parameters from the portable configuration file. @@ -23,7 +23,7 @@ public class HttpServerHostContext : IDisposable public InitializationParameterCollection Parameters { get; } = new InitializationParameterCollection(); /// - /// Gets the host Http server. + /// Gets the host HTTP server. /// public HttpServer HttpServer { get; private set; } @@ -66,7 +66,7 @@ internal HttpServerHostContext(HttpServer httpServer) } /// - /// Starts the Http server. + /// Starts the HTTP server. /// /// Optional. Specifies if the application should pause the main application loop. /// Optional. Specifies if the application should write the listening prefix welcome message. @@ -86,7 +86,7 @@ public void Start(bool verbose = true, bool preventHault = true) } /// - /// Asynchronously starts the Http server. + /// Asynchronously starts the HTTP server. /// /// Optional. Specifies if the application should pause the main application loop. /// Optional. Specifies if the application should write the listening prefix welcome message. diff --git a/src/Http/Hosting/HttpServerHostContextBuilder.cs b/src/Http/Hosting/HttpServerHostContextBuilder.cs index 57e7e9b..91f8886 100644 --- a/src/Http/Hosting/HttpServerHostContextBuilder.cs +++ b/src/Http/Hosting/HttpServerHostContextBuilder.cs @@ -60,9 +60,9 @@ public HttpServerHostContext Build() } /// - /// Defines a function that will be executed immediately before starting the Http server. + /// Defines a function that will be executed immediately before starting the HTTP server. /// - /// The action which will be executed before the Http server start. + /// The action which will be executed before the HTTP server start. public HttpServerHostContextBuilder UseBootstraper(Action bootstrapAction) { _context.HttpServer.handler._default._serverBootstraping = bootstrapAction; @@ -134,7 +134,7 @@ public HttpServerHostContextBuilder UseListeningPort(string uri) /// /// Sets the main of this host builder. /// - /// The object which the Http server will listen to. + /// The object which the HTTP server will listen to. public HttpServerHostContextBuilder UseListeningPort(ListeningPort listeningPort) { _context.ServerConfiguration.ListeningHosts[0].Ports[0] = listeningPort; diff --git a/src/Http/Hosting/IConfigurationReader.cs b/src/Http/Hosting/IConfigurationReader.cs index 9c7bfb7..4f8d5d0 100644 --- a/src/Http/Hosting/IConfigurationReader.cs +++ b/src/Http/Hosting/IConfigurationReader.cs @@ -7,8 +7,6 @@ // File name: IConfigurationReader.cs // Repository: https://github.com/sisk-http/core -using Sisk.Core.Internal.ServiceProvider; - namespace Sisk.Core.Http.Hosting; /// diff --git a/src/Http/Hosting/InitializationParameterCollection.cs b/src/Http/Hosting/InitializationParameterCollection.cs index 5e46ebb..ec6000d 100644 --- a/src/Http/Hosting/InitializationParameterCollection.cs +++ b/src/Http/Hosting/InitializationParameterCollection.cs @@ -18,7 +18,7 @@ namespace Sisk.Core.Http.Hosting; /// /// Provides a collection of HTTP server initialization variables. /// -public class InitializationParameterCollection : IDictionary +public sealed class InitializationParameterCollection : IDictionary { private readonly NameValueCollection _decorator = new NameValueCollection(); private bool _isReadonly = false; diff --git a/src/Http/HtmlContent.cs b/src/Http/HtmlContent.cs index 548a59e..7e909cc 100644 --- a/src/Http/HtmlContent.cs +++ b/src/Http/HtmlContent.cs @@ -15,7 +15,7 @@ namespace Sisk.Core.Http; /// /// Provides HTTP content based on HTML contents. /// -public class HtmlContent : ByteArrayContent +public class HtmlContent : StringContent { /// /// Gets or sets the default encoding which will be used on constructors. @@ -27,10 +27,8 @@ public class HtmlContent : ByteArrayContent /// /// The HTML content string. /// The encoding which will encode the HTML contents. - public HtmlContent(string content, Encoding encoding) : base(GetContentBytes(content, encoding)) + public HtmlContent(string content, Encoding encoding) : base(content, encoding, "text/html") { - Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/html"); - Headers.ContentType.CharSet = encoding.HeaderName; } /// @@ -38,11 +36,4 @@ public HtmlContent(string content, Encoding encoding) : base(GetContentBytes(con /// /// The HTML content string. public HtmlContent(string content) : this(content, DefaultEncoding) { } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte[] GetContentBytes(string content, Encoding encoder) - { - ArgumentNullException.ThrowIfNull(content, nameof(content)); - return (encoder ?? Encoding.UTF8).GetBytes(content); - } } diff --git a/src/Http/HttpContext.cs b/src/Http/HttpContext.cs index 31101c4..9046f73 100644 --- a/src/Http/HttpContext.cs +++ b/src/Http/HttpContext.cs @@ -13,9 +13,9 @@ namespace Sisk.Core.Http { /// - /// Represents an context for Http requests. + /// Represents an context that is shared in a entire HTTP session. /// - public class HttpContext + public sealed class HttpContext { /// /// Gets or sets an indicating HTTP headers which @@ -34,7 +34,7 @@ public class HttpContext public HttpContextBagRepository RequestBag { get; set; } = new HttpContextBagRepository(); /// - /// Gets the context Http Server instance. + /// Gets the context HTTP Server instance. /// public HttpServer HttpServer { get; private set; } @@ -49,7 +49,7 @@ public class HttpContext public HttpRequest Request { get; private set; } /// - /// Gets the matched Http Route object from the Router. + /// Gets the matched HTTP Route object from the Router. /// public Route? MatchedRoute { get; internal set; } diff --git a/src/Http/HttpContextBagRepository.cs b/src/Http/HttpContextBagRepository.cs index 78291fe..3f13c80 100644 --- a/src/Http/HttpContextBagRepository.cs +++ b/src/Http/HttpContextBagRepository.cs @@ -14,6 +14,6 @@ namespace Sisk.Core.Http; /// /// Represents a repository of information stored over the lifetime of a request. /// -public class HttpContextBagRepository : TypedValueDictionary +public sealed class HttpContextBagRepository : TypedValueDictionary { } diff --git a/src/Http/HttpRequest.cs b/src/Http/HttpRequest.cs index e7db93f..02aec7b 100644 --- a/src/Http/HttpRequest.cs +++ b/src/Http/HttpRequest.cs @@ -178,21 +178,28 @@ public NameValueCollection Cookies { string cookieExpression = cookiePairs[i]; + if (string.IsNullOrWhiteSpace(cookieExpression)) + continue; + int eqPos = cookieExpression.IndexOf('='); if (eqPos < 0) { - throw new HttpRequestException(SR.HttpRequest_InvalidCookieSyntax); + cookies[cookieExpression] = ""; + continue; } + else + { + string cookieName = cookieExpression.Substring(0, eqPos).Trim(); + string cookieValue = cookieExpression.Substring(eqPos + 1).Trim(); - string cookieName = cookieExpression.Substring(0, eqPos).Trim(); - string cookieValue = cookieExpression.Substring(eqPos + 1).Trim(); + if (string.IsNullOrWhiteSpace(cookieName)) + { + // provided an name/value pair, but no name + continue; + } - if (string.IsNullOrWhiteSpace(cookieName)) - { - throw new HttpRequestException(SR.HttpRequest_InvalidCookieSyntax); + cookies[cookieName] = WebUtility.UrlDecode(cookieValue); } - - cookies[cookieName] = WebUtility.UrlDecode(cookieValue); } } } diff --git a/src/Http/HttpResponse.cs b/src/Http/HttpResponse.cs index 0753be3..ddda57f 100644 --- a/src/Http/HttpResponse.cs +++ b/src/Http/HttpResponse.cs @@ -40,6 +40,7 @@ public static HttpResponse CreateEmptyResponse() /// Creates an new redirect with given location header. /// /// The absolute or relative URL path which the client must be redirected to. + [Obsolete("This method is deprecated and should not be used.")] public static HttpResponse CreateRedirectResponse(string location) { HttpResponse res = new HttpResponse(); @@ -53,6 +54,7 @@ public static HttpResponse CreateRedirectResponse(string location) /// Creates an new redirect which redirects to the route path defined in a action. The provided method must have a valid RouteAttribute attribute. /// /// The receiving action contains a RouteAttribute attribute and its method is GET or ANY. + [Obsolete("This method is deprecated and should not be used.")] public static HttpResponse CreateRedirectResponse(RouteAction action) { var definition = RouteDefinition.GetFromCallback(action); diff --git a/src/Http/HttpServer.cs b/src/Http/HttpServer.cs index 92f85d0..f56a235 100644 --- a/src/Http/HttpServer.cs +++ b/src/Http/HttpServer.cs @@ -18,7 +18,7 @@ namespace Sisk.Core.Http /// /// Provides an lightweight HTTP server powered by Sisk. /// - public partial class HttpServer : IDisposable + public sealed partial class HttpServer : IDisposable { /// /// Gets the X-Powered-By Sisk header value. diff --git a/src/Http/HttpServerConfiguration.cs b/src/Http/HttpServerConfiguration.cs index c34d549..e39dc25 100644 --- a/src/Http/HttpServerConfiguration.cs +++ b/src/Http/HttpServerConfiguration.cs @@ -15,7 +15,7 @@ namespace Sisk.Core.Http /// /// Provides execution parameters for an . /// - public class HttpServerConfiguration : IDisposable + public sealed class HttpServerConfiguration : IDisposable { private int _maximumContentLength = 0; diff --git a/src/Http/HttpServerExecutionResult.cs b/src/Http/HttpServerExecutionResult.cs index abcab33..1c54da4 100644 --- a/src/Http/HttpServerExecutionResult.cs +++ b/src/Http/HttpServerExecutionResult.cs @@ -26,7 +26,7 @@ namespace Sisk.Core.Http /// /// Represents the results of executing a request on the server. /// - public class HttpServerExecutionResult + public sealed class HttpServerExecutionResult { /// /// Represents the request received in this diagnosis. diff --git a/src/Http/HttpServerFlags.cs b/src/Http/HttpServerFlags.cs index bb5d5cc..385a6d9 100644 --- a/src/Http/HttpServerFlags.cs +++ b/src/Http/HttpServerFlags.cs @@ -8,14 +8,13 @@ // Repository: https://github.com/sisk-http/core using Sisk.Core.Routing; -using System.Net; namespace Sisk.Core.Http { /// /// Provides advanced fields for Sisk server behavior. /// - public class HttpServerFlags + public sealed class HttpServerFlags { /// /// Determines if the HTTP server should drop requests which has content body in GET, OPTIONS, HEAD and TRACE methods. diff --git a/src/Http/HttpServer__Core.cs b/src/Http/HttpServer__Core.cs index 3df2107..e19c211 100644 --- a/src/Http/HttpServer__Core.cs +++ b/src/Http/HttpServer__Core.cs @@ -81,7 +81,7 @@ private void UnbindRouters() for (int i = 0; i < ServerConfiguration.ListeningHosts.Count; i++) { var lh = ServerConfiguration.ListeningHosts[i]; - if (lh.Router is { } router && ReferenceEquals(this, router.ParentServer)) + if (lh.Router is { } router && ReferenceEquals(this, router.parentServer)) { router.FreeHttpServer(); } @@ -93,7 +93,7 @@ private void BindRouters() for (int i = 0; i < ServerConfiguration.ListeningHosts.Count; i++) { var lh = ServerConfiguration.ListeningHosts[i]; - if (lh.Router is { } router && router.ParentServer is null) + if (lh.Router is { } router && router.parentServer is null) { router.BindServer(this); } diff --git a/src/Http/HttpStatusInformation.cs b/src/Http/HttpStatusInformation.cs index 21f45cc..3fbc530 100644 --- a/src/Http/HttpStatusInformation.cs +++ b/src/Http/HttpStatusInformation.cs @@ -17,10 +17,10 @@ namespace Sisk.Core.Http /// /// Represents a structure that holds an HTTP response status information, with it's status code and description. /// - public struct HttpStatusInformation : IComparable, IEquatable + public readonly struct HttpStatusInformation : IEquatable, IEquatable { - private int __statusCode; - private string __description; + private readonly int __statusCode; + private readonly string __description; /// /// Gets or sets the short description of the HTTP message. @@ -31,11 +31,6 @@ public struct HttpStatusInformation : IComparable, IEquatable __description; - set - { - ValidateDescription(value); - __description = value; - } } /// @@ -44,11 +39,6 @@ public string Description public int StatusCode { get => __statusCode; - set - { - ValidateStatusCode(value); - __statusCode = value; - } } /// @@ -155,19 +145,6 @@ public readonly bool Equals(HttpStatusInformation other) return other.__statusCode.Equals(this.__statusCode) && other.__description.Equals(this.__description); } - /// - /// - public int CompareTo(object? obj) - { - if (obj is HttpStatusInformation other) - { - if (other.__statusCode == this.__statusCode) return 0; - if (other.__statusCode > this.__statusCode) return 1; - if (other.__statusCode < this.__statusCode) return -1; - } - return -1; - } - /// /// public override bool Equals([NotNullWhen(true)] object? obj) @@ -183,7 +160,7 @@ public override bool Equals([NotNullWhen(true)] object? obj) /// public override int GetHashCode() { - return this.__statusCode.GetHashCode() ^ this.__description.GetHashCode(); + return HashCode.Combine(this.__statusCode, this.__description); } /// @@ -193,5 +170,26 @@ public override string ToString() { return $"{__statusCode} {__description}"; } + + /// + /// + public bool Equals(HttpStatusCode other) + { + return this.StatusCode == (int)other; + } + + /// + /// + public static implicit operator HttpStatusInformation(HttpStatusCode statusCode) + { + return new HttpStatusInformation(statusCode); + } + + /// + /// + public static implicit operator HttpStatusInformation(int statusCode) + { + return new HttpStatusInformation(statusCode); + } } } diff --git a/src/Http/ListeningHost.cs b/src/Http/ListeningHost.cs index 9ad0b87..2d58af6 100644 --- a/src/Http/ListeningHost.cs +++ b/src/Http/ListeningHost.cs @@ -15,7 +15,7 @@ namespace Sisk.Core.Http /// /// Provides a structure to contain the fields needed by an http server host. /// - public class ListeningHost + public sealed class ListeningHost { private ListeningPort[] _ports = null!; internal ushort[] _numericPorts = null!; diff --git a/src/Http/ListeningHostRepository.cs b/src/Http/ListeningHostRepository.cs index 7c4cd78..9dd89c7 100644 --- a/src/Http/ListeningHostRepository.cs +++ b/src/Http/ListeningHostRepository.cs @@ -16,7 +16,7 @@ namespace Sisk.Core.Http /// /// Represents an fluent repository of that can add, modify, or remove listening hosts while an is running. /// - public class ListeningHostRepository : ICollection, IEnumerable + public sealed class ListeningHostRepository : ICollection, IEnumerable { private readonly List _hosts = new List(); @@ -135,7 +135,7 @@ IEnumerator IEnumerable.GetEnumerator() } } } - } + } return null; } } diff --git a/src/Http/ListeningPort.cs b/src/Http/ListeningPort.cs index e4fb6d7..65743f1 100644 --- a/src/Http/ListeningPort.cs +++ b/src/Http/ListeningPort.cs @@ -38,22 +38,22 @@ namespace Sisk.Core.Http /// http://182.32.112.223:5251/ /// /// - public struct ListeningPort : IEquatable + public readonly struct ListeningPort : IEquatable { /// /// Gets or sets the DNS hostname pattern where this listening port will refer. /// - public string Hostname { get; set; } + public string Hostname { get; } /// /// Gets or sets the port where this listening port will refer. /// - public ushort Port { get; set; } + public ushort Port { get; } /// /// Gets or sets whether the server should listen to this port securely (SSL). /// - public bool Secure { get; set; } + public bool Secure { get; } /// /// Creates an new instance with default parameters. @@ -156,7 +156,7 @@ public override bool Equals(object? obj) /// The another object which will be used to compare. public bool Equals(ListeningPort other) { - return other.Secure == Secure && other.Port == Port && string.Compare(this.Hostname, other.Hostname, true) == 0; + return this.GetHashCode().Equals(other.GetHashCode()); } /// @@ -164,7 +164,7 @@ public bool Equals(ListeningPort other) /// public override int GetHashCode() { - return (Secure.GetHashCode()) ^ (Port.GetHashCode()) ^ (Hostname.GetHashCode()); + return HashCode.Combine(this.Hostname, this.Port, this.Secure); } /// diff --git a/src/Http/LogStream.cs b/src/Http/LogStream.cs index e02a80b..f516b4c 100644 --- a/src/Http/LogStream.cs +++ b/src/Http/LogStream.cs @@ -8,23 +8,26 @@ // Repository: https://github.com/sisk-http/core using Sisk.Core.Entity; +using System.Collections.Concurrent; using System.Text; namespace Sisk.Core.Http { /// - /// Provides a managed, asynchronous log writer which supports writing safe data to log files or streams. + /// Provides a managed, asynchronous log writer which supports writing safe data to log files or text streams. /// public class LogStream : IDisposable { - private readonly Queue logQueue = new Queue(); - private readonly ManualResetEvent watcher = new ManualResetEvent(false); - private readonly ManualResetEvent waiter = new ManualResetEvent(false); - private readonly ManualResetEvent terminate = new ManualResetEvent(false); - private readonly Thread loggingThread; - private bool isBlocking = false; + readonly BlockingCollection logQueue = new BlockingCollection(); + string? filePath; + private bool isDisposed; + Thread consumerThread; + AutoResetEvent logQueueEmptyEvent = new AutoResetEvent(true); + CircularBuffer? _bufferingContent = null; + internal RotatingLogPolicy? rotatingLogPolicy = null; - internal CircularBuffer? _bufferingContent = null; + internal ManualResetEvent rotatingPolicyLocker = new ManualResetEvent(true); + internal SemaphoreSlim sm = new SemaphoreSlim(1); /// /// Represents a LogStream that writes its output to the stream. @@ -34,9 +37,6 @@ public class LogStream : IDisposable /// /// Gets the defined for this . /// - /// - /// Internally, this property creates a new for this log stream if it is not defined before. - /// public RotatingLogPolicy RotatingPolicy { get @@ -79,7 +79,6 @@ public string? FilePath } } } - string? filePath; /// /// Gets the object where the log is being written to. @@ -97,9 +96,9 @@ public string? FilePath /// public LogStream() { - loggingThread = new Thread(new ThreadStart(ProcessQueue)); - loggingThread.IsBackground = true; - loggingThread.Start(); + consumerThread = new Thread(new ThreadStart(ProcessQueue)); + consumerThread.IsBackground = true; + consumerThread.Start(); } /// @@ -121,16 +120,26 @@ public LogStream(string filename) : this() } /// - /// Creates an new instance which writes text to an file and an . + /// Creates an new instance which writes text to an file and an . /// /// The file path where this instance will write log to. - /// Represents the text writer which this instance will write log to. + /// The text writer which this instance will write log to. public LogStream(string? filename, TextWriter? tw) : this() { if (filename is not null) FilePath = Path.GetFullPath(filename); TextWriter = tw; } + /// + /// Clears the current log queue and blocks the current thread until all content is written to the underlying streams. + /// + public void Flush() + { + logQueueEmptyEvent.Reset(); + logQueueEmptyEvent.WaitOne(); + ; + } + /// /// Reads the output buffer. To use this method, it's required to set this /// buffering with . @@ -150,29 +159,6 @@ public string Peek() } } - /// - /// Waits for the log to finish writing the current queue state. - /// - /// Block next writings until that instance is released by the method. - public void Wait(bool blocking = false) - { - if (blocking) - { - watcher.Reset(); - isBlocking = true; - } - waiter.WaitOne(); - } - - /// - /// Releases the execution of the queue. - /// - public void Set() - { - watcher.Set(); - isBlocking = false; - } - /// /// Start buffering all output to an alternate stream in memory for readability with later. /// @@ -191,55 +177,38 @@ public void StopBuffering() _bufferingContent = null; } - private void setWatcher() - { - if (!isBlocking) - watcher.Set(); - } - private void ProcessQueue() { - while (true) + while (!isDisposed) { - waiter.Set(); - int i = WaitHandle.WaitAny(new WaitHandle[] { watcher, terminate }); - if (i == 1) return; // terminate - - watcher.Reset(); - waiter.Reset(); - - object?[] copy; - lock (logQueue) + if (!rotatingPolicyLocker.WaitOne(2500)) { - copy = logQueue.ToArray(); - logQueue.Clear(); + continue; } - - StringBuilder exitBuffer = new StringBuilder(); - foreach (object? line in copy) + if (!logQueue.TryTake(out object? data, 2500)) { - exitBuffer.AppendLine(line?.ToString()); + continue; } - if (FilePath is null && TextWriter is null) - { - throw new InvalidOperationException(SR.LogStream_NoOutput); - } + string? dataStr = data?.ToString(); + if (dataStr is null) + continue; - if (_bufferingContent is not null) + if (TextWriter is not null) { - _bufferingContent.Add(exitBuffer.ToString()); + TextWriter.WriteLine(dataStr); + TextWriter.Flush(); } - - // writes log to outputs - if (FilePath is not null) + if (filePath is not null) { - File.AppendAllText(FilePath, exitBuffer.ToString(), Encoding); + File.AppendAllLines(filePath, contents: new string[] { dataStr }); } - if (TextWriter is not null) + + _bufferingContent?.Add(dataStr); + + if (logQueue.Count == 0) { - TextWriter?.Write(exitBuffer.ToString()); - TextWriter?.Flush(); + logQueueEmptyEvent.Set(); } } } @@ -305,18 +274,6 @@ protected virtual void WriteLineInternal(string line) EnqueueMessageLine(line.Normalize()); } - /// - /// Writes all pending logs from the queue and closes all resources used by this object. - /// - public void Dispose() - { - terminate.Set(); - loggingThread.Join(); - logQueue.Clear(); - TextWriter?.Flush(); - TextWriter?.Close(); - } - /// /// Defines the time interval and size threshold for starting the task, and then starts the task. This method is an /// shortcut for calling of this defined method. @@ -338,8 +295,7 @@ void EnqueueMessageLine(string message) ArgumentNullException.ThrowIfNull(message, nameof(message)); lock (logQueue) { - logQueue.Enqueue(message); - setWatcher(); + logQueue.Add(message); } } @@ -361,5 +317,44 @@ void WriteExceptionInternal(StringBuilder exceptionSbuilder, Exception exp, int } } } + + /// + /// Writes all pending logs from the queue and closes all resources used by this object. + /// + protected virtual void Dispose(bool disposing) + { + if (!isDisposed) + { + if (disposing) + { + Flush(); + TextWriter?.Dispose(); + rotatingPolicyLocker?.Dispose(); + logQueueEmptyEvent?.Dispose(); + rotatingLogPolicy?.Dispose(); + consumerThread.Join(); + } + + _bufferingContent = null; + isDisposed = true; + } + } + + /// + ~LogStream() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + /// + /// Writes all pending logs from the queue and closes all resources used by this object. + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Http/RotatingLogPolicy.cs b/src/Http/RotatingLogPolicy.cs index d32c73c..cdf70e4 100644 --- a/src/Http/RotatingLogPolicy.cs +++ b/src/Http/RotatingLogPolicy.cs @@ -8,6 +8,7 @@ // Repository: https://github.com/sisk-http/core using System.IO.Compression; +using System.Timers; namespace Sisk.Core.Http { @@ -16,9 +17,10 @@ namespace Sisk.Core.Http /// public sealed class RotatingLogPolicy : IDisposable { - private readonly Thread checkThread; + private System.Timers.Timer? checkTimer = null; private bool isTerminating = false; internal LogStream? _logStream; + private bool disposedValue; /// /// Gets the file size threshold in bytes for when the file will be compressed and then cleared. @@ -41,8 +43,6 @@ public RotatingLogPolicy(LogStream ls) } _logStream = ls; _logStream.rotatingLogPolicy = this; - checkThread = new Thread(new ThreadStart(Check)); - checkThread.IsBackground = true; } /// @@ -55,68 +55,99 @@ public RotatingLogPolicy(LogStream ls) /// The time interval between checks. public void Configure(long maximumSize, TimeSpan due) { + if (checkTimer?.Enabled == true) + { + return; + } if (string.IsNullOrEmpty(_logStream?.FilePath)) { throw new NotSupportedException(SR.LogStream_RotatingLogPolicy_NotLocalFile); } - if (checkThread.IsAlive) + if (due == TimeSpan.Zero) { - throw new NotSupportedException(SR.LogStream_RotatingLogPolicy_AlreadyRunning); + throw new ArgumentException(SR.LogStream_RotatingLogPolicy_IntervalZero); } + MaximumSize = maximumSize; Due = due; - checkThread.Start(); + + if (checkTimer is null) + checkTimer = new System.Timers.Timer() + { + AutoReset = false + }; + + checkTimer.Interval = Due.TotalMilliseconds; + checkTimer.Elapsed += Check; + checkTimer?.Start(); } - private void Check() + private void Check(object? state, ElapsedEventArgs e) { - while (!isTerminating) + if (isTerminating) return; + if (_logStream is null) return; + if (checkTimer is null) return; + + string file = _logStream.FilePath!; + + if (File.Exists(file)) { - if (_logStream == null) continue; - string file = _logStream.FilePath!; + FileInfo fileInfo = new FileInfo(file); - if (File.Exists(file)) + if (fileInfo.Length > MaximumSize) { - FileInfo fileInfo = new FileInfo(file); - if (fileInfo.Length > MaximumSize) + DateTime now = DateTime.Now; + string ext = fileInfo.Extension; + string safeDatetime = $"{now.Day:D2}-{now.Month:D2}-{now.Year}T{now.Hour:D2}-{now.Minute:D2}-{now.Second:D2}.{now.Millisecond:D4}"; + string gzippedFilename = $"{fileInfo.FullName}.{safeDatetime}{ext}.gz"; + + try { - DateTime now = DateTime.Now; - string ext = fileInfo.Extension; - string safeDatetime = $"{now.Day:D2}-{now.Month:D2}-{now.Year}T{now.Hour:D2}-{now.Minute:D2}-{now.Second:D2}.{now.Millisecond:D4}"; - string gzippedFilename = $"{fileInfo.FullName}.{safeDatetime}{ext}.gz"; + _logStream.rotatingPolicyLocker.Reset(); + _logStream.Flush(); - try + using (FileStream logSs = fileInfo.Open(FileMode.OpenOrCreate)) + using (FileStream gzFileSs = new FileInfo(gzippedFilename).Create()) + using (GZipStream gzSs = new GZipStream(gzFileSs, CompressionMode.Compress)) { - // Console.WriteLine("{0,20}{1,20}", "wait queue", ""); - _logStream.Wait(true); - // Console.WriteLine("{0,20}{1,20}", "gz++", ""); - using (FileStream logSs = fileInfo.Open(FileMode.OpenOrCreate)) - using (FileStream gzFileSs = new FileInfo(gzippedFilename).Create()) - using (GZipStream gzSs = new GZipStream(gzFileSs, CompressionMode.Compress)) - { - logSs.CopyTo(gzSs); - logSs.SetLength(0); - } - } - finally - { - //Console.WriteLine("{0,20}{1,20}", "gz--", ""); - _logStream.Set(); + logSs.CopyTo(gzSs); + logSs.SetLength(0); } } + catch + { + ; + } + finally + { + _logStream.rotatingPolicyLocker.Set(); + } + } + } + + checkTimer.Interval = Due.TotalMilliseconds; + checkTimer.Start(); + } + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _logStream?.Dispose(); } - Thread.Sleep(Due); + _logStream = null; + disposedValue = true; } } - /// - /// Waits for the last scheduled run and terminates this class and its resources. - /// + /// public void Dispose() { - isTerminating = true; - checkThread.Join(); + Dispose(disposing: true); + GC.SuppressFinalize(this); } } } diff --git a/src/Http/Streams/HttpRequestEventSource.cs b/src/Http/Streams/HttpRequestEventSource.cs index 92860fa..19a0170 100644 --- a/src/Http/Streams/HttpRequestEventSource.cs +++ b/src/Http/Streams/HttpRequestEventSource.cs @@ -14,7 +14,7 @@ namespace Sisk.Core.Http.Streams /// /// An instance opens a persistent connection to the request, which sends events in text/event-stream format. /// - public class HttpRequestEventSource : IDisposable + public sealed class HttpRequestEventSource : IDisposable { readonly ManualResetEvent terminatingMutex = new ManualResetEvent(false); readonly HttpStreamPingPolicy pingPolicy; @@ -32,7 +32,7 @@ public class HttpRequestEventSource : IDisposable // // isClosed determines if this instance has some connection or not // isDisposed determines if this object was removed from their collection but wasnt collected by gc yet - // + // private bool isClosed = false; private bool isDisposed = false; diff --git a/src/Http/Streams/HttpWebSocket.cs b/src/Http/Streams/HttpWebSocket.cs index fc2b330..a0fdec2 100644 --- a/src/Http/Streams/HttpWebSocket.cs +++ b/src/Http/Streams/HttpWebSocket.cs @@ -222,7 +222,7 @@ public void Send(ReadOnlyMemory buffer) } /// - /// Closes the connection between the client and the server and returns an Http resposne indicating that the connection has been terminated. + /// Closes the connection between the client and the server and returns an HTTP resposne indicating that the connection has been terminated. /// This method will not throw an exception if the connection is already closed. /// public HttpResponse Close() diff --git a/src/Internal/SR.cs b/src/Internal/SR.cs index 6cdec40..0a9542c 100644 --- a/src/Internal/SR.cs +++ b/src/Internal/SR.cs @@ -49,8 +49,8 @@ static partial class SR public const string LogStream_ExceptionDump_TrimmedFooter = " + ... other trimmed inner exceptions"; public const string LogStream_NoFormat = "No format is defined in this LogStream."; public const string LogStream_RotatingLogPolicy_NotLocalFile = "Cannot link an rotaging log policy to an log stream which ins't pointing to an local file."; - public const string LogStream_RotatingLogPolicy_AlreadyRunning = "This RotatingLogPolicy has already been configured and it is running."; public const string LogStream_RotatingLogPolicy_AlreadyBind = "The specified LogStream is already binded to another RotatingLogPolicy."; + public const string LogStream_RotatingLogPolicy_IntervalZero = "The RotatingLogPolicy interval must be greater than zero."; public const string HttpRequestEventSource_KeepAliveDisposed = "Cannot keep alive an instance that has it's connection disposed."; diff --git a/src/Routing/Route.cs b/src/Routing/Route.cs index 6ddbfda..2b28bd3 100644 --- a/src/Routing/Route.cs +++ b/src/Routing/Route.cs @@ -15,7 +15,7 @@ namespace Sisk.Core.Routing /// /// Represents an HTTP route to be matched by an object. /// - public class Route + public sealed class Route { internal RouteAction? _callback { get; set; } internal bool isReturnTypeTask; diff --git a/src/Routing/RouteDefinition.cs b/src/Routing/RouteDefinition.cs index c4a3efb..3e22d8d 100644 --- a/src/Routing/RouteDefinition.cs +++ b/src/Routing/RouteDefinition.cs @@ -11,6 +11,7 @@ namespace Sisk.Core.Routing; +// TODO: remove this class in future internal class RouteDefinition { public RouteMethod Method { get; set; } diff --git a/src/Routing/Router.cs b/src/Routing/Router.cs index 8b8ace4..e0fccb3 100644 --- a/src/Routing/Router.cs +++ b/src/Routing/Router.cs @@ -23,17 +23,17 @@ namespace Sisk.Core.Routing public sealed partial class Router { internal record RouterExecutionResult(HttpResponse? Response, Route? Route, RouteMatchResult Result, Exception? Exception); - internal HttpServer? ParentServer { get; private set; } + internal HttpServer? parentServer; internal List _routesList = new(); internal List _actionHandlersList = new(); [MethodImpl(MethodImplOptions.AggressiveOptimization)] internal void BindServer(HttpServer server) { - if (ParentServer is not null) + if (parentServer is not null) { - if (ReferenceEquals(server, ParentServer)) + if (ReferenceEquals(server, parentServer)) { return; } @@ -45,7 +45,7 @@ internal void BindServer(HttpServer server) else { server.handler.SetupRouter(this); - ParentServer = server; + parentServer = server; } } @@ -61,7 +61,7 @@ public static string CombinePaths(params string[] paths) /// /// Gets an boolean indicating where this is read-only or not. /// - public bool IsReadOnly { get => ParentServer is not null; } + public bool IsReadOnly { get => parentServer is not null; } /// /// Gets or sets whether this will match routes ignoring case. @@ -69,7 +69,7 @@ public static string CombinePaths(params string[] paths) public bool MatchRoutesIgnoreCase { get; set; } = false; /// - /// Creates an new instance with default properties values. + /// Creates an new instance with default values. /// public Router() { @@ -112,6 +112,10 @@ public Router() public bool TryResolveActionResult(object? result, [NotNullWhen(true)] out HttpResponse? response) { bool wasLocked = false; + + // IsReadOnly garantes that _actionHandlersList and + // _routesList will be not modified during span reading + ; if (!IsReadOnly) { wasLocked = true; @@ -195,7 +199,7 @@ HttpResponse ResolveAction(object routeResult) internal void FreeHttpServer() { - ParentServer = null; + parentServer = null; } } diff --git a/src/Routing/Router__CoreInvoker.cs b/src/Routing/Router__CoreInvoker.cs index 1d2b0a7..997b757 100644 --- a/src/Routing/Router__CoreInvoker.cs +++ b/src/Routing/Router__CoreInvoker.cs @@ -93,7 +93,7 @@ internal bool InvokeRequestHandlerGroup(RequestHandlerExecutionMode mode, IReque catch (Exception ex) { exception = ex; - if (!ParentServer!.ServerConfiguration.ThrowExceptions) + if (!parentServer!.ServerConfiguration.ThrowExceptions) { if (CallbackErrorHandler is not null) { @@ -111,14 +111,14 @@ internal bool InvokeRequestHandlerGroup(RequestHandlerExecutionMode mode, IReque [MethodImpl(MethodImplOptions.AggressiveOptimization)] internal RouterExecutionResult Execute(HttpContext context) { - if (ParentServer == null) throw new InvalidOperationException(SR.Router_NotBinded); + if (parentServer == null) throw new InvalidOperationException(SR.Router_NotBinded); context.Router = this; HttpRequest request = context.Request; Route? matchedRoute = null; RouteMatchResult matchResult = RouteMatchResult.NotMatched; - HttpServerFlags flag = ParentServer!.ServerConfiguration.Flags; + HttpServerFlags flag = parentServer!.ServerConfiguration.Flags; Span rspan = CollectionsMarshal.AsSpan(_routesList); ref Route rPointer = ref MemoryMarshal.GetReference(rspan); @@ -224,7 +224,7 @@ internal RouterExecutionResult Execute(HttpContext context) return new RouterExecutionResult(res, matchedRoute, matchResult, null); } - ParentServer?.handler.ContextBagCreated(context.RequestBag); + parentServer?.handler.ContextBagCreated(context.RequestBag); #region Before-response handlers HttpResponse? rhResponse; @@ -268,7 +268,7 @@ internal RouterExecutionResult Execute(HttpContext context) } catch (Exception ex) { - if (!ParentServer!.ServerConfiguration.ThrowExceptions && (ex is not HttpListenerException)) + if (!parentServer!.ServerConfiguration.ThrowExceptions && (ex is not HttpListenerException)) { if (CallbackErrorHandler is not null) { diff --git a/src/Routing/Router__CoreSetters.cs b/src/Routing/Router__CoreSetters.cs index a71195b..734e102 100644 --- a/src/Routing/Router__CoreSetters.cs +++ b/src/Routing/Router__CoreSetters.cs @@ -35,10 +35,10 @@ public partial class Router /// The route path. public bool IsDefined(RouteMethod method, string path) { - return GetCollisionRoute(method, path) != null; + return GetCollisionRoute(method, path) is not null; } - /// + /// /// Gets an route object by their name that is defined in this Router. /// /// The route name. @@ -205,7 +205,7 @@ private void SetInternal(MethodInfo[] methods, Type callerType, object? instance RouterModule? rmodule = instance as RouterModule; string? prefix; - if (rmodule?.Prefix == null) + if (rmodule?.Prefix is null) { RoutePrefixAttribute? rPrefix = callerType.GetCustomAttribute(); prefix = rPrefix?.Prefix; @@ -215,75 +215,84 @@ private void SetInternal(MethodInfo[] methods, Type callerType, object? instance prefix = rmodule.Prefix; } - foreach (var method in methods) + for (int imethod = 0; imethod < methods.Length; imethod++) { - IEnumerable routesAttributes = method.GetCustomAttributes(); - foreach (var atrInstance in routesAttributes) + MethodInfo? method = methods[imethod]; + + RouteAttribute? routeAttribute = null; + object[] methodAttributes = method.GetCustomAttributes(true); + List methodAttrReqHandlers = new List(methodAttributes.Length); + + for (int imethodAttribute = 0; imethodAttribute < methodAttributes.Length; imethodAttribute++) + { + object attrInstance = methodAttributes[imethodAttribute]; + + if (attrInstance is RequestHandlerAttribute reqHandlerAttr) + { + IRequestHandler? rhandler = (IRequestHandler?)Activator.CreateInstance(reqHandlerAttr.RequestHandlerType, reqHandlerAttr.ConstructorArguments); + if (rhandler is not null) + methodAttrReqHandlers.Add(rhandler); + } + else if (attrInstance is RouteAttribute routeAttributeItem) + { + routeAttribute = routeAttributeItem; + } + } + + if (routeAttribute is not null) { - IEnumerable handlersInstances = method.GetCustomAttributes(); - if (atrInstance != null) + if (rmodule?.RequestHandlers.Count > 0) + { + for (int imodReqHandler = 0; imodReqHandler < rmodule.RequestHandlers.Count; imodReqHandler++) + { + IRequestHandler handler = rmodule.RequestHandlers[imodReqHandler]; + methodAttrReqHandlers.Add(handler); + } + } + try { - List methodHandlers = new List(); - if (handlersInstances.Count() > 0) + RouteAction r; + + if (instance == null) { - foreach (RequestHandlerAttribute atr in handlersInstances) - { - IRequestHandler rhandler = (IRequestHandler)Activator.CreateInstance(atr.RequestHandlerType, atr.ConstructorArguments)!; - methodHandlers.Add(rhandler); - } + r = (RouteAction)Delegate.CreateDelegate(typeof(RouteAction), method); } - if (rmodule?.RequestHandlers.Count() > 0) + else { - foreach (IRequestHandler handler in rmodule.RequestHandlers) - { - methodHandlers.Add(handler); - } + r = (RouteAction)Delegate.CreateDelegate(typeof(RouteAction), instance, method); } - try + string path = routeAttribute.Path; + + if (prefix is not null && !routeAttribute.UseRegex) { - RouteAction r; - - if (instance == null) - { - r = (RouteAction)Delegate.CreateDelegate(typeof(RouteAction), method); - } - else - { - r = (RouteAction)Delegate.CreateDelegate(typeof(RouteAction), instance, method); - } - - string path = atrInstance.Path; - if (prefix != null && !atrInstance.UseRegex) - { - path = PathUtility.CombinePaths(prefix, path); - } - - Route route = new Route() - { - Method = atrInstance.Method, - Path = path, - Action = r, - Name = atrInstance.Name, - RequestHandlers = methodHandlers.ToArray(), - LogMode = atrInstance.LogMode, - UseCors = atrInstance.UseCors, - UseRegex = atrInstance.UseRegex - }; - - Route? collisonRoute; - if ((collisonRoute = GetCollisionRoute(route.Method, route.Path)) != null) - { - throw new ArgumentException(string.Format(SR.Router_Set_Collision, route, collisonRoute)); - } - - rmodule?.OnRouteCreating(route); - SetRoute(route); + path = PathUtility.CombinePaths(prefix, path); } - catch (Exception ex) + + Route route = new Route() + { + Method = routeAttribute.Method, + Path = path, + Action = r, + Name = routeAttribute.Name, + RequestHandlers = methodAttrReqHandlers.ToArray(), + LogMode = routeAttribute.LogMode, + UseCors = routeAttribute.UseCors, + UseRegex = routeAttribute.UseRegex + }; + + Route? collisonRoute; + if ((collisonRoute = GetCollisionRoute(route.Method, route.Path)) != null) { - throw new Exception(string.Format(SR.Router_Set_Exception, method.DeclaringType?.FullName, method.Name), ex); + throw new ArgumentException(string.Format(SR.Router_Set_Collision, route, collisonRoute)); } + + rmodule?.OnRouteCreating(route); + SetRoute(route); + } + catch (Exception ex) + { + throw new Exception(string.Format(SR.Router_Set_Exception, method.DeclaringType?.FullName, method.Name), ex); } } } diff --git a/src/Routing/ValueResult.cs b/src/Routing/ValueResult.cs index 005047c..780ab15 100644 --- a/src/Routing/ValueResult.cs +++ b/src/Routing/ValueResult.cs @@ -15,7 +15,7 @@ namespace Sisk.Core.Routing; /// Represents a mutable type for boxing objects by value or reference in a response from a router. /// /// The type of object to be boxed. -public class ValueResult +public sealed class ValueResult { // //