Skip to content

Commit

Permalink
Add request/response inspection handler (#538)
Browse files Browse the repository at this point in the history
* Remove `Parent` from `IHandler`, removing lots of boiler plate code
* Add support for YAML serialization
  • Loading branch information
Kaliumhexacyanoferrat authored Nov 1, 2024
1 parent b9fbba5 commit 876eecf
Show file tree
Hide file tree
Showing 112 changed files with 1,021 additions and 656 deletions.
32 changes: 6 additions & 26 deletions API/Content/Concerns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,16 @@
public static class Concerns
{

/// <summary>
/// Creates a handler chain to wrap the specified handler into the
/// specified concerns.
/// </summary>
/// <remarks>
/// Use this utility within the handler builders to add concerns
/// to the resulting handler instance. The last concern added
/// to the list of concerns will be the root handler returned by
/// this method.
/// </remarks>
/// <param name="parent">The parent handler of the chain</param>
/// <param name="concerns">The concerns that should be wrapped around the inner handler</param>
/// <param name="factory">The logic creating the actual handler to be chained</param>
/// <returns>The outermost handler or root of the chain</returns>
public static IHandler Chain(IHandler parent, IEnumerable<IConcernBuilder> concerns, Func<IHandler, IHandler> factory)
public static IHandler Chain(IEnumerable<IConcernBuilder> concerns, IHandler handler)
{
var stack = new Stack<IConcernBuilder>(concerns);
var content = handler;

return Chain(parent, stack, factory);
}

private static IHandler Chain(IHandler parent, Stack<IConcernBuilder> remainders, Func<IHandler, IHandler> factory)
{
if (remainders.Count > 0)
foreach (var concern in concerns)
{
var concern = remainders.Pop();

return concern.Build(parent, p => Chain(p, remainders, factory));
content = concern.Build(content);
}

return factory(parent);
return content;
}

}
11 changes: 3 additions & 8 deletions API/Content/IConcernBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
namespace GenHTTP.Api.Content;

/// <summary>
/// Interface which needs to be implementd by factories providing concern instances.
/// Interface which needs to be implemented by factories providing concern instances.
/// </summary>
public interface IConcernBuilder
{

/// <summary>
/// Builds the concern and sets the inner handler of it.
/// </summary>
/// <param name="parent">The parent of the resulting concern handler instance</param>
/// <param name="contentFactory">A method providing the content of the concern</param>
/// <returns>The newly created concern instance</returns>
IConcern Build(IHandler parent, Func<IHandler, IHandler> contentFactory);
IConcern Build(IHandler content);

}
6 changes: 1 addition & 5 deletions API/Content/IHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ namespace GenHTTP.Api.Content;
public interface IHandler
{

/// <summary>
/// The parent of this handler within the routing tree.
/// </summary>
IHandler Parent { get; }

/// <summary>
/// Invoked to perform computation heavy or IO bound work
/// that initializes the handler before handling the
Expand All @@ -38,4 +33,5 @@ public interface IHandler
/// <param name="request">The request to be handled</param>
/// <returns>The response to be sent to the requesting client</returns>
ValueTask<IResponse?> HandleAsync(IRequest request);

}
5 changes: 3 additions & 2 deletions API/Content/IHandlerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ public interface IHandlerBuilder
/// <summary>
/// Creates the configured handler instance.
/// </summary>
/// <param name="parent">The parent of the handler to be created</param>
/// <returns>The newly created handler instance</returns>
IHandler Build(IHandler parent);
IHandler Build();

}

public interface IHandlerBuilder<out TBuilder> : IHandlerBuilder where TBuilder : IHandlerBuilder<TBuilder>
Expand All @@ -26,4 +26,5 @@ public interface IHandlerBuilder<out TBuilder> : IHandlerBuilder where TBuilder
/// </remarks>
/// <param name="concern">The concern to be added to the resulting handler</param>
TBuilder Add(IConcernBuilder concern);

}
14 changes: 13 additions & 1 deletion API/Infrastructure/IServerBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;

using GenHTTP.Api.Content;

namespace GenHTTP.Api.Infrastructure;
Expand All @@ -27,7 +28,18 @@ public interface IServerBuilder<out T> : IBuilder<IServer>
/// Note that only a single handler is supported. To build are more
/// complex application, consider passing a Layout instead.
/// </remarks>
T Handler(IHandlerBuilder handler);
T Handler(IHandler handler);

/// <summary>
/// Specifies the root handler that will be invoked when
/// a client request needs to be handled.
/// </summary>
/// <param name="handler">The handler to be invoked to handle requests</param>
/// <remarks>
/// Note that only a single handler is supported. To build are more
/// complex application, consider passing a Layout instead.
/// </remarks>
T Handler(IHandlerBuilder handlerBuilder) => Handler(handlerBuilder.Build());

#endregion

Expand Down
8 changes: 8 additions & 0 deletions API/Protocol/ContentType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public enum ContentType
/// </summary>
TextCss,

/// <summary>
/// A human readable YAML file.
/// </summary>
TextYaml,

/// <summary>
/// A JavaScript source file.
/// </summary>
Expand Down Expand Up @@ -462,6 +467,9 @@ public class FlexibleContentType
{
ContentType.TextEventStream, "text/event-stream"
},
{
ContentType.TextYaml, "text/yaml"
},
{
ContentType.Video3Gpp, "video/3gpp"
},
Expand Down
2 changes: 1 addition & 1 deletion Engine/Hosting/ServerHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public IServerHost RequestReadTimeout(TimeSpan timeout)
return this;
}

public IServerHost Handler(IHandlerBuilder handler)
public IServerHost Handler(IHandler handler)
{
_Builder.Handler(handler);
return this;
Expand Down
16 changes: 5 additions & 11 deletions Engine/Infrastructure/CoreRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,19 @@ namespace GenHTTP.Engine.Infrastructure;
internal sealed class CoreRouter : IHandler
{

#region Initialization
#region Get-/Setters

internal CoreRouter(IHandlerBuilder content, IEnumerable<IConcernBuilder> concerns)
{
Content = Concerns.Chain(this, concerns, content.Build);
}
internal IHandler Content { get; }

#endregion

#region Get-/Setters
#region Initialization

public IHandler Parent
internal CoreRouter(IHandler content, IEnumerable<IConcernBuilder> concerns)
{
get => throw new NotSupportedException("Core router has no parent");
set => throw new NotSupportedException("Setting core router's parent is not allowed");
Content = Concerns.Chain(concerns, content);
}

internal IHandler Content { get; }

#endregion

#region Functionality
Expand Down
7 changes: 5 additions & 2 deletions Engine/Infrastructure/ThreadedServerBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;

using GenHTTP.Api.Content;
using GenHTTP.Api.Infrastructure;

using GenHTTP.Engine.Infrastructure.Endpoints;

using GenHTTP.Modules.ErrorHandling;

namespace GenHTTP.Engine.Infrastructure;
Expand All @@ -19,7 +22,7 @@ internal sealed class ThreadedServerBuilder : IServerBuilder

private bool _Development;

private IHandlerBuilder? _Handler;
private IHandler? _Handler;

private ushort _Port = 8080;

Expand All @@ -30,7 +33,7 @@ internal sealed class ThreadedServerBuilder : IServerBuilder

#region Content

public IServerBuilder Handler(IHandlerBuilder handler)
public IServerBuilder Handler(IHandler handler)
{
_Handler = handler;
return this;
Expand Down
7 changes: 7 additions & 0 deletions GenHTTP.sln
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenHTTP.Modules.Websockets"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenHTTP.Modules.ServerSentEvents", "Modules\ServerSentEvents\GenHTTP.Modules.ServerSentEvents.csproj", "{69F3862A-0027-4312-A890-45549AF5D2B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenHTTP.Modules.Inspection", "Modules\Inspection\GenHTTP.Modules.Inspection.csproj", "{2FE9B758-187F-41B3-96BF-1C2BB006F809}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -228,6 +230,10 @@ Global
{69F3862A-0027-4312-A890-45549AF5D2B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69F3862A-0027-4312-A890-45549AF5D2B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69F3862A-0027-4312-A890-45549AF5D2B1}.Release|Any CPU.Build.0 = Release|Any CPU
{2FE9B758-187F-41B3-96BF-1C2BB006F809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2FE9B758-187F-41B3-96BF-1C2BB006F809}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2FE9B758-187F-41B3-96BF-1C2BB006F809}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2FE9B758-187F-41B3-96BF-1C2BB006F809}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -267,6 +273,7 @@ Global
{A5149821-D510-4854-9DC9-D489323BC545} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
{9D3D3B40-691D-4EE1-B948-82525F28FBB2} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
{69F3862A-0027-4312-A890-45549AF5D2B1} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
{2FE9B758-187F-41B3-96BF-1C2BB006F809} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9C67B3AF-0BF6-4E21-8C39-3F74CFCF9632}
Expand Down
28 changes: 13 additions & 15 deletions Modules/Authentication/ApiKey/ApiKeyConcern.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
using GenHTTP.Api.Content;
using GenHTTP.Api.Content.Authentication;

using GenHTTP.Api.Protocol;

namespace GenHTTP.Modules.Authentication.ApiKey;

public sealed class ApiKeyConcern : IConcern
{

#region Initialization

public ApiKeyConcern(IHandler parent, Func<IHandler, IHandler> contentFactory, Func<IRequest, string?> keyExtractor, Func<IRequest, string, ValueTask<IUser?>> authenticator)
{
Parent = parent;
Content = contentFactory(this);

KeyExtractor = keyExtractor;
Authenticator = authenticator;
}

#endregion

#region Get-/Setters

public IHandler Parent { get; }

public IHandler Content { get; }

private Func<IRequest, string?> KeyExtractor { get; }
Expand All @@ -32,6 +18,18 @@ public ApiKeyConcern(IHandler parent, Func<IHandler, IHandler> contentFactory, F

#endregion

#region Initialization

public ApiKeyConcern(IHandler content, Func<IRequest, string?> keyExtractor, Func<IRequest, string, ValueTask<IUser?>> authenticator)
{
Content = content;

KeyExtractor = keyExtractor;
Authenticator = authenticator;
}

#endregion

#region Functionality

public async ValueTask<IResponse?> HandleAsync(IRequest request)
Expand Down
4 changes: 2 additions & 2 deletions Modules/Authentication/ApiKey/ApiKeyConcernBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ public ApiKeyConcernBuilder Keys(params string[] allowedKeys)
return this;
}

public IConcern Build(IHandler parent, Func<IHandler, IHandler> contentFactory)
public IConcern Build(IHandler content)
{
var keyExtractor = _KeyExtractor ?? throw new BuilderMissingPropertyException("KeyExtractor");

var authenticator = _Authenticator ?? throw new BuilderMissingPropertyException("Authenticator");

return new ApiKeyConcern(parent, contentFactory, keyExtractor, authenticator);
return new ApiKeyConcern(content, keyExtractor, authenticator);
}

#endregion
Expand Down
29 changes: 13 additions & 16 deletions Modules/Authentication/Basic/BasicAuthenticationConcern.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;

using GenHTTP.Api.Content;
using GenHTTP.Api.Content.Authentication;
using GenHTTP.Api.Protocol;
Expand All @@ -8,32 +9,28 @@ namespace GenHTTP.Modules.Authentication.Basic;
public sealed class BasicAuthenticationConcern : IConcern
{

#region Initialization

public BasicAuthenticationConcern(IHandler parent, Func<IHandler, IHandler> contentFactory, string realm,
Func<string, string, ValueTask<IUser?>> authenticator)
{
Parent = parent;
Content = contentFactory(this);

Realm = realm;
Authenticator = authenticator;
}

#endregion

#region Get-/Setters

public IHandler Content { get; }

public IHandler Parent { get; }

private string Realm { get; }

private Func<string, string, ValueTask<IUser?>> Authenticator { get; }

#endregion

#region Initialization

public BasicAuthenticationConcern(IHandler content, string realm, Func<string, string, ValueTask<IUser?>> authenticator)
{
Content = content;

Realm = realm;
Authenticator = authenticator;
}

#endregion

#region Functionality

public ValueTask PrepareAsync() => Content.PrepareAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ public BasicAuthenticationConcernBuilder Handler(Func<string, string, ValueTask<
return this;
}

public IConcern Build(IHandler parent, Func<IHandler, IHandler> contentFactory)
public IConcern Build(IHandler content)
{
var realm = _Realm ?? throw new BuilderMissingPropertyException("Realm");

var handler = _Handler ?? throw new BuilderMissingPropertyException("Handler");

return new BasicAuthenticationConcern(parent, contentFactory, realm, handler);
return new BasicAuthenticationConcern(content, realm, handler);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ public BasicAuthenticationKnownUsersBuilder Add(string user, string password)
return this;
}

public IConcern Build(IHandler parent, Func<IHandler, IHandler> contentFactory)
public IConcern Build(IHandler content)
{
var realm = _Realm ?? throw new BuilderMissingPropertyException("Realm");

return new BasicAuthenticationConcern(parent, contentFactory, realm, (user, password) =>
return new BasicAuthenticationConcern(content, realm, (user, password) =>
{
if (_Users.TryGetValue(user, out var expected))
{
Expand Down
Loading

0 comments on commit 876eecf

Please sign in to comment.