diff --git a/README.md b/README.md
index 79e7e30..ef60615 100644
--- a/README.md
+++ b/README.md
@@ -87,4 +87,12 @@ Sisk can do web development the way you want. Create MVC, MVVC, SOLID applicatio
Sisk is a highly modular and sustainable framework designed for creating a variety of applications, including Restful applications, gRPC, Websockets, file servers, GraphQL, Entity Framework, and more. Its development is ongoing, with a focus on simplicity, easy maintenance, and an enjoyable experience for developers. Sisk is known for its efficiency, even in low-performance environments, and it can handle over twenty thousand requests per second. The framework is compatible with any machine supporting .NET, including those that do not require Native AOT. Sisk also offers additional implementations and packages for extended functionality.
-While Sisk draws inspiration from ASP.NET, it aims for simpler and more performant development without the need for installing additional components. It provides a straightforward and robust development model, allowing developers to handle requests efficiently and explicitly. Sisk simplifies HTTP-related tasks and offers comprehensive documentation and open-source access to its source code, making it accessible and easy to learn for web developers.
\ No newline at end of file
+While Sisk draws inspiration from ASP.NET, it aims for simpler and more performant development without the need for installing additional components. It provides a straightforward and robust development model, allowing developers to handle requests efficiently and explicitly. Sisk simplifies HTTP-related tasks and offers comprehensive documentation and open-source access to its source code, making it accessible and easy to learn for web developers.
+
+## Stargazers over time
+
+[![Stargazers over time](https://starchart.cc/sisk-http/core.svg?variant=light)](https://starchart.cc/sisk-http/core)
+
+## License
+
+The entire Sisk ecosystem is licensed under the [MIT License](https://sisk.project-principium.dev/license).
\ No newline at end of file
diff --git a/src/Http/Hosting/HttpServerHostContext.cs b/src/Http/Hosting/HttpServerHostContext.cs
index 05bb5b7..0903066 100644
--- a/src/Http/Hosting/HttpServerHostContext.cs
+++ b/src/Http/Hosting/HttpServerHostContext.cs
@@ -16,12 +16,12 @@ namespace Sisk.Core.Http.Hosting;
/// Represents the class that hosts most of the components needed to run a Sisk application.
///
///
-/// public class HttpServerHostContext
+/// public class HttpServerHostContext : IDisposable
///
///
/// Class
///
-public class HttpServerHostContext
+public class HttpServerHostContext : IDisposable
{
///
/// Gets the initialization parameters from the portable configuration file.
@@ -154,4 +154,18 @@ public async Task StartAsync(bool verbose = true, bool preventHault = true)
{
await Task.Run(() => Start(verbose, preventHault));
}
+
+ ///
+ /// Invalidates this class and releases the resources used by it, and permanently closes the HTTP server.
+ ///
+ ///
+ /// public void Dispose()
+ ///
+ ///
+ /// Method
+ ///
+ public void Dispose()
+ {
+ HttpServer.Dispose();
+ }
}
diff --git a/src/Http/HttpServer.cs b/src/Http/HttpServer.cs
index e06a473..2f4f983 100644
--- a/src/Http/HttpServer.cs
+++ b/src/Http/HttpServer.cs
@@ -21,7 +21,7 @@ namespace Sisk.Core.Http
///
///
/// public class HttpServer : IDisposable
- ///
+ ///
///
/// Class
///
@@ -58,6 +58,10 @@ public partial class HttpServer : IDisposable
internal HttpWebSocketConnectionCollection _wsCollection = new HttpWebSocketConnectionCollection();
internal List? listeningPrefixes;
internal HttpServerHandlerRepository handler;
+
+ internal AutoResetEvent waitNextEvent = new AutoResetEvent(false);
+ internal bool isWaitingNextEvent = false;
+ internal HttpServerExecutionResult? waitingExecutionResult;
static HttpServer()
{
@@ -74,7 +78,7 @@ static HttpServer()
/// public static HttpServerHostContext CreateBuilder(Action{{HttpServerHostContextBuilder}} handler)
///
///
- /// Static method
+ /// Static method
///
public static HttpServerHostContext CreateBuilder(Action handler)
{
@@ -83,6 +87,22 @@ public static HttpServerHostContext CreateBuilder(Action
+ /// Builds an empty context with predefined listening port.
+ ///
+ ///
+ /// public static HttpServerHostContext CreateBuilder(ushort port)
+ ///
+ ///
+ /// Static method
+ ///
+ public static HttpServerHostContext CreateBuilder(ushort port)
+ {
+ var builder = new HttpServerHostContextBuilder();
+ builder.UseListeningPort(port);
+ return builder.Build();
+ }
+
///
/// Builds an empty context.
///
@@ -90,7 +110,7 @@ public static HttpServerHostContext CreateBuilder(Action
///
- /// Static method
+ /// Static method
///
public static HttpServerHostContext CreateBuilder()
{
@@ -111,7 +131,7 @@ public static HttpServerHostContext CreateBuilder()
/// public static HttpServer Emit(in ushort insecureHttpPort, out HttpServerConfiguration configuration, out ListeningHost host, out Router router)
///
///
- /// Static method
+ /// Static method
///
public static HttpServer Emit(
in ushort insecureHttpPort,
@@ -281,7 +301,7 @@ public HttpServer(HttpServerConfiguration configuration)
///
/// The instance of the server handler.
///
- /// public void RegisterHandler(HttpServerHandler obj)
+ /// public void RegisterHandler(HttpServerHandler obj)
///
///
/// Method
@@ -291,6 +311,52 @@ public void RegisterHandler(HttpServerHandler obj)
handler.RegisterHandler(obj);
}
+ ///
+ /// Waits for the next execution result from the server. This method obtains the next completed context from the HTTP server,
+ /// both with the request and its response. This method does not interrupt the asynchronous processing of requests.
+ ///
+ ///
+ /// Calling this method, it starts the HTTP server if it ins't started yet.
+ ///
+ ///
+ /// public HttpServerExecutionResult WaitNext()
+ ///
+ ///
+ /// Method
+ ///
+ public HttpServerExecutionResult WaitNext()
+ {
+ if (!IsListening)
+ Start();
+ if (isWaitingNextEvent)
+ throw new InvalidOperationException(SR.Httpserver_WaitNext_Race_Condition);
+
+ waitingExecutionResult = null;
+ isWaitingNextEvent = true;
+ waitNextEvent.WaitOne();
+
+ return waitingExecutionResult!;
+ }
+
+ ///
+ /// Waits for the next execution result from the server asynchronously. This method obtains the next completed context from the HTTP server,
+ /// both with the request and its response. This method does not interrupt the asynchronous processing of requests.
+ ///
+ ///
+ /// Calling this method, it starts the HTTP server if it ins't started yet.
+ ///
+ ///
+ /// public async Task{{HttpServerExecutionResult}} WaitNextAsync()
+ ///
+ ///
+ /// Method
+ ///
+ public async Task WaitNextAsync()
+ {
+ return await Task.Run(WaitNext);
+ }
+
+
///
/// Restarts this HTTP server, sending all processing responses and starting them again, reading the listening ports again.
///
diff --git a/src/Http/HttpServer__Core.cs b/src/Http/HttpServer__Core.cs
index fd8fa1b..79dfdf6 100644
--- a/src/Http/HttpServer__Core.cs
+++ b/src/Http/HttpServer__Core.cs
@@ -308,7 +308,7 @@ private async Task ProcessRequest(HttpListenerContext context)
// determines the content type
baseResponse.ContentType = resHeaders[HttpKnownHeaderNames.ContentType] ?? response.Content.Headers.ContentType?.ToString();
- // determines if the response should be sent as chunked or normal
+ // determines if the response should be sent as chunked or normal
if (response.SendChunked)
{
baseResponse.SendChunked = true;
@@ -441,7 +441,7 @@ response.Content is StreamContent ||
catch (Exception)
{
baseResponse.Abort();
- }
+ }
}
if (OnConnectionClose != null)
@@ -492,6 +492,13 @@ response.Content is StreamContent ||
ServerConfiguration.AccessLogsStream?.WriteLine(line);
}
+ if (isWaitingNextEvent)
+ {
+ waitingExecutionResult = executionResult;
+ waitNextEvent.Set();
+ isWaitingNextEvent = false;
+ }
+
if (!flag.AsyncRequestProcessing)
{
Monitor.Exit(httpListener);
diff --git a/src/Internal/HttpStringInternals.cs b/src/Internal/HttpStringInternals.cs
index 8ad9da8..3d1b62d 100644
--- a/src/Internal/HttpStringInternals.cs
+++ b/src/Internal/HttpStringInternals.cs
@@ -7,6 +7,7 @@
// File name: HttpStringInternals.cs
// Repository: https://github.com/sisk-http/core
+using Sisk.Core.Routing;
using System.Collections.Specialized;
using System.Runtime.CompilerServices;
@@ -23,6 +24,11 @@ public static bool PathRouteMatch(ReadOnlySpan routeA, ReadOnlySpan
const string ROUTE_GROUP_START = "<";
const string ROUTE_GROUP_END = ">";
+ if (routeA == Route.AnyPath || routeB == Route.AnyPath)
+ {
+ return true;
+ }
+
int pathPatternSepCount = routeA.Count(SEPARATOR);
int reqsPatternSepCount = routeB.Count(SEPARATOR);
@@ -69,6 +75,11 @@ public static PathMatchResult IsPathMatch(ReadOnlySpan pathPattern, ReadOn
const string ROUTE_GROUP_START = "<";
const string ROUTE_GROUP_END = ">";
+ if (pathPattern == Route.AnyPath)
+ {
+ return new PathMatchResult(true, null);
+ }
+
NameValueCollection? query = null;
int pathPatternSepCount = pathPattern.Count(SEPARATOR);
@@ -113,6 +124,11 @@ public static PathMatchResult IsPathMatch(ReadOnlySpan pathPattern, ReadOn
#else
public static bool PathRouteMatch(string routeA, string routeB, bool ignoreCase)
{
+ if (routeA == Route.AnyPath || routeB == Route.AnyPath)
+ {
+ return true;
+ }
+
string[] routeAP = routeA.Split('/', StringSplitOptions.RemoveEmptyEntries);
string[] routeBP = routeB.Split('/', StringSplitOptions.RemoveEmptyEntries);
@@ -146,6 +162,11 @@ public static bool PathRouteMatch(string routeA, string routeB, bool ignoreCase)
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static PathMatchResult IsPathMatch(string pathPattern, string requestPath, bool ignoreCase)
{
+ if (pathPattern == Route.AnyPath)
+ {
+ return new PathMatchResult(true, null);
+ }
+
NameValueCollection? query = null;
string[] pathPatternParts = pathPattern.Split('/', StringSplitOptions.RemoveEmptyEntries);
diff --git a/src/Internal/SR.cs b/src/Internal/SR.cs
index a3ab128..3b7822f 100644
--- a/src/Internal/SR.cs
+++ b/src/Internal/SR.cs
@@ -29,6 +29,7 @@ static class SR
public const string Httpserver_StartMessage = "The HTTP server is listening at:";
public const string Httpserver_Commons_HeaderAfterContents = "Cannot send headers or status code after sending response contents.";
public const string Httpserver_Commons_RouterTimeout = "Request maximum execution time exceeded it's limit.";
+ public const string Httpserver_WaitNext_Race_Condition = "This HTTP server is already waiting for the next request in another call of this method.";
public const string HttpStatusCode_IllegalStatusCode = "The HTTP status code must be three-digits long.";
public const string HttpStatusCode_IllegalStatusReason = "The HTTP reason phrase must be equal or smaller than 8192 characters.";
diff --git a/src/Routing/Route.cs b/src/Routing/Route.cs
index 0e4099f..c3b119b 100644
--- a/src/Routing/Route.cs
+++ b/src/Routing/Route.cs
@@ -27,6 +27,17 @@ public class Route
internal bool isReturnTypeTask;
internal Regex? routeRegex;
private string path;
+
+ ///
+ /// Represents an route path which captures any URL path.
+ ///
+ ///
+ /// public const string AnyPath = "**RouteAnyPath";
+ ///
+ ///
+ /// Constant
+ ///
+ public const string AnyPath = "/<>";
///
/// Gets or sets an for this route, which can hold contextual variables