diff --git a/.nuget/README.md b/.nuget/README.md
index 8fca84b..91b76ec 100644
--- a/.nuget/README.md
+++ b/.nuget/README.md
@@ -1,4 +1,4 @@
-**Sisk** is a **web development framework** that is lightweight, agnostic, easy, simple, and robust. The perfect choice for your next project.
+**Sisk** is a **web development framework** that is lightweight, agnostic, easy, simple, and robust. The perfect choice for your next project.
- [Discover Sisk](https://www.sisk-framework.org/)
- [Documentation](https://docs.sisk-framework.org/)
@@ -19,23 +19,19 @@ It can handle multiple requests asynchronously, provides useful tools to manage
```c#
using Sisk.Core.Http;
-using Sisk.Core.Routing;
-
-namespace myProgram;
class Program
{
- static void Main(string[] args)
+ static async Task Main(string[] args)
{
- var app = HttpServer.CreateBuilder();
+ using var app = HttpServer.CreateBuilder(5555).Build();
- app.Router += new Route(RouteMethod.Get, "/", request =>
+ app.Router.MapGet("/", request =>
{
- return new HttpResponse(200)
- .WithContent("Hello, world!");
+ return new HttpResponse("Hello, world!");
});
- app.Start();
+ await app.StartAsync(); // 🚀 app is listening on http://localhost:5555/
}
}
```
diff --git a/README.md b/README.md
index 818e5c0..1bbb805 100644
--- a/README.md
+++ b/README.md
@@ -54,20 +54,19 @@ It can handle multiple requests asynchronously, provides useful tools to manage
```c#
using Sisk.Core.Http;
-using Sisk.Core.Routing;
class Program
{
- static void Main(string[] args)
+ static async Task Main(string[] args)
{
- var app = HttpServer.CreateBuilder(5000);
+ using var app = HttpServer.CreateBuilder(5555).Build();
- app.Router.SetRoute(RouteMethod.Get, "/", request =>
+ app.Router.MapGet("/", request =>
{
return new HttpResponse("Hello, world!");
});
- app.Start(); // 🚀 app is listening on http://localhost:5000/
+ await app.StartAsync(); // 🚀 app is listening on http://localhost:5555/
}
}
```
diff --git a/src/Http/HttpRequest.cs b/src/Http/HttpRequest.cs
index 786b95e..243872b 100644
--- a/src/Http/HttpRequest.cs
+++ b/src/Http/HttpRequest.cs
@@ -470,6 +470,10 @@ public object SendTo(RouteAction otherCallback)
///
/// Closes this HTTP request and their connection with the remote client without sending any response.
///
+ ///
+ /// This method is now obsolete and will be removed in next Sisk versions. Please, use instead.
+ ///
+ [Obsolete("This method is now obsolete and will be removed in next Sisk versions. Please, use HttpResponse.Refuse() instead.")]
public HttpResponse Close()
{
return new HttpResponse(HttpResponse.HTTPRESPONSE_SERVER_REFUSE);
diff --git a/src/Http/HttpResponse.cs b/src/Http/HttpResponse.cs
index be8ca98..b4509b4 100644
--- a/src/Http/HttpResponse.cs
+++ b/src/Http/HttpResponse.cs
@@ -9,7 +9,6 @@
using Sisk.Core.Entity;
using Sisk.Core.Routing;
-using System.Collections.Specialized;
using System.Net;
using System.Text;
@@ -20,17 +19,30 @@ namespace Sisk.Core.Http
///
public class HttpResponse : CookieHelper
{
- internal const byte HTTPRESPONSE_EMPTY = 2;
+ internal const byte HTTPRESPONSE_EMPTY = 2; // <- theres no reason for this to exist
internal const byte HTTPRESPONSE_SERVER_REFUSE = 4;
internal const byte HTTPRESPONSE_SERVER_CLOSE = 6;
internal const byte HTTPRESPONSE_CLIENT_CLOSE = 32;
- internal const byte HTTPRESPONSE_ERROR = 8;
+ internal const byte HTTPRESPONSE_UNHANDLED_EXCEPTION = 8;
+
internal long CalculedLength = -1;
///
- /// Creates an new empty with no status code or contents. This will cause to the HTTP server to close the
- /// connection between the server and the client and don't deliver any response.
+ /// Creates an object which closes the connection with the client immediately (ECONNRESET).
+ ///
+ ///
+ public static HttpResponse Refuse()
+ {
+ return new HttpResponse(HTTPRESPONSE_SERVER_REFUSE);
+ }
+
+ ///
+ /// Creates an object which closes the connection with the client immediately (ECONNRESET).
///
+ ///
+ /// This method is obsolete and replaced by .
+ ///
+ [Obsolete("This method should be avoided and will be removed in next Sisk versions.")]
public static HttpResponse CreateEmptyResponse()
{
return new HttpResponse(HTTPRESPONSE_EMPTY);
diff --git a/src/Http/HttpServer.cs b/src/Http/HttpServer.cs
index 20a5ce5..81980f2 100644
--- a/src/Http/HttpServer.cs
+++ b/src/Http/HttpServer.cs
@@ -92,15 +92,44 @@ public static HttpServerHostContextBuilder CreateBuilder()
}
///
- /// Outputs an non-listening HTTP server with configuration, listening host, and router.
+ /// Gets an listening and running HTTP server in an random port.
+ ///
+ public static HttpServer CreateListener() => CreateListener(ListeningPort.GetRandomPort().Port, out _, out _, out _);
+
+ ///
+ /// Gets an listening and running HTTP server in the specified port.
+ ///
+ /// The listening port of the HTTP server.
+ public static HttpServer CreateListener(ushort port) => CreateListener(port, out _, out _, out _);
+
+ ///
+ /// Gets an listening and running HTTP server in the specified port.
+ ///
+ /// The insecure port where the HTTP server will listen.
+ /// The object issued from this method.
+ /// The object issued from this method.
+ /// The object issued from this method.
+ public static HttpServer CreateListener(
+ ushort insecureHttpPort,
+ out HttpServerConfiguration configuration,
+ out ListeningHost host,
+ out Router router
+ )
+ {
+ var s = Emit(insecureHttpPort, out configuration, out host, out router);
+ s.Start();
+ return s;
+ }
+
+ ///
+ /// Gets an non-listening HTTP server with configuration, listening host, and router.
///
- /// This method is not appropriate to running production servers.
/// The insecure port where the HTTP server will listen.
/// The object issued from this method.
/// The object issued from this method.
/// The object issued from this method.
public static HttpServer Emit(
- in ushort insecureHttpPort,
+ ushort insecureHttpPort,
out HttpServerConfiguration configuration,
out ListeningHost host,
out Router router
diff --git a/src/Http/HttpServer__Core.cs b/src/Http/HttpServer__Core.cs
index 705eb6f..ab4633a 100644
--- a/src/Http/HttpServer__Core.cs
+++ b/src/Http/HttpServer__Core.cs
@@ -13,6 +13,7 @@
using System.Diagnostics;
using System.Net;
using System.Runtime.CompilerServices;
+using System.Text;
namespace Sisk.Core.Http;
@@ -50,29 +51,44 @@ internal static string HumanReadableSize(in float size)
internal static void SetCorsHeaders(HttpServerFlags serverFlags, HttpListenerRequest baseRequest, CrossOriginResourceSharingHeaders cors, HttpListenerResponse baseResponse)
{
- if (!serverFlags.SendCorsHeaders) return;
- if (cors.AllowHeaders.Length > 0) baseResponse.Headers.Set("Access-Control-Allow-Headers", string.Join(", ", cors.AllowHeaders));
- if (cors.AllowMethods.Length > 0) baseResponse.Headers.Set("Access-Control-Allow-Methods", string.Join(", ", cors.AllowMethods));
- if (cors.AllowOrigin != null) baseResponse.Headers.Set("Access-Control-Allow-Origin", cors.AllowOrigin);
+ if (!serverFlags.SendCorsHeaders)
+ return;
+
+ if (cors.AllowHeaders.Length > 0)
+ baseResponse.Headers.Set(HttpKnownHeaderNames.AccessControlAllowHeaders, string.Join(", ", cors.AllowHeaders));
+
+ if (cors.AllowMethods.Length > 0)
+ baseResponse.Headers.Set(HttpKnownHeaderNames.AccessControlAllowMethods, string.Join(", ", cors.AllowMethods));
+
+ if (cors.AllowOrigin is not null)
+ baseResponse.Headers.Set(HttpKnownHeaderNames.AccessControlAllowOrigin, cors.AllowOrigin);
+
if (cors.AllowOrigins?.Length > 0)
{
- string? origin = baseRequest.Headers["Origin"];
- if (origin != null)
+ string? origin = baseRequest.Headers[HttpKnownHeaderNames.Origin];
+
+ if (origin is not null)
{
for (int i = 0; i < cors.AllowOrigins.Length; i++)
{
string definedOrigin = cors.AllowOrigins[i];
if (string.Compare(definedOrigin, origin, true) == 0)
{
- baseResponse.Headers.Set("Access-Control-Allow-Origin", origin);
+ baseResponse.Headers.Set(HttpKnownHeaderNames.AccessControlAllowOrigin, origin);
break;
}
}
}
}
- if (cors.AllowCredentials != null) baseResponse.Headers.Set("Access-Control-Allow-Credentials", cors.AllowCredentials.ToString()!.ToLower());
- if (cors.ExposeHeaders.Length > 0) baseResponse.Headers.Set("Access-Control-Expose-Headers", string.Join(", ", cors.ExposeHeaders));
- if (cors.MaxAge.TotalSeconds > 0) baseResponse.Headers.Set("Access-Control-Max-Age", cors.MaxAge.TotalSeconds.ToString());
+
+ if (cors.AllowCredentials == true)
+ baseResponse.Headers.Set(HttpKnownHeaderNames.AccessControlAllowCredentials, "true");
+
+ if (cors.ExposeHeaders.Length > 0)
+ baseResponse.Headers.Set(HttpKnownHeaderNames.AccessControlExposeHeaders, string.Join(", ", cors.ExposeHeaders));
+
+ if (cors.MaxAge.TotalSeconds > 0)
+ baseResponse.Headers.Set(HttpKnownHeaderNames.AccessControlMaxAge, cors.MaxAge.TotalSeconds.ToString());
}
private void UnbindRouters()
@@ -123,8 +139,8 @@ private void ProcessRequest(HttpListenerContext context)
long outcomingSize = 0;
bool closeStream = true;
bool useCors = false;
- bool hasAccessLogging = ServerConfiguration.AccessLogsStream != null;
- bool hasErrorLogging = ServerConfiguration.ErrorsLogsStream != null;
+ bool hasAccessLogging = ServerConfiguration.AccessLogsStream is not null;
+ bool hasErrorLogging = ServerConfiguration.ErrorsLogsStream is not null;
IPAddress otherParty = baseRequest.RemoteEndPoint.Address;
Uri? connectingUri = baseRequest.Url;
Router.RouterExecutionResult? routerResult = null;
@@ -134,7 +150,7 @@ private void ProcessRequest(HttpListenerContext context)
string _debugState = "begin";
#pragma warning restore CS0219
- if (ServerConfiguration.DefaultCultureInfo != null)
+ if (ServerConfiguration.DefaultCultureInfo is not null)
{
Thread.CurrentThread.CurrentCulture = ServerConfiguration.DefaultCultureInfo;
Thread.CurrentThread.CurrentUICulture = ServerConfiguration.DefaultCultureInfo;
@@ -277,11 +293,11 @@ private void ProcessRequest(HttpListenerContext context)
executionResult.Status = HttpServerExecutionStatus.NoResponse;
goto finishSending;
}
- else if (response.internalStatus == HttpResponse.HTTPRESPONSE_ERROR)
+ else if (response.internalStatus == HttpResponse.HTTPRESPONSE_UNHANDLED_EXCEPTION)
{
executionResult.Status = HttpServerExecutionStatus.UncaughtExceptionThrown;
baseResponse.StatusCode = 500;
- if (routerResult.Exception != null)
+ if (routerResult.Exception is not null)
throw routerResult.Exception;
goto finishSending;
}
@@ -431,6 +447,7 @@ response.Content is StreamContent ||
executionResult.Status = HttpServerExecutionStatus.ConnectionClosed;
executionResult.ServerException = netException;
hasErrorLogging = false;
+ hasAccessLogging = false;
}
catch (HttpRequestException requestException)
{
@@ -472,7 +489,7 @@ response.Content is StreamContent ||
handler.HttpRequestClose(executionResult);
- if (executionResult.ServerException != null)
+ if (executionResult.ServerException is not null)
handler.Exception(executionResult.ServerException);
LogOutput logMode;
@@ -490,9 +507,26 @@ response.Content is StreamContent ||
bool canAccessLog = logMode.HasFlag(LogOutput.AccessLog) && hasAccessLogging;
bool canErrorLog = logMode.HasFlag(LogOutput.ErrorLog) && hasErrorLogging;
- if (executionResult.ServerException != null && canErrorLog)
+ if (executionResult.ServerException is { } ex && canErrorLog)
{
- ServerConfiguration.ErrorsLogsStream?.WriteException(executionResult.ServerException);
+ StringBuilder errLineBuilder = new StringBuilder();
+ errLineBuilder.Append("[");
+ errLineBuilder.Append(DateTime.Now.ToString("G"));
+ errLineBuilder.Append("] ");
+ errLineBuilder.Append(baseRequest.HttpMethod);
+ errLineBuilder.Append(" ");
+ errLineBuilder.Append(baseRequest.RawUrl);
+ errLineBuilder.AppendLine(":");
+ errLineBuilder.AppendLine(ex.ToString());
+ if (ex.InnerException is { } iex)
+ {
+ errLineBuilder.AppendLine("[inner exception]");
+ errLineBuilder.AppendLine(iex.ToString());
+ }
+
+ errLineBuilder.AppendLine();
+
+ ServerConfiguration.ErrorsLogsStream?.WriteLine(errLineBuilder);
}
if (canAccessLog)
diff --git a/src/Internal/SR.cs b/src/Internal/SR.cs
index e3b6b27..682e495 100644
--- a/src/Internal/SR.cs
+++ b/src/Internal/SR.cs
@@ -68,6 +68,8 @@ static partial class SR
public const string Router_NoRouteActionDefined = "No route action was defined to the route {0}.";
public const string Router_ReadOnlyException = "It's not possible to modify the routes or handlers for this router, as it is read-only.";
+ public const string RequestHandler_ActivationException = "Couldn't activate an instance of the IRequestHandler {0} with the {1} arguments.";
+
public const string Route_Action_ValueTypeSet = "Defining actions which their return type is an value type is not supported. Encapsulate it with ValueResult.";
public const string Route_Action_AsyncMissingGenericType = "Async route {0} action must return an object in addition to Task.";
diff --git a/src/Routing/RequestHandledAttribute.cs b/src/Routing/RequestHandledAttribute.cs
index ac3c9ef..4f475ce 100644
--- a/src/Routing/RequestHandledAttribute.cs
+++ b/src/Routing/RequestHandledAttribute.cs
@@ -45,7 +45,7 @@ public RequestHandlerAttribute(params object?[] constructorArguments) : base(typ
///
/// Specifies that the method, when used on this attribute, will instantiate the type and call the with given parameters.
///
- [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class RequestHandlerAttribute : Attribute
{
///
@@ -83,7 +83,17 @@ public RequestHandlerAttribute([DynamicallyAccessedMembers(
)] Type handledBy)
{
RequestHandlerType = handledBy;
- ConstructorArguments = new object?[] { };
+ ConstructorArguments = Array.Empty