Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
CypherPotato committed Aug 22, 2024
1 parent 5d9a0dc commit 904aed1
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 75 deletions.
16 changes: 6 additions & 10 deletions .nuget/README.md
Original file line number Diff line number Diff line change
@@ -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/)
Expand All @@ -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/
}
}
```
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
}
}
```
Expand Down
4 changes: 4 additions & 0 deletions src/Http/HttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,10 @@ public object SendTo(RouteAction otherCallback)
/// <summary>
/// Closes this HTTP request and their connection with the remote client without sending any response.
/// </summary>
/// <remarks>
/// This method is now obsolete and will be removed in next Sisk versions. Please, use <see cref="HttpResponse.Refuse"/> instead.
/// </remarks>
[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);
Expand Down
22 changes: 17 additions & 5 deletions src/Http/HttpResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

using Sisk.Core.Entity;
using Sisk.Core.Routing;
using System.Collections.Specialized;
using System.Net;
using System.Text;

Expand All @@ -20,17 +19,30 @@ namespace Sisk.Core.Http
/// </summary>
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;

/// <summary>
/// Creates an new empty <see cref="HttpResponse"/> 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 <see cref="HttpResponse"/> object which closes the connection with the client immediately (ECONNRESET).
/// </summary>
/// <returns></returns>
public static HttpResponse Refuse()
{
return new HttpResponse(HTTPRESPONSE_SERVER_REFUSE);
}

/// <summary>
/// Creates an <see cref="HttpResponse"/> object which closes the connection with the client immediately (ECONNRESET).
/// </summary>
/// <remarks>
/// This method is obsolete and replaced by <see cref="Refuse"/>.
/// </remarks>
[Obsolete("This method should be avoided and will be removed in next Sisk versions.")]
public static HttpResponse CreateEmptyResponse()
{
return new HttpResponse(HTTPRESPONSE_EMPTY);
Expand Down
35 changes: 32 additions & 3 deletions src/Http/HttpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,44 @@ public static HttpServerHostContextBuilder CreateBuilder()
}

/// <summary>
/// Outputs an non-listening HTTP server with configuration, listening host, and router.
/// Gets an listening and running HTTP server in an random port.
/// </summary>
public static HttpServer CreateListener() => CreateListener(ListeningPort.GetRandomPort().Port, out _, out _, out _);

/// <summary>
/// Gets an listening and running HTTP server in the specified port.
/// </summary>
/// <param name="port">The listening port of the HTTP server.</param>
public static HttpServer CreateListener(ushort port) => CreateListener(port, out _, out _, out _);

/// <summary>
/// Gets an listening and running HTTP server in the specified port.
/// </summary>
/// <param name="insecureHttpPort">The insecure port where the HTTP server will listen.</param>
/// <param name="configuration">The <see cref="HttpServerConfiguration"/> object issued from this method.</param>
/// <param name="host">The <see cref="ListeningHost"/> object issued from this method.</param>
/// <param name="router">The <see cref="Router"/> object issued from this method.</param>
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;
}

/// <summary>
/// Gets an non-listening HTTP server with configuration, listening host, and router.
/// </summary>
/// <remarks>This method is not appropriate to running production servers.</remarks>
/// <param name="insecureHttpPort">The insecure port where the HTTP server will listen.</param>
/// <param name="configuration">The <see cref="HttpServerConfiguration"/> object issued from this method.</param>
/// <param name="host">The <see cref="ListeningHost"/> object issued from this method.</param>
/// <param name="router">The <see cref="Router"/> object issued from this method.</param>
public static HttpServer Emit(
in ushort insecureHttpPort,
ushort insecureHttpPort,
out HttpServerConfiguration configuration,
out ListeningHost host,
out Router router
Expand Down
70 changes: 52 additions & 18 deletions src/Http/HttpServer__Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Diagnostics;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;

namespace Sisk.Core.Http;

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -431,6 +447,7 @@ response.Content is StreamContent ||
executionResult.Status = HttpServerExecutionStatus.ConnectionClosed;
executionResult.ServerException = netException;
hasErrorLogging = false;
hasAccessLogging = false;
}
catch (HttpRequestException requestException)
{
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/Internal/SR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>.";
public const string Route_Action_AsyncMissingGenericType = "Async route {0} action must return an object in addition to Task.";

Expand Down
14 changes: 12 additions & 2 deletions src/Routing/RequestHandledAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public RequestHandlerAttribute(params object?[] constructorArguments) : base(typ
/// <summary>
/// Specifies that the method, when used on this attribute, will instantiate the type and call the <see cref="IRequestHandler"/> with given parameters.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class RequestHandlerAttribute : Attribute
{
/// <summary>
Expand Down Expand Up @@ -83,7 +83,17 @@ public RequestHandlerAttribute([DynamicallyAccessedMembers(
)] Type handledBy)
{
RequestHandlerType = handledBy;
ConstructorArguments = new object?[] { };
ConstructorArguments = Array.Empty<object?>();
}

internal IRequestHandler Activate()
{
IRequestHandler? rhandler = Activator.CreateInstance(RequestHandlerType, ConstructorArguments) as IRequestHandler;
if (rhandler is null)
{
throw new ArgumentException(SR.Format(SR.RequestHandler_ActivationException, RequestHandlerType.FullName, ConstructorArguments.Length));
}
return rhandler;
}
}
}
6 changes: 3 additions & 3 deletions src/Routing/Route.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ public RouteAction? Action
/// <summary>
/// Gets or sets the request handlers instances to run before the route's Action.
/// </summary>
public IRequestHandler[]? RequestHandlers { get; set; }
public IRequestHandler[] RequestHandlers { get; set; } = Array.Empty<IRequestHandler>();

/// <summary>
/// Gets or sets the global request handlers instances that will not run on this route.
/// </summary>
public IRequestHandler[]? BypassGlobalRequestHandlers { get; set; }
public IRequestHandler[] BypassGlobalRequestHandlers { get; set; } = Array.Empty<IRequestHandler>();

/// <summary>
/// Creates an new <see cref="Route"/> instance with given parameters.
Expand Down Expand Up @@ -157,7 +157,7 @@ public Route(RouteMethod method, string path, string? name, RouteAction action,
this.path = path;
Name = name;
Action = action;
RequestHandlers = beforeCallback;
RequestHandlers = beforeCallback ?? Array.Empty<IRequestHandler>();
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/Routing/Router.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

record struct RouteDictItem(System.Type type, Delegate lambda);


namespace Sisk.Core.Routing
{
/// <summary>
Expand Down
Loading

0 comments on commit 904aed1

Please sign in to comment.