diff --git a/API/Content/Authentication/IUser.cs b/API/Content/Authentication/IUser.cs index 39e9d7b0..f4fb9925 100644 --- a/API/Content/Authentication/IUser.cs +++ b/API/Content/Authentication/IUser.cs @@ -1,19 +1,16 @@ -namespace GenHTTP.Api.Content.Authentication +namespace GenHTTP.Api.Content.Authentication; + +/// +/// Information about an user that is associated with +/// the currently handled request. +/// +public interface IUser { - + /// - /// Information about an user that is associated with - /// the currently handled request. + /// The name of the user as it should be shown on + /// the UI (e.g. a rendered, themed page). /// - public interface IUser - { - - /// - /// The name of the user as it should be shown on - /// the UI (e.g. a rendered, themed page). - /// - string DisplayName { get; } - - } + string DisplayName { get; } } diff --git a/API/Content/Caching/ICache.cs b/API/Content/Caching/ICache.cs index 5dc48b93..07bb8973 100644 --- a/API/Content/Caching/ICache.cs +++ b/API/Content/Caching/ICache.cs @@ -2,50 +2,47 @@ using System.IO; using System.Threading.Tasks; -namespace GenHTTP.Api.Content.Caching +namespace GenHTTP.Api.Content.Caching; + +/// +/// Saves intermediate results for fast access. +/// +/// The type of the results to be cached +public interface ICache { /// - /// Saves intermediate results for fast access. + /// Fetches all entries that are stored in the + /// cache with the given key. /// - /// The type of the results to be cached - public interface ICache - { - - /// - /// Fetches all entries that are stored in the - /// cache with the given key. - /// - /// The key to look up - /// The entries stored for this key - ValueTask GetEntriesAsync(string key); - - /// - /// Attempts to fetch a single entry with the given key - /// and variation. - /// - /// The key of the entry to be fetched - /// The variation to be fetched - /// The requested entry, if any - ValueTask GetEntryAsync(string key, string variation); + /// The key to look up + /// The entries stored for this key + ValueTask GetEntriesAsync(string key); - /// - /// Stores the given entry in the cache. - /// - /// The key of the entry (should be file system compatible) - /// The variation specification of the entry - /// The entry to be stored (or to be deleted, if null) - ValueTask StoreAsync(string key, string variation, T? entry); + /// + /// Attempts to fetch a single entry with the given key + /// and variation. + /// + /// The key of the entry to be fetched + /// The variation to be fetched + /// The requested entry, if any + ValueTask GetEntryAsync(string key, string variation); - /// - /// Exposes the stream which can be written to to - /// update the specified entry. - /// - /// The key of the entry to be written (should be file system compatible) - /// The variation specification of the entry - /// A callback that allows to write the entry to the target stream - ValueTask StoreDirectAsync(string key, string variation, Func asyncWriter); + /// + /// Stores the given entry in the cache. + /// + /// The key of the entry (should be file system compatible) + /// The variation specification of the entry + /// The entry to be stored (or to be deleted, if null) + ValueTask StoreAsync(string key, string variation, T? entry); - } + /// + /// Exposes the stream which can be written to to + /// update the specified entry. + /// + /// The key of the entry to be written (should be file system compatible) + /// The variation specification of the entry + /// A callback that allows to write the entry to the target stream + ValueTask StoreDirectAsync(string key, string variation, Func asyncWriter); } diff --git a/API/Content/Concerns.cs b/API/Content/Concerns.cs index 6b10ad0a..2caca27f 100644 --- a/API/Content/Concerns.cs +++ b/API/Content/Concerns.cs @@ -1,38 +1,37 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Api.Content +namespace GenHTTP.Api.Content; + +/// +/// Utility class to work with concerns. +/// +public static class Concerns { /// - /// Utility class to work with concerns. + /// Creates a handler chain to wrap the specified handler into the + /// specified concerns. /// - public static class Concerns + /// + /// 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. + /// + /// The parent handler of the chain + /// The concerns that should be wrapped around the inner handler + /// The logic creating the actual handler to be chained + /// The outermost handler or root of the chain + public static IHandler Chain(IHandler parent, IEnumerable concerns, Func factory) { - - /// - /// Creates a handler chain to wrap the specified handler into the - /// specified concerns. - /// - /// - /// 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. - /// - /// The parent handler of the chain - /// The concerns that should be wrapped around the inner handler - /// The logic creating the actual handler to be chained - /// The outermost handler or root of the chain - public static IHandler Chain(IHandler parent, IEnumerable concerns, Func factory) - { var stack = new Stack(concerns); return Chain(parent, stack, factory); } - private static IHandler Chain(IHandler parent, Stack remainders, Func factory) - { + private static IHandler Chain(IHandler parent, Stack remainders, Func factory) + { if (remainders.Count > 0) { var concern = remainders.Pop(); @@ -43,6 +42,4 @@ private static IHandler Chain(IHandler parent, Stack remainders return factory(parent); } - } - } diff --git a/API/Content/IConcern.cs b/API/Content/IConcern.cs index abc390ec..018fcfa0 100644 --- a/API/Content/IConcern.cs +++ b/API/Content/IConcern.cs @@ -1,18 +1,15 @@ -namespace GenHTTP.Api.Content +namespace GenHTTP.Api.Content; + +/// +/// Functionality that wraps around a regular handler to add a +/// concern such as response compression. +/// +public interface IConcern : IHandler { /// - /// Functionality that wraps around a regular handler to add a - /// concern such as response compression. + /// The actual handler the concern is added to. /// - public interface IConcern : IHandler - { - - /// - /// The actual handler the concern is added to. - /// - IHandler Content { get; } - - } + IHandler Content { get; } } diff --git a/API/Content/IConcernBuilder.cs b/API/Content/IConcernBuilder.cs index 31669605..254db5e4 100644 --- a/API/Content/IConcernBuilder.cs +++ b/API/Content/IConcernBuilder.cs @@ -1,22 +1,19 @@ using System; -namespace GenHTTP.Api.Content +namespace GenHTTP.Api.Content; + +/// +/// Interface which needs to be implementd by factories providing concern instances. +/// +public interface IConcernBuilder { /// - /// Interface which needs to be implementd by factories providing concern instances. + /// Builds the concern and sets the inner handler of it. /// - public interface IConcernBuilder - { - - /// - /// Builds the concern and sets the inner handler of it. - /// - /// The parent of the resulting concern handler instance - /// A method providing the content of the concern - /// The newly created concern instance - IConcern Build(IHandler parent, Func contentFactory); - - } + /// The parent of the resulting concern handler instance + /// A method providing the content of the concern + /// The newly created concern instance + IConcern Build(IHandler parent, Func contentFactory); } diff --git a/API/Content/IErrorMapper.cs b/API/Content/IErrorMapper.cs index 65fe085c..76be7ff5 100644 --- a/API/Content/IErrorMapper.cs +++ b/API/Content/IErrorMapper.cs @@ -3,34 +3,31 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Api.Content +namespace GenHTTP.Api.Content; + +/// +/// Can be used with the error handling module to generate custom +/// responses for exceptions thrown during request handling. +/// +/// The type of exception to be mapped (others will not be handled) +public interface IErrorMapper where T : Exception { /// - /// Can be used with the error handling module to generate custom - /// responses for exceptions thrown during request handling. + /// Generates a HTTP response to be sent for the given exception. /// - /// The type of exception to be mapped (others will not be handled) - public interface IErrorMapper where T : Exception - { - - /// - /// Generates a HTTP response to be sent for the given exception. - /// - /// The request which caused the error - /// The handler which catched the exception - /// The actual exception to be mapped - /// A HTTP response to be sent or null, if the error should be handled as not found by the next error handler in the chain - ValueTask Map(IRequest request, IHandler handler, T error); - - /// - /// Generates a HTTP response for a resource that has not been found. - /// - /// The currently handled request - /// The inner handler of the error handling concern - /// A HTTP response to be sent or null, if the error should be handled as not found by the next error handler in the chain - ValueTask GetNotFound(IRequest request, IHandler handler); + /// The request which caused the error + /// The handler which catched the exception + /// The actual exception to be mapped + /// A HTTP response to be sent or null, if the error should be handled as not found by the next error handler in the chain + ValueTask Map(IRequest request, IHandler handler, T error); - } + /// + /// Generates a HTTP response for a resource that has not been found. + /// + /// The currently handled request + /// The inner handler of the error handling concern + /// A HTTP response to be sent or null, if the error should be handled as not found by the next error handler in the chain + ValueTask GetNotFound(IRequest request, IHandler handler); } diff --git a/API/Content/IHandler.cs b/API/Content/IHandler.cs index af1555b9..1ba3ab21 100644 --- a/API/Content/IHandler.cs +++ b/API/Content/IHandler.cs @@ -2,46 +2,43 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Api.Content +namespace GenHTTP.Api.Content; + +/// +/// Content provider that is able to handle a request and return a HTTP +/// response to it. +/// +public interface IHandler { /// - /// Content provider that is able to handle a request and return a HTTP - /// response to it. + /// The parent of this handler within the routing tree. /// - public interface IHandler - { - - /// - /// The parent of this handler within the routing tree. - /// - IHandler Parent { get; } + IHandler Parent { get; } - /// - /// Invoked to perform computation heavy or IO bound work - /// that initializes the handler before handling the - /// first requests. - /// - /// - /// Intended to keep the response time for the first - /// requests low. Handlers should relay this call to dependent - /// child handlers to initialize the whole handler chain. - /// May be called multiple times depending on the setup - /// the handler is used in. - /// - ValueTask PrepareAsync(); - - /// - /// Handles the given request and returns a response, if applicable. - /// - /// - /// Not returning a response causes the server to respond with a not found - /// response code. - /// - /// The request to be handled - /// The response to be sent to the requesting client - ValueTask HandleAsync(IRequest request); + /// + /// Invoked to perform computation heavy or IO bound work + /// that initializes the handler before handling the + /// first requests. + /// + /// + /// Intended to keep the response time for the first + /// requests low. Handlers should relay this call to dependent + /// child handlers to initialize the whole handler chain. + /// May be called multiple times depending on the setup + /// the handler is used in. + /// + ValueTask PrepareAsync(); - } + /// + /// Handles the given request and returns a response, if applicable. + /// + /// + /// Not returning a response causes the server to respond with a not found + /// response code. + /// + /// The request to be handled + /// The response to be sent to the requesting client + ValueTask HandleAsync(IRequest request); } diff --git a/API/Content/IHandlerBuilder.cs b/API/Content/IHandlerBuilder.cs index ac85706f..bbaf9ce4 100644 --- a/API/Content/IHandlerBuilder.cs +++ b/API/Content/IHandlerBuilder.cs @@ -1,34 +1,31 @@ -namespace GenHTTP.Api.Content +namespace GenHTTP.Api.Content; + +/// +/// Allows to create a handler instance. +/// +public interface IHandlerBuilder { /// - /// Allows to create a handler instance. + /// Creates the configured handler instance. /// - public interface IHandlerBuilder - { - - /// - /// Creates the configured handler instance. - /// - /// The parent of the handler to be created - /// The newly created handler instance - IHandler Build(IHandler parent); + /// The parent of the handler to be created + /// The newly created handler instance + IHandler Build(IHandler parent); - } - - public interface IHandlerBuilder : IHandlerBuilder where TBuilder : IHandlerBuilder - { +} - /// - /// Adds the given concern to the resulting handler. - /// - /// - /// The first concern added to the builder will be the new root - /// of the chain returned by the builder. - /// - /// The concern to be added to the resulting handler - TBuilder Add(IConcernBuilder concern); +public interface IHandlerBuilder : IHandlerBuilder where TBuilder : IHandlerBuilder +{ - } + /// + /// Adds the given concern to the resulting handler. + /// + /// + /// The first concern added to the builder will be the new root + /// of the chain returned by the builder. + /// + /// The concern to be added to the resulting handler + TBuilder Add(IConcernBuilder concern); } diff --git a/API/Content/IO/ICompressionAlgorithm.cs b/API/Content/IO/ICompressionAlgorithm.cs index ec4add1b..e65107aa 100644 --- a/API/Content/IO/ICompressionAlgorithm.cs +++ b/API/Content/IO/ICompressionAlgorithm.cs @@ -3,37 +3,34 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Api.Content.IO +namespace GenHTTP.Api.Content.IO; + +/// +/// The implementation of an algorithm allowing to transfer content +/// in a compressed form to the client. +/// +public interface ICompressionAlgorithm { /// - /// The implementation of an algorithm allowing to transfer content - /// in a compressed form to the client. + /// The name of the algorithm as specified by the client in the + /// "Accept-Encoding" HTTP header. /// - public interface ICompressionAlgorithm - { - - /// - /// The name of the algorithm as specified by the client in the - /// "Accept-Encoding" HTTP header. - /// - string Name { get; } + string Name { get; } - /// - /// The priority of the algorithm. The algorithm with the highest - /// priority will be selected if mutliple algorithms can be applied - /// to a response. - /// - Priority Priority { get; } - - /// - /// Returns a content instance allowing the server to stream the compressed content. - /// - /// The content of the response to be compressed - /// The compression level to be applied - /// A result representing the compressed content - IResponseContent Compress(IResponseContent content, CompressionLevel level); + /// + /// The priority of the algorithm. The algorithm with the highest + /// priority will be selected if mutliple algorithms can be applied + /// to a response. + /// + Priority Priority { get; } - } + /// + /// Returns a content instance allowing the server to stream the compressed content. + /// + /// The content of the response to be compressed + /// The compression level to be applied + /// A result representing the compressed content + IResponseContent Compress(IResponseContent content, CompressionLevel level); } diff --git a/API/Content/IO/IResource.cs b/API/Content/IO/IResource.cs index 41d6c198..154f3b0d 100644 --- a/API/Content/IO/IResource.cs +++ b/API/Content/IO/IResource.cs @@ -3,68 +3,65 @@ using System.Threading.Tasks; using GenHTTP.Api.Protocol; -namespace GenHTTP.Api.Content.IO +namespace GenHTTP.Api.Content.IO; + +/// +/// Allows content providers to access a resource without the need +/// to know the actual way of access (such as database, web, or +/// file system). +/// +/// +/// As resources may change (e.g. if an user changes the file that +/// is provided as a resource), content providers must not +/// cache the results of a method call. +/// +public interface IResource { /// - /// Allows content providers to access a resource without the need - /// to know the actual way of access (such as database, web, or - /// file system). + /// The name of this resource, if known. /// - /// - /// As resources may change (e.g. if an user changes the file that - /// is provided as a resource), content providers must not - /// cache the results of a method call. - /// - public interface IResource - { - - /// - /// The name of this resource, if known. - /// - string? Name { get; } - - /// - /// The point in time, when the resource was last modified, if known. - /// - DateTime? Modified { get; } + string? Name { get; } - /// - /// The content type of this resource, if known. - /// - FlexibleContentType? ContentType { get; } + /// + /// The point in time, when the resource was last modified, if known. + /// + DateTime? Modified { get; } - /// - /// The number of bytes provided by this resource. - /// - /// - /// This field will not be used to control the HTTP flow, but - /// just as meta information (e.g. to be rendered by the - /// directory listing handler). For optimzed data transfer, - /// the stream provided by this resource should be seekable - /// and return a sane length. - /// - ulong? Length { get; } + /// + /// The content type of this resource, if known. + /// + FlexibleContentType? ContentType { get; } - /// - /// Calculates the checksum of the resource. - /// - /// The checksum of the resource - ValueTask CalculateChecksumAsync(); + /// + /// The number of bytes provided by this resource. + /// + /// + /// This field will not be used to control the HTTP flow, but + /// just as meta information (e.g. to be rendered by the + /// directory listing handler). For optimzed data transfer, + /// the stream provided by this resource should be seekable + /// and return a sane length. + /// + ulong? Length { get; } - /// - /// Returns the read-only stream of the resource to be accessed. - /// - /// The resource to be accessed - ValueTask GetContentAsync(); + /// + /// Calculates the checksum of the resource. + /// + /// The checksum of the resource + ValueTask CalculateChecksumAsync(); - /// - /// Writes the content of the resource to the given stream. - /// - /// The stream to write to - /// The buffer size to be used for the operation - ValueTask WriteAsync(Stream target, uint bufferSize); + /// + /// Returns the read-only stream of the resource to be accessed. + /// + /// The resource to be accessed + ValueTask GetContentAsync(); - } + /// + /// Writes the content of the resource to the given stream. + /// + /// The stream to write to + /// The buffer size to be used for the operation + ValueTask WriteAsync(Stream target, uint bufferSize); } diff --git a/API/Content/IO/IResourceBuilder.cs b/API/Content/IO/IResourceBuilder.cs index 26e3e691..8769fade 100644 --- a/API/Content/IO/IResourceBuilder.cs +++ b/API/Content/IO/IResourceBuilder.cs @@ -3,46 +3,43 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Api.Content.IO +namespace GenHTTP.Api.Content.IO; + +/// +/// When implemented by builders providing resource instances, +/// this interface allows to configure common properties of +/// resources in an unified way. +/// +public interface IResourceBuilder : IBuilder where T : IResourceBuilder { /// - /// When implemented by builders providing resource instances, - /// this interface allows to configure common properties of - /// resources in an unified way. + /// Sets the name of the resource. /// - public interface IResourceBuilder : IBuilder where T : IResourceBuilder - { - - /// - /// Sets the name of the resource. - /// - /// The name of the resource - T Name(string name); - - /// - /// Sets the content type of the resource. - /// - /// The content type of the resource - T Type(FlexibleContentType contentType); - - /// - /// Sets the modification date and time of the resource. - /// - /// The modification date and time of the resource - T Modified(DateTime modified); - - } - - public static class IResourceMetaDataBuilderExtensions - { - - /// - /// Sets the content type of the resource. - /// - /// The content type of the resource - public static T Type(this IResourceBuilder builder, ContentType contentType) where T : IResourceBuilder => builder.Type(FlexibleContentType.Get(contentType)); - - } + /// The name of the resource + T Name(string name); + + /// + /// Sets the content type of the resource. + /// + /// The content type of the resource + T Type(FlexibleContentType contentType); + + /// + /// Sets the modification date and time of the resource. + /// + /// The modification date and time of the resource + T Modified(DateTime modified); + +} + +public static class IResourceMetaDataBuilderExtensions +{ + + /// + /// Sets the content type of the resource. + /// + /// The content type of the resource + public static T Type(this IResourceBuilder builder, ContentType contentType) where T : IResourceBuilder => builder.Type(FlexibleContentType.Get(contentType)); } diff --git a/API/Content/IO/IResourceContainer.cs b/API/Content/IO/IResourceContainer.cs index ddb9a409..6c47fa3e 100644 --- a/API/Content/IO/IResourceContainer.cs +++ b/API/Content/IO/IResourceContainer.cs @@ -2,48 +2,45 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace GenHTTP.Api.Content.IO +namespace GenHTTP.Api.Content.IO; + +/// +/// Provides a single hierarchy level in a structure +/// provided by a resource tree. +/// +public interface IResourceContainer { /// - /// Provides a single hierarchy level in a structure - /// provided by a resource tree. + /// The point in time when the container was modified (if known). + /// + DateTime? Modified { get; } + + /// + /// Tries to fetch the child node with the given name. + /// + /// The name of the node to be fetched + /// The node fetched from the container, if the node could be found + ValueTask TryGetNodeAsync(string name); + + /// + /// Returns the child nodes provided by this container. + /// + /// The child nodes provided by this container + ValueTask> GetNodes(); + + /// + /// Tries to fetch the resource with the given name. + /// + /// The name of the resource to be fetched + /// + /// The resource fetched from the container, if the resource could be found + ValueTask TryGetResourceAsync(string name); + + /// + /// Returns the resources provided by this container. /// - public interface IResourceContainer - { - - /// - /// The point in time when the container was modified (if known). - /// - DateTime? Modified { get; } - - /// - /// Tries to fetch the child node with the given name. - /// - /// The name of the node to be fetched - /// The node fetched from the container, if the node could be found - ValueTask TryGetNodeAsync(string name); - - /// - /// Returns the child nodes provided by this container. - /// - /// The child nodes provided by this container - ValueTask> GetNodes(); - - /// - /// Tries to fetch the resource with the given name. - /// - /// The name of the resource to be fetched - /// - /// The resource fetched from the container, if the resource could be found - ValueTask TryGetResourceAsync(string name); - - /// - /// Returns the resources provided by this container. - /// - /// The resources provided by this container - ValueTask> GetResources(); - - } + /// The resources provided by this container + ValueTask> GetResources(); } diff --git a/API/Content/IO/IResourceNode.cs b/API/Content/IO/IResourceNode.cs index 7683f69d..8e0e4ef6 100644 --- a/API/Content/IO/IResourceNode.cs +++ b/API/Content/IO/IResourceNode.cs @@ -1,23 +1,20 @@ -namespace GenHTTP.Api.Content.IO +namespace GenHTTP.Api.Content.IO; + +/// +/// Provides a single hierarchy level in a structure +/// provided by a resource tree. +/// +public interface IResourceNode : IResourceContainer { /// - /// Provides a single hierarchy level in a structure - /// provided by a resource tree. + /// The name of this node. /// - public interface IResourceNode : IResourceContainer - { - - /// - /// The name of this node. - /// - string Name { get; } - - /// - /// The parent of this node. - /// - IResourceContainer Parent { get; } + string Name { get; } - } + /// + /// The parent of this node. + /// + IResourceContainer Parent { get; } } diff --git a/API/Content/IO/IResourceTree.cs b/API/Content/IO/IResourceTree.cs index e8151174..c4f3f691 100644 --- a/API/Content/IO/IResourceTree.cs +++ b/API/Content/IO/IResourceTree.cs @@ -1,13 +1,10 @@ -namespace GenHTTP.Api.Content.IO -{ - - /// - /// Provides resources organized into a tree structure - /// (e.g. a directory or embedded ressources). - /// - public interface IResourceTree : IResourceContainer - { +namespace GenHTTP.Api.Content.IO; - } +/// +/// Provides resources organized into a tree structure +/// (e.g. a directory or embedded ressources). +/// +public interface IResourceTree : IResourceContainer +{ } diff --git a/API/Content/ProviderException.cs b/API/Content/ProviderException.cs index b6b9c84a..438990fb 100644 --- a/API/Content/ProviderException.cs +++ b/API/Content/ProviderException.cs @@ -2,41 +2,38 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Api.Content +namespace GenHTTP.Api.Content; + +/// +/// If thrown by a content provider or router, the server will return +/// the specified HTTP response status to the client instead of +/// indicating a server error. +/// +[Serializable] +public class ProviderException : Exception { + #region Get-/Setters + /// - /// If thrown by a content provider or router, the server will return - /// the specified HTTP response status to the client instead of - /// indicating a server error. + /// The status to be returned to the client. /// - [Serializable] - public class ProviderException : Exception - { - - #region Get-/Setters - - /// - /// The status to be returned to the client. - /// - public ResponseStatus Status { get; } + public ResponseStatus Status { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ProviderException(ResponseStatus status, string message) : base(message) - { + public ProviderException(ResponseStatus status, string message) : base(message) + { Status = status; } - public ProviderException(ResponseStatus status, string message, Exception inner) : base(message, inner) - { + public ProviderException(ResponseStatus status, string message, Exception inner) : base(message, inner) + { Status = status; } - #endregion - - } + #endregion } diff --git a/API/Infrastructure/BindingException.cs b/API/Infrastructure/BindingException.cs index 3678fda1..21a65ee6 100644 --- a/API/Infrastructure/BindingException.cs +++ b/API/Infrastructure/BindingException.cs @@ -1,20 +1,17 @@ using System; -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// Will be thrown, if the server cannot bind to the requested port for some reason. +/// +[Serializable] +public class BindingException : Exception { - /// - /// Will be thrown, if the server cannot bind to the requested port for some reason. - /// - [Serializable] - public class BindingException : Exception + public BindingException(string message, Exception inner) : base(message, inner) { - public BindingException(string message, Exception inner) : base(message, inner) - { - } - } - } diff --git a/API/Infrastructure/BuilderMissingPropertyException.cs b/API/Infrastructure/BuilderMissingPropertyException.cs index a053c899..df710325 100644 --- a/API/Infrastructure/BuilderMissingPropertyException.cs +++ b/API/Infrastructure/BuilderMissingPropertyException.cs @@ -1,34 +1,31 @@ using System; -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// Will be thrown, if a builder is missing a required property +/// that is needed to create the target instance. +/// +[Serializable] +public class BuilderMissingPropertyException : Exception { + #region Get-/Setters + /// - /// Will be thrown, if a builder is missing a required property - /// that is needed to create the target instance. + /// The name of the property which has not been set. /// - [Serializable] - public class BuilderMissingPropertyException : Exception - { - - #region Get-/Setters + public string Property { get; } - /// - /// The name of the property which has not been set. - /// - public string Property { get; } + #endregion - #endregion + #region Initialization - #region Initialization - - public BuilderMissingPropertyException(string property) : base($"Missing required property '{property}'") - { + public BuilderMissingPropertyException(string property) : base($"Missing required property '{property}'") + { Property = property; } - #endregion - - } + #endregion } diff --git a/API/Infrastructure/IBuilder.cs b/API/Infrastructure/IBuilder.cs index a566eefb..6bcc3fa4 100644 --- a/API/Infrastructure/IBuilder.cs +++ b/API/Infrastructure/IBuilder.cs @@ -1,25 +1,22 @@ -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// General interface implemented by every builder resposible to +/// configure and setup an instance. +/// +/// The type which should be produced by the builder +/// +/// Builders must not change their internal state when building an object, allowing +/// builder instances to be re-used if required. +/// +public interface IBuilder { /// - /// General interface implemented by every builder resposible to - /// configure and setup an instance. + /// Creates a new instance of the specified type using + /// the current configuration. /// - /// The type which should be produced by the builder - /// - /// Builders must not change their internal state when building an object, allowing - /// builder instances to be re-used if required. - /// - public interface IBuilder - { - - /// - /// Creates a new instance of the specified type using - /// the current configuration. - /// - /// The newly created instance - T Build(); - - } + /// The newly created instance + T Build(); } diff --git a/API/Infrastructure/ICertificateProvider.cs b/API/Infrastructure/ICertificateProvider.cs index 68409477..2dfcd8f6 100644 --- a/API/Infrastructure/ICertificateProvider.cs +++ b/API/Infrastructure/ICertificateProvider.cs @@ -1,22 +1,19 @@ using System.Security.Cryptography.X509Certificates; -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// Allows secure endpoints to select the certificate they should +/// authenticate the client with. +/// +public interface ICertificateProvider { /// - /// Allows secure endpoints to select the certificate they should - /// authenticate the client with. + /// Select a certificate for authentication based on the given host. /// - public interface ICertificateProvider - { - - /// - /// Select a certificate for authentication based on the given host. - /// - /// The name of the host, if specified by the client - /// The certificate to be used to authenticate the client - X509Certificate2? Provide(string? host); - - } + /// The name of the host, if specified by the client + /// The certificate to be used to authenticate the client + X509Certificate2? Provide(string? host); } diff --git a/API/Infrastructure/IClientConnection.cs b/API/Infrastructure/IClientConnection.cs index 294cedeb..95d4d9a4 100644 --- a/API/Infrastructure/IClientConnection.cs +++ b/API/Infrastructure/IClientConnection.cs @@ -2,32 +2,29 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// The remote client which requests a resource from the server. +/// +public interface IClientConnection { /// - /// The remote client which requests a resource from the server. + /// The IP address of the remotely connected client. /// - public interface IClientConnection - { - - /// - /// The IP address of the remotely connected client. - /// - IPAddress IPAddress { get; } + IPAddress IPAddress { get; } - /// - /// The protocol used by the client to connect - /// to the server. - /// - ClientProtocol? Protocol { get; } - - /// - /// The host name used by the client to connect - /// to the server. - /// - string? Host { get; } + /// + /// The protocol used by the client to connect + /// to the server. + /// + ClientProtocol? Protocol { get; } - } + /// + /// The host name used by the client to connect + /// to the server. + /// + string? Host { get; } } diff --git a/API/Infrastructure/IEndPoint.cs b/API/Infrastructure/IEndPoint.cs index b1e4fec2..0d2074d5 100644 --- a/API/Infrastructure/IEndPoint.cs +++ b/API/Infrastructure/IEndPoint.cs @@ -1,34 +1,31 @@ using System; using System.Net; -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// An endpoint the server will listen on for incoming requests. +/// +public interface IEndPoint : IDisposable { /// - /// An endpoint the server will listen on for incoming requests. + /// The IP address the endpoint is bound to. /// - public interface IEndPoint : IDisposable - { - - /// - /// The IP address the endpoint is bound to. - /// - /// - /// Can be a specific IPv4/IPv6 address or a more generic one - /// such as . - /// - IPAddress IPAddress { get; } + /// + /// Can be a specific IPv4/IPv6 address or a more generic one + /// such as . + /// + IPAddress IPAddress { get; } - /// - /// The port the endpoint is listening on. - /// - ushort Port { get; } - - /// - /// Specifies, whether this is is an endpoint secured via SSL/TLS. - /// - bool Secure { get; } + /// + /// The port the endpoint is listening on. + /// + ushort Port { get; } - } + /// + /// Specifies, whether this is is an endpoint secured via SSL/TLS. + /// + bool Secure { get; } } diff --git a/API/Infrastructure/IEndPointCollection.cs b/API/Infrastructure/IEndPointCollection.cs index 20fcd86e..c5d17da0 100644 --- a/API/Infrastructure/IEndPointCollection.cs +++ b/API/Infrastructure/IEndPointCollection.cs @@ -1,14 +1,11 @@ using System.Collections.Generic; -namespace GenHTTP.Api.Infrastructure -{ - - /// - /// Provides a list of endpoints a server is listening to. - /// - public interface IEndPointCollection : IReadOnlyList - { +namespace GenHTTP.Api.Infrastructure; - } +/// +/// Provides a list of endpoints a server is listening to. +/// +public interface IEndPointCollection : IReadOnlyList +{ } diff --git a/API/Infrastructure/IServer.cs b/API/Infrastructure/IServer.cs index fded7336..f02bc459 100644 --- a/API/Infrastructure/IServer.cs +++ b/API/Infrastructure/IServer.cs @@ -1,54 +1,51 @@ using System; using GenHTTP.Api.Content; -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// Listens for incoming HTTP requests and dispatches them +/// to the registered routers and content providers. +/// +public interface IServer : IDisposable { /// - /// Listens for incoming HTTP requests and dispatches them - /// to the registered routers and content providers. + /// The version of the server software. + /// + /// + /// This property is for informational use only. Do not change + /// your code depending on the version you are working with. + /// + string Version { get; } + + /// + /// Specifies, whether the server still serves requests or + /// whether it is currently shut down. + /// + bool Running { get; } + + /// + /// If enabled, components may provide additional information + /// allowing developers to further debug web applications. + /// + bool Development { get; } + + /// + /// The endpoints the server is listening on. + /// + IEndPointCollection EndPoints { get; } + + /// + /// An instance that will be called on certain events such as + /// handled requests or errors that occur within the engine. + /// + IServerCompanion? Companion { get; } + + /// + /// The main router that will be used by the server to dispatch + /// incoming HTTP requests. /// - public interface IServer : IDisposable - { - - /// - /// The version of the server software. - /// - /// - /// This property is for informational use only. Do not change - /// your code depending on the version you are working with. - /// - string Version { get; } - - /// - /// Specifies, whether the server still serves requests or - /// whether it is currently shut down. - /// - bool Running { get; } - - /// - /// If enabled, components may provide additional information - /// allowing developers to further debug web applications. - /// - bool Development { get; } - - /// - /// The endpoints the server is listening on. - /// - IEndPointCollection EndPoints { get; } - - /// - /// An instance that will be called on certain events such as - /// handled requests or errors that occur within the engine. - /// - IServerCompanion? Companion { get; } - - /// - /// The main router that will be used by the server to dispatch - /// incoming HTTP requests. - /// - IHandler Handler { get; } - - } + IHandler Handler { get; } } diff --git a/API/Infrastructure/IServerBuilder.cs b/API/Infrastructure/IServerBuilder.cs index ad581471..77e01350 100644 --- a/API/Infrastructure/IServerBuilder.cs +++ b/API/Infrastructure/IServerBuilder.cs @@ -4,173 +4,170 @@ using System.Security.Cryptography.X509Certificates; using GenHTTP.Api.Content; -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// Allows to configure and create a new instance. +/// +public interface IServerBuilder : IServerBuilder { } + +/// +/// Allows to configure and create a new instance. +/// +public interface IServerBuilder : IBuilder { + #region Content + + /// + /// Specifies the root handler that will be invoked when + /// a client request needs to be handled. + /// + /// The handler to be invoked to handle requests + /// + /// Note that only a single handler is supported. To build are more + /// complex application, consider passing a Layout instead. + /// + T Handler(IHandlerBuilder handler); + + #endregion + + #region Infrastructure + + /// + /// Registers a companion that will log all handled requests and + /// errors to the console. + /// + T Console(); + + /// + /// Registers the given companion to be used by the server, allowing + /// to log and handle requests and errors. + /// + /// The companion to be used by the server + T Companion(IServerCompanion companion); + /// - /// Allows to configure and create a new instance. + /// Enables or disables the development mode on the server instance. When in + /// development mode, the server will return additional information + /// useful for developers of web applications. /// - public interface IServerBuilder : IServerBuilder { } + /// Whether the development should be active + /// + /// By default, the development mode is disabled. + /// + T Development(bool developmentMode = true); + + #endregion + + #region Binding /// - /// Allows to configure and create a new instance. + /// Specifies the port, the server will listen on (defaults to 8080 on IPv4/IPv6). /// - public interface IServerBuilder : IBuilder - { - - #region Content - - /// - /// Specifies the root handler that will be invoked when - /// a client request needs to be handled. - /// - /// The handler to be invoked to handle requests - /// - /// Note that only a single handler is supported. To build are more - /// complex application, consider passing a Layout instead. - /// - T Handler(IHandlerBuilder handler); - - #endregion - - #region Infrastructure - - /// - /// Registers a companion that will log all handled requests and - /// errors to the console. - /// - T Console(); - - /// - /// Registers the given companion to be used by the server, allowing - /// to log and handle requests and errors. - /// - /// The companion to be used by the server - T Companion(IServerCompanion companion); - - /// - /// Enables or disables the development mode on the server instance. When in - /// development mode, the server will return additional information - /// useful for developers of web applications. - /// - /// Whether the development should be active - /// - /// By default, the development mode is disabled. - /// - T Development(bool developmentMode = true); - - #endregion - - #region Binding - - /// - /// Specifies the port, the server will listen on (defaults to 8080 on IPv4/IPv6). - /// - /// The port the server should listen on - /// - /// If you register custom endpoints using the Bind methods, this value - /// will be ignored. - /// - T Port(ushort port); - - /// - /// Registers an endpoint for the given address and port the server will - /// bind to on startup to listen for incomming HTTP requests. - /// - /// The address to bind to - /// The port to listen on - T Bind(IPAddress address, ushort port); - - /// - /// Registers a secure endpoint the server will bind to on - /// startup to listen for incoming HTTPS requests. - /// - /// The address to bind to - /// The port to listen on - /// The certificate used to negoiate a connection with - /// - /// By default, the endpoint will accept TLS 1.2 connections only. - /// - T Bind(IPAddress address, ushort port, X509Certificate2 certificate); - - /// - /// Registers a secure endpoint the server will bind to on - /// startup to listen for incoming HTTPS requests. - /// - /// The address to bind to - /// The port to listen on - /// The certificate used to negoiate a connection with - /// The SSL/TLS protocl versions which should be supported by the endpoint - T Bind(IPAddress address, ushort port, X509Certificate2 certificate, SslProtocols protocols); - - /// - /// Registers a secure endpoint the server will bind to on - /// startup to listen for incoming HTTPS requests. - /// - /// The address to bind to - /// The port to listen on - /// The provider to select the certificate used to negoiate a connection with - /// - /// By default, the endpoint will accept TLS 1.2 connections only. - /// - T Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider); - - /// - /// Registers a secure endpoint the server will bind to on - /// startup to listen for incoming HTTPS requests. - /// - /// The address to bind to - /// The port to listen on - /// The provider to select the certificate used to negoiate a connection with - /// The SSL/TLS protocl versions which should be supported by the endpoint - T Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols); - - #endregion - - #region Extensibility - - /// - /// Adds a concern to the server instance which will be executed before - /// and after the root handler is invoked. - /// - /// The concern to be added to the instance - T Add(IConcernBuilder concern); - - #endregion - - #region Network settings - - /// - /// Configures the number of connections the operating system will accept - /// while they not have yet been accepted by the server. - /// - /// The number of connections to be accepted - /// - /// Adjust this value only, if you expect large bursts of simultaneous requests - /// or your server requires very long to generate requests. - /// - T Backlog(ushort backlog); - - /// - /// Specifies the period of time after which the server will - /// assume the client connection timed out. - /// - T RequestReadTimeout(TimeSpan timeout); - - /// - /// Requests smaller than this limit (in bytes) will be held in memory, while - /// larger requests will be cached in a temporary file. - /// - T RequestMemoryLimit(uint limit); - - /// - /// Size of the buffer that will be used to read or write large - /// data streams (such as uploads or downloads). - /// - T TransferBufferSize(uint bufferSize); - - #endregion - - } + /// The port the server should listen on + /// + /// If you register custom endpoints using the Bind methods, this value + /// will be ignored. + /// + T Port(ushort port); + + /// + /// Registers an endpoint for the given address and port the server will + /// bind to on startup to listen for incomming HTTP requests. + /// + /// The address to bind to + /// The port to listen on + T Bind(IPAddress address, ushort port); + + /// + /// Registers a secure endpoint the server will bind to on + /// startup to listen for incoming HTTPS requests. + /// + /// The address to bind to + /// The port to listen on + /// The certificate used to negoiate a connection with + /// + /// By default, the endpoint will accept TLS 1.2 connections only. + /// + T Bind(IPAddress address, ushort port, X509Certificate2 certificate); + + /// + /// Registers a secure endpoint the server will bind to on + /// startup to listen for incoming HTTPS requests. + /// + /// The address to bind to + /// The port to listen on + /// The certificate used to negoiate a connection with + /// The SSL/TLS protocl versions which should be supported by the endpoint + T Bind(IPAddress address, ushort port, X509Certificate2 certificate, SslProtocols protocols); + + /// + /// Registers a secure endpoint the server will bind to on + /// startup to listen for incoming HTTPS requests. + /// + /// The address to bind to + /// The port to listen on + /// The provider to select the certificate used to negoiate a connection with + /// + /// By default, the endpoint will accept TLS 1.2 connections only. + /// + T Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider); + + /// + /// Registers a secure endpoint the server will bind to on + /// startup to listen for incoming HTTPS requests. + /// + /// The address to bind to + /// The port to listen on + /// The provider to select the certificate used to negoiate a connection with + /// The SSL/TLS protocl versions which should be supported by the endpoint + T Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols); + + #endregion + + #region Extensibility + + /// + /// Adds a concern to the server instance which will be executed before + /// and after the root handler is invoked. + /// + /// The concern to be added to the instance + T Add(IConcernBuilder concern); + + #endregion + + #region Network settings + + /// + /// Configures the number of connections the operating system will accept + /// while they not have yet been accepted by the server. + /// + /// The number of connections to be accepted + /// + /// Adjust this value only, if you expect large bursts of simultaneous requests + /// or your server requires very long to generate requests. + /// + T Backlog(ushort backlog); + + /// + /// Specifies the period of time after which the server will + /// assume the client connection timed out. + /// + T RequestReadTimeout(TimeSpan timeout); + + /// + /// Requests smaller than this limit (in bytes) will be held in memory, while + /// larger requests will be cached in a temporary file. + /// + T RequestMemoryLimit(uint limit); + + /// + /// Size of the buffer that will be used to read or write large + /// data streams (such as uploads or downloads). + /// + T TransferBufferSize(uint bufferSize); + + #endregion } diff --git a/API/Infrastructure/IServerCompanion.cs b/API/Infrastructure/IServerCompanion.cs index 2464d24e..1078f9cd 100644 --- a/API/Infrastructure/IServerCompanion.cs +++ b/API/Infrastructure/IServerCompanion.cs @@ -2,85 +2,82 @@ using System.Net; using GenHTTP.Api.Protocol; -namespace GenHTTP.Api.Infrastructure -{ +namespace GenHTTP.Api.Infrastructure; + +#region Error scopes - #region Error scopes +/// +/// The kind of errors which may occur within the +/// server engine. +/// +public enum ServerErrorScope +{ /// - /// The kind of errors which may occur within the - /// server engine. + /// Errors which occur within the regular lifecycle, + /// such as startup errors. /// - public enum ServerErrorScope - { + General, - /// - /// Errors which occur within the regular lifecycle, - /// such as startup errors. - /// - General, - - /// - /// Errors which occur when listening for requests or - /// when handling them. - /// - ServerConnection, + /// + /// Errors which occur when listening for requests or + /// when handling them. + /// + ServerConnection, - /// - /// Errors which occur when communicating with the client, - /// such as aborted connections. - /// - ClientConnection, + /// + /// Errors which occur when communicating with the client, + /// such as aborted connections. + /// + ClientConnection, - /// - /// Errors which occur when trying to establish a secure - /// connection with the client. - /// - Security, + /// + /// Errors which occur when trying to establish a secure + /// connection with the client. + /// + Security, - /// - /// Errors which occur when the server tries to generate a default - /// error page, e.g. because the template somehow fails to render. - /// - PageGeneration, + /// + /// Errors which occur when the server tries to generate a default + /// error page, e.g. because the template somehow fails to render. + /// + PageGeneration, - /// - /// An error which occurred within an extension. - /// - Extension, + /// + /// An error which occurred within an extension. + /// + Extension, - } +} - #endregion +#endregion + +/// +/// A companion which can be registered at the server to handle +/// requests and error messages. +/// +/// +/// Bad runtime characteristics of an implementing class can severly +/// lower the throughput of your server instance. If you would like to +/// perform long-running tasks such as logging to a database, it's recommended +/// to add some kind of asynchronous worker mechanism. +/// +public interface IServerCompanion +{ /// - /// A companion which can be registered at the server to handle - /// requests and error messages. + /// Will be invoked after request has been handled by the server. /// - /// - /// Bad runtime characteristics of an implementing class can severly - /// lower the throughput of your server instance. If you would like to - /// perform long-running tasks such as logging to a database, it's recommended - /// to add some kind of asynchronous worker mechanism. - /// - public interface IServerCompanion - { + /// The request which has been handled + /// The response which has been generated by the server + void OnRequestHandled(IRequest request, IResponse response); - /// - /// Will be invoked after request has been handled by the server. - /// - /// The request which has been handled - /// The response which has been generated by the server - void OnRequestHandled(IRequest request, IResponse response); - - /// - /// Will be invoked if an error occurred within the server engine. - /// - /// The scope of the error - /// The endpoint of the client which caused this error (if any) - /// The actual exception which occurred - void OnServerError(ServerErrorScope scope, IPAddress? client, Exception error); - - } + /// + /// Will be invoked if an error occurred within the server engine. + /// + /// The scope of the error + /// The endpoint of the client which caused this error (if any) + /// The actual exception which occurred + void OnServerError(ServerErrorScope scope, IPAddress? client, Exception error); -} +} \ No newline at end of file diff --git a/API/Infrastructure/IServerHost.cs b/API/Infrastructure/IServerHost.cs index a0faffc7..c1559d09 100644 --- a/API/Infrastructure/IServerHost.cs +++ b/API/Infrastructure/IServerHost.cs @@ -1,45 +1,42 @@ -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// Allows applications to manage the lifecycle of a server instance. +/// +public interface IServerHost : IServerBuilder { /// - /// Allows applications to manage the lifecycle of a server instance. + /// The server instance maintained by the host, if started. /// - public interface IServerHost : IServerBuilder - { - - /// - /// The server instance maintained by the host, if started. - /// - IServer? Instance { get; } - - /// - /// Builds a server instance from the current configuration - /// and starts it. - /// - IServerHost Start(); + IServer? Instance { get; } - /// - /// Stops the currently running server instance, if any. - /// - IServerHost Stop(); + /// + /// Builds a server instance from the current configuration + /// and starts it. + /// + IServerHost Start(); - /// - /// Stops the currently running server instance and starts - /// a new one. - /// - IServerHost Restart(); + /// + /// Stops the currently running server instance, if any. + /// + IServerHost Stop(); - /// - /// Builds a server instance from the current configuration - /// and keeps it running until the application process exits. - /// - /// - /// Convenience method that can be used as a one-liner by - /// console or docker based applications. - /// - /// The return code to be passed to the operating system - int Run(); + /// + /// Stops the currently running server instance and starts + /// a new one. + /// + IServerHost Restart(); - } + /// + /// Builds a server instance from the current configuration + /// and keeps it running until the application process exits. + /// + /// + /// Convenience method that can be used as a one-liner by + /// console or docker based applications. + /// + /// The return code to be passed to the operating system + int Run(); } diff --git a/API/Infrastructure/Priority.cs b/API/Infrastructure/Priority.cs index b9aa0f7e..aeed3e9b 100644 --- a/API/Infrastructure/Priority.cs +++ b/API/Infrastructure/Priority.cs @@ -1,11 +1,8 @@ -namespace GenHTTP.Api.Infrastructure -{ - - public enum Priority - { - Low = 0, - Medium = 50, - High = 100 - } +namespace GenHTTP.Api.Infrastructure; +public enum Priority +{ + Low = 0, + Medium = 50, + High = 100 } diff --git a/API/Infrastructure/PriorityEvaluation.cs b/API/Infrastructure/PriorityEvaluation.cs index 9f5f3ce7..8a382ffd 100644 --- a/API/Infrastructure/PriorityEvaluation.cs +++ b/API/Infrastructure/PriorityEvaluation.cs @@ -1,8 +1,5 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Api.Infrastructure -{ +namespace GenHTTP.Api.Infrastructure; - public delegate Priority PriorityEvaluation(IRequest request); - -} +public delegate Priority PriorityEvaluation(IRequest request); diff --git a/API/Infrastructure/SecureUpgrade.cs b/API/Infrastructure/SecureUpgrade.cs index f2a2a298..1e03c211 100644 --- a/API/Infrastructure/SecureUpgrade.cs +++ b/API/Infrastructure/SecureUpgrade.cs @@ -1,31 +1,28 @@ -namespace GenHTTP.Api.Infrastructure +namespace GenHTTP.Api.Infrastructure; + +/// +/// Specifies the strategy of the server to redirect +/// unsecure requests to HTTPS secured endpoints. +/// +public enum SecureUpgrade { /// - /// Specifies the strategy of the server to redirect - /// unsecure requests to HTTPS secured endpoints. + /// The server will not attempt to upgrade requests. /// - public enum SecureUpgrade - { - - /// - /// The server will not attempt to upgrade requests. - /// - None, + None, - /// - /// Clients may request an upgrade via the Upgrade-Insecure-Requests - /// header. Aside from that, the server will not attempt to upgrade - /// insecure requests. - /// - Allow, - - /// - /// The server will always redirect requests to an insecure endpoint - /// to the HTTPS secured one. - /// - Force + /// + /// Clients may request an upgrade via the Upgrade-Insecure-Requests + /// header. Aside from that, the server will not attempt to upgrade + /// insecure requests. + /// + Allow, - } + /// + /// The server will always redirect requests to an insecure endpoint + /// to the HTTPS secured one. + /// + Force } diff --git a/API/Protocol/ClientProtocol.cs b/API/Protocol/ClientProtocol.cs index 007bf14f..1a12f94c 100644 --- a/API/Protocol/ClientProtocol.cs +++ b/API/Protocol/ClientProtocol.cs @@ -1,10 +1,7 @@ -namespace GenHTTP.Api.Protocol -{ - - public enum ClientProtocol - { - HTTP, - HTTPS - } +namespace GenHTTP.Api.Protocol; -} +public enum ClientProtocol +{ + HTTP, + HTTPS +} \ No newline at end of file diff --git a/API/Protocol/ContentType.cs b/API/Protocol/ContentType.cs index 08988cb8..05ac89c5 100644 --- a/API/Protocol/ContentType.cs +++ b/API/Protocol/ContentType.cs @@ -3,422 +3,421 @@ using System.Collections.Generic; using System.Linq; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +#region Known Types + +/// +/// The content type of the response. +/// +public enum ContentType { - #region Known Types + /// + /// A html page. + /// + TextHtml, /// - /// The content type of the response. + /// A stylesheet. /// - public enum ContentType - { + TextCss, + + /// + /// A JavaScript source file. + /// + ApplicationJavaScript, + + /// + /// A JSON file. + /// + ApplicationJson, + + /// + /// A PNG image. + /// + ImagePng, + + /// + /// A BMP image. + /// + ImageBmp, + + /// + /// A JPG image. + /// + ImageJpg, + + /// + /// A GIF image. + /// + ImageGif, + + /// + /// A download. + /// + ApplicationForceDownload, + + /// + /// Anything else - data. + /// + ApplicationOctetStream, + + /// + /// A MP4 audio file. + /// + AudioMp4, + + /// + /// A OGG audio file. + /// + AudioOgg, - /// - /// A html page. - /// - TextHtml, - - /// - /// A stylesheet. - /// - TextCss, - - /// - /// A JavaScript source file. - /// - ApplicationJavaScript, - - /// - /// A JSON file. - /// - ApplicationJson, - - /// - /// A PNG image. - /// - ImagePng, - - /// - /// A BMP image. - /// - ImageBmp, - - /// - /// A JPG image. - /// - ImageJpg, - - /// - /// A GIF image. - /// - ImageGif, - - /// - /// A download. - /// - ApplicationForceDownload, - - /// - /// Anything else - data. - /// - ApplicationOctetStream, - - /// - /// A MP4 audio file. - /// - AudioMp4, - - /// - /// A OGG audio file. - /// - AudioOgg, - - /// - /// A MPEG audio file. - /// - AudioMpeg, - - /// - /// A TIFF image. - /// - ImageTiff, - - /// - /// A CSV file. - /// - TextCsv, - - /// - /// A RTF file. - /// - TextRichText, - - /// - /// Plain text. - /// - TextPlain, - - /// - /// A XML file. - /// - TextXml, - - /// - /// A JavaScript file. - /// - TextJavaScript, - - /// - /// A uncompressed audio file. - /// - AudioWav, - - /// - /// Word processing document (e.g. docx). - /// - ApplicationOfficeDocumentWordProcessing, - - /// - /// A presentation (e.g. pptx). - /// - ApplicationOfficeDocumentPresentation, - - /// - /// A slideshow (e.g. .ppsx). - /// - ApplicationOfficeDocumentSlideshow, - - /// - /// A sheet (e.g. .xlsx). - /// - ApplicationOfficeDocumentSheet, - - /// - /// An icon. - /// - ImageIcon, - - /// - /// Microsoft, embedded otf. - /// - FontEmbeddedOpenTypeFont, - - /// - /// True type font (.ttf) - /// - FontTrueTypeFont, - - /// - /// Woff font (.woff) - /// - FontWoff, - - /// - /// Woff 2 font (.woff2) - /// - FontWoff2, - - /// - /// Open type fonf (.otf) - /// - FontOpenTypeFont, - - /// - /// Scalable Vector Graphics (.svg) - /// - ImageScalableVectorGraphics, - - /// - /// Scalable Vector Graphics (.svg) - /// - ImageScalableVectorGraphicsXml, - - /// - /// Scalable Vector Graphics (compressed, .svgz) - /// - ImageScalableVectorGraphicsCompressed, - - /// - /// Url encoded form data. - /// - ApplicationWwwFormUrlEncoded, - - /// - /// A Protobuf message. - /// - ApplicationProtobuf, - - /// - /// 3GPP video file container (.3gp). - /// - Video3Gpp, - - /// - /// 3GPP2 video files (.3g2). - /// - Video3Gpp2, - - /// - /// AV1 video file (.av1). - /// - VideoAV1, - - /// - /// A MPEG4 Part 10 (H.264) video file (.avc). - /// - VideoAvc, - - /// - /// Digital video file (.dv). - /// - VideoDV, - - /// - /// A H.261 video file. - /// - VideoH261, - - /// - /// A H.263 video file. - /// - VideoH263, - - /// - /// A H.264 encoded video file. - /// - VideoH264, - - /// - /// A H.265 video file. - /// - VideoH265, - - /// - /// A H.266 video file. - /// - VideoH266, - - /// - /// A Matroska video file (.mkv). - /// - VideoMatroska, - - /// - /// A 3D Matroska video file (.mk3d). - /// - VideoMatroska3D, - - /// - /// A Motion JPEG 2000 video file (.mj2). - /// - VideoMJ2, - - /// - /// A MP4 video file (.mp4). - /// - VideoMP4, - - /// - /// A MPEG video file. - /// - VideoMpeg, - - /// - /// A MPEG-4 video file. - /// - VideoMpeg4Generic, - - /// - /// A MPEG-2 elementary stream video (.mpv). - /// - VideoMpv, - - /// - /// An Apple quick time video file (.mov or .hdmov). - /// - VideoQuicktime, - - /// - /// A raw video file. - /// - VideoRaw, - - /// - /// A SMPTE 421M video file (.vc1). - /// - VideoVC1, - - /// - /// A SMPTE VC-2 video file. - /// - VideoVC2, - - /// - /// A VP8 encoded video file (.webm). - /// - VideoVP8, - - /// - /// A VP9 encoded video file (.webm). - /// - VideoVP9, - - /// - /// A WebM video file (.webm). - /// - VideoWebM - - } + /// + /// A MPEG audio file. + /// + AudioMpeg, + + /// + /// A TIFF image. + /// + ImageTiff, + + /// + /// A CSV file. + /// + TextCsv, + + /// + /// A RTF file. + /// + TextRichText, + + /// + /// Plain text. + /// + TextPlain, + + /// + /// A XML file. + /// + TextXml, + + /// + /// A JavaScript file. + /// + TextJavaScript, + + /// + /// A uncompressed audio file. + /// + AudioWav, + + /// + /// Word processing document (e.g. docx). + /// + ApplicationOfficeDocumentWordProcessing, + + /// + /// A presentation (e.g. pptx). + /// + ApplicationOfficeDocumentPresentation, + + /// + /// A slideshow (e.g. .ppsx). + /// + ApplicationOfficeDocumentSlideshow, + + /// + /// A sheet (e.g. .xlsx). + /// + ApplicationOfficeDocumentSheet, + + /// + /// An icon. + /// + ImageIcon, + + /// + /// Microsoft, embedded otf. + /// + FontEmbeddedOpenTypeFont, + + /// + /// True type font (.ttf) + /// + FontTrueTypeFont, + + /// + /// Woff font (.woff) + /// + FontWoff, + + /// + /// Woff 2 font (.woff2) + /// + FontWoff2, + + /// + /// Open type fonf (.otf) + /// + FontOpenTypeFont, + + /// + /// Scalable Vector Graphics (.svg) + /// + ImageScalableVectorGraphics, + + /// + /// Scalable Vector Graphics (.svg) + /// + ImageScalableVectorGraphicsXml, + + /// + /// Scalable Vector Graphics (compressed, .svgz) + /// + ImageScalableVectorGraphicsCompressed, + + /// + /// Url encoded form data. + /// + ApplicationWwwFormUrlEncoded, + + /// + /// A Protobuf message. + /// + ApplicationProtobuf, + + /// + /// 3GPP video file container (.3gp). + /// + Video3Gpp, + + /// + /// 3GPP2 video files (.3g2). + /// + Video3Gpp2, + + /// + /// AV1 video file (.av1). + /// + VideoAV1, + + /// + /// A MPEG4 Part 10 (H.264) video file (.avc). + /// + VideoAvc, + + /// + /// Digital video file (.dv). + /// + VideoDV, + + /// + /// A H.261 video file. + /// + VideoH261, + + /// + /// A H.263 video file. + /// + VideoH263, + + /// + /// A H.264 encoded video file. + /// + VideoH264, + + /// + /// A H.265 video file. + /// + VideoH265, + + /// + /// A H.266 video file. + /// + VideoH266, + + /// + /// A Matroska video file (.mkv). + /// + VideoMatroska, + + /// + /// A 3D Matroska video file (.mk3d). + /// + VideoMatroska3D, + + /// + /// A Motion JPEG 2000 video file (.mj2). + /// + VideoMJ2, + + /// + /// A MP4 video file (.mp4). + /// + VideoMP4, + + /// + /// A MPEG video file. + /// + VideoMpeg, + + /// + /// A MPEG-4 video file. + /// + VideoMpeg4Generic, + + /// + /// A MPEG-2 elementary stream video (.mpv). + /// + VideoMpv, + + /// + /// An Apple quick time video file (.mov or .hdmov). + /// + VideoQuicktime, + + /// + /// A raw video file. + /// + VideoRaw, + + /// + /// A SMPTE 421M video file (.vc1). + /// + VideoVC1, + + /// + /// A SMPTE VC-2 video file. + /// + VideoVC2, + + /// + /// A VP8 encoded video file (.webm). + /// + VideoVP8, + + /// + /// A VP9 encoded video file (.webm). + /// + VideoVP9, + + /// + /// A WebM video file (.webm). + /// + VideoWebM + +} + +#endregion + +/// +/// The type of content which is sent to or received from a client. +/// +public class FlexibleContentType +{ + private static readonly ConcurrentDictionary _RawCache = new(StringComparer.InvariantCultureIgnoreCase); + + private static readonly Dictionary _KnownCache = new(); + + #region Get-/Setters + + /// + /// The known, enumerated type, if any. + /// + public ContentType? KnownType { get; } + + /// + /// The raw type. + /// + public string RawType { get; } + + /// + /// The charset of the content, if any. + /// + public string? Charset { get; } #endregion + #region Mapping + + private static readonly Dictionary MAPPING = new() + { + { ContentType.AudioMp4, "audio/mp4" }, + { ContentType.AudioOgg, "audio/ogg" }, + { ContentType.AudioMpeg, "audio/mpeg" }, + { ContentType.AudioWav, "audio/wav" }, + { ContentType.ApplicationJavaScript, "application/javascript" }, + { ContentType.ApplicationOfficeDocumentWordProcessing, "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, + { ContentType.ApplicationOfficeDocumentPresentation, "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, + { ContentType.ApplicationOfficeDocumentSlideshow, "application/vnd.openxmlformats-officedocument.presentationml.slideshow" }, + { ContentType.ApplicationOfficeDocumentSheet, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, + { ContentType.ApplicationForceDownload, "application/force-download" }, + { ContentType.ApplicationOctetStream, "application/octet-stream" }, + { ContentType.ApplicationJson, "application/json" }, + { ContentType.ApplicationWwwFormUrlEncoded, "application/x-www-form-urlencoded" }, + { ContentType.ApplicationProtobuf, "application/protobuf" }, + { ContentType.FontEmbeddedOpenTypeFont, "font/eot" }, + { ContentType.FontOpenTypeFont, "font/otf" }, + { ContentType.FontTrueTypeFont, "font/ttf" }, + { ContentType.FontWoff, "font/woff" }, + { ContentType.FontWoff2, "font/woff2" }, + { ContentType.ImageIcon, "image/x-icon" }, + { ContentType.ImageGif, "image/gif" }, + { ContentType.ImageJpg, "image/jpg" }, + { ContentType.ImagePng, "image/png" }, + { ContentType.ImageTiff, "image/tiff" }, + { ContentType.ImageBmp, "image/bmp" }, + { ContentType.ImageScalableVectorGraphics, "image/svg" }, + { ContentType.ImageScalableVectorGraphicsXml, "image/svg+xml" }, + { ContentType.ImageScalableVectorGraphicsCompressed, "image/svgz" }, + { ContentType.TextHtml, "text/html" }, + { ContentType.TextCss, "text/css" }, + { ContentType.TextCsv, "text/csv" }, + { ContentType.TextRichText, "text/richtext" }, + { ContentType.TextPlain, "text/plain" }, + { ContentType.TextJavaScript, "text/javascript" }, + { ContentType.TextXml, "text/xml" }, + { ContentType.Video3Gpp, "video/3gpp" }, + { ContentType.Video3Gpp2, "video/3gpp2" }, + { ContentType.VideoAV1, "video/av1" }, + { ContentType.VideoAvc, "video/av" }, + { ContentType.VideoDV, "video/dv" }, + { ContentType.VideoH261, "video/H261" }, + { ContentType.VideoH263, "video/H263" }, + { ContentType.VideoH264, "video/H264" }, + { ContentType.VideoH265, "video/H265" }, + { ContentType.VideoH266, "video/H266" }, + { ContentType.VideoMatroska, "video/matroska" }, + { ContentType.VideoMatroska3D, "video/matroska-3d" }, + { ContentType.VideoMJ2, "video/mj2" }, + { ContentType.VideoMP4, "video/mp4" }, + { ContentType.VideoMpeg, "video/mpeg" }, + { ContentType.VideoMpeg4Generic, "video/mpeg4-generic" }, + { ContentType.VideoMpv, "video/MPV" }, + { ContentType.VideoQuicktime, "video/quicktime" }, + { ContentType.VideoRaw, "video/raw" }, + { ContentType.VideoVC1, "video/vc1" }, + { ContentType.VideoVC2, "video/vc2" }, + { ContentType.VideoVP8, "video/VP8" }, + { ContentType.VideoVP9, "video/VP9" }, + { ContentType.VideoWebM, "video/webm" } + }; + + private static readonly Dictionary MAPPING_REVERSE = MAPPING.ToDictionary(x => x.Value, x => x.Key); + + #endregion + + #region Initialization + /// - /// The type of content which is sent to or received from a client. + /// Create a new content type from the given string. /// - public class FlexibleContentType + /// The string representation of the content type + /// The charset of the content, if known + public FlexibleContentType(string rawType, string? charset = null) { - private static readonly ConcurrentDictionary _RawCache = new(StringComparer.InvariantCultureIgnoreCase); - - private static readonly Dictionary _KnownCache = new(); - - #region Get-/Setters - - /// - /// The known, enumerated type, if any. - /// - public ContentType? KnownType { get; } - - /// - /// The raw type. - /// - public string RawType { get; } - - /// - /// The charset of the content, if any. - /// - public string? Charset { get; } - - #endregion - - #region Mapping - - private static readonly Dictionary MAPPING = new() - { - { ContentType.AudioMp4, "audio/mp4" }, - { ContentType.AudioOgg, "audio/ogg" }, - { ContentType.AudioMpeg, "audio/mpeg" }, - { ContentType.AudioWav, "audio/wav" }, - { ContentType.ApplicationJavaScript, "application/javascript" }, - { ContentType.ApplicationOfficeDocumentWordProcessing, "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, - { ContentType.ApplicationOfficeDocumentPresentation, "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, - { ContentType.ApplicationOfficeDocumentSlideshow, "application/vnd.openxmlformats-officedocument.presentationml.slideshow" }, - { ContentType.ApplicationOfficeDocumentSheet, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, - { ContentType.ApplicationForceDownload, "application/force-download" }, - { ContentType.ApplicationOctetStream, "application/octet-stream" }, - { ContentType.ApplicationJson, "application/json" }, - { ContentType.ApplicationWwwFormUrlEncoded, "application/x-www-form-urlencoded" }, - { ContentType.ApplicationProtobuf, "application/protobuf" }, - { ContentType.FontEmbeddedOpenTypeFont, "font/eot" }, - { ContentType.FontOpenTypeFont, "font/otf" }, - { ContentType.FontTrueTypeFont, "font/ttf" }, - { ContentType.FontWoff, "font/woff" }, - { ContentType.FontWoff2, "font/woff2" }, - { ContentType.ImageIcon, "image/x-icon" }, - { ContentType.ImageGif, "image/gif" }, - { ContentType.ImageJpg, "image/jpg" }, - { ContentType.ImagePng, "image/png" }, - { ContentType.ImageTiff, "image/tiff" }, - { ContentType.ImageBmp, "image/bmp" }, - { ContentType.ImageScalableVectorGraphics, "image/svg" }, - { ContentType.ImageScalableVectorGraphicsXml, "image/svg+xml" }, - { ContentType.ImageScalableVectorGraphicsCompressed, "image/svgz" }, - { ContentType.TextHtml, "text/html" }, - { ContentType.TextCss, "text/css" }, - { ContentType.TextCsv, "text/csv" }, - { ContentType.TextRichText, "text/richtext" }, - { ContentType.TextPlain, "text/plain" }, - { ContentType.TextJavaScript, "text/javascript" }, - { ContentType.TextXml, "text/xml" }, - { ContentType.Video3Gpp, "video/3gpp" }, - { ContentType.Video3Gpp2, "video/3gpp2" }, - { ContentType.VideoAV1, "video/av1" }, - { ContentType.VideoAvc, "video/av" }, - { ContentType.VideoDV, "video/dv" }, - { ContentType.VideoH261, "video/H261" }, - { ContentType.VideoH263, "video/H263" }, - { ContentType.VideoH264, "video/H264" }, - { ContentType.VideoH265, "video/H265" }, - { ContentType.VideoH266, "video/H266" }, - { ContentType.VideoMatroska, "video/matroska" }, - { ContentType.VideoMatroska3D, "video/matroska-3d" }, - { ContentType.VideoMJ2, "video/mj2" }, - { ContentType.VideoMP4, "video/mp4" }, - { ContentType.VideoMpeg, "video/mpeg" }, - { ContentType.VideoMpeg4Generic, "video/mpeg4-generic" }, - { ContentType.VideoMpv, "video/MPV" }, - { ContentType.VideoQuicktime, "video/quicktime" }, - { ContentType.VideoRaw, "video/raw" }, - { ContentType.VideoVC1, "video/vc1" }, - { ContentType.VideoVC2, "video/vc2" }, - { ContentType.VideoVP8, "video/VP8" }, - { ContentType.VideoVP9, "video/VP9" }, - { ContentType.VideoWebM, "video/webm" } - }; - - private static readonly Dictionary MAPPING_REVERSE = MAPPING.ToDictionary(x => x.Value, x => x.Key); - - #endregion - - #region Initialization - - /// - /// Create a new content type from the given string. - /// - /// The string representation of the content type - /// The charset of the content, if known - public FlexibleContentType(string rawType, string? charset = null) - { RawType = rawType; Charset = charset; @@ -432,30 +431,30 @@ public FlexibleContentType(string rawType, string? charset = null) } } - /// - /// Create a new content type from the given known type. - /// - /// The known type - /// The charset of the content, if known - public FlexibleContentType(ContentType type, string? charset = null) - { + /// + /// Create a new content type from the given known type. + /// + /// The known type + /// The charset of the content, if known + public FlexibleContentType(ContentType type, string? charset = null) + { KnownType = type; RawType = MAPPING[type]; Charset = charset; } - #endregion + #endregion - #region Functionality + #region Functionality - /// - /// Fetches a cached instance for the given content type. - /// - /// The raw string to be resolved - /// The content type instance to be used - public static FlexibleContentType Get(string rawType, string? charset = null) - { + /// + /// Fetches a cached instance for the given content type. + /// + /// The raw string to be resolved + /// The content type instance to be used + public static FlexibleContentType Get(string rawType, string? charset = null) + { if (charset is not null) { return new(rawType, charset); @@ -473,13 +472,13 @@ public static FlexibleContentType Get(string rawType, string? charset = null) return type; } - /// - /// Fetches a cached instance for the given content type. - /// - /// The known type to be resolved - /// The content type instance to be used - public static FlexibleContentType Get(ContentType knownType, string? charset = null) - { + /// + /// Fetches a cached instance for the given content type. + /// + /// The known type to be resolved + /// The content type instance to be used + public static FlexibleContentType Get(ContentType knownType, string? charset = null) + { if (charset is not null) { return new(knownType, charset); @@ -497,13 +496,13 @@ public static FlexibleContentType Get(ContentType knownType, string? charset = n return type; } - /// - /// Parses the given header value into a content type structure. - /// - /// The header to be parsed - /// The parsed content type - public static FlexibleContentType Parse(string header) - { + /// + /// Parses the given header value into a content type structure. + /// + /// The header to be parsed + /// The parsed content type + public static FlexibleContentType Parse(string header) + { var span = header.AsSpan(); var index = span.IndexOf(';'); @@ -529,24 +528,22 @@ public static FlexibleContentType Parse(string header) } } - #endregion - - #region Convenience + #endregion - public static bool operator ==(FlexibleContentType type, ContentType knownType) => type.KnownType == knownType; + #region Convenience - public static bool operator !=(FlexibleContentType type, ContentType knownType) => type.KnownType != knownType; + public static bool operator ==(FlexibleContentType type, ContentType knownType) => type.KnownType == knownType; - public static bool operator ==(FlexibleContentType type, string rawType) => type.RawType == rawType; + public static bool operator !=(FlexibleContentType type, ContentType knownType) => type.KnownType != knownType; - public static bool operator !=(FlexibleContentType type, string rawType) => type.RawType != rawType; + public static bool operator ==(FlexibleContentType type, string rawType) => type.RawType == rawType; - public override bool Equals(object? obj) => obj is FlexibleContentType type && RawType == type.RawType; + public static bool operator !=(FlexibleContentType type, string rawType) => type.RawType != rawType; - public override int GetHashCode() => RawType.GetHashCode(); + public override bool Equals(object? obj) => obj is FlexibleContentType type && RawType == type.RawType; - #endregion + public override int GetHashCode() => RawType.GetHashCode(); - } + #endregion } diff --git a/API/Protocol/Cookie.cs b/API/Protocol/Cookie.cs index b1e5bfb4..c1e1379d 100644 --- a/API/Protocol/Cookie.cs +++ b/API/Protocol/Cookie.cs @@ -1,59 +1,56 @@ -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +/// +/// Represents a cookie that can be send to or received from a client. +/// +public struct Cookie { + #region Get-/Setters + /// - /// Represents a cookie that can be send to or received from a client. + /// The name of the cookie. /// - public struct Cookie - { - - #region Get-/Setters + public string Name { get; } - /// - /// The name of the cookie. - /// - public string Name { get; } - - /// - /// The value of the cookie. - /// - public string Value { get; set; } + /// + /// The value of the cookie. + /// + public string Value { get; set; } - /// - /// The number of seconds after the cookie will be discarded by the client. - /// - public ulong? MaxAge { get; set; } + /// + /// The number of seconds after the cookie will be discarded by the client. + /// + public ulong? MaxAge { get; set; } - #endregion + #endregion - #region Initialization + #region Initialization - /// - /// Creates a new cookie with the given name and value. - /// - /// The name of the cookie - /// The value of the cookie - public Cookie(string name, string value) - { + /// + /// Creates a new cookie with the given name and value. + /// + /// The name of the cookie + /// The value of the cookie + public Cookie(string name, string value) + { Name = name; Value = value; MaxAge = null; } - /// - /// Creates a new cookie with the given name and value. - /// - /// The name of the cookie - /// The value of the cookie - /// The number of seconds until the cookie will be discarded - public Cookie(string name, string value, ulong maxAge) : this(name, value) - { + /// + /// Creates a new cookie with the given name and value. + /// + /// The name of the cookie + /// The value of the cookie + /// The number of seconds until the cookie will be discarded + public Cookie(string name, string value, ulong maxAge) : this(name, value) + { MaxAge = maxAge; } - #endregion - - } + #endregion } diff --git a/API/Protocol/Forwarding.cs b/API/Protocol/Forwarding.cs index f6ba38a2..469167cc 100644 --- a/API/Protocol/Forwarding.cs +++ b/API/Protocol/Forwarding.cs @@ -1,12 +1,9 @@ using System.Net; -namespace GenHTTP.Api.Protocol -{ +namespace GenHTTP.Api.Protocol; - /// - /// Stores information how a request has been proxied - /// to the server. - /// - public record Forwarding(IPAddress? For, string? Host, ClientProtocol? Protocol); - -} +/// +/// Stores information how a request has been proxied +/// to the server. +/// +public record Forwarding(IPAddress? For, string? Host, ClientProtocol? Protocol); diff --git a/API/Protocol/HttpProtocol.cs b/API/Protocol/HttpProtocol.cs index fc0eb2da..ec9c6018 100644 --- a/API/Protocol/HttpProtocol.cs +++ b/API/Protocol/HttpProtocol.cs @@ -1,19 +1,16 @@ -namespace GenHTTP.Api.Protocol -{ +namespace GenHTTP.Api.Protocol; +/// +/// The type of protocol to use for the response. +/// +public enum HttpProtocol +{ /// - /// The type of protocol to use for the response. + /// HTTP/1.0 /// - public enum HttpProtocol - { - /// - /// HTTP/1.0 - /// - Http_1_0, - /// - /// HTTP/1.1 - /// - Http_1_1 - } - + Http_1_0, + /// + /// HTTP/1.1 + /// + Http_1_1 } diff --git a/API/Protocol/ICookieCollection.cs b/API/Protocol/ICookieCollection.cs index a21885ce..a343dc0a 100644 --- a/API/Protocol/ICookieCollection.cs +++ b/API/Protocol/ICookieCollection.cs @@ -1,16 +1,13 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Api.Protocol -{ - - /// - /// A collection representing the cookies of an - /// or . - /// - public interface ICookieCollection : IReadOnlyDictionary, IDisposable - { +namespace GenHTTP.Api.Protocol; - } +/// +/// A collection representing the cookies of an +/// or . +/// +public interface ICookieCollection : IReadOnlyDictionary, IDisposable +{ } diff --git a/API/Protocol/IEditableHeaderCollection.cs b/API/Protocol/IEditableHeaderCollection.cs index ff6c9e5f..3fba3c0e 100644 --- a/API/Protocol/IEditableHeaderCollection.cs +++ b/API/Protocol/IEditableHeaderCollection.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Api.Protocol -{ - - public interface IEditableHeaderCollection : IDictionary, IDisposable - { +namespace GenHTTP.Api.Protocol; - } +public interface IEditableHeaderCollection : IDictionary, IDisposable +{ -} +} \ No newline at end of file diff --git a/API/Protocol/IForwardingCollection.cs b/API/Protocol/IForwardingCollection.cs index 4e2a39bf..51c32351 100644 --- a/API/Protocol/IForwardingCollection.cs +++ b/API/Protocol/IForwardingCollection.cs @@ -1,11 +1,8 @@ using System.Collections.Generic; -namespace GenHTTP.Api.Protocol -{ - - public interface IForwardingCollection : IList - { +namespace GenHTTP.Api.Protocol; - } +public interface IForwardingCollection : IList +{ } diff --git a/API/Protocol/IHeaderCollection.cs b/API/Protocol/IHeaderCollection.cs index 29865665..d4b05466 100644 --- a/API/Protocol/IHeaderCollection.cs +++ b/API/Protocol/IHeaderCollection.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Api.Protocol -{ - - /// - /// The headers of an or . - /// - public interface IHeaderCollection : IReadOnlyDictionary, IDisposable - { +namespace GenHTTP.Api.Protocol; - } +/// +/// The headers of an or . +/// +public interface IHeaderCollection : IReadOnlyDictionary, IDisposable +{ } diff --git a/API/Protocol/IRequest.cs b/API/Protocol/IRequest.cs index 3be74196..c487dbdc 100644 --- a/API/Protocol/IRequest.cs +++ b/API/Protocol/IRequest.cs @@ -4,148 +4,145 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Routing; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +/// +/// A request send by the currently connected client. +/// +public interface IRequest : IDisposable { + #region General Infrastructure + + /// + /// The server handling the request. + /// + IServer Server { get; } + + /// + /// The endpoint the request originates from. + /// + IEndPoint EndPoint { get; } + + /// + /// The client which sent the request. + /// + IClientConnection Client { get; } + + /// + /// If the request has been forwarded by a proxy, the client property + /// will return the originating client where this property will return + /// the information of the proxy. + /// + IClientConnection LocalClient { get; } + + #endregion + + #region HTTP Protocol + + /// + /// The requested protocol type. + /// + HttpProtocol ProtocolType { get; } + + /// + /// The HTTP method used by the client to issue this request. + /// + FlexibleRequestMethod Method { get; } + + /// + /// The path requested by the client (with no query parameters attached). + /// + RoutingTarget Target { get; } + + #endregion + + #region Headers + /// - /// A request send by the currently connected client. - /// - public interface IRequest : IDisposable - { + /// The user agent which issued this request, if any. + /// + string? UserAgent { get; } - #region General Infrastructure - - /// - /// The server handling the request. - /// - IServer Server { get; } - - /// - /// The endpoint the request originates from. - /// - IEndPoint EndPoint { get; } + /// + /// The referrer which caused the invociation of this request, if any. + /// + string? Referer { get; } - /// - /// The client which sent the request. - /// - IClientConnection Client { get; } - - /// - /// If the request has been forwarded by a proxy, the client property - /// will return the originating client where this property will return - /// the information of the proxy. - /// - IClientConnection LocalClient { get; } + /// + /// The host requested by the client, if any. + /// + string? Host { get; } - #endregion + /// + /// Read an additional header value from the request. + /// + /// The name of the header field to be read + /// The value of the header field, if specified by the client + string? this[string additionalHeader] { get; } - #region HTTP Protocol + /// + /// The query parameters passed by the client. + /// + IRequestQuery Query { get; } - /// - /// The requested protocol type. - /// - HttpProtocol ProtocolType { get; } + /// + /// The cookies passed by the client. + /// + ICookieCollection Cookies { get; } - /// - /// The HTTP method used by the client to issue this request. - /// - FlexibleRequestMethod Method { get; } + /// + /// If the request has been forwarded by one or more proxies, this collection may contain + /// additional information about the initial request by the originating client. + /// + /// + /// Use to quickly access the requesting client without the need + /// of scrolling through the forwardings. + /// + IForwardingCollection Forwardings { get; } - /// - /// The path requested by the client (with no query parameters attached). - /// - RoutingTarget Target { get; } + /// + /// The headers of this HTTP request. + /// + IHeaderCollection Headers { get; } - #endregion + #endregion - #region Headers + #region Body - /// - /// The user agent which issued this request, if any. - /// - string? UserAgent { get; } + /// + /// The content transmitted by the client, if any. + /// + Stream? Content { get; } - /// - /// The referrer which caused the invociation of this request, if any. - /// - string? Referer { get; } + /// + /// The type of content transmitted by the client, if any. + /// + FlexibleContentType? ContentType { get; } - /// - /// The host requested by the client, if any. - /// - string? Host { get; } + #endregion - /// - /// Read an additional header value from the request. - /// - /// The name of the header field to be read - /// The value of the header field, if specified by the client - string? this[string additionalHeader] { get; } + #region Functionality - /// - /// The query parameters passed by the client. - /// - IRequestQuery Query { get; } + /// + /// Generates a new response for this request to be send to the client. + /// + /// The newly created response + IResponseBuilder Respond(); - /// - /// The cookies passed by the client. - /// - ICookieCollection Cookies { get; } + #endregion - /// - /// If the request has been forwarded by one or more proxies, this collection may contain - /// additional information about the initial request by the originating client. - /// - /// - /// Use to quickly access the requesting client without the need - /// of scrolling through the forwardings. - /// - IForwardingCollection Forwardings { get; } + #region Extensibility - /// - /// The headers of this HTTP request. - /// - IHeaderCollection Headers { get; } + /// + /// Additional properties that have been attached to the request. + /// + /// + /// Can be used to store additional state on request level if needed. + /// Should be avoided in favor of more strict coupling. + /// + IRequestProperties Properties { get; } - #endregion - - #region Body - - /// - /// The content transmitted by the client, if any. - /// - Stream? Content { get; } - - /// - /// The type of content transmitted by the client, if any. - /// - FlexibleContentType? ContentType { get; } - - #endregion - - #region Functionality - - /// - /// Generates a new response for this request to be send to the client. - /// - /// The newly created response - IResponseBuilder Respond(); - - #endregion - - #region Extensibility - - /// - /// Additional properties that have been attached to the request. - /// - /// - /// Can be used to store additional state on request level if needed. - /// Should be avoided in favor of more strict coupling. - /// - IRequestProperties Properties { get; } - - #endregion - - } + #endregion } diff --git a/API/Protocol/IRequestProperties.cs b/API/Protocol/IRequestProperties.cs index aeb7e636..233d9cde 100644 --- a/API/Protocol/IRequestProperties.cs +++ b/API/Protocol/IRequestProperties.cs @@ -1,39 +1,36 @@ using System; using System.Diagnostics.CodeAnalysis; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +/// +/// Property bag to store additional data within the +/// currently running request context. +/// +public interface IRequestProperties : IDisposable { /// - /// Property bag to store additional data within the - /// currently running request context. + /// Accesses a value that is stored within + /// the property bag. /// - public interface IRequestProperties : IDisposable - { - - /// - /// Accesses a value that is stored within - /// the property bag. - /// - /// The key of the item to be fetched - /// Thrown if the given key is not present - object this[string key] { get; set; } + /// The key of the item to be fetched + /// Thrown if the given key is not present + object this[string key] { get; set; } - /// - /// Attempts to fetch the typed value for the given key from the property bag. - /// - /// The key of the value to be fetched - /// The entry read from the property bag, if any - /// The expected type of value to be returned - /// True if the value could be read, false otherwise - bool TryGet(string key, [MaybeNullWhen(returnValue: false)] out T entry); - - /// - /// Removes the entry with the given name. - /// - /// The entry to be removed - void Clear(string key); + /// + /// Attempts to fetch the typed value for the given key from the property bag. + /// + /// The key of the value to be fetched + /// The entry read from the property bag, if any + /// The expected type of value to be returned + /// True if the value could be read, false otherwise + bool TryGet(string key, [MaybeNullWhen(returnValue: false)] out T entry); - } + /// + /// Removes the entry with the given name. + /// + /// The entry to be removed + void Clear(string key); -} +} \ No newline at end of file diff --git a/API/Protocol/IRequestQuery.cs b/API/Protocol/IRequestQuery.cs index 4a6e2699..31770b36 100644 --- a/API/Protocol/IRequestQuery.cs +++ b/API/Protocol/IRequestQuery.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Api.Protocol -{ - - /// - /// Stores the query sent by the client. - /// - public interface IRequestQuery : IReadOnlyDictionary, IDisposable - { +namespace GenHTTP.Api.Protocol; - } +/// +/// Stores the query sent by the client. +/// +public interface IRequestQuery : IReadOnlyDictionary, IDisposable +{ } diff --git a/API/Protocol/IResponse.cs b/API/Protocol/IResponse.cs index 08f47eef..ebf946bc 100644 --- a/API/Protocol/IResponse.cs +++ b/API/Protocol/IResponse.cs @@ -1,89 +1,86 @@ using System; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +/// +/// The response to be send to the connected client for a given request. +/// +public interface IResponse : IDisposable { + #region Protocol + /// - /// The response to be send to the connected client for a given request. + /// The HTTP response code. /// - public interface IResponse : IDisposable - { - - #region Protocol - - /// - /// The HTTP response code. - /// - FlexibleResponseStatus Status { get; set; } + FlexibleResponseStatus Status { get; set; } - #endregion + #endregion - #region Headers + #region Headers - /// - /// Define, when this resource will expire. - /// - DateTime? Expires { get; set; } - - /// - /// Define, when this ressource has been changed the last time. - /// - DateTime? Modified { get; set; } + /// + /// Define, when this resource will expire. + /// + DateTime? Expires { get; set; } - /// - /// Retrieve or set the value of a header field. - /// - /// The name of the header field - /// The value of the header field - string? this[string field] { get; set; } + /// + /// Define, when this ressource has been changed the last time. + /// + DateTime? Modified { get; set; } - /// - /// The headers of the HTTP response. - /// - IEditableHeaderCollection Headers { get; } + /// + /// Retrieve or set the value of a header field. + /// + /// The name of the header field + /// The value of the header field + string? this[string field] { get; set; } - /// - /// The cookies to be sent to the client. - /// - ICookieCollection Cookies { get; } + /// + /// The headers of the HTTP response. + /// + IEditableHeaderCollection Headers { get; } - /// - /// True, if there are cookies to be sent with this respone. - /// - bool HasCookies { get; } + /// + /// The cookies to be sent to the client. + /// + ICookieCollection Cookies { get; } - /// - /// Adds the given cookie to the cookie collection of this response. - /// - /// The cookie to be added - void SetCookie(Cookie cookie); + /// + /// True, if there are cookies to be sent with this respone. + /// + bool HasCookies { get; } - #endregion + /// + /// Adds the given cookie to the cookie collection of this response. + /// + /// The cookie to be added + void SetCookie(Cookie cookie); - #region Content + #endregion - /// - /// The type of the content. - /// - FlexibleContentType? ContentType { get; set; } + #region Content - /// - /// The encoding of the content (e.g. "br"). - /// - string? ContentEncoding { get; set; } + /// + /// The type of the content. + /// + FlexibleContentType? ContentType { get; set; } - /// - /// The number of bytes the content consists of. - /// - ulong? ContentLength { get; set; } + /// + /// The encoding of the content (e.g. "br"). + /// + string? ContentEncoding { get; set; } - /// - /// The response that will be sent to the requesting client. - /// - IResponseContent? Content { get; set; } + /// + /// The number of bytes the content consists of. + /// + ulong? ContentLength { get; set; } - #endregion + /// + /// The response that will be sent to the requesting client. + /// + IResponseContent? Content { get; set; } - } + #endregion } diff --git a/API/Protocol/IResponseBuilder.cs b/API/Protocol/IResponseBuilder.cs index 566d73fa..26222344 100644 --- a/API/Protocol/IResponseBuilder.cs +++ b/API/Protocol/IResponseBuilder.cs @@ -1,26 +1,23 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +/// +/// Allows to configure a HTTP response to be send. +/// +public interface IResponseBuilder : IBuilder, IResponseModification { /// - /// Allows to configure a HTTP response to be send. + /// Specifies the content to be sent to the client. /// - public interface IResponseBuilder : IBuilder, IResponseModification - { - - /// - /// Specifies the content to be sent to the client. - /// - /// The content to be send to the client - IResponseBuilder Content(IResponseContent content); - - /// - /// Specifies the length of the content stream, if known. - /// - /// The length of the content stream - IResponseBuilder Length(ulong length); + /// The content to be send to the client + IResponseBuilder Content(IResponseContent content); - } + /// + /// Specifies the length of the content stream, if known. + /// + /// The length of the content stream + IResponseBuilder Length(ulong length); } diff --git a/API/Protocol/IResponseContent.cs b/API/Protocol/IResponseContent.cs index c8d1152c..d092e99a 100644 --- a/API/Protocol/IResponseContent.cs +++ b/API/Protocol/IResponseContent.cs @@ -1,51 +1,48 @@ using System.IO; using System.Threading.Tasks; -namespace GenHTTP.Api.Protocol -{ +namespace GenHTTP.Api.Protocol; +/// +/// Represents the content of a HTTP response to be sent to the client. +/// +/// +/// Allows to efficiently stream data into the network stream used by the server. +/// +public interface IResponseContent +{ + /// - /// Represents the content of a HTTP response to be sent to the client. + /// The number of bytes to be sent to the client (if known). /// /// - /// Allows to efficiently stream data into the network stream used by the server. + /// If null is returned by this method, the server needs + /// to use chunked encoding to send the data to the client. Therefore, + /// try to determine the correct length of the content to be sent + /// whenever possible. + /// + /// Writing more or less bytes than indicated by this property to the + /// target stream will cause HTTP client errors or timeouts to occur. /// - public interface IResponseContent - { - - /// - /// The number of bytes to be sent to the client (if known). - /// - /// - /// If null is returned by this method, the server needs - /// to use chunked encoding to send the data to the client. Therefore, - /// try to determine the correct length of the content to be sent - /// whenever possible. - /// - /// Writing more or less bytes than indicated by this property to the - /// target stream will cause HTTP client errors or timeouts to occur. - /// - ulong? Length { get; } - - /// - /// A checksum of the content represented by this instance. - /// - /// - /// The checksum calculation should be as fast as possible but - /// still allow to reliably detect changes. For efficient processing, - /// this also means that the content should actually be expanded - /// when the Write call is invoked, not when the content instance - /// is constructed. - /// - ValueTask CalculateChecksumAsync(); + ulong? Length { get; } - /// - /// Writes the content to the specified target stream. - /// - /// The stream to write the data to - /// The buffer size to be used to write the data - ValueTask WriteAsync(Stream target, uint bufferSize); + /// + /// A checksum of the content represented by this instance. + /// + /// + /// The checksum calculation should be as fast as possible but + /// still allow to reliably detect changes. For efficient processing, + /// this also means that the content should actually be expanded + /// when the Write call is invoked, not when the content instance + /// is constructed. + /// + ValueTask CalculateChecksumAsync(); - } + /// + /// Writes the content to the specified target stream. + /// + /// The stream to write the data to + /// The buffer size to be used to write the data + ValueTask WriteAsync(Stream target, uint bufferSize); } diff --git a/API/Protocol/IResponseModification.cs b/API/Protocol/IResponseModification.cs index eb86eeb0..771f134c 100644 --- a/API/Protocol/IResponseModification.cs +++ b/API/Protocol/IResponseModification.cs @@ -1,76 +1,73 @@ using System; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +/// +/// Allows the response generated by a builder or handler to be +/// adjusted. +/// +/// +/// This can be useful if you would like to add behavior that the +/// original handler (such as a page renderer) does not provide. +/// +/// For example, as the page handlers implement this interface, +/// you can add an additional header to the response being generated +/// for a page. +/// +/// The type of builder used as a return value +public interface IResponseModification { /// - /// Allows the response generated by a builder or handler to be - /// adjusted. + /// Specifies the HTTP status code of the response. /// - /// - /// This can be useful if you would like to add behavior that the - /// original handler (such as a page renderer) does not provide. - /// - /// For example, as the page handlers implement this interface, - /// you can add an additional header to the response being generated - /// for a page. - /// - /// The type of builder used as a return value - public interface IResponseModification - { - - /// - /// Specifies the HTTP status code of the response. - /// - /// The HTTP status code of the response - TBuilder Status(ResponseStatus status); - - /// - /// Specifies the HTTP status code of the response. - /// - /// The status code of the response - /// The reason phrase of the response (such as "Not Found" for 404) - TBuilder Status(int status, string reason); + /// The HTTP status code of the response + TBuilder Status(ResponseStatus status); - /// - /// Sets the given header field on the response. Changing HTTP - /// protocol headers may cause incorrect behavior. - /// - /// The name of the header to be set - /// The value of the header field - TBuilder Header(string key, string value); + /// + /// Specifies the HTTP status code of the response. + /// + /// The status code of the response + /// The reason phrase of the response (such as "Not Found" for 404) + TBuilder Status(int status, string reason); - /// - /// Sets the expiration date of the response. - /// - /// The expiration date of the response - TBuilder Expires(DateTime expiryDate); + /// + /// Sets the given header field on the response. Changing HTTP + /// protocol headers may cause incorrect behavior. + /// + /// The name of the header to be set + /// The value of the header field + TBuilder Header(string key, string value); - /// - /// Sets the point in time when the requested resource has been - /// modified last. - /// - /// The point in time when the requested resource has been modified last - TBuilder Modified(DateTime modificationDate); + /// + /// Sets the expiration date of the response. + /// + /// The expiration date of the response + TBuilder Expires(DateTime expiryDate); - /// - /// Adds the given cookie to the response. - /// - /// The cookie to be added - TBuilder Cookie(Cookie cookie); + /// + /// Sets the point in time when the requested resource has been + /// modified last. + /// + /// The point in time when the requested resource has been modified last + TBuilder Modified(DateTime modificationDate); - /// - /// Specifies the content type of this response. - /// - /// The content type of this response - TBuilder Type(FlexibleContentType contentType); + /// + /// Adds the given cookie to the response. + /// + /// The cookie to be added + TBuilder Cookie(Cookie cookie); - /// - /// Sets the encoding of the content. - /// - /// The encoding of the content - TBuilder Encoding(string encoding); + /// + /// Specifies the content type of this response. + /// + /// The content type of this response + TBuilder Type(FlexibleContentType contentType); - } + /// + /// Sets the encoding of the content. + /// + /// The encoding of the content + TBuilder Encoding(string encoding); } diff --git a/API/Protocol/ProtocolException.cs b/API/Protocol/ProtocolException.cs index 9e9b4be8..6de1639a 100644 --- a/API/Protocol/ProtocolException.cs +++ b/API/Protocol/ProtocolException.cs @@ -1,26 +1,23 @@ using System; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +/// +/// Thrown by the server, if the HTTP protocol has +/// somehow been violated (either by the server or the client). +/// +[Serializable] +public class ProtocolException : Exception { - /// - /// Thrown by the server, if the HTTP protocol has - /// somehow been violated (either by the server or the client). - /// - [Serializable] - public class ProtocolException : Exception + public ProtocolException(string reason) : base(reason) { - public ProtocolException(string reason) : base(reason) - { - } - public ProtocolException(string reason, Exception inner) : base(reason, inner) - { + public ProtocolException(string reason, Exception inner) : base(reason, inner) + { } - } - } diff --git a/API/Protocol/RequestMethod.cs b/API/Protocol/RequestMethod.cs index 9d1fa2cf..3f06fa32 100644 --- a/API/Protocol/RequestMethod.cs +++ b/API/Protocol/RequestMethod.cs @@ -1,106 +1,105 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +public enum RequestMethod { + GET, + HEAD, + POST, + PUT, + PATCH, + DELETE, + OPTIONS, + PROPFIND, + PROPPATCH, + MKCOL, + COPY, + MOVE, + LOCK, + UNLOCK +} - public enum RequestMethod +/// +/// The kind of request sent by the client. +/// +public class FlexibleRequestMethod +{ + private static readonly Dictionary _RawCache = new(StringComparer.InvariantCultureIgnoreCase) + { + { "HEAD", new(RequestMethod.HEAD) }, + { "GET", new(RequestMethod.GET) }, + { "POST", new(RequestMethod.POST) }, + { "PUT", new(RequestMethod.PUT) }, + { "DELETE", new(RequestMethod.DELETE) }, + { "OPTIONS", new(RequestMethod.OPTIONS) } + }; + + private static readonly Dictionary _KnownCache = new() { - GET, - HEAD, - POST, - PUT, - PATCH, - DELETE, - OPTIONS, - PROPFIND, - PROPPATCH, - MKCOL, - COPY, - MOVE, - LOCK, - UNLOCK - } + { RequestMethod.HEAD, new(RequestMethod.HEAD) }, + { RequestMethod.GET, new(RequestMethod.GET) }, + { RequestMethod.POST, new(RequestMethod.POST) }, + { RequestMethod.PUT, new(RequestMethod.PUT) }, + { RequestMethod.DELETE, new(RequestMethod.DELETE) }, + { RequestMethod.OPTIONS, new(RequestMethod.OPTIONS) } + }; + + #region Get-/Setters /// - /// The kind of request sent by the client. + /// The known method of the request, if any. /// - public class FlexibleRequestMethod + public RequestMethod? KnownMethod { get; } + + /// + /// The raw method of the request. + /// + public string RawMethod { get; } + + #endregion + + #region Mapping + + private static readonly Dictionary MAPPING = new(StringComparer.OrdinalIgnoreCase) + { + { "GET", RequestMethod.GET }, + { "HEAD", RequestMethod.HEAD }, + { "POST", RequestMethod.POST }, + { "PUT", RequestMethod.PUT }, + { "PATCH", RequestMethod.PATCH }, + { "DELETE", RequestMethod.DELETE }, + { "OPTIONS", RequestMethod.OPTIONS }, + { "PROPFIND", RequestMethod.PROPFIND }, + { "PROPPATCH", RequestMethod.PROPPATCH }, + { "MKCOL", RequestMethod.MKCOL }, + { "COPY", RequestMethod.COPY }, + { "MOVE", RequestMethod.MOVE }, + { "LOCK", RequestMethod.LOCK }, + { "UNLOCK", RequestMethod.UNLOCK } + }; + + #endregion + + #region Initialization + + /// + /// Creates a new request method instance from a known type. + /// + /// The known type to be used + public FlexibleRequestMethod(RequestMethod method) { - private static readonly Dictionary _RawCache = new(StringComparer.InvariantCultureIgnoreCase) - { - { "HEAD", new(RequestMethod.HEAD) }, - { "GET", new(RequestMethod.GET) }, - { "POST", new(RequestMethod.POST) }, - { "PUT", new(RequestMethod.PUT) }, - { "DELETE", new(RequestMethod.DELETE) }, - { "OPTIONS", new(RequestMethod.OPTIONS) } - }; - - private static readonly Dictionary _KnownCache = new() - { - { RequestMethod.HEAD, new(RequestMethod.HEAD) }, - { RequestMethod.GET, new(RequestMethod.GET) }, - { RequestMethod.POST, new(RequestMethod.POST) }, - { RequestMethod.PUT, new(RequestMethod.PUT) }, - { RequestMethod.DELETE, new(RequestMethod.DELETE) }, - { RequestMethod.OPTIONS, new(RequestMethod.OPTIONS) } - }; - - #region Get-/Setters - - /// - /// The known method of the request, if any. - /// - public RequestMethod? KnownMethod { get; } - - /// - /// The raw method of the request. - /// - public string RawMethod { get; } - - #endregion - - #region Mapping - - private static readonly Dictionary MAPPING = new(StringComparer.OrdinalIgnoreCase) - { - { "GET", RequestMethod.GET }, - { "HEAD", RequestMethod.HEAD }, - { "POST", RequestMethod.POST }, - { "PUT", RequestMethod.PUT }, - { "PATCH", RequestMethod.PATCH }, - { "DELETE", RequestMethod.DELETE }, - { "OPTIONS", RequestMethod.OPTIONS }, - { "PROPFIND", RequestMethod.PROPFIND }, - { "PROPPATCH", RequestMethod.PROPPATCH }, - { "MKCOL", RequestMethod.MKCOL }, - { "COPY", RequestMethod.COPY }, - { "MOVE", RequestMethod.MOVE }, - { "LOCK", RequestMethod.LOCK }, - { "UNLOCK", RequestMethod.UNLOCK } - }; - - #endregion - - #region Initialization - - /// - /// Creates a new request method instance from a known type. - /// - /// The known type to be used - public FlexibleRequestMethod(RequestMethod method) - { KnownMethod = method; RawMethod = Enum.GetName(method) ?? throw new ArgumentException("The given method cannot be mapped", nameof(method)); } - /// - /// Create a new request method instance. - /// - /// The raw type transmitted by the client - public FlexibleRequestMethod(string rawType) - { + /// + /// Create a new request method instance. + /// + /// The raw type transmitted by the client + public FlexibleRequestMethod(string rawType) + { RawMethod = rawType; if (MAPPING.TryGetValue(rawType, out var type)) @@ -113,17 +112,17 @@ public FlexibleRequestMethod(string rawType) } } - #endregion + #endregion - #region Functionality + #region Functionality - /// - /// Fetches a cached instance for the given content type. - /// - /// The raw string to be resolved - /// The content type instance to be used - public static FlexibleRequestMethod Get(string rawMethod) - { + /// + /// Fetches a cached instance for the given content type. + /// + /// The raw string to be resolved + /// The content type instance to be used + public static FlexibleRequestMethod Get(string rawMethod) + { if (_RawCache.TryGetValue(rawMethod, out var found)) { return found; @@ -136,13 +135,13 @@ public static FlexibleRequestMethod Get(string rawMethod) return method; } - /// - /// Fetches a cached instance for the given content type. - /// - /// The known value to be resolved - /// The content type instance to be used - public static FlexibleRequestMethod Get(RequestMethod knownMethod) - { + /// + /// Fetches a cached instance for the given content type. + /// + /// The known value to be resolved + /// The content type instance to be used + public static FlexibleRequestMethod Get(RequestMethod knownMethod) + { if (_KnownCache.TryGetValue(knownMethod, out var found)) { return found; @@ -155,24 +154,22 @@ public static FlexibleRequestMethod Get(RequestMethod knownMethod) return method; } - #endregion - - #region Convenience + #endregion - public static bool operator ==(FlexibleRequestMethod method, RequestMethod knownMethod) => method.KnownMethod == knownMethod; + #region Convenience - public static bool operator !=(FlexibleRequestMethod method, RequestMethod knownMethod) => method.KnownMethod != knownMethod; + public static bool operator ==(FlexibleRequestMethod method, RequestMethod knownMethod) => method.KnownMethod == knownMethod; - public static bool operator ==(FlexibleRequestMethod method, string rawMethod) => method.RawMethod == rawMethod; + public static bool operator !=(FlexibleRequestMethod method, RequestMethod knownMethod) => method.KnownMethod != knownMethod; - public static bool operator !=(FlexibleRequestMethod method, string rawMethod) => method.RawMethod != rawMethod; + public static bool operator ==(FlexibleRequestMethod method, string rawMethod) => method.RawMethod == rawMethod; - public override bool Equals(object? obj) => obj is FlexibleRequestMethod method && RawMethod == method.RawMethod; + public static bool operator !=(FlexibleRequestMethod method, string rawMethod) => method.RawMethod != rawMethod; - public override int GetHashCode() => RawMethod.GetHashCode(); + public override bool Equals(object? obj) => obj is FlexibleRequestMethod method && RawMethod == method.RawMethod; - #endregion + public override int GetHashCode() => RawMethod.GetHashCode(); - } + #endregion } diff --git a/API/Protocol/ResponseModificationBuilder.cs b/API/Protocol/ResponseModificationBuilder.cs index af7c73b2..586d0732 100644 --- a/API/Protocol/ResponseModificationBuilder.cs +++ b/API/Protocol/ResponseModificationBuilder.cs @@ -3,32 +3,31 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +/// +/// Allows to build a response modification object so that +/// individual handlers do not need to implement the logic +/// theirselves. +/// +public class ResponseModificationBuilder : IResponseModification, IBuilder { + private FlexibleResponseStatus? _Status; - /// - /// Allows to build a response modification object so that - /// individual handlers do not need to implement the logic - /// theirselves. - /// - public class ResponseModificationBuilder : IResponseModification, IBuilder - { - private FlexibleResponseStatus? _Status; - - private FlexibleContentType? _ContentType; + private FlexibleContentType? _ContentType; - private List? _Cookies; + private List? _Cookies; - private string? _Encoding; + private string? _Encoding; - private DateTime? _ExpiryDate, _ModificationDate; + private DateTime? _ExpiryDate, _ModificationDate; - private Dictionary? _Headers; + private Dictionary? _Headers; - #region Functionality + #region Functionality - public ResponseModificationBuilder Cookie(Cookie cookie) - { + public ResponseModificationBuilder Cookie(Cookie cookie) + { if (_Cookies == null) { _Cookies = new(); @@ -39,20 +38,20 @@ public ResponseModificationBuilder Cookie(Cookie cookie) return this; } - public ResponseModificationBuilder Encoding(string encoding) - { + public ResponseModificationBuilder Encoding(string encoding) + { _Encoding = encoding; return this; } - public ResponseModificationBuilder Expires(DateTime expiryDate) - { + public ResponseModificationBuilder Expires(DateTime expiryDate) + { _ExpiryDate = expiryDate; return this; } - public ResponseModificationBuilder Header(string key, string value) - { + public ResponseModificationBuilder Header(string key, string value) + { if (_Headers == null) { _Headers = new(); @@ -63,32 +62,32 @@ public ResponseModificationBuilder Header(string key, string value) return this; } - public ResponseModificationBuilder Modified(DateTime modificationDate) - { + public ResponseModificationBuilder Modified(DateTime modificationDate) + { _ModificationDate = modificationDate; return this; } - public ResponseModificationBuilder Status(ResponseStatus status) - { + public ResponseModificationBuilder Status(ResponseStatus status) + { _Status = new(status); return this; } - public ResponseModificationBuilder Status(int status, string reason) - { + public ResponseModificationBuilder Status(int status, string reason) + { _Status = new(status, reason); return this; } - public ResponseModificationBuilder Type(FlexibleContentType contentType) - { + public ResponseModificationBuilder Type(FlexibleContentType contentType) + { _ContentType = contentType; return this; } - public ResponseModifications? Build() - { + public ResponseModifications? Build() + { if ((_Status != null) || (_Encoding != null) || (null != _ContentType) || (_ExpiryDate != null) || (_ModificationDate != null) || (_Cookies?.Count > 0) || (_Headers?.Count > 0)) @@ -99,8 +98,6 @@ public ResponseModificationBuilder Type(FlexibleContentType contentType) return null; } - #endregion - - } + #endregion -} +} \ No newline at end of file diff --git a/API/Protocol/ResponseModifications.cs b/API/Protocol/ResponseModifications.cs index 110cc58d..a4c49225 100644 --- a/API/Protocol/ResponseModifications.cs +++ b/API/Protocol/ResponseModifications.cs @@ -1,60 +1,59 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Api.Protocol -{ +namespace GenHTTP.Api.Protocol; - /// - /// A set of custom modifications to be applied to a response. - /// - public sealed class ResponseModifications - { +/// +/// A set of custom modifications to be applied to a response. +/// +public sealed class ResponseModifications +{ - #region Get-/Setters + #region Get-/Setters - /// - /// The status to be set on the response, if set. - /// - public FlexibleResponseStatus? Status { get; } + /// + /// The status to be set on the response, if set. + /// + public FlexibleResponseStatus? Status { get; } - /// - /// The content type to be set on the response, if set. - /// - public FlexibleContentType? ContentType { get; } + /// + /// The content type to be set on the response, if set. + /// + public FlexibleContentType? ContentType { get; } - /// - /// The cookies to be set on the response, if set. - /// - public List? Cookies { get; } + /// + /// The cookies to be set on the response, if set. + /// + public List? Cookies { get; } - /// - /// The encoding to be set on the response, if set. - /// - public string? Encoding { get; } + /// + /// The encoding to be set on the response, if set. + /// + public string? Encoding { get; } - /// - /// The expiration date to be set on the response, if set. - /// - public DateTime? ExpiryDate { get; } + /// + /// The expiration date to be set on the response, if set. + /// + public DateTime? ExpiryDate { get; } - /// - /// The modification date to be set on the response, if set. - /// - public DateTime? ModificationDate { get; } + /// + /// The modification date to be set on the response, if set. + /// + public DateTime? ModificationDate { get; } - /// - /// The headers to be set on the response, if set. - /// - public Dictionary? Headers { get; } + /// + /// The headers to be set on the response, if set. + /// + public Dictionary? Headers { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ResponseModifications(FlexibleResponseStatus? status, FlexibleContentType? contentType, - List? cookies, string? encoding, DateTime? expiryDate, DateTime? modificationDate, - Dictionary? headers) - { + public ResponseModifications(FlexibleResponseStatus? status, FlexibleContentType? contentType, + List? cookies, string? encoding, DateTime? expiryDate, DateTime? modificationDate, + Dictionary? headers) + { Status = status; ContentType = contentType; @@ -67,17 +66,17 @@ public ResponseModifications(FlexibleResponseStatus? status, FlexibleContentType Headers = headers; } - #endregion + #endregion - #region Functionality + #region Functionality - /// - /// Applies the modifications configured in this instance to the - /// given response. - /// - /// The response to be adjusted - public void Apply(IResponseBuilder builder) - { + /// + /// Applies the modifications configured in this instance to the + /// given response. + /// + /// The response to be adjusted + public void Apply(IResponseBuilder builder) + { if (Status != null) { builder.Status(Status.Value.RawStatus, Status.Value.Phrase); @@ -120,8 +119,6 @@ public void Apply(IResponseBuilder builder) } } - #endregion - - } + #endregion } diff --git a/API/Protocol/ResponseStatus.cs b/API/Protocol/ResponseStatus.cs index 9c9a0f0d..c3dc7ce0 100644 --- a/API/Protocol/ResponseStatus.cs +++ b/API/Protocol/ResponseStatus.cs @@ -2,210 +2,209 @@ using System.Linq; using System.Collections.Generic; -namespace GenHTTP.Api.Protocol +namespace GenHTTP.Api.Protocol; + +#region Known Types + +public enum ResponseStatus { - #region Known Types + Continue = 100, - public enum ResponseStatus - { + SwitchingProtocols = 101, - Continue = 100, + Processing = 102, - SwitchingProtocols = 101, + OK = 200, - Processing = 102, + Created = 201, - OK = 200, + Accepted = 202, - Created = 201, + NoContent = 204, - Accepted = 202, + PartialContent = 206, - NoContent = 204, + MultiStatus = 207, - PartialContent = 206, + AlreadyReported = 208, - MultiStatus = 207, + MovedPermanently = 301, - AlreadyReported = 208, + Found = 302, - MovedPermanently = 301, + SeeOther = 303, - Found = 302, + NotModified = 304, - SeeOther = 303, + TemporaryRedirect = 307, - NotModified = 304, + PermanentRedirect = 308, - TemporaryRedirect = 307, + BadRequest = 400, - PermanentRedirect = 308, + Unauthorized = 401, - BadRequest = 400, + Forbidden = 403, - Unauthorized = 401, + NotFound = 404, - Forbidden = 403, + MethodNotAllowed = 405, - NotFound = 404, + NotAcceptable = 406, - MethodNotAllowed = 405, + ProxyAuthenticationRequired = 407, - NotAcceptable = 406, + Conflict = 409, - ProxyAuthenticationRequired = 407, + Gone = 410, - Conflict = 409, + LengthRequired = 411, - Gone = 410, + PreconditionFailed = 412, - LengthRequired = 411, + RequestEntityTooLarge = 413, - PreconditionFailed = 412, + RequestUriTooLong = 414, - RequestEntityTooLarge = 413, + UnsupportedMediaType = 415, - RequestUriTooLong = 414, + RequestedRangeNotSatisfiable = 416, - UnsupportedMediaType = 415, + ExpectationFailed = 417, - RequestedRangeNotSatisfiable = 416, + UnprocessableEntity = 422, - ExpectationFailed = 417, + Locked = 423, - UnprocessableEntity = 422, + FailedDependency = 424, - Locked = 423, + ReservedForWebDAV = 425, - FailedDependency = 424, + UpgradeRequired = 426, - ReservedForWebDAV = 425, + PreconditionRequired = 428, - UpgradeRequired = 426, + TooManyRequests = 429, - PreconditionRequired = 428, + RequestHeaderFieldsTooLarge = 431, - TooManyRequests = 429, + UnavailableForLegalReasons = 451, - RequestHeaderFieldsTooLarge = 431, + InternalServerError = 500, - UnavailableForLegalReasons = 451, + NotImplemented = 501, - InternalServerError = 500, + BadGateway = 502, - NotImplemented = 501, + ServiceUnavailable = 503, - BadGateway = 502, + GatewayTimeout = 504, - ServiceUnavailable = 503, + HttpVersionNotSupported = 505, - GatewayTimeout = 504, + InsufficientStorage = 507, - HttpVersionNotSupported = 505, + LoopDetected = 508, - InsufficientStorage = 507, + NotExtended = 510, - LoopDetected = 508, + NetworkAuthenticationRequired = 511 - NotExtended = 510, +} - NetworkAuthenticationRequired = 511 +#endregion - } +/// +/// The status of the response send to the client. +/// +public struct FlexibleResponseStatus +{ - #endregion + #region Get-/Setters + + /// + /// The known status, if any. + /// + public ResponseStatus? KnownStatus { get; } + + /// + /// The raw HTTP status. + /// + public int RawStatus { get; } /// - /// The status of the response send to the client. + /// The reason phrase to be send. /// - public struct FlexibleResponseStatus + public string Phrase { get; } + + #endregion + + #region Mapping + + private static readonly Dictionary MAPPING = new() { + { ResponseStatus.Accepted, "Accepted" }, + { ResponseStatus.BadGateway, "Bad Gateway" }, + { ResponseStatus.BadRequest, "Bad Request" }, + { ResponseStatus.Created, "Created" }, + { ResponseStatus.Forbidden, "Forbidden" }, + { ResponseStatus.InternalServerError, "Internal Server Error" }, + { ResponseStatus.MethodNotAllowed, "Method Not Allowed" }, + { ResponseStatus.MovedPermanently, "Moved Permamently" }, + { ResponseStatus.Found, "Found" }, + { ResponseStatus.NoContent, "No Content" }, + { ResponseStatus.NotFound, "Not Found" }, + { ResponseStatus.NotImplemented, "Not Implemented" }, + { ResponseStatus.NotModified, "Not Modified" }, + { ResponseStatus.OK, "OK" }, + { ResponseStatus.ServiceUnavailable, "Service Unavailable"}, + { ResponseStatus.Unauthorized, "Unauthorized"}, + { ResponseStatus.PartialContent, "Partial Content"}, + { ResponseStatus.MultiStatus, "Multi-Status"}, + { ResponseStatus.AlreadyReported, "Already Reported"}, + { ResponseStatus.SeeOther, "See Other" }, + { ResponseStatus.TemporaryRedirect, "Temporary Redirect"}, + { ResponseStatus.PermanentRedirect, "Permanent Redirect"}, + { ResponseStatus.Continue, "Continue" }, + { ResponseStatus.SwitchingProtocols, "Switching Protocols" }, + { ResponseStatus.NotAcceptable, "Not Acceptable" }, + { ResponseStatus.ProxyAuthenticationRequired, "Proxy Authentication Required" }, + { ResponseStatus.Conflict, "Conflict" }, + { ResponseStatus.Gone, "Gone" }, + { ResponseStatus.LengthRequired, "Length Required" }, + { ResponseStatus.PreconditionFailed, "Precondition Failed" }, + { ResponseStatus.RequestEntityTooLarge, "Request Entity Too Large" }, + { ResponseStatus.RequestUriTooLong, "Request Uri Too Long" }, + { ResponseStatus.UnsupportedMediaType, "Unsupported Media Type" }, + { ResponseStatus.RequestedRangeNotSatisfiable, "Requested Range Not Satisfiable" }, + { ResponseStatus.ExpectationFailed, "Expectation Failed" }, + { ResponseStatus.UnprocessableEntity, "Unprocessable Entity" }, + { ResponseStatus.Locked, "Locked" }, + { ResponseStatus.FailedDependency, "Failed Dependency" }, + { ResponseStatus.ReservedForWebDAV, "Reserved For WebDAV" }, + { ResponseStatus.UpgradeRequired, "Upgrade Required" }, + { ResponseStatus.PreconditionRequired, "Precondition Required" }, + { ResponseStatus.TooManyRequests, "Too Many Requests" }, + { ResponseStatus.RequestHeaderFieldsTooLarge, "Request Header Fields Too Large" }, + { ResponseStatus.UnavailableForLegalReasons, "Unavailable For Legal Reasons" }, + { ResponseStatus.GatewayTimeout, "Gateway Timeout" }, + { ResponseStatus.HttpVersionNotSupported, "HTTP Version Not Supported" }, + { ResponseStatus.InsufficientStorage, "Insufficient Storage" }, + { ResponseStatus.LoopDetected, "Loop Detected" }, + { ResponseStatus.NotExtended, "Not Extended" }, + { ResponseStatus.NetworkAuthenticationRequired, "Network Authentication Required" }, + { ResponseStatus.Processing, "Processing" } + }; + + private static readonly Dictionary CODE_MAPPING = MAPPING.Keys.ToDictionary((k) => (int)k, (k) => k); + + #endregion - #region Get-/Setters - - /// - /// The known status, if any. - /// - public ResponseStatus? KnownStatus { get; } - - /// - /// The raw HTTP status. - /// - public int RawStatus { get; } - - /// - /// The reason phrase to be send. - /// - public string Phrase { get; } - - #endregion - - #region Mapping - - private static readonly Dictionary MAPPING = new() - { - { ResponseStatus.Accepted, "Accepted" }, - { ResponseStatus.BadGateway, "Bad Gateway" }, - { ResponseStatus.BadRequest, "Bad Request" }, - { ResponseStatus.Created, "Created" }, - { ResponseStatus.Forbidden, "Forbidden" }, - { ResponseStatus.InternalServerError, "Internal Server Error" }, - { ResponseStatus.MethodNotAllowed, "Method Not Allowed" }, - { ResponseStatus.MovedPermanently, "Moved Permamently" }, - { ResponseStatus.Found, "Found" }, - { ResponseStatus.NoContent, "No Content" }, - { ResponseStatus.NotFound, "Not Found" }, - { ResponseStatus.NotImplemented, "Not Implemented" }, - { ResponseStatus.NotModified, "Not Modified" }, - { ResponseStatus.OK, "OK" }, - { ResponseStatus.ServiceUnavailable, "Service Unavailable"}, - { ResponseStatus.Unauthorized, "Unauthorized"}, - { ResponseStatus.PartialContent, "Partial Content"}, - { ResponseStatus.MultiStatus, "Multi-Status"}, - { ResponseStatus.AlreadyReported, "Already Reported"}, - { ResponseStatus.SeeOther, "See Other" }, - { ResponseStatus.TemporaryRedirect, "Temporary Redirect"}, - { ResponseStatus.PermanentRedirect, "Permanent Redirect"}, - { ResponseStatus.Continue, "Continue" }, - { ResponseStatus.SwitchingProtocols, "Switching Protocols" }, - { ResponseStatus.NotAcceptable, "Not Acceptable" }, - { ResponseStatus.ProxyAuthenticationRequired, "Proxy Authentication Required" }, - { ResponseStatus.Conflict, "Conflict" }, - { ResponseStatus.Gone, "Gone" }, - { ResponseStatus.LengthRequired, "Length Required" }, - { ResponseStatus.PreconditionFailed, "Precondition Failed" }, - { ResponseStatus.RequestEntityTooLarge, "Request Entity Too Large" }, - { ResponseStatus.RequestUriTooLong, "Request Uri Too Long" }, - { ResponseStatus.UnsupportedMediaType, "Unsupported Media Type" }, - { ResponseStatus.RequestedRangeNotSatisfiable, "Requested Range Not Satisfiable" }, - { ResponseStatus.ExpectationFailed, "Expectation Failed" }, - { ResponseStatus.UnprocessableEntity, "Unprocessable Entity" }, - { ResponseStatus.Locked, "Locked" }, - { ResponseStatus.FailedDependency, "Failed Dependency" }, - { ResponseStatus.ReservedForWebDAV, "Reserved For WebDAV" }, - { ResponseStatus.UpgradeRequired, "Upgrade Required" }, - { ResponseStatus.PreconditionRequired, "Precondition Required" }, - { ResponseStatus.TooManyRequests, "Too Many Requests" }, - { ResponseStatus.RequestHeaderFieldsTooLarge, "Request Header Fields Too Large" }, - { ResponseStatus.UnavailableForLegalReasons, "Unavailable For Legal Reasons" }, - { ResponseStatus.GatewayTimeout, "Gateway Timeout" }, - { ResponseStatus.HttpVersionNotSupported, "HTTP Version Not Supported" }, - { ResponseStatus.InsufficientStorage, "Insufficient Storage" }, - { ResponseStatus.LoopDetected, "Loop Detected" }, - { ResponseStatus.NotExtended, "Not Extended" }, - { ResponseStatus.NetworkAuthenticationRequired, "Network Authentication Required" }, - { ResponseStatus.Processing, "Processing" } - }; - - private static readonly Dictionary CODE_MAPPING = MAPPING.Keys.ToDictionary((k) => (int)k, (k) => k); - - #endregion - - #region Initialization - - public FlexibleResponseStatus(int status, string phrase) - { + #region Initialization + + public FlexibleResponseStatus(int status, string phrase) + { RawStatus = status; Phrase = phrase; @@ -219,32 +218,30 @@ public FlexibleResponseStatus(int status, string phrase) } } - public FlexibleResponseStatus(ResponseStatus status) - { + public FlexibleResponseStatus(ResponseStatus status) + { KnownStatus = status; RawStatus = (int)status; Phrase = MAPPING[status]; } - #endregion - - #region Convenience + #endregion - public static bool operator ==(FlexibleResponseStatus status, ResponseStatus knownStatus) => status.KnownStatus == knownStatus; + #region Convenience - public static bool operator !=(FlexibleResponseStatus status, ResponseStatus knownStatus) => status.KnownStatus != knownStatus; + public static bool operator ==(FlexibleResponseStatus status, ResponseStatus knownStatus) => status.KnownStatus == knownStatus; - public static bool operator ==(FlexibleResponseStatus status, int rawStatus) => status.RawStatus == rawStatus; + public static bool operator !=(FlexibleResponseStatus status, ResponseStatus knownStatus) => status.KnownStatus != knownStatus; - public static bool operator !=(FlexibleResponseStatus status, int rawStatus) => status.RawStatus != rawStatus; + public static bool operator ==(FlexibleResponseStatus status, int rawStatus) => status.RawStatus == rawStatus; - public override bool Equals(object? obj) => obj is FlexibleResponseStatus status && RawStatus == status.RawStatus; + public static bool operator !=(FlexibleResponseStatus status, int rawStatus) => status.RawStatus != rawStatus; - public override int GetHashCode() => RawStatus.GetHashCode(); + public override bool Equals(object? obj) => obj is FlexibleResponseStatus status && RawStatus == status.RawStatus; - #endregion + public override int GetHashCode() => RawStatus.GetHashCode(); - } + #endregion } diff --git a/API/Routing/PathBuilder.cs b/API/Routing/PathBuilder.cs index 0a6984d2..3834bd10 100644 --- a/API/Routing/PathBuilder.cs +++ b/API/Routing/PathBuilder.cs @@ -3,58 +3,57 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Api.Routing -{ +namespace GenHTTP.Api.Routing; - /// - /// Allows to build a instance. - /// - public sealed class PathBuilder : IBuilder - { - private readonly List _Segments; +/// +/// Allows to build a instance. +/// +public sealed class PathBuilder : IBuilder +{ + private readonly List _Segments; - private bool _TrailingSlash; + private bool _TrailingSlash; - #region Get-/Setters + #region Get-/Setters - /// - /// True, if no segments have (yet) been added to - /// this path. - /// - public bool IsEmpty => _Segments.Count == 0; + /// + /// True, if no segments have (yet) been added to + /// this path. + /// + public bool IsEmpty => _Segments.Count == 0; - #endregion + #endregion - #region Initialization + #region Initialization - /// - /// Creates a new, empty path builder. - /// - /// Whether the resulting path should end with a slash - public PathBuilder(bool trailingSlash) - { + /// + /// Creates a new, empty path builder. + /// + /// Whether the resulting path should end with a slash + public PathBuilder(bool trailingSlash) + { _Segments = new(); _TrailingSlash = trailingSlash; } - /// - /// Creates a new path builder with the given segments. - /// - /// The segments of the path - /// Whether the resulting path should end with a slash - public PathBuilder(IEnumerable parts, bool trailingSlash) - { + /// + /// Creates a new path builder with the given segments. + /// + /// The segments of the path + /// Whether the resulting path should end with a slash + public PathBuilder(IEnumerable parts, bool trailingSlash) + { _Segments = new(parts); _TrailingSlash = trailingSlash; } - /// - /// Creates a new path builder from the given absolute - /// or relative path. - /// - /// The path to be parsed - public PathBuilder(string path) - { + /// + /// Creates a new path builder from the given absolute + /// or relative path. + /// + /// The path to be parsed + public PathBuilder(string path) + { var splitted = path.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); var parts = new List(splitted.Length); @@ -68,84 +67,82 @@ public PathBuilder(string path) _TrailingSlash = path.EndsWith('/'); } - #endregion + #endregion - #region Functionality + #region Functionality - /// - /// Adds the given segment to the beginning of the resulting path. - /// - /// The segment to be prepended - public PathBuilder Preprend(string segment) - { + /// + /// Adds the given segment to the beginning of the resulting path. + /// + /// The segment to be prepended + public PathBuilder Preprend(string segment) + { _Segments.Insert(0, new WebPathPart(segment)); return this; } - /// - /// Adds the given part to the beginning of the resulting path. - /// - /// The part to be prepended - public PathBuilder Preprend(WebPathPart part) - { + /// + /// Adds the given part to the beginning of the resulting path. + /// + /// The part to be prepended + public PathBuilder Preprend(WebPathPart part) + { _Segments.Insert(0, part); return this; } - /// - /// Adds the given path to the beginning of the resulting path. - /// - /// The path to be prepended - public PathBuilder Preprend(WebPath path) - { + /// + /// Adds the given path to the beginning of the resulting path. + /// + /// The path to be prepended + public PathBuilder Preprend(WebPath path) + { _Segments.InsertRange(0, path.Parts); return this; } - /// - /// Adds the given segment to the end of the resulting path. - /// - /// The segment to be appended - public PathBuilder Append(string segment) - { + /// + /// Adds the given segment to the end of the resulting path. + /// + /// The segment to be appended + public PathBuilder Append(string segment) + { _Segments.Add(new WebPathPart(segment)); return this; } - /// - /// Adds the given segment to the end of the resulting path. - /// - /// The segment to be appended - public PathBuilder Append(WebPathPart segment) - { + /// + /// Adds the given segment to the end of the resulting path. + /// + /// The segment to be appended + public PathBuilder Append(WebPathPart segment) + { _Segments.Add(segment); return this; } - /// - /// Adds the given path to the end of the resulting path. - /// - /// The path to be appended - public PathBuilder Append(WebPath path) - { + /// + /// Adds the given path to the end of the resulting path. + /// + /// The path to be appended + public PathBuilder Append(WebPath path) + { _Segments.AddRange(path.Parts); return this; } - /// - /// Specifies, whether the resulting path ends with a slash or not. - /// - /// True, if the path should end with a trailing slash - public PathBuilder TrailingSlash(bool existent) - { + /// + /// Specifies, whether the resulting path ends with a slash or not. + /// + /// True, if the path should end with a trailing slash + public PathBuilder TrailingSlash(bool existent) + { _TrailingSlash = existent; return this; } - public WebPath Build() => new(_Segments, _TrailingSlash); - - #endregion + public WebPath Build() => new(_Segments, _TrailingSlash); - } + #endregion } diff --git a/API/Routing/RoutingTarget.cs b/API/Routing/RoutingTarget.cs index 82c5489f..08a60a0d 100644 --- a/API/Routing/RoutingTarget.cs +++ b/API/Routing/RoutingTarget.cs @@ -1,67 +1,66 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Api.Routing +namespace GenHTTP.Api.Routing; + +/// +/// Provides a view on the target path of a request. +/// +/// +/// Stores the state of the routing mechanism and allows handlers to +/// get the remaining parts to be handled. +/// +public sealed class RoutingTarget { - - /// - /// Provides a view on the target path of a request. - /// - /// - /// Stores the state of the routing mechanism and allows handlers to - /// get the remaining parts to be handled. - /// - public sealed class RoutingTarget - { - private static readonly List EMPTY_LIST = new(); + private static readonly List EMPTY_LIST = new(); - private int _Index = 0; + private int _Index = 0; - #region Get-/Setters + #region Get-/Setters - /// - /// The path of the request to be handled by the server. - /// - public WebPath Path { get; } + /// + /// The path of the request to be handled by the server. + /// + public WebPath Path { get; } - /// - /// The segment to be currently handled by the responsible handler. - /// - public WebPathPart? Current => (_Index < Path.Parts.Count) ? Path.Parts[_Index] : null; + /// + /// The segment to be currently handled by the responsible handler. + /// + public WebPathPart? Current => (_Index < Path.Parts.Count) ? Path.Parts[_Index] : null; - /// - /// Specifies, whether the end of the path has been reached. - /// - public bool Ended => (_Index >= Path.Parts.Count); + /// + /// Specifies, whether the end of the path has been reached. + /// + public bool Ended => (_Index >= Path.Parts.Count); - /// - /// Specifies, whether the last part of the path has been reached. - /// - public bool Last => (_Index == Path.Parts.Count - 1); + /// + /// Specifies, whether the last part of the path has been reached. + /// + public bool Last => (_Index == Path.Parts.Count - 1); - #endregion + #endregion - #region Initialization + #region Initialization - /// - /// Creates a new routing target and sets the pointer to the beginning of the path. - /// - /// The targeted path - public RoutingTarget(WebPath path) - { + /// + /// Creates a new routing target and sets the pointer to the beginning of the path. + /// + /// The targeted path + public RoutingTarget(WebPath path) + { Path = path; } - #endregion + #endregion - #region Functionality + #region Functionality - /// - /// Acknowledges the currently handled segment and advances the - /// pointer to the next one. - /// - public void Advance() - { + /// + /// Acknowledges the currently handled segment and advances the + /// pointer to the next one. + /// + public void Advance() + { if (Ended) { throw new InvalidOperationException("Already at the end of the path"); @@ -70,12 +69,12 @@ public void Advance() _Index++; } - /// - /// Retrieves the part of the path that still needs to be routed. - /// - /// The remaining part of the path - public WebPath GetRemaining() - { + /// + /// Retrieves the part of the path that still needs to be routed. + /// + /// The remaining part of the path + public WebPath GetRemaining() + { var remaining = Path.Parts.Count - _Index; var resultList = (remaining > 0) ? new List(remaining) : EMPTY_LIST; @@ -88,8 +87,6 @@ public WebPath GetRemaining() return new(resultList, Path.TrailingSlash); } - #endregion - - } + #endregion -} +} \ No newline at end of file diff --git a/API/Routing/WebPath.cs b/API/Routing/WebPath.cs index 697835bf..c597acf3 100644 --- a/API/Routing/WebPath.cs +++ b/API/Routing/WebPath.cs @@ -1,40 +1,39 @@ using System.Collections.Generic; using System.Linq; -namespace GenHTTP.Api.Routing +namespace GenHTTP.Api.Routing; + +/// +/// Specifies a resource available on the server. +/// +public sealed class WebPath { + #region Get-/Setters + /// - /// Specifies a resource available on the server. + /// The segments the path consists of. /// - public sealed class WebPath - { - - #region Get-/Setters + public IReadOnlyList Parts { get; } - /// - /// The segments the path consists of. - /// - public IReadOnlyList Parts { get; } - - /// - /// Specifies, whether the path ends with a trailing slash. - /// - public bool TrailingSlash { get; } + /// + /// Specifies, whether the path ends with a trailing slash. + /// + public bool TrailingSlash { get; } - /// - /// Specifies, whether the path equals the root of the server instance. - /// - public bool IsRoot => (Parts.Count == 0); + /// + /// Specifies, whether the path equals the root of the server instance. + /// + public bool IsRoot => (Parts.Count == 0); - /// - /// The name of the file that is referenced by this path (if this is - /// the path to a file). - /// - public string? File + /// + /// The name of the file that is referenced by this path (if this is + /// the path to a file). + /// + public string? File + { + get { - get - { if (!TrailingSlash) { var part = (Parts.Count > 0) ? Parts[Parts.Count - 1] : null; @@ -44,38 +43,38 @@ public string? File return null; } - } + } - #endregion + #endregion - #region Initialization + #region Initialization - public WebPath(IReadOnlyList parts, bool trailingSlash) - { + public WebPath(IReadOnlyList parts, bool trailingSlash) + { Parts = parts; TrailingSlash = trailingSlash; } - #endregion + #endregion - #region Functionality + #region Functionality - /// - /// Creates a builder that allows to edit this path. - /// - /// Specifies, whether the new path will have a trailing slash - /// The newly created builder instance - public PathBuilder Edit(bool trailingSlash) => new(Parts, trailingSlash); + /// + /// Creates a builder that allows to edit this path. + /// + /// Specifies, whether the new path will have a trailing slash + /// The newly created builder instance + public PathBuilder Edit(bool trailingSlash) => new(Parts, trailingSlash); - public override string ToString() => ToString(false); + public override string ToString() => ToString(false); - /// - /// Generates the string representation of this path. - /// - /// Specifies, whether special characters in the path should be percent encoded - /// The string representation of the path - public string ToString(bool encoded) - { + /// + /// Generates the string representation of this path. + /// + /// Specifies, whether special characters in the path should be percent encoded + /// The string representation of the path + public string ToString(bool encoded) + { if (!IsRoot) { var parts = Parts.Select(p => (encoded) ? p.Original : p.Value); @@ -86,8 +85,6 @@ public string ToString(bool encoded) return "/"; } - #endregion - - } + #endregion } diff --git a/API/Routing/WebPathPart.cs b/API/Routing/WebPathPart.cs index fd65e8ef..b211754b 100644 --- a/API/Routing/WebPathPart.cs +++ b/API/Routing/WebPathPart.cs @@ -1,29 +1,28 @@ using System; -namespace GenHTTP.Api.Routing +namespace GenHTTP.Api.Routing; + +/// +/// Represents a part of an URL (between two slashes). +/// +public class WebPathPart { + private string? _Value = null; + + #region Get-/Setters /// - /// Represents a part of an URL (between two slashes). + /// The string as received by the server (e.g. "some%20path"). /// - public class WebPathPart - { - private string? _Value = null; - - #region Get-/Setters - - /// - /// The string as received by the server (e.g. "some%20path"). - /// - public string Original { get; } + public string Original { get; } - /// - /// The decoded representation of the path (e.g. "some path"). - /// - public string Value + /// + /// The decoded representation of the path (e.g. "some path"). + /// + public string Value + { + get { - get - { if (_Value is null) { _Value = Original.Contains('%') ? Uri.UnescapeDataString(Original) : Original; @@ -31,35 +30,35 @@ public string Value return _Value; } - } + } - #endregion + #endregion - #region Initialization + #region Initialization - /// - /// Creates a new part from the original string. - /// - /// The original string - public WebPathPart(string original) - { + /// + /// Creates a new part from the original string. + /// + /// The original string + public WebPathPart(string original) + { Original = original; } - #endregion + #endregion - #region Functionality + #region Functionality - public override string ToString() => Value; + public override string ToString() => Value; - public override int GetHashCode() => HashCode.Combine(Original, Value); + public override int GetHashCode() => HashCode.Combine(Original, Value); - public static bool operator ==(WebPathPart part, string value) => part.Original == value || part.Value == value; + public static bool operator ==(WebPathPart part, string value) => part.Original == value || part.Value == value; - public static bool operator !=(WebPathPart part, string value) => part.Original != value && part.Value != value; + public static bool operator !=(WebPathPart part, string value) => part.Original != value && part.Value != value; - public override bool Equals(object? obj) - { + public override bool Equals(object? obj) + { if (ReferenceEquals(this, obj)) { return true; @@ -73,8 +72,6 @@ public override bool Equals(object? obj) return (obj as WebPathPart)?.GetHashCode() == GetHashCode(); } - #endregion - - } + #endregion } diff --git a/Engine/Host.cs b/Engine/Host.cs index cce3474e..0286d562 100644 --- a/Engine/Host.cs +++ b/Engine/Host.cs @@ -1,19 +1,16 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Engine.Hosting; -namespace GenHTTP.Engine -{ - - public static class Host - { +namespace GenHTTP.Engine; - /// - /// Provides a new server host that can be used to run a - /// server instance of the GenHTTP webserver. - /// - /// The host which can be used to run a server instance - public static IServerHost Create() => new ServerHost(); +public static class Host +{ - } + /// + /// Provides a new server host that can be used to run a + /// server instance of the GenHTTP webserver. + /// + /// The host which can be used to run a server instance + public static IServerHost Create() => new ServerHost(); } diff --git a/Engine/Hosting/ServerHost.cs b/Engine/Hosting/ServerHost.cs index fd25d2c5..a1abde2c 100644 --- a/Engine/Hosting/ServerHost.cs +++ b/Engine/Hosting/ServerHost.cs @@ -8,59 +8,58 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Engine.Hosting -{ +namespace GenHTTP.Engine.Hosting; - public sealed class ServerHost : IServerHost - { - private readonly IServerBuilder _Builder = Server.Create(); +public sealed class ServerHost : IServerHost +{ + private readonly IServerBuilder _Builder = Server.Create(); - #region Builder facade + #region Builder facade - public IServerHost Backlog(ushort backlog) { _Builder.Backlog(backlog); return this; } + public IServerHost Backlog(ushort backlog) { _Builder.Backlog(backlog); return this; } - public IServerHost Bind(IPAddress address, ushort port) { _Builder.Bind(address, port); return this; } + public IServerHost Bind(IPAddress address, ushort port) { _Builder.Bind(address, port); return this; } - public IServerHost Bind(IPAddress address, ushort port, X509Certificate2 certificate) { _Builder.Bind(address, port, certificate); return this; } + public IServerHost Bind(IPAddress address, ushort port, X509Certificate2 certificate) { _Builder.Bind(address, port, certificate); return this; } - public IServerHost Bind(IPAddress address, ushort port, X509Certificate2 certificate, SslProtocols protocols) { _Builder.Bind(address, port, certificate, protocols); return this; } + public IServerHost Bind(IPAddress address, ushort port, X509Certificate2 certificate, SslProtocols protocols) { _Builder.Bind(address, port, certificate, protocols); return this; } - public IServerHost Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider) { _Builder.Bind(address, port, certificateProvider); return this; } + public IServerHost Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider) { _Builder.Bind(address, port, certificateProvider); return this; } - public IServerHost Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols) { _Builder.Bind(address, port, certificateProvider, protocols); return this; } + public IServerHost Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols) { _Builder.Bind(address, port, certificateProvider, protocols); return this; } - public IServerHost Companion(IServerCompanion companion) { _Builder.Companion(companion); return this; } + public IServerHost Companion(IServerCompanion companion) { _Builder.Companion(companion); return this; } - public IServerHost Console() { _Builder.Console(); return this; } + public IServerHost Console() { _Builder.Console(); return this; } - public IServerHost Development(bool developmentMode = true) { _Builder.Development(developmentMode); return this; } + public IServerHost Development(bool developmentMode = true) { _Builder.Development(developmentMode); return this; } - public IServerHost Port(ushort port) { _Builder.Port(port); return this; } + public IServerHost Port(ushort port) { _Builder.Port(port); return this; } - public IServerHost RequestMemoryLimit(uint limit) { _Builder.RequestMemoryLimit(limit); return this; } + public IServerHost RequestMemoryLimit(uint limit) { _Builder.RequestMemoryLimit(limit); return this; } - public IServerHost RequestReadTimeout(TimeSpan timeout) { _Builder.RequestReadTimeout(timeout); return this; } + public IServerHost RequestReadTimeout(TimeSpan timeout) { _Builder.RequestReadTimeout(timeout); return this; } - public IServerHost Handler(IHandlerBuilder handler) { _Builder.Handler(handler); return this; } + public IServerHost Handler(IHandlerBuilder handler) { _Builder.Handler(handler); return this; } - public IServerHost TransferBufferSize(uint bufferSize) { _Builder.TransferBufferSize(bufferSize); return this; } + public IServerHost TransferBufferSize(uint bufferSize) { _Builder.TransferBufferSize(bufferSize); return this; } - public IServerHost Add(IConcernBuilder concern) { _Builder.Add(concern); return this; } + public IServerHost Add(IConcernBuilder concern) { _Builder.Add(concern); return this; } - public IServer Build() => _Builder.Build(); + public IServer Build() => _Builder.Build(); - #endregion + #endregion - #region Get-/Setters + #region Get-/Setters - public IServer? Instance { get; private set; } + public IServer? Instance { get; private set; } - #endregion + #endregion - #region Functionality + #region Functionality - public int Run() - { + public int Run() + { try { var waitEvent = new ManualResetEvent(false); @@ -105,32 +104,30 @@ public int Run() } } - public IServerHost Start() - { + public IServerHost Start() + { Stop(); Instance = Build(); return this; } - public IServerHost Stop() - { + public IServerHost Stop() + { Instance?.Dispose(); Instance = null; return this; } - public IServerHost Restart() - { + public IServerHost Restart() + { Stop(); Start(); return this; } - #endregion - - } + #endregion } diff --git a/Engine/Infrastructure/Configuration.cs b/Engine/Infrastructure/Configuration.cs index a441b2b2..fbe3dfc0 100644 --- a/Engine/Infrastructure/Configuration.cs +++ b/Engine/Infrastructure/Configuration.cs @@ -2,20 +2,16 @@ using System.Collections.Generic; using System.Net; using System.Security.Authentication; - using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Engine.Infrastructure.Configuration -{ - - internal record ServerConfiguration(bool DevelopmentMode, IEnumerable EndPoints, - NetworkConfiguration Network); +namespace GenHTTP.Engine.Infrastructure; - internal record NetworkConfiguration(TimeSpan RequestReadTimeout, uint RequestMemoryLimit, - uint TransferBufferSize, ushort Backlog); +internal record ServerConfiguration(bool DevelopmentMode, IEnumerable EndPoints, + NetworkConfiguration Network); - internal record EndPointConfiguration(IPAddress Address, ushort Port, SecurityConfiguration? Security); +internal record NetworkConfiguration(TimeSpan RequestReadTimeout, uint RequestMemoryLimit, + uint TransferBufferSize, ushort Backlog); - internal record SecurityConfiguration(ICertificateProvider Certificate, SslProtocols Protocols); +internal record EndPointConfiguration(IPAddress Address, ushort Port, SecurityConfiguration? Security); -} +internal record SecurityConfiguration(ICertificateProvider Certificate, SslProtocols Protocols); diff --git a/Engine/Infrastructure/ConsoleCompanion.cs b/Engine/Infrastructure/ConsoleCompanion.cs index c6a84012..21bdb971 100644 --- a/Engine/Infrastructure/ConsoleCompanion.cs +++ b/Engine/Infrastructure/ConsoleCompanion.cs @@ -4,22 +4,19 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Engine.Infrastructure +namespace GenHTTP.Engine.Infrastructure; + +internal sealed class ConsoleCompanion : IServerCompanion { - internal sealed class ConsoleCompanion : IServerCompanion + public void OnRequestHandled(IRequest request, IResponse response) { - - public void OnRequestHandled(IRequest request, IResponse response) - { Console.WriteLine($"REQ - {request.Client.IPAddress} - {request.Method.RawMethod} {request.Target.Path} - {response.Status.RawStatus} - {response.ContentLength ?? 0}"); } - public void OnServerError(ServerErrorScope scope, IPAddress? client, Exception error) - { + public void OnServerError(ServerErrorScope scope, IPAddress? client, Exception error) + { Console.WriteLine($"ERR - {client : 'n/a'} - {scope} - {error}"); } - } - } diff --git a/Engine/Infrastructure/CoreRouter.cs b/Engine/Infrastructure/CoreRouter.cs index d17c5837..662425d9 100644 --- a/Engine/Infrastructure/CoreRouter.cs +++ b/Engine/Infrastructure/CoreRouter.cs @@ -5,46 +5,43 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Engine.Infrastructure +namespace GenHTTP.Engine.Infrastructure; + +/// +/// Request handler which is installed by the engine as the root handler - all +/// requests will start processing from here on. Provides core functionality +/// such as rendering exceptions when they bubble up uncatched. +/// +internal sealed class CoreRouter : IHandler { - /// - /// Request handler which is installed by the engine as the root handler - all - /// requests will start processing from here on. Provides core functionality - /// such as rendering exceptions when they bubble up uncatched. - /// - internal sealed class CoreRouter : IHandler - { - - #region Get-/Setters + #region Get-/Setters - public IHandler Parent - { - get { throw new NotSupportedException("Core router has no parent"); } - set { throw new NotSupportedException("Setting core router's parent is not allowed"); } - } + public IHandler Parent + { + get { throw new NotSupportedException("Core router has no parent"); } + set { throw new NotSupportedException("Setting core router's parent is not allowed"); } + } - public IHandler Content { get; } + public IHandler Content { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal CoreRouter(IHandlerBuilder content, IEnumerable concerns) - { + internal CoreRouter(IHandlerBuilder content, IEnumerable concerns) + { Content = Concerns.Chain(this, concerns, (p) => content.Build(p)); } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask HandleAsync(IRequest request) => Content.HandleAsync(request); + public ValueTask HandleAsync(IRequest request) => Content.HandleAsync(request); - public ValueTask PrepareAsync() => Content.PrepareAsync(); + public ValueTask PrepareAsync() => Content.PrepareAsync(); - #endregion - - } + #endregion } diff --git a/Engine/Infrastructure/Endpoints/EndPoint.cs b/Engine/Infrastructure/Endpoints/EndPoint.cs index 8ae4e351..632450c8 100644 --- a/Engine/Infrastructure/Endpoints/EndPoint.cs +++ b/Engine/Infrastructure/Endpoints/EndPoint.cs @@ -6,45 +6,42 @@ using System.Threading.Tasks; using GenHTTP.Api.Infrastructure; - -using GenHTTP.Engine.Infrastructure.Configuration; - +using GenHTTP.Engine.Protocol; using PooledAwait; -namespace GenHTTP.Engine.Infrastructure.Endpoints -{ +namespace GenHTTP.Engine.Infrastructure.Endpoints; - internal abstract class EndPoint : IEndPoint - { +internal abstract class EndPoint : IEndPoint +{ - #region Get-/Setters + #region Get-/Setters - protected IServer Server { get; } + protected IServer Server { get; } - protected NetworkConfiguration Configuration { get; } + protected NetworkConfiguration Configuration { get; } - private Task Task { get; set; } + private Task Task { get; set; } - private IPEndPoint Endpoint { get; } + private IPEndPoint Endpoint { get; } - private Socket Socket { get; } + private Socket Socket { get; } - #endregion + #endregion - #region Basic Information + #region Basic Information - public IPAddress IPAddress { get; } + public IPAddress IPAddress { get; } - public ushort Port { get; } + public ushort Port { get; } - public abstract bool Secure { get; } + public abstract bool Secure { get; } - #endregion + #endregion - #region Initialization + #region Initialization - protected EndPoint(IServer server, IPEndPoint endPoint, NetworkConfiguration configuration) - { + protected EndPoint(IServer server, IPEndPoint endPoint, NetworkConfiguration configuration) + { Server = server; Endpoint = endPoint; @@ -68,12 +65,12 @@ protected EndPoint(IServer server, IPEndPoint endPoint, NetworkConfiguration con Task = Task.Run(() => Listen()); } - #endregion + #endregion - #region Functionality + #region Functionality - private async Task Listen() - { + private async Task Listen() + { try { do @@ -91,30 +88,30 @@ private async Task Listen() } } - private void Handle(Socket client) - { + private void Handle(Socket client) + { using var _ = ExecutionContext.SuppressFlow(); Task.Run(() => Accept(client)); } - protected abstract PooledValueTask Accept(Socket client); + protected abstract PooledValueTask Accept(Socket client); - protected PooledValueTask Handle(Socket client, Stream inputStream) - { + protected PooledValueTask Handle(Socket client, Stream inputStream) + { client.NoDelay = true; return new ClientHandler(client, inputStream, Server, this, Configuration).Run(); } - #endregion + #endregion - #region IDisposable Support + #region IDisposable Support - private bool disposed = false, shuttingDown = false; + private bool disposed = false, shuttingDown = false; - protected virtual void Dispose(bool disposing) - { + protected virtual void Dispose(bool disposing) + { shuttingDown = true; if (!disposed) @@ -138,14 +135,12 @@ protected virtual void Dispose(bool disposing) } } - public void Dispose() - { + public void Dispose() + { Dispose(true); GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Engine/Infrastructure/Endpoints/EndpointCollection.cs b/Engine/Infrastructure/Endpoints/EndpointCollection.cs index 837c8307..f56d8d88 100644 --- a/Engine/Infrastructure/Endpoints/EndpointCollection.cs +++ b/Engine/Infrastructure/Endpoints/EndpointCollection.cs @@ -3,26 +3,24 @@ using System.Net; using GenHTTP.Api.Infrastructure; -using GenHTTP.Engine.Infrastructure.Configuration; -namespace GenHTTP.Engine.Infrastructure.Endpoints -{ +namespace GenHTTP.Engine.Infrastructure.Endpoints; - internal sealed class EndPointCollection : List, IDisposable, IEndPointCollection - { +internal sealed class EndPointCollection : List, IDisposable, IEndPointCollection +{ - #region Get-/Setters + #region Get-/Setters - private IServer Server { get; } + private IServer Server { get; } - private NetworkConfiguration NetworkConfiguration { get; } + private NetworkConfiguration NetworkConfiguration { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public EndPointCollection(IServer server, IEnumerable configuration, NetworkConfiguration networkConfiguration) - { + public EndPointCollection(IServer server, IEnumerable configuration, NetworkConfiguration networkConfiguration) + { Server = server; NetworkConfiguration = networkConfiguration; @@ -32,12 +30,12 @@ public EndPointCollection(IServer server, IEnumerable con } } - #endregion + #endregion - #region Functionality + #region Functionality - private EndPoint Start(EndPointConfiguration configuration) - { + private EndPoint Start(EndPointConfiguration configuration) + { var endpoint = new IPEndPoint(configuration.Address, configuration.Port); if (configuration.Security is null) @@ -50,14 +48,14 @@ private EndPoint Start(EndPointConfiguration configuration) } } - #endregion + #endregion - #region IDisposable Support + #region IDisposable Support - private bool disposed = false; + private bool disposed = false; - public void Dispose() - { + public void Dispose() + { if (!disposed) { foreach (IEndPoint endpoint in this) @@ -80,8 +78,6 @@ public void Dispose() GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Engine/Infrastructure/Endpoints/InsecureEndPoint.cs b/Engine/Infrastructure/Endpoints/InsecureEndPoint.cs index 1ede3487..34b70e25 100644 --- a/Engine/Infrastructure/Endpoints/InsecureEndPoint.cs +++ b/Engine/Infrastructure/Endpoints/InsecureEndPoint.cs @@ -2,40 +2,35 @@ using System.Net.Sockets; using GenHTTP.Api.Infrastructure; - -using GenHTTP.Engine.Infrastructure.Configuration; using GenHTTP.Engine.Utilities; using PooledAwait; -namespace GenHTTP.Engine.Infrastructure.Endpoints -{ +namespace GenHTTP.Engine.Infrastructure.Endpoints; - internal sealed class InsecureEndPoint : EndPoint - { +internal sealed class InsecureEndPoint : EndPoint +{ - #region Get-/Setters + #region Get-/Setters - public override bool Secure => false; + public override bool Secure => false; - #endregion + #endregion - #region Initialization + #region Initialization - internal InsecureEndPoint(IServer server, IPEndPoint endPoint, NetworkConfiguration configuration) - : base(server, endPoint, configuration) - { + internal InsecureEndPoint(IServer server, IPEndPoint endPoint, NetworkConfiguration configuration) + : base(server, endPoint, configuration) + { } - #endregion + #endregion - #region Functionality + #region Functionality - protected override PooledValueTask Accept(Socket client) => Handle(client, new PoolBufferedStream(new NetworkStream(client), Configuration.TransferBufferSize)); + protected override PooledValueTask Accept(Socket client) => Handle(client, new PoolBufferedStream(new NetworkStream(client), Configuration.TransferBufferSize)); - #endregion - - } + #endregion } diff --git a/Engine/Infrastructure/Endpoints/SecureEndPoint.cs b/Engine/Infrastructure/Endpoints/SecureEndPoint.cs index 9c326097..780b813d 100644 --- a/Engine/Infrastructure/Endpoints/SecureEndPoint.cs +++ b/Engine/Infrastructure/Endpoints/SecureEndPoint.cs @@ -7,34 +7,31 @@ using System.Threading.Tasks; using GenHTTP.Api.Infrastructure; - -using GenHTTP.Engine.Infrastructure.Configuration; using GenHTTP.Engine.Protocol; using GenHTTP.Engine.Utilities; using PooledAwait; -namespace GenHTTP.Engine.Infrastructure.Endpoints -{ +namespace GenHTTP.Engine.Infrastructure.Endpoints; - internal sealed class SecureEndPoint : EndPoint - { +internal sealed class SecureEndPoint : EndPoint +{ - #region Get-/Setters + #region Get-/Setters - internal SecurityConfiguration Options { get; } + internal SecurityConfiguration Options { get; } - public override bool Secure => true; + public override bool Secure => true; - private SslServerAuthenticationOptions AuthenticationOptions { get; } + private SslServerAuthenticationOptions AuthenticationOptions { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal SecureEndPoint(IServer server, IPEndPoint endPoint, SecurityConfiguration options, NetworkConfiguration configuration) - : base(server, endPoint, configuration) - { + internal SecureEndPoint(IServer server, IPEndPoint endPoint, SecurityConfiguration options, NetworkConfiguration configuration) + : base(server, endPoint, configuration) + { Options = options; AuthenticationOptions = new() @@ -49,12 +46,12 @@ internal SecureEndPoint(IServer server, IPEndPoint endPoint, SecurityConfigurati }; } - #endregion + #endregion - #region Functionality + #region Functionality - protected override async PooledValueTask Accept(Socket client) - { + protected override async PooledValueTask Accept(Socket client) + { var stream = await TryAuthenticate(client); if (stream is not null) @@ -75,8 +72,8 @@ protected override async PooledValueTask Accept(Socket client) } } - private async ValueTask TryAuthenticate(Socket client) - { + private async ValueTask TryAuthenticate(Socket client) + { try { var stream = new SslStream(new NetworkStream(client), false); @@ -93,8 +90,8 @@ protected override async PooledValueTask Accept(Socket client) } } - private X509Certificate2 SelectCertificate(object sender, string? hostName) - { + private X509Certificate2 SelectCertificate(object sender, string? hostName) + { var certificate = Options.Certificate.Provide(hostName); if (certificate is null) @@ -105,8 +102,6 @@ private X509Certificate2 SelectCertificate(object sender, string? hostName) return certificate; } - #endregion - - } + #endregion -} +} \ No newline at end of file diff --git a/Engine/Infrastructure/Endpoints/SimpleCertificateProvider.cs b/Engine/Infrastructure/Endpoints/SimpleCertificateProvider.cs index c2cc8237..a2e9e587 100644 --- a/Engine/Infrastructure/Endpoints/SimpleCertificateProvider.cs +++ b/Engine/Infrastructure/Endpoints/SimpleCertificateProvider.cs @@ -2,33 +2,30 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Engine.Infrastructure.Endpoints -{ +namespace GenHTTP.Engine.Infrastructure.Endpoints; - internal sealed class SimpleCertificateProvider : ICertificateProvider - { +internal sealed class SimpleCertificateProvider : ICertificateProvider +{ - #region Get-/Setters + #region Get-/Setters - internal X509Certificate2 Certificate { get; } + internal X509Certificate2 Certificate { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal SimpleCertificateProvider(X509Certificate2 certificate) - { + internal SimpleCertificateProvider(X509Certificate2 certificate) + { Certificate = certificate; } - #endregion - - #region Functionaliy + #endregion - public X509Certificate2? Provide(string? host) => Certificate; + #region Functionaliy - #endregion + public X509Certificate2? Provide(string? host) => Certificate; - } + #endregion -} +} \ No newline at end of file diff --git a/Engine/Infrastructure/ThreadedServer.cs b/Engine/Infrastructure/ThreadedServer.cs index 2658fe1b..e0fb6201 100644 --- a/Engine/Infrastructure/ThreadedServer.cs +++ b/Engine/Infrastructure/ThreadedServer.cs @@ -4,39 +4,36 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Infrastructure; - -using GenHTTP.Engine.Infrastructure.Configuration; using GenHTTP.Engine.Infrastructure.Endpoints; -namespace GenHTTP.Engine.Infrastructure -{ +namespace GenHTTP.Engine.Infrastructure; - internal sealed class ThreadedServer : IServer - { - private readonly EndPointCollection _EndPoints; +internal sealed class ThreadedServer : IServer +{ + private readonly EndPointCollection _EndPoints; - #region Get-/Setters + #region Get-/Setters - public string Version { get; } + public string Version { get; } - public bool Running => !disposed; + public bool Running => !disposed; - public bool Development => Configuration.DevelopmentMode; + public bool Development => Configuration.DevelopmentMode; - public IHandler Handler { get; private set; } + public IHandler Handler { get; private set; } - public IServerCompanion? Companion { get; private set; } + public IServerCompanion? Companion { get; private set; } - public IEndPointCollection EndPoints => _EndPoints; + public IEndPointCollection EndPoints => _EndPoints; - internal ServerConfiguration Configuration { get; } + internal ServerConfiguration Configuration { get; } - #endregion + #endregion - #region Constructors + #region Constructors - internal ThreadedServer(IServerCompanion? companion, ServerConfiguration configuration, IHandler handler) - { + internal ThreadedServer(IServerCompanion? companion, ServerConfiguration configuration, IHandler handler) + { Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "(n/a)"; Companion = companion; @@ -49,8 +46,8 @@ internal ThreadedServer(IServerCompanion? companion, ServerConfiguration configu _EndPoints = new(this, configuration.EndPoints, configuration.Network); } - private static async ValueTask PrepareHandlerAsync(IHandler handler, IServerCompanion? companion) - { + private static async ValueTask PrepareHandlerAsync(IHandler handler, IServerCompanion? companion) + { try { await handler.PrepareAsync(); @@ -61,14 +58,14 @@ private static async ValueTask PrepareHandlerAsync(IHandler handler, IServerComp } } - #endregion + #endregion - #region IDisposable Support + #region IDisposable Support - private bool disposed = false; + private bool disposed = false; - public void Dispose() - { + public void Dispose() + { if (!disposed) { _EndPoints.Dispose(); @@ -79,8 +76,6 @@ public void Dispose() GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Engine/Infrastructure/ThreadedServerBuilder.cs b/Engine/Infrastructure/ThreadedServerBuilder.cs index 74fa1e18..9806a96c 100644 --- a/Engine/Infrastructure/ThreadedServerBuilder.cs +++ b/Engine/Infrastructure/ThreadedServerBuilder.cs @@ -7,70 +7,67 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Infrastructure; - -using GenHTTP.Engine.Infrastructure.Configuration; using GenHTTP.Engine.Infrastructure.Endpoints; using GenHTTP.Modules.ErrorHandling; -namespace GenHTTP.Engine.Infrastructure -{ +namespace GenHTTP.Engine.Infrastructure; - internal sealed class ThreadedServerBuilder : IServerBuilder - { - private ushort _Backlog = 1024; - private ushort _Port = 8080; +internal sealed class ThreadedServerBuilder : IServerBuilder +{ + private ushort _Backlog = 1024; + private ushort _Port = 8080; - private uint _RequestMemoryLimit = 1 * 1024 * 1024; // 1 MB - private uint _TransferBufferSize = 65 * 1024; // 65 KB + private uint _RequestMemoryLimit = 1 * 1024 * 1024; // 1 MB + private uint _TransferBufferSize = 65 * 1024; // 65 KB - private bool _Development = false; + private bool _Development = false; - private TimeSpan _RequestReadTimeout = TimeSpan.FromSeconds(10); + private TimeSpan _RequestReadTimeout = TimeSpan.FromSeconds(10); - private IHandlerBuilder? _Handler; - private IServerCompanion? _Companion; + private IHandlerBuilder? _Handler; + private IServerCompanion? _Companion; - private readonly List _EndPoints = new(); + private readonly List _EndPoints = new(); - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - #region Content + #region Content - public IServerBuilder Handler(IHandlerBuilder handler) - { + public IServerBuilder Handler(IHandlerBuilder handler) + { _Handler = handler; return this; } - #endregion + #endregion - #region Infrastructure + #region Infrastructure - public IServerBuilder Console() - { + public IServerBuilder Console() + { _Companion = new ConsoleCompanion(); return this; } - public IServerBuilder Companion(IServerCompanion companion) - { + public IServerBuilder Companion(IServerCompanion companion) + { _Companion = companion; return this; } - public IServerBuilder Development(bool developmentMode = true) - { + public IServerBuilder Development(bool developmentMode = true) + { _Development = developmentMode; return this; } - #endregion + #endregion - #region Binding + #region Binding - public IServerBuilder Port(ushort port) - { + public IServerBuilder Port(ushort port) + { if (port == 0) { throw new ArgumentOutOfRangeException(nameof(port)); @@ -80,78 +77,78 @@ public IServerBuilder Port(ushort port) return this; } - public IServerBuilder Bind(IPAddress address, ushort port) - { + public IServerBuilder Bind(IPAddress address, ushort port) + { _EndPoints.Add(new EndPointConfiguration(address, port, null)); return this; } - public IServerBuilder Bind(IPAddress address, ushort port, X509Certificate2 certificate) - { + public IServerBuilder Bind(IPAddress address, ushort port, X509Certificate2 certificate) + { return Bind(address, port, new SimpleCertificateProvider(certificate)); } - public IServerBuilder Bind(IPAddress address, ushort port, X509Certificate2 certificate, SslProtocols protocols) - { + public IServerBuilder Bind(IPAddress address, ushort port, X509Certificate2 certificate, SslProtocols protocols) + { return Bind(address, port, new SimpleCertificateProvider(certificate), protocols); } - public IServerBuilder Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider) - { + public IServerBuilder Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider) + { _EndPoints.Add(new EndPointConfiguration(address, port, new SecurityConfiguration(certificateProvider, SslProtocols.Tls12 | SslProtocols.Tls13))); return this; } - public IServerBuilder Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols) - { + public IServerBuilder Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols) + { _EndPoints.Add(new EndPointConfiguration(address, port, new SecurityConfiguration(certificateProvider, protocols))); return this; } - #endregion + #endregion - #region Network settings + #region Network settings - public IServerBuilder Backlog(ushort backlog) - { + public IServerBuilder Backlog(ushort backlog) + { _Backlog = backlog; return this; } - public IServerBuilder RequestReadTimeout(TimeSpan timeout) - { + public IServerBuilder RequestReadTimeout(TimeSpan timeout) + { _RequestReadTimeout = timeout; return this; } - public IServerBuilder RequestMemoryLimit(uint limit) - { + public IServerBuilder RequestMemoryLimit(uint limit) + { _RequestMemoryLimit = limit; return this; } - public IServerBuilder TransferBufferSize(uint bufferSize) - { + public IServerBuilder TransferBufferSize(uint bufferSize) + { _TransferBufferSize = bufferSize; return this; } - #endregion + #endregion - #region Extensibility + #region Extensibility - public IServerBuilder Add(IConcernBuilder concern) - { + public IServerBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - #endregion + #endregion - #region Builder + #region Builder - public IServer Build() - { + public IServer Build() + { if (_Handler is null) { throw new BuilderMissingPropertyException("Handler"); @@ -176,8 +173,6 @@ public IServer Build() return new ThreadedServer(_Companion, config, handler); } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/ChunkedStream.cs b/Engine/Protocol/ChunkedStream.cs index 3906ab64..804eef46 100644 --- a/Engine/Protocol/ChunkedStream.cs +++ b/Engine/Protocol/ChunkedStream.cs @@ -5,66 +5,65 @@ using GenHTTP.Modules.IO.Streaming; -namespace GenHTTP.Engine.Protocol +namespace GenHTTP.Engine.Protocol; + +/// +/// Implements chunked transfer encoding by letting the client +/// know how many bytes have been written to the response stream. +/// +/// +/// Response streams are always wrapped into a chunked stream as +/// soon as there is no known content length. To avoid this overhead, +/// specify the length of your content whenever possible. +/// +public sealed class ChunkedStream : Stream { + private static readonly string NL = "\r\n"; - /// - /// Implements chunked transfer encoding by letting the client - /// know how many bytes have been written to the response stream. - /// - /// - /// Response streams are always wrapped into a chunked stream as - /// soon as there is no known content length. To avoid this overhead, - /// specify the length of your content whenever possible. - /// - public sealed class ChunkedStream : Stream - { - private static readonly string NL = "\r\n"; - - #region Get-/Setters + #region Get-/Setters - public override bool CanRead => false; + public override bool CanRead => false; - public override bool CanSeek => false; + public override bool CanSeek => false; - public override bool CanWrite => true; + public override bool CanWrite => true; - public override long Length => throw new NotSupportedException(); + public override long Length => throw new NotSupportedException(); - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - private Stream Target { get; } + private Stream Target { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ChunkedStream(Stream target) - { + public ChunkedStream(Stream target) + { Target = target; } - #endregion + #endregion - #region Functionality + #region Functionality - public override int Read(byte[] buffer, int offset, int count) - { + public override int Read(byte[] buffer, int offset, int count) + { throw new NotSupportedException(); } - public override long Seek(long offset, SeekOrigin origin) - { + public override long Seek(long offset, SeekOrigin origin) + { throw new NotSupportedException(); } - public override void SetLength(long value) - { + public override void SetLength(long value) + { throw new NotSupportedException(); } - public override void Write(byte[] buffer, int offset, int count) - { + public override void Write(byte[] buffer, int offset, int count) + { if (count > 0) { Write(count); @@ -75,8 +74,8 @@ public override void Write(byte[] buffer, int offset, int count) } } - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { if (count > 0) { await WriteAsync(count); @@ -87,8 +86,8 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc } } - public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { if (!buffer.IsEmpty) { await WriteAsync(buffer.Length); @@ -99,26 +98,24 @@ public override async ValueTask WriteAsync(ReadOnlyMemory buffer, Cancella } } - public async ValueTask FinishAsync() - { + public async ValueTask FinishAsync() + { await WriteAsync("0\r\n\r\n"); } - public override void Flush() - { + public override void Flush() + { Target.Flush(); } - public override Task FlushAsync(CancellationToken cancellationToken) => Target.FlushAsync(cancellationToken); - - private void Write(int value) => $"{value:X}\r\n".Write(Target); + public override Task FlushAsync(CancellationToken cancellationToken) => Target.FlushAsync(cancellationToken); - private ValueTask WriteAsync(string text) => text.WriteAsync(Target); + private void Write(int value) => $"{value:X}\r\n".Write(Target); - private ValueTask WriteAsync(int value) => $"{value:X}\r\n".WriteAsync(Target); + private ValueTask WriteAsync(string text) => text.WriteAsync(Target); - #endregion + private ValueTask WriteAsync(int value) => $"{value:X}\r\n".WriteAsync(Target); - } + #endregion } diff --git a/Engine/Protocol/ClientConnection.cs b/Engine/Protocol/ClientConnection.cs index c680859b..4c0ec66e 100644 --- a/Engine/Protocol/ClientConnection.cs +++ b/Engine/Protocol/ClientConnection.cs @@ -3,9 +3,6 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Engine.Protocol -{ +namespace GenHTTP.Engine.Protocol; - internal record ClientConnection(IPAddress IPAddress, ClientProtocol? Protocol, string? Host) : IClientConnection; - -} +internal record ClientConnection(IPAddress IPAddress, ClientProtocol? Protocol, string? Host) : IClientConnection; diff --git a/Engine/Protocol/ClientHandler.cs b/Engine/Protocol/ClientHandler.cs index d6bc4ba8..200a3ac8 100644 --- a/Engine/Protocol/ClientHandler.cs +++ b/Engine/Protocol/ClientHandler.cs @@ -3,55 +3,49 @@ using System.IO; using System.IO.Pipelines; using System.Net.Sockets; - using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; - -using GenHTTP.Engine.Infrastructure.Configuration; -using GenHTTP.Engine.Protocol; +using GenHTTP.Engine.Infrastructure; using GenHTTP.Engine.Protocol.Parser; - using GenHTTP.Modules.IO.Strings; - using PooledAwait; -namespace GenHTTP.Engine +namespace GenHTTP.Engine.Protocol; + +/// +/// Maintains a single connection to a client, continuously reading +/// requests and generating responses. +/// +/// +/// Implements keep alive and maintains the connection state (e.g. by +/// closing it after the last request has been handled). +/// +internal sealed class ClientHandler { + private static readonly StreamPipeReaderOptions READER_OPTIONS = new(pool: MemoryPool.Shared, leaveOpen: true, bufferSize: 65536); - /// - /// Maintains a single connection to a client, continuously reading - /// requests and generating responses. - /// - /// - /// Implements keep alive and maintains the connection state (e.g. by - /// closing it after the last request has been handled). - /// - internal sealed class ClientHandler - { - private static readonly StreamPipeReaderOptions READER_OPTIONS = new(pool: MemoryPool.Shared, leaveOpen: true, bufferSize: 65536); - - #region Get-/Setter + #region Get-/Setter - public IServer Server { get; } + public IServer Server { get; } - public IEndPoint EndPoint { get; } + public IEndPoint EndPoint { get; } - internal NetworkConfiguration Configuration { get; } + internal NetworkConfiguration Configuration { get; } - internal Socket Connection { get; } + internal Socket Connection { get; } - internal Stream Stream { get; } + internal Stream Stream { get; } - private bool? KeepAlive { get; set; } + private bool? KeepAlive { get; set; } - private ResponseHandler ResponseHandler { get; set; } + private ResponseHandler ResponseHandler { get; set; } - #endregion + #endregion - #region Initialization + #region Initialization - internal ClientHandler(Socket socket, Stream stream, IServer server, IEndPoint endPoint, NetworkConfiguration config) - { + internal ClientHandler(Socket socket, Stream stream, IServer server, IEndPoint endPoint, NetworkConfiguration config) + { Server = server; EndPoint = endPoint; @@ -63,12 +57,12 @@ internal ClientHandler(Socket socket, Stream stream, IServer server, IEndPoint e ResponseHandler = new ResponseHandler(Server, Stream, Configuration); } - #endregion + #endregion - #region Functionality + #region Functionality - internal async PooledValueTask Run() - { + internal async PooledValueTask Run() + { try { await HandlePipe(PipeReader.Create(Stream, READER_OPTIONS)); @@ -103,8 +97,8 @@ internal async PooledValueTask Run() } } - private async PooledValueTask HandlePipe(PipeReader reader) - { + private async PooledValueTask HandlePipe(PipeReader reader) + { try { using var buffer = new RequestBuffer(reader, Configuration); @@ -142,8 +136,8 @@ private async PooledValueTask HandlePipe(PipeReader reader) } } - private async PooledValueTask HandleRequest(RequestBuilder builder, bool dataRemaining) - { + private async PooledValueTask HandleRequest(RequestBuilder builder, bool dataRemaining) + { using var request = builder.Connection(Server, EndPoint, Connection.GetAddress()).Build(); KeepAlive ??= request["Connection"]?.Equals("Keep-Alive", StringComparison.InvariantCultureIgnoreCase) ?? (request.ProtocolType == HttpProtocol.Http_1_1); @@ -157,8 +151,8 @@ private async PooledValueTask HandleRequest(RequestBuilder builder, bool d return (success && keepAlive); } - private async PooledValueTask SendError(Exception e, ResponseStatus status) - { + private async PooledValueTask SendError(Exception e, ResponseStatus status) + { try { var message = Server.Development ? e.ToString() : e.Message; @@ -172,8 +166,6 @@ private async PooledValueTask SendError(Exception e, ResponseStatus status) catch { /* no recovery here */ } } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/CookieCollection.cs b/Engine/Protocol/CookieCollection.cs index 5e095de5..036ef6b9 100644 --- a/Engine/Protocol/CookieCollection.cs +++ b/Engine/Protocol/CookieCollection.cs @@ -5,20 +5,19 @@ using GenHTTP.Engine.Utilities; -namespace GenHTTP.Engine.Protocol +namespace GenHTTP.Engine.Protocol; + +internal sealed class CookieCollection : PooledDictionary, ICookieCollection { + internal const int DEFAULT_SIZE = 6; - internal sealed class CookieCollection : PooledDictionary, ICookieCollection + internal CookieCollection() : base(DEFAULT_SIZE, StringComparer.InvariantCultureIgnoreCase) { - internal const int DEFAULT_SIZE = 6; - - internal CookieCollection() : base(DEFAULT_SIZE, StringComparer.InvariantCultureIgnoreCase) - { } - internal void Add(string header) - { + internal void Add(string header) + { foreach (var cookie in Parse(header)) { if (!ContainsKey(cookie.Name)) @@ -28,8 +27,8 @@ internal void Add(string header) } } - private static List Parse(string value) - { + private static List Parse(string value) + { var result = new List(2); var cookies = value.Split("; ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); @@ -47,6 +46,4 @@ private static List Parse(string value) return result; } - } - } diff --git a/Engine/Protocol/DateHeader.cs b/Engine/Protocol/DateHeader.cs index 7fb2945d..055d1db1 100644 --- a/Engine/Protocol/DateHeader.cs +++ b/Engine/Protocol/DateHeader.cs @@ -1,22 +1,21 @@ using System; -namespace GenHTTP.Engine.Protocol +namespace GenHTTP.Engine.Protocol; + +/// +/// Caches the value of the date header for one second +/// before creating a new value, saving some allocations. +/// +public static class DateHeader { - - /// - /// Caches the value of the date header for one second - /// before creating a new value, saving some allocations. - /// - public static class DateHeader - { - private static string _Value = string.Empty; + private static string _Value = string.Empty; - private static byte _Second = 61; + private static byte _Second = 61; - #region Functionality + #region Functionality - public static string GetValue() - { + public static string GetValue() + { var now = DateTime.UtcNow; var second = now.Second; @@ -30,8 +29,6 @@ public static string GetValue() return _Value; } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/ForwardingCollection.cs b/Engine/Protocol/ForwardingCollection.cs index 4d8cc0d0..d0fbbc27 100644 --- a/Engine/Protocol/ForwardingCollection.cs +++ b/Engine/Protocol/ForwardingCollection.cs @@ -4,30 +4,29 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Engine.Protocol -{ +namespace GenHTTP.Engine.Protocol; - /// - /// Parses and stores forwarding information passed by proxy - /// servers along with the request. - /// - internal sealed class ForwardingCollection : List, IForwardingCollection - { - private const int DEFAULT_SIZE = 1; +/// +/// Parses and stores forwarding information passed by proxy +/// servers along with the request. +/// +internal sealed class ForwardingCollection : List, IForwardingCollection +{ + private const int DEFAULT_SIZE = 1; - private const string HEADER_FOR = "X-Forwarded-For"; - private const string HEADER_HOST = "X-Forwarded-Host"; - private const string HEADER_PROTO = "X-Forwarded-Proto"; + private const string HEADER_FOR = "X-Forwarded-For"; + private const string HEADER_HOST = "X-Forwarded-Host"; + private const string HEADER_PROTO = "X-Forwarded-Proto"; - internal ForwardingCollection() : base(DEFAULT_SIZE) - { + internal ForwardingCollection() : base(DEFAULT_SIZE) + { } - internal void Add(string header) => AddRange(Parse(header)); + internal void Add(string header) => AddRange(Parse(header)); - internal void TryAddLegacy(RequestHeaderCollection headers) - { + internal void TryAddLegacy(RequestHeaderCollection headers) + { IPAddress? address = null; ClientProtocol? protocol = null; @@ -49,8 +48,8 @@ internal void TryAddLegacy(RequestHeaderCollection headers) } } - private static IEnumerable Parse(string value) - { + private static IEnumerable Parse(string value) + { var forwardings = value.Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (var forwarding in forwardings) @@ -93,8 +92,8 @@ private static IEnumerable Parse(string value) } } - private static ClientProtocol? ParseProtocol(string? protocol) - { + private static ClientProtocol? ParseProtocol(string? protocol) + { if (protocol != null) { if (string.Equals(protocol, "https", StringComparison.OrdinalIgnoreCase)) @@ -110,8 +109,8 @@ private static IEnumerable Parse(string value) return null; } - private static IPAddress? ParseAddress(string? address) - { + private static IPAddress? ParseAddress(string? address) + { if (address != null) { if (IPAddress.TryParse(address, out var ip)) @@ -123,6 +122,4 @@ private static IEnumerable Parse(string value) return null; } - } - } diff --git a/Engine/Protocol/Parser/ChunkedContentParser.cs b/Engine/Protocol/Parser/ChunkedContentParser.cs index 3e78ffa5..4353ada6 100644 --- a/Engine/Protocol/Parser/ChunkedContentParser.cs +++ b/Engine/Protocol/Parser/ChunkedContentParser.cs @@ -4,43 +4,41 @@ using System.Threading.Tasks; using GenHTTP.Api.Protocol; - -using GenHTTP.Engine.Infrastructure.Configuration; +using GenHTTP.Engine.Infrastructure; using GenHTTP.Engine.Protocol.Parser.Conversion; -namespace GenHTTP.Engine.Protocol.Parser +namespace GenHTTP.Engine.Protocol.Parser; + +/// +/// Reads the chunked encoded body of a client request into +/// a stream. +/// +/// +/// As we cannot know the length of the request beforehand, +/// this will always use a file stream for buffering. +/// +internal sealed class ChunkedContentParser { - /// - /// Reads the chunked encoded body of a client request into - /// a stream. - /// - /// - /// As we cannot know the length of the request beforehand, - /// this will always use a file stream for buffering. - /// - internal sealed class ChunkedContentParser - { - - #region Get-/Setters + #region Get-/Setters - private NetworkConfiguration Configuration { get; } + private NetworkConfiguration Configuration { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal ChunkedContentParser(NetworkConfiguration networkConfiguration) - { + internal ChunkedContentParser(NetworkConfiguration networkConfiguration) + { Configuration = networkConfiguration; } - #endregion + #endregion - #region Functionality + #region Functionality - internal async ValueTask GetBody(RequestBuffer buffer) - { + internal async ValueTask GetBody(RequestBuffer buffer) + { var body = TemporaryFileStream.Create(); var bufferSize = Configuration.TransferBufferSize; @@ -52,8 +50,8 @@ internal async ValueTask GetBody(RequestBuffer buffer) return body; } - private static async ValueTask NextChunkAsync(RequestBuffer buffer, Stream target, uint bufferSize) - { + private static async ValueTask NextChunkAsync(RequestBuffer buffer, Stream target, uint bufferSize) + { await EnsureDataAsync(buffer); // @@ -81,8 +79,8 @@ private static async ValueTask NextChunkAsync(RequestBuffer buffer, Stream } } - private static long GetChunkSize(RequestBuffer buffer) - { + private static long GetChunkSize(RequestBuffer buffer) + { var reader = new SequenceReader(buffer.Data); if (reader.IsNext((byte)'0')) @@ -109,8 +107,8 @@ private static long GetChunkSize(RequestBuffer buffer) } } - private static async ValueTask EnsureDataAsync(RequestBuffer buffer) - { + private static async ValueTask EnsureDataAsync(RequestBuffer buffer) + { if (buffer.ReadRequired) { if (await buffer.ReadAsync() == null) @@ -120,8 +118,6 @@ private static async ValueTask EnsureDataAsync(RequestBuffer buffer) } } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/Parser/Conversion/HeaderConverter.cs b/Engine/Protocol/Parser/Conversion/HeaderConverter.cs index 7eb3c08c..586603c6 100644 --- a/Engine/Protocol/Parser/Conversion/HeaderConverter.cs +++ b/Engine/Protocol/Parser/Conversion/HeaderConverter.cs @@ -1,16 +1,15 @@ using System.Buffers; -namespace GenHTTP.Engine.Protocol.Parser.Conversion -{ +namespace GenHTTP.Engine.Protocol.Parser.Conversion; - internal static class HeaderConverter - { - private static readonly string[] KNOWN_HEADERS = new[] { "Host", "User-Agent", "Accept", "Content-Type", "Content-Length" }; +internal static class HeaderConverter +{ + private static readonly string[] KNOWN_HEADERS = new[] { "Host", "User-Agent", "Accept", "Content-Type", "Content-Length" }; - private static readonly string[] KNOWN_VALUES = new[] { "*/*" }; + private static readonly string[] KNOWN_VALUES = new[] { "*/*" }; - internal static string ToKey(ReadOnlySequence value) - { + internal static string ToKey(ReadOnlySequence value) + { foreach (var known in KNOWN_HEADERS) { if (ValueConverter.CompareTo(value, known)) return known; @@ -19,8 +18,8 @@ internal static string ToKey(ReadOnlySequence value) return ValueConverter.GetString(value); } - internal static string ToValue(ReadOnlySequence value) - { + internal static string ToValue(ReadOnlySequence value) + { foreach (var known in KNOWN_VALUES) { if (ValueConverter.CompareTo(value, known)) return known; @@ -29,6 +28,4 @@ internal static string ToValue(ReadOnlySequence value) return ValueConverter.GetString(value); } - } - } diff --git a/Engine/Protocol/Parser/Conversion/MethodConverter.cs b/Engine/Protocol/Parser/Conversion/MethodConverter.cs index abf8c355..2f1e678f 100644 --- a/Engine/Protocol/Parser/Conversion/MethodConverter.cs +++ b/Engine/Protocol/Parser/Conversion/MethodConverter.cs @@ -3,24 +3,23 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Engine.Protocol.Parser.Conversion -{ +namespace GenHTTP.Engine.Protocol.Parser.Conversion; - internal static class MethodConverter +internal static class MethodConverter +{ + private static readonly Dictionary KNOWN_METHODS = new(7) { - private static readonly Dictionary KNOWN_METHODS = new(7) - { - { "GET", RequestMethod.GET }, - { "HEAD", RequestMethod.HEAD }, - { "POST", RequestMethod.POST }, - { "PUT", RequestMethod.PUT }, - { "PATCH", RequestMethod.PATCH }, - { "DELETE", RequestMethod.DELETE }, - { "OPTIONS", RequestMethod.OPTIONS } - }; + { "GET", RequestMethod.GET }, + { "HEAD", RequestMethod.HEAD }, + { "POST", RequestMethod.POST }, + { "PUT", RequestMethod.PUT }, + { "PATCH", RequestMethod.PATCH }, + { "DELETE", RequestMethod.DELETE }, + { "OPTIONS", RequestMethod.OPTIONS } + }; - internal static FlexibleRequestMethod ToRequestMethod(ReadOnlySequence value) - { + internal static FlexibleRequestMethod ToRequestMethod(ReadOnlySequence value) + { foreach (var kv in KNOWN_METHODS) { if (ValueConverter.CompareTo(value, kv.Key)) @@ -32,6 +31,4 @@ internal static FlexibleRequestMethod ToRequestMethod(ReadOnlySequence val return FlexibleRequestMethod.Get(ValueConverter.GetString(value)); } - } - } diff --git a/Engine/Protocol/Parser/Conversion/PathConverter.cs b/Engine/Protocol/Parser/Conversion/PathConverter.cs index c8c24e8d..11d4891a 100644 --- a/Engine/Protocol/Parser/Conversion/PathConverter.cs +++ b/Engine/Protocol/Parser/Conversion/PathConverter.cs @@ -3,16 +3,15 @@ using GenHTTP.Api.Routing; -namespace GenHTTP.Engine.Protocol.Parser.Conversion -{ +namespace GenHTTP.Engine.Protocol.Parser.Conversion; - internal static class PathConverter - { +internal static class PathConverter +{ - private static readonly WebPath ROOT = new(new List(), true); + private static readonly WebPath ROOT = new(new List(), true); - internal static WebPath ToPath(ReadOnlySequence value) - { + internal static WebPath ToPath(ReadOnlySequence value) + { if (value.Length == 1) { return ROOT; @@ -40,6 +39,4 @@ internal static WebPath ToPath(ReadOnlySequence value) return new WebPath(parts, true); } - } - } diff --git a/Engine/Protocol/Parser/Conversion/ProtocolConverter.cs b/Engine/Protocol/Parser/Conversion/ProtocolConverter.cs index 83ff00c4..db159576 100644 --- a/Engine/Protocol/Parser/Conversion/ProtocolConverter.cs +++ b/Engine/Protocol/Parser/Conversion/ProtocolConverter.cs @@ -2,14 +2,13 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Engine.Protocol.Parser.Conversion +namespace GenHTTP.Engine.Protocol.Parser.Conversion; + +internal static class ProtocolConverter { - internal static class ProtocolConverter + internal static HttpProtocol ToProtocol(ReadOnlySequence value) { - - internal static HttpProtocol ToProtocol(ReadOnlySequence value) - { var reader = new SequenceReader(value); if (value.Length != 8) @@ -35,6 +34,4 @@ internal static HttpProtocol ToProtocol(ReadOnlySequence value) throw new ProtocolException($"Unexpected protocol version '{versionString}'"); } - } - } diff --git a/Engine/Protocol/Parser/Conversion/QueryConverter.cs b/Engine/Protocol/Parser/Conversion/QueryConverter.cs index 955caff3..e7467b46 100644 --- a/Engine/Protocol/Parser/Conversion/QueryConverter.cs +++ b/Engine/Protocol/Parser/Conversion/QueryConverter.cs @@ -1,14 +1,13 @@ using System; using System.Buffers; -namespace GenHTTP.Engine.Protocol.Parser.Conversion +namespace GenHTTP.Engine.Protocol.Parser.Conversion; + +internal static class QueryConverter { - internal static class QueryConverter + internal static RequestQuery? ToQuery(ReadOnlySequence value) { - - internal static RequestQuery? ToQuery(ReadOnlySequence value) - { if (!value.IsEmpty) { var query = new RequestQuery(); @@ -32,8 +31,8 @@ internal static class QueryConverter return null; } - private static void AppendSegment(RequestQuery query, ReadOnlySequence segment) - { + private static void AppendSegment(RequestQuery query, ReadOnlySequence segment) + { if (!segment.IsEmpty) { var reader = new SequenceReader(segment); @@ -60,6 +59,4 @@ private static void AppendSegment(RequestQuery query, ReadOnlySequence seg } } - } - } diff --git a/Engine/Protocol/Parser/Conversion/ValueConverter.cs b/Engine/Protocol/Parser/Conversion/ValueConverter.cs index 4076f594..22374f2f 100644 --- a/Engine/Protocol/Parser/Conversion/ValueConverter.cs +++ b/Engine/Protocol/Parser/Conversion/ValueConverter.cs @@ -1,16 +1,15 @@ using System.Buffers; using System.Text; -namespace GenHTTP.Engine.Protocol.Parser.Conversion -{ +namespace GenHTTP.Engine.Protocol.Parser.Conversion; - internal static class ValueConverter - { +internal static class ValueConverter +{ - private static readonly Encoding ASCII = Encoding.ASCII; + private static readonly Encoding ASCII = Encoding.ASCII; - internal static bool CompareTo(ReadOnlySequence buffer, string expected) - { + internal static bool CompareTo(ReadOnlySequence buffer, string expected) + { var i = 0; if (buffer.Length != expected.Length) @@ -32,8 +31,8 @@ internal static bool CompareTo(ReadOnlySequence buffer, string expected) return true; } - internal static string GetString(ReadOnlySequence buffer) - { + internal static string GetString(ReadOnlySequence buffer) + { if (buffer.Length > 0) { var result = string.Create((int)buffer.Length, buffer, (span, sequence) => @@ -51,6 +50,4 @@ internal static string GetString(ReadOnlySequence buffer) return string.Empty; } - } - } diff --git a/Engine/Protocol/Parser/RequestContentParser.cs b/Engine/Protocol/Parser/RequestContentParser.cs index 03f3841a..71854c5f 100644 --- a/Engine/Protocol/Parser/RequestContentParser.cs +++ b/Engine/Protocol/Parser/RequestContentParser.cs @@ -1,40 +1,39 @@ using System; using System.IO; using System.Threading.Tasks; -using GenHTTP.Engine.Infrastructure.Configuration; +using GenHTTP.Engine.Infrastructure; -namespace GenHTTP.Engine.Protocol.Parser -{ +namespace GenHTTP.Engine.Protocol.Parser; - /// - /// Efficiently reads the body from the HTTP request, storing it - /// in a temporary file if it exceeds the buffering limits. - /// - internal sealed class RequestContentParser - { +/// +/// Efficiently reads the body from the HTTP request, storing it +/// in a temporary file if it exceeds the buffering limits. +/// +internal sealed class RequestContentParser +{ - #region Get-/Setters + #region Get-/Setters - internal long Length { get; } + internal long Length { get; } - internal NetworkConfiguration Configuration { get; } + internal NetworkConfiguration Configuration { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal RequestContentParser(long length, NetworkConfiguration configuration) - { + internal RequestContentParser(long length, NetworkConfiguration configuration) + { Length = length; Configuration = configuration; } - #endregion + #endregion - #region Functionality + #region Functionality - internal async Task GetBody(RequestBuffer buffer) - { + internal async Task GetBody(RequestBuffer buffer) + { var body = Length > Configuration.RequestMemoryLimit ? TemporaryFileStream.Create() : new MemoryStream((int)Length); await CopyAsync(buffer, body, Length, Configuration.TransferBufferSize); @@ -44,8 +43,8 @@ internal async Task GetBody(RequestBuffer buffer) return body; } - internal static async ValueTask CopyAsync(RequestBuffer source, Stream target, long length, uint bufferSize) - { + internal static async ValueTask CopyAsync(RequestBuffer source, Stream target, long length, uint bufferSize) + { var toFetch = length; while (toFetch > 0) @@ -74,8 +73,6 @@ internal static async ValueTask CopyAsync(RequestBuffer source, Stream target, l } } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/Parser/RequestParser.cs b/Engine/Protocol/Parser/RequestParser.cs index d58b8496..072d627d 100644 --- a/Engine/Protocol/Parser/RequestParser.cs +++ b/Engine/Protocol/Parser/RequestParser.cs @@ -1,50 +1,49 @@ using GenHTTP.Api.Protocol; -using GenHTTP.Engine.Infrastructure.Configuration; +using GenHTTP.Engine.Infrastructure; using GenHTTP.Engine.Protocol.Parser.Conversion; using PooledAwait; -namespace GenHTTP.Engine.Protocol.Parser +namespace GenHTTP.Engine.Protocol.Parser; + +/// +/// Reads the next HTTP request to be handled by the server from +/// the client connection. +/// +/// +/// Be aware that this code path is heavily optimized for low +/// memory allocations. Changes to this class should allocate +/// as few memory as possible to avoid the performance of +/// the server from being impacted in a negative manner. +/// +internal sealed class RequestParser { + private RequestBuilder? _Builder; - /// - /// Reads the next HTTP request to be handled by the server from - /// the client connection. - /// - /// - /// Be aware that this code path is heavily optimized for low - /// memory allocations. Changes to this class should allocate - /// as few memory as possible to avoid the performance of - /// the server from being impacted in a negative manner. - /// - internal sealed class RequestParser - { - private RequestBuilder? _Builder; - - #region Get-/Setters + #region Get-/Setters - private NetworkConfiguration Configuration { get; } + private NetworkConfiguration Configuration { get; } - private RequestBuilder Request => _Builder ??= new(); + private RequestBuilder Request => _Builder ??= new(); - private RequestScanner Scanner { get; } + private RequestScanner Scanner { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal RequestParser(NetworkConfiguration configuration) - { + internal RequestParser(NetworkConfiguration configuration) + { Configuration = configuration; Scanner = new(); } - #endregion + #endregion - #region Functionality + #region Functionality - internal async PooledValueTask TryParseAsync(RequestBuffer buffer) - { + internal async PooledValueTask TryParseAsync(RequestBuffer buffer) + { if (!await Type(buffer)) { if (!buffer.Data.IsEmpty) @@ -69,8 +68,8 @@ internal RequestParser(NetworkConfiguration configuration) return result; } - private async PooledValueTask Type(RequestBuffer buffer) - { + private async PooledValueTask Type(RequestBuffer buffer) + { Scanner.Mode = ScannerMode.Words; if (await Scanner.Next(buffer, RequestToken.Word, allowNone: true)) @@ -84,8 +83,8 @@ private async PooledValueTask Type(RequestBuffer buffer) } } - private async PooledValueTask Path(RequestBuffer buffer) - { + private async PooledValueTask Path(RequestBuffer buffer) + { Scanner.Mode = ScannerMode.Path; var token = await Scanner.Next(buffer); @@ -118,8 +117,8 @@ private async PooledValueTask Path(RequestBuffer buffer) } } - private async PooledValueTask Protocol(RequestBuffer buffer) - { + private async PooledValueTask Protocol(RequestBuffer buffer) + { Scanner.Mode = ScannerMode.Words; if (await Scanner.Next(buffer, RequestToken.Word)) @@ -128,8 +127,8 @@ private async PooledValueTask Protocol(RequestBuffer buffer) } } - private async PooledValueTask Headers(RequestBuffer buffer) - { + private async PooledValueTask Headers(RequestBuffer buffer) + { Scanner.Mode = ScannerMode.HeaderKey; while (await Scanner.Next(buffer) == RequestToken.Word) @@ -147,8 +146,8 @@ private async PooledValueTask Headers(RequestBuffer buffer) } } - private async PooledValueTask Body(RequestBuffer buffer) - { + private async PooledValueTask Body(RequestBuffer buffer) + { if (Request.Headers.TryGetValue("Content-Length", out var bodyLength)) { if (long.TryParse(bodyLength, out var length)) @@ -176,8 +175,6 @@ private async PooledValueTask Body(RequestBuffer buffer) } } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/Parser/RequestScanner.cs b/Engine/Protocol/Parser/RequestScanner.cs index f53fabd4..5bb60cf6 100644 --- a/Engine/Protocol/Parser/RequestScanner.cs +++ b/Engine/Protocol/Parser/RequestScanner.cs @@ -4,26 +4,25 @@ using PooledAwait; -namespace GenHTTP.Engine.Protocol.Parser -{ +namespace GenHTTP.Engine.Protocol.Parser; - internal sealed class RequestScanner - { +internal sealed class RequestScanner +{ - internal RequestToken Current { get; private set; } + internal RequestToken Current { get; private set; } - internal ReadOnlySequence Value { get; private set; } + internal ReadOnlySequence Value { get; private set; } - internal ScannerMode Mode { get; set; } + internal ScannerMode Mode { get; set; } - internal RequestScanner() - { + internal RequestScanner() + { Current = RequestToken.None; Mode = ScannerMode.Words; } - internal async PooledValueTask Next(RequestBuffer buffer, RequestToken expectedToken, bool allowNone = false, bool includeWhitespace = false) - { + internal async PooledValueTask Next(RequestBuffer buffer, RequestToken expectedToken, bool allowNone = false, bool includeWhitespace = false) + { var read = await Next(buffer, forceRead: false, includeWhitespace: includeWhitespace); if (allowNone && (read == RequestToken.None)) @@ -39,8 +38,8 @@ internal async PooledValueTask Next(RequestBuffer buffer, RequestToken exp return true; } - internal async PooledValueTask Next(RequestBuffer buffer, bool forceRead = false, bool includeWhitespace = false) - { + internal async PooledValueTask Next(RequestBuffer buffer, bool forceRead = false, bool includeWhitespace = false) + { // ensure we have data to be scanned if (await Fill(buffer, forceRead)) { @@ -63,8 +62,8 @@ internal async PooledValueTask Next(RequestBuffer buffer, bool for } } - private RequestToken? ScanData(RequestBuffer buffer, bool includeWhitespace = false) - { + private RequestToken? ScanData(RequestBuffer buffer, bool includeWhitespace = false) + { if (SkipWhitespace(buffer) && includeWhitespace) { Value = new(); @@ -115,8 +114,8 @@ internal async PooledValueTask Next(RequestBuffer buffer, bool for return null; } - private static bool SkipWhitespace(RequestBuffer buffer) - { + private static bool SkipWhitespace(RequestBuffer buffer) + { var count = 0; var done = false; @@ -149,8 +148,8 @@ private static bool SkipWhitespace(RequestBuffer buffer) return (count > 0); } - private static bool IsNewLine(RequestBuffer buffer) - { + private static bool IsNewLine(RequestBuffer buffer) + { if (buffer.Data.FirstSpan[0] == (byte)'\r') { buffer.Advance(2); @@ -160,8 +159,8 @@ private static bool IsNewLine(RequestBuffer buffer) return false; } - private bool ReadTo(RequestBuffer buffer, char delimiter, char? boundary = null, byte skipAdditionally = 0) - { + private bool ReadTo(RequestBuffer buffer, char delimiter, char? boundary = null, byte skipAdditionally = 0) + { var reader = new SequenceReader(buffer.Data); if (reader.TryReadTo(out ReadOnlySequence value, (byte)delimiter, true)) @@ -188,8 +187,8 @@ private bool ReadTo(RequestBuffer buffer, char delimiter, char? boundary = null, return false; } - private static async PooledValueTask Fill(RequestBuffer buffer, bool force = false) - { + private static async PooledValueTask Fill(RequestBuffer buffer, bool force = false) + { if (buffer.ReadRequired || force) { await buffer.ReadAsync(force); @@ -198,6 +197,4 @@ private static async PooledValueTask Fill(RequestBuffer buffer, bool force return !buffer.Data.IsEmpty; } - } - } diff --git a/Engine/Protocol/Parser/RequestToken.cs b/Engine/Protocol/Parser/RequestToken.cs index 9aafc5c7..298283cd 100644 --- a/Engine/Protocol/Parser/RequestToken.cs +++ b/Engine/Protocol/Parser/RequestToken.cs @@ -1,13 +1,10 @@ -namespace GenHTTP.Engine.Protocol.Parser -{ - - internal enum RequestToken - { - None, - Word, - Path, - PathWithQuery, - NewLine - } +namespace GenHTTP.Engine.Protocol.Parser; +internal enum RequestToken +{ + None, + Word, + Path, + PathWithQuery, + NewLine } diff --git a/Engine/Protocol/Parser/ScannerMode.cs b/Engine/Protocol/Parser/ScannerMode.cs index 88e0e41e..3aa8dbde 100644 --- a/Engine/Protocol/Parser/ScannerMode.cs +++ b/Engine/Protocol/Parser/ScannerMode.cs @@ -1,12 +1,9 @@ -namespace GenHTTP.Engine.Protocol.Parser -{ - - internal enum ScannerMode - { - Words, - Path, - HeaderKey, - HeaderValue - } +namespace GenHTTP.Engine.Protocol.Parser; +internal enum ScannerMode +{ + Words, + Path, + HeaderKey, + HeaderValue } diff --git a/Engine/Protocol/Request.cs b/Engine/Protocol/Request.cs index df4e6f0b..836e8838 100644 --- a/Engine/Protocol/Request.cs +++ b/Engine/Protocol/Request.cs @@ -1,54 +1,50 @@ using System; using System.IO; - using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; using GenHTTP.Api.Routing; -using GenHTTP.Engine.Protocol; +namespace GenHTTP.Engine.Protocol; -namespace GenHTTP.Engine +/// +/// Provides methods to access a recieved http request. +/// +internal sealed class Request : IRequest { + private ICookieCollection? _Cookies; - /// - /// Provides methods to access a recieved http request. - /// - internal sealed class Request : IRequest - { - private ICookieCollection? _Cookies; - - private IForwardingCollection? _Forwardings; + private IForwardingCollection? _Forwardings; - private IRequestQuery? _Query; + private IRequestQuery? _Query; - private RequestProperties? _Properties; + private RequestProperties? _Properties; - private FlexibleContentType? _ContentType; + private FlexibleContentType? _ContentType; - #region Get-/Setters + #region Get-/Setters - public IServer Server { get; } + public IServer Server { get; } - public IEndPoint EndPoint { get; } + public IEndPoint EndPoint { get; } - public IClientConnection Client { get; } + public IClientConnection Client { get; } - public IClientConnection LocalClient { get; } + public IClientConnection LocalClient { get; } - public HttpProtocol ProtocolType { get; } + public HttpProtocol ProtocolType { get; } - public FlexibleRequestMethod Method { get; } + public FlexibleRequestMethod Method { get; } - public RoutingTarget Target { get; } + public RoutingTarget Target { get; } - public IHeaderCollection Headers { get; } + public IHeaderCollection Headers { get; } - public Stream? Content { get; } + public Stream? Content { get; } - public FlexibleContentType? ContentType + public FlexibleContentType? ContentType + { + get { - get - { if (_ContentType is not null) { return _ContentType; @@ -63,18 +59,18 @@ public FlexibleContentType? ContentType return null; } - } + } - public string? Host => Client.Host; + public string? Host => Client.Host; - public string? Referer => this["Referer"]; + public string? Referer => this["Referer"]; - public string? UserAgent => this["User-Agent"]; + public string? UserAgent => this["User-Agent"]; - public string? this[string additionalHeader] + public string? this[string additionalHeader] + { + get { - get - { if (Headers.ContainsKey(additionalHeader)) { return Headers[additionalHeader]; @@ -82,36 +78,36 @@ public string? this[string additionalHeader] return null; } - } + } - public ICookieCollection Cookies - { - get { return _Cookies ??= new CookieCollection(); } - } + public ICookieCollection Cookies + { + get { return _Cookies ??= new CookieCollection(); } + } - public IForwardingCollection Forwardings - { - get { return _Forwardings ??= new ForwardingCollection(); } - } + public IForwardingCollection Forwardings + { + get { return _Forwardings ??= new ForwardingCollection(); } + } - public IRequestQuery Query - { - get { return _Query ??= new RequestQuery(); } - } + public IRequestQuery Query + { + get { return _Query ??= new RequestQuery(); } + } - public IRequestProperties Properties - { - get { return _Properties ??= new RequestProperties(); } - } + public IRequestProperties Properties + { + get { return _Properties ??= new RequestProperties(); } + } - #endregion + #endregion - #region Initialization + #region Initialization - internal Request(IServer server, IEndPoint endPoint, IClientConnection client, IClientConnection localClient, HttpProtocol protocol, FlexibleRequestMethod method, - RoutingTarget target, IHeaderCollection headers, ICookieCollection? cookies, IForwardingCollection? forwardings, - IRequestQuery? query, Stream? content) - { + internal Request(IServer server, IEndPoint endPoint, IClientConnection client, IClientConnection localClient, HttpProtocol protocol, FlexibleRequestMethod method, + RoutingTarget target, IHeaderCollection headers, ICookieCollection? cookies, IForwardingCollection? forwardings, + IRequestQuery? query, Stream? content) + { Client = client; LocalClient = localClient; @@ -131,23 +127,23 @@ internal Request(IServer server, IEndPoint endPoint, IClientConnection client, I Content = content; } - #endregion + #endregion - #region Functionality + #region Functionality - public IResponseBuilder Respond() - { + public IResponseBuilder Respond() + { return new ResponseBuilder().Status(ResponseStatus.OK); } - #endregion + #endregion - #region IDisposable Support + #region IDisposable Support - private bool disposed = false; + private bool disposed = false; - public void Dispose() - { + public void Dispose() + { if (!disposed) { Headers.Dispose(); @@ -166,8 +162,6 @@ public void Dispose() GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/RequestBuffer.cs b/Engine/Protocol/RequestBuffer.cs index 4523271a..0729444a 100644 --- a/Engine/Protocol/RequestBuffer.cs +++ b/Engine/Protocol/RequestBuffer.cs @@ -2,59 +2,56 @@ using System.Buffers; using System.IO.Pipelines; using System.Threading; - -using GenHTTP.Engine.Infrastructure.Configuration; - +using GenHTTP.Engine.Infrastructure; using PooledAwait; -namespace GenHTTP.Engine.Protocol +namespace GenHTTP.Engine.Protocol; + +/// +/// Buffers the data received from a client, converting it into a contiguous chunk of data, +/// therefore making it easier for the parser engine to be processed. +/// +/// +/// Depending on how fast a client is able to upload request data, +/// the server may need to wait for the whole request to be available. +/// Additionally, keep alive connections will be held open by the client +/// until there is a new request to be sent. The buffer implements request +/// read timeouts by continuously reading data from the underlying network +/// stream. If the read operation times out, the server will close the +/// connection. +/// +internal sealed class RequestBuffer : IDisposable { + private ReadOnlySequence? _Data; - /// - /// Buffers the data received from a client, converting it into a contiguous chunk of data, - /// therefore making it easier for the parser engine to be processed. - /// - /// - /// Depending on how fast a client is able to upload request data, - /// the server may need to wait for the whole request to be available. - /// Additionally, keep alive connections will be held open by the client - /// until there is a new request to be sent. The buffer implements request - /// read timeouts by continuously reading data from the underlying network - /// stream. If the read operation times out, the server will close the - /// connection. - /// - internal sealed class RequestBuffer : IDisposable - { - private ReadOnlySequence? _Data; + #region Get-/Setters - #region Get-/Setters + private PipeReader Reader { get; } - private PipeReader Reader { get; } + private NetworkConfiguration Configuration { get; } - private NetworkConfiguration Configuration { get; } + private CancellationTokenSource? Cancellation { get; set; } - private CancellationTokenSource? Cancellation { get; set; } + internal ReadOnlySequence Data => _Data ?? new(); - internal ReadOnlySequence Data => _Data ?? new(); + internal bool ReadRequired => (_Data == null) || _Data.Value.IsEmpty; - internal bool ReadRequired => (_Data == null) || _Data.Value.IsEmpty; + #endregion - #endregion + #region Initialization - #region Initialization - - internal RequestBuffer(PipeReader reader, NetworkConfiguration configuration) - { + internal RequestBuffer(PipeReader reader, NetworkConfiguration configuration) + { Reader = reader; Configuration = configuration; } - #endregion + #endregion - #region Functionality + #region Functionality - internal async PooledValueTask ReadAsync(bool force = false) - { + internal async PooledValueTask ReadAsync(bool force = false) + { if (ReadRequired || force) { if (Cancellation is null) @@ -82,20 +79,20 @@ internal RequestBuffer(PipeReader reader, NetworkConfiguration configuration) return Data.Length; } - internal void Advance(long bytes) - { + internal void Advance(long bytes) + { _Data = Data.Slice(bytes); Reader.AdvanceTo(_Data.Value.Start); } - #endregion + #endregion - #region Disposing + #region Disposing - private bool disposedValue; + private bool disposedValue; - public void Dispose() - { + public void Dispose() + { if (!disposedValue) { if (Cancellation is not null) @@ -110,8 +107,6 @@ public void Dispose() GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/RequestBuilder.cs b/Engine/Protocol/RequestBuilder.cs index 8f0b804f..56bc18d4 100644 --- a/Engine/Protocol/RequestBuilder.cs +++ b/Engine/Protocol/RequestBuilder.cs @@ -6,58 +6,57 @@ using GenHTTP.Api.Protocol; using GenHTTP.Api.Routing; -namespace GenHTTP.Engine.Protocol -{ +namespace GenHTTP.Engine.Protocol; - internal sealed class RequestBuilder : IBuilder - { - private IServer? _Server; - private IEndPoint? _EndPoint; +internal sealed class RequestBuilder : IBuilder +{ + private IServer? _Server; + private IEndPoint? _EndPoint; - private IPAddress? _Address; + private IPAddress? _Address; - private FlexibleRequestMethod? _RequestMethod; - private HttpProtocol? _Protocol; + private FlexibleRequestMethod? _RequestMethod; + private HttpProtocol? _Protocol; - private RoutingTarget? _Target; + private RoutingTarget? _Target; - private Stream? _Content; + private Stream? _Content; - private RequestQuery? _Query; + private RequestQuery? _Query; - private CookieCollection? _Cookies; + private CookieCollection? _Cookies; - private ForwardingCollection? _Forwardings; + private ForwardingCollection? _Forwardings; - #region Get-/Setters + #region Get-/Setters - private CookieCollection Cookies - { - get { return _Cookies ??= new(); } - } + private CookieCollection Cookies + { + get { return _Cookies ??= new(); } + } - private ForwardingCollection Forwardings - { - get { return _Forwardings ??= new(); } - } + private ForwardingCollection Forwardings + { + get { return _Forwardings ??= new(); } + } - internal RequestHeaderCollection Headers { get; } + internal RequestHeaderCollection Headers { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal RequestBuilder() - { + internal RequestBuilder() + { Headers = new(); } - #endregion + #endregion - #region Functionality + #region Functionality - public RequestBuilder Connection(IServer server, IEndPoint endPoint, IPAddress? address) - { + public RequestBuilder Connection(IServer server, IEndPoint endPoint, IPAddress? address) + { _Server = server; _Address = address; _EndPoint = endPoint; @@ -65,32 +64,32 @@ public RequestBuilder Connection(IServer server, IEndPoint endPoint, IPAddress? return this; } - public RequestBuilder Protocol(HttpProtocol version) - { + public RequestBuilder Protocol(HttpProtocol version) + { _Protocol = version; return this; } - public RequestBuilder Type(FlexibleRequestMethod type) - { + public RequestBuilder Type(FlexibleRequestMethod type) + { _RequestMethod = type; return this; } - public RequestBuilder Path(WebPath path) - { + public RequestBuilder Path(WebPath path) + { _Target = new(path); return this; } - public RequestBuilder Query(RequestQuery query) - { + public RequestBuilder Query(RequestQuery query) + { _Query = query; return this; } - public RequestBuilder Header(string key, string value) - { + public RequestBuilder Header(string key, string value) + { if (string.Equals(key, "cookie", StringComparison.OrdinalIgnoreCase)) { Cookies.Add(value); @@ -107,14 +106,14 @@ public RequestBuilder Header(string key, string value) return this; } - public RequestBuilder Content(Stream content) - { + public RequestBuilder Content(Stream content) + { _Content = content; return this; } - public IRequest Build() - { + public IRequest Build() + { try { if (_Server is null) @@ -177,8 +176,8 @@ public IRequest Build() } } - private ClientConnection? DetermineClient() - { + private ClientConnection? DetermineClient() + { if (_Forwardings is not null) { foreach (var forwarding in Forwardings) @@ -193,8 +192,6 @@ public IRequest Build() return null; } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/RequestHeaderCollection.cs b/Engine/Protocol/RequestHeaderCollection.cs index 6d71ed1d..3890fc71 100644 --- a/Engine/Protocol/RequestHeaderCollection.cs +++ b/Engine/Protocol/RequestHeaderCollection.cs @@ -4,22 +4,19 @@ using GenHTTP.Engine.Utilities; -namespace GenHTTP.Engine.Protocol -{ +namespace GenHTTP.Engine.Protocol; - internal sealed class RequestHeaderCollection : PooledDictionary, IHeaderCollection, IEditableHeaderCollection - { - private const int DEFAULT_SIZE = 18; +internal sealed class RequestHeaderCollection : PooledDictionary, IHeaderCollection, IEditableHeaderCollection +{ + private const int DEFAULT_SIZE = 18; - #region Initialization + #region Initialization - internal RequestHeaderCollection() : base(DEFAULT_SIZE, StringComparer.InvariantCultureIgnoreCase) - { + internal RequestHeaderCollection() : base(DEFAULT_SIZE, StringComparer.InvariantCultureIgnoreCase) + { } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/RequestProperties.cs b/Engine/Protocol/RequestProperties.cs index 3b2438e5..a2710ae2 100644 --- a/Engine/Protocol/RequestProperties.cs +++ b/Engine/Protocol/RequestProperties.cs @@ -5,21 +5,20 @@ using GenHTTP.Engine.Utilities; -namespace GenHTTP.Engine.Protocol -{ +namespace GenHTTP.Engine.Protocol; - public sealed class RequestProperties : IRequestProperties - { - private PooledDictionary? _Data; +public sealed class RequestProperties : IRequestProperties +{ + private PooledDictionary? _Data; - private bool _Disposed; + private bool _Disposed; - #region Get-/Setters + #region Get-/Setters - public object this[string key] + public object this[string key] + { + get { - get - { object? result = null; if (Data.TryGetValue(key, out var value)) @@ -34,20 +33,20 @@ public object this[string key] return result; } - set - { + set + { Data[key] = value; } - } + } - private PooledDictionary Data => _Data ??= new(); + private PooledDictionary Data => _Data ??= new(); - #endregion + #endregion - #region Functionality + #region Functionality - public bool TryGet(string key, [MaybeNullWhen(returnValue: false)] out T entry) - { + public bool TryGet(string key, [MaybeNullWhen(returnValue: false)] out T entry) + { if (Data.TryGetValue(key, out var value)) { if (value is T result) @@ -61,17 +60,17 @@ public bool TryGet(string key, [MaybeNullWhen(returnValue: false)] out T entr return false; } - public void Clear(string key) - { + public void Clear(string key) + { Data[key] = null; } - #endregion + #endregion - #region Disposal + #region Disposal - private void Dispose(bool disposing) - { + private void Dispose(bool disposing) + { if (!_Disposed) { if (disposing) @@ -84,14 +83,12 @@ private void Dispose(bool disposing) } } - public void Dispose() - { + public void Dispose() + { Dispose(disposing: true); System.GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/RequestQuery.cs b/Engine/Protocol/RequestQuery.cs index 5c3e257d..69a54e8b 100644 --- a/Engine/Protocol/RequestQuery.cs +++ b/Engine/Protocol/RequestQuery.cs @@ -3,18 +3,15 @@ using GenHTTP.Api.Protocol; using GenHTTP.Engine.Utilities; -namespace GenHTTP.Engine.Protocol +namespace GenHTTP.Engine.Protocol; + +internal sealed class RequestQuery : PooledDictionary, IRequestQuery { - - internal sealed class RequestQuery : PooledDictionary, IRequestQuery - { - private const int DEFAULT_SIZE = 12; + private const int DEFAULT_SIZE = 12; - internal RequestQuery() : base(DEFAULT_SIZE, StringComparer.OrdinalIgnoreCase) - { + internal RequestQuery() : base(DEFAULT_SIZE, StringComparer.OrdinalIgnoreCase) + { } - } - } diff --git a/Engine/Protocol/Response.cs b/Engine/Protocol/Response.cs index 1bf70f91..6b479527 100644 --- a/Engine/Protocol/Response.cs +++ b/Engine/Protocol/Response.cs @@ -1,45 +1,42 @@ using System; - using GenHTTP.Api.Protocol; -using GenHTTP.Engine.Protocol; -namespace GenHTTP.Engine -{ +namespace GenHTTP.Engine.Protocol; - internal sealed class Response : IResponse - { - private static readonly FlexibleResponseStatus STATUS_OK = new(ResponseStatus.OK); +internal sealed class Response : IResponse +{ + private static readonly FlexibleResponseStatus STATUS_OK = new(ResponseStatus.OK); - private CookieCollection? _Cookies; + private CookieCollection? _Cookies; - private readonly ResponseHeaderCollection _Headers = new(); + private readonly ResponseHeaderCollection _Headers = new(); - #region Get-/Setters + #region Get-/Setters - public FlexibleResponseStatus Status { get; set; } + public FlexibleResponseStatus Status { get; set; } - public DateTime? Expires { get; set; } + public DateTime? Expires { get; set; } - public DateTime? Modified { get; set; } + public DateTime? Modified { get; set; } - public FlexibleContentType? ContentType { get; set; } + public FlexibleContentType? ContentType { get; set; } - public string? ContentEncoding { get; set; } + public string? ContentEncoding { get; set; } - public ulong? ContentLength { get; set; } + public ulong? ContentLength { get; set; } - public IResponseContent? Content { get; set; } + public IResponseContent? Content { get; set; } - public ICookieCollection Cookies => WriteableCookies; + public ICookieCollection Cookies => WriteableCookies; - public bool HasCookies => (_Cookies is not null) && (_Cookies.Count > 0); + public bool HasCookies => (_Cookies is not null) && (_Cookies.Count > 0); - public IEditableHeaderCollection Headers => _Headers; + public IEditableHeaderCollection Headers => _Headers; - public string? this[string field] + public string? this[string field] + { + get { - get - { if (_Headers.TryGetValue(field, out var value)) { return value; @@ -47,8 +44,8 @@ public string? this[string field] return null; } - set - { + set + { if (value is not null) { _Headers[field] = value; @@ -58,39 +55,39 @@ public string? this[string field] _Headers.Remove(field); } } - } + } - internal CookieCollection WriteableCookies - { - get { return _Cookies ??= new(); } - } + internal CookieCollection WriteableCookies + { + get { return _Cookies ??= new(); } + } - #endregion + #endregion - #region Initialization + #region Initialization - internal Response() - { + internal Response() + { Status = STATUS_OK; } - #endregion + #endregion - #region Functionality + #region Functionality - public void SetCookie(Cookie cookie) - { + public void SetCookie(Cookie cookie) + { WriteableCookies[cookie.Name] = cookie; } - #endregion + #endregion - #region IDisposable Support + #region IDisposable Support - private bool disposed = false; + private bool disposed = false; - public void Dispose() - { + public void Dispose() + { if (!disposed) { Headers.Dispose(); @@ -108,8 +105,6 @@ public void Dispose() GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/ResponseBuilder.cs b/Engine/Protocol/ResponseBuilder.cs index 05fb3e1a..566bee74 100644 --- a/Engine/Protocol/ResponseBuilder.cs +++ b/Engine/Protocol/ResponseBuilder.cs @@ -2,81 +2,78 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Engine.Protocol -{ +namespace GenHTTP.Engine.Protocol; - internal sealed class ResponseBuilder : IResponseBuilder - { - private readonly Response _Response = new(); +internal sealed class ResponseBuilder : IResponseBuilder +{ + private readonly Response _Response = new(); - #region Functionality + #region Functionality - public IResponseBuilder Length(ulong length) - { + public IResponseBuilder Length(ulong length) + { _Response.ContentLength = length; return this; } - public IResponseBuilder Content(IResponseContent content) - { + public IResponseBuilder Content(IResponseContent content) + { _Response.Content = content; _Response.ContentLength = content.Length; return this; } - public IResponseBuilder Type(FlexibleContentType contentType) - { + public IResponseBuilder Type(FlexibleContentType contentType) + { _Response.ContentType = contentType; return this; } - public IResponseBuilder Cookie(Cookie cookie) - { + public IResponseBuilder Cookie(Cookie cookie) + { _Response.WriteableCookies[cookie.Name] = cookie; return this; } - public IResponseBuilder Header(string key, string value) - { + public IResponseBuilder Header(string key, string value) + { _Response.Headers.Add(key, value); return this; } - public IResponseBuilder Encoding(string encoding) - { + public IResponseBuilder Encoding(string encoding) + { _Response.ContentEncoding = encoding; return this; } - public IResponseBuilder Expires(DateTime expiryDate) - { + public IResponseBuilder Expires(DateTime expiryDate) + { _Response.Expires = expiryDate; return this; } - public IResponseBuilder Modified(DateTime modificationDate) - { + public IResponseBuilder Modified(DateTime modificationDate) + { _Response.Modified = modificationDate; return this; } - public IResponseBuilder Status(ResponseStatus status) - { + public IResponseBuilder Status(ResponseStatus status) + { _Response.Status = new(status); return this; } - public IResponseBuilder Status(int status, string reason) - { + public IResponseBuilder Status(int status, string reason) + { _Response.Status = new(status, reason); return this; } - public IResponse Build() => _Response; - - #endregion + public IResponse Build() => _Response; - } + #endregion } diff --git a/Engine/Protocol/ResponseHandler.cs b/Engine/Protocol/ResponseHandler.cs index 20609eb0..2f425d75 100644 --- a/Engine/Protocol/ResponseHandler.cs +++ b/Engine/Protocol/ResponseHandler.cs @@ -6,37 +6,35 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; - -using GenHTTP.Engine.Infrastructure.Configuration; +using GenHTTP.Engine.Infrastructure; using GenHTTP.Engine.Utilities; -namespace GenHTTP.Engine.Protocol -{ +namespace GenHTTP.Engine.Protocol; - internal sealed class ResponseHandler - { - private const string SERVER_HEADER = "Server"; +internal sealed class ResponseHandler +{ + private const string SERVER_HEADER = "Server"; - private static readonly string NL = "\r\n"; + private static readonly string NL = "\r\n"; - private static readonly Encoding ASCII = Encoding.ASCII; + private static readonly Encoding ASCII = Encoding.ASCII; - private static readonly ArrayPool POOL = ArrayPool.Shared; + private static readonly ArrayPool POOL = ArrayPool.Shared; - #region Get-/Setters + #region Get-/Setters - private IServer Server { get; } + private IServer Server { get; } - private Stream OutputStream { get; } + private Stream OutputStream { get; } - internal NetworkConfiguration Configuration { get; } + internal NetworkConfiguration Configuration { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal ResponseHandler(IServer server, Stream outputstream, NetworkConfiguration configuration) - { + internal ResponseHandler(IServer server, Stream outputstream, NetworkConfiguration configuration) + { Server = server; OutputStream = outputstream; @@ -44,12 +42,12 @@ internal ResponseHandler(IServer server, Stream outputstream, NetworkConfigurati Configuration = configuration; } - #endregion + #endregion - #region Functionality + #region Functionality - internal async ValueTask Handle(IRequest? request, IResponse response, bool keepAlive, bool dataRemaining) - { + internal async ValueTask Handle(IRequest? request, IResponse response, bool keepAlive, bool dataRemaining) + { try { await WriteStatus(request, response); @@ -84,8 +82,8 @@ internal async ValueTask Handle(IRequest? request, IResponse response, boo } } - private static bool ShouldSendBody(IRequest? request, IResponse response) - { + private static bool ShouldSendBody(IRequest? request, IResponse response) + { return ((request == null) || (request.Method.KnownMethod != RequestMethod.HEAD)) && ( (response.ContentLength > 0) || (response.Content?.Length > 0) || @@ -93,15 +91,15 @@ private static bool ShouldSendBody(IRequest? request, IResponse response) ); } - private ValueTask WriteStatus(IRequest? request, IResponse response) - { + private ValueTask WriteStatus(IRequest? request, IResponse response) + { var version = (request?.ProtocolType == HttpProtocol.Http_1_1) ? "1.1" : "1.0"; return Write("HTTP/", version, " ", NumberStringCache.Convert(response.Status.RawStatus), " ", response.Status.Phrase, NL); } - private async ValueTask WriteHeader(IResponse response, bool keepAlive) - { + private async ValueTask WriteHeader(IResponse response, bool keepAlive) + { if (response.Headers.TryGetValue(SERVER_HEADER, out var server)) { await WriteHeaderLine(SERVER_HEADER, server); @@ -175,8 +173,8 @@ private async ValueTask WriteHeader(IResponse response, bool keepAlive) } } - private async ValueTask WriteBody(IResponse response) - { + private async ValueTask WriteBody(IResponse response) + { if (response.Content is not null) { if (response.ContentLength is null) @@ -194,16 +192,16 @@ private async ValueTask WriteBody(IResponse response) } } - #endregion + #endregion - #region Helpers + #region Helpers - private ValueTask WriteHeaderLine(string key, string value) => Write(key, ": ", value, NL); + private ValueTask WriteHeaderLine(string key, string value) => Write(key, ": ", value, NL); - private ValueTask WriteHeaderLine(string key, DateTime value) => WriteHeaderLine(key, value.ToUniversalTime().ToString("r")); + private ValueTask WriteHeaderLine(string key, DateTime value) => WriteHeaderLine(key, value.ToUniversalTime().ToString("r")); - private async ValueTask WriteCookie(Cookie cookie) - { + private async ValueTask WriteCookie(Cookie cookie) + { await Write("Set-Cookie: ", cookie.Name, "=", cookie.Value); if (cookie.MaxAge is not null) @@ -214,17 +212,17 @@ private async ValueTask WriteCookie(Cookie cookie) await Write("; Path=/", NL); } - /// - /// Writes the given parts to the output stream. - /// - /// - /// Reduces the number of writes to the output stream by collecting - /// data to be written. Cannot use params keyword because it allocates - /// an array. - /// - private async ValueTask Write(string part1, string? part2 = null, string? part3 = null, - string? part4 = null, string? part5 = null, string? part6 = null, string? part7 = null) - { + /// + /// Writes the given parts to the output stream. + /// + /// + /// Reduces the number of writes to the output stream by collecting + /// data to be written. Cannot use params keyword because it allocates + /// an array. + /// + private async ValueTask Write(string part1, string? part2 = null, string? part3 = null, + string? part4 = null, string? part5 = null, string? part6 = null, string? part7 = null) + { var length = part1.Length + (part2?.Length ?? 0) + (part3?.Length ?? 0) + (part4?.Length ?? 0) + (part5?.Length ?? 0) + (part6?.Length ?? 0) + (part7?.Length ?? 0); @@ -272,8 +270,6 @@ private async ValueTask Write(string part1, string? part2 = null, string? part3 } } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/ResponseHeaderCollection.cs b/Engine/Protocol/ResponseHeaderCollection.cs index 1391ff4c..871b6e62 100644 --- a/Engine/Protocol/ResponseHeaderCollection.cs +++ b/Engine/Protocol/ResponseHeaderCollection.cs @@ -5,69 +5,66 @@ using GenHTTP.Engine.Utilities; -namespace GenHTTP.Engine.Protocol +namespace GenHTTP.Engine.Protocol; + +internal sealed class ResponseHeaderCollection : PooledDictionary, IHeaderCollection, IEditableHeaderCollection { + private const int DEFAULT_SIZE = 18; - internal sealed class ResponseHeaderCollection : PooledDictionary, IHeaderCollection, IEditableHeaderCollection + private static readonly HashSet RESERVED_HEADERS = new(StringComparer.InvariantCultureIgnoreCase) { - private const int DEFAULT_SIZE = 18; - - private static readonly HashSet RESERVED_HEADERS = new(StringComparer.InvariantCultureIgnoreCase) - { - "Date", "Connection", "Content-Type", "Content-Encoding", "Content-Length", - "Transfer-Encoding", "Last-Modified", "Expires" - }; + "Date", "Connection", "Content-Type", "Content-Encoding", "Content-Length", + "Transfer-Encoding", "Last-Modified", "Expires" + }; - #region Get-/Setters + #region Get-/Setters - public override string this[string key] + public override string this[string key] + { + get { - get - { return base[key]; } - set - { + set + { CheckKey(key); base[key] = value; } - } + } - #endregion + #endregion - #region Initialization + #region Initialization - internal ResponseHeaderCollection() : base(DEFAULT_SIZE, StringComparer.InvariantCultureIgnoreCase) - { + internal ResponseHeaderCollection() : base(DEFAULT_SIZE, StringComparer.InvariantCultureIgnoreCase) + { } - #endregion + #endregion - #region Functionality + #region Functionality - public override void Add(string key, string value) - { + public override void Add(string key, string value) + { CheckKey(key); base.Add(key, value); } - public override void Add(KeyValuePair item) - { + public override void Add(KeyValuePair item) + { CheckKey(item.Key); base.Add(item); } - private static void CheckKey(string key) - { + private static void CheckKey(string key) + { if (RESERVED_HEADERS.Contains(key)) { throw new ArgumentException($"Header '{key}' cannot be set via header. Please use the designated property instead."); } } - #endregion - - } + #endregion } diff --git a/Engine/Protocol/SocketExtensions.cs b/Engine/Protocol/SocketExtensions.cs index 3202cc6d..4d181fa7 100644 --- a/Engine/Protocol/SocketExtensions.cs +++ b/Engine/Protocol/SocketExtensions.cs @@ -1,14 +1,11 @@ using System.Net; using System.Net.Sockets; -namespace GenHTTP.Engine.Protocol -{ - - internal static class SocketExtensions - { +namespace GenHTTP.Engine.Protocol; - public static IPAddress? GetAddress(this Socket socket) => (socket.RemoteEndPoint as IPEndPoint)?.Address; +internal static class SocketExtensions +{ - } + public static IPAddress? GetAddress(this Socket socket) => (socket.RemoteEndPoint as IPEndPoint)?.Address; -} +} \ No newline at end of file diff --git a/Engine/Protocol/TemporaryFileStream.cs b/Engine/Protocol/TemporaryFileStream.cs index 3d18b8b1..9b8ccdcd 100644 --- a/Engine/Protocol/TemporaryFileStream.cs +++ b/Engine/Protocol/TemporaryFileStream.cs @@ -1,44 +1,43 @@ using System; using System.IO; -namespace GenHTTP.Engine.Protocol -{ +namespace GenHTTP.Engine.Protocol; - /// - /// Provides and maintains a temporary file used by the server engine - /// to save information, such as the body of a large request. - /// - internal sealed class TemporaryFileStream : FileStream - { +/// +/// Provides and maintains a temporary file used by the server engine +/// to save information, such as the body of a large request. +/// +internal sealed class TemporaryFileStream : FileStream +{ - #region Get-/Setters + #region Get-/Setters - internal string TemporaryFile { get; } + internal string TemporaryFile { get; } - #endregion + #endregion - #region Initialization + #region Initialization - private TemporaryFileStream(string file) : base(file, FileMode.CreateNew, FileAccess.ReadWrite) - { + private TemporaryFileStream(string file) : base(file, FileMode.CreateNew, FileAccess.ReadWrite) + { TemporaryFile = file; } - /// - /// Creates a new temporary file which can be used for data storage. - /// - /// The newly created file stream - internal static Stream Create() - { + /// + /// Creates a new temporary file which can be used for data storage. + /// + /// The newly created file stream + internal static Stream Create() + { return new TemporaryFileStream(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".genhttp.tmp")); } - #endregion + #endregion - #region Lifecycle + #region Lifecycle - protected override void Dispose(bool disposing) - { + protected override void Dispose(bool disposing) + { base.Dispose(disposing); if (disposing) @@ -47,8 +46,6 @@ protected override void Dispose(bool disposing) } } - #endregion - - } + #endregion } diff --git a/Engine/Server.cs b/Engine/Server.cs index fbfd6d1d..f1aea9a1 100644 --- a/Engine/Server.cs +++ b/Engine/Server.cs @@ -1,25 +1,22 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Engine.Infrastructure; -namespace GenHTTP.Engine +namespace GenHTTP.Engine; + +/// +/// Allows to create server instances. +/// +public static class Server { /// - /// Allows to create server instances. + /// Create a new, configurable server instance with + /// default values. /// - public static class Server + /// The builder to create the instance + public static IServerBuilder Create() { - - /// - /// Create a new, configurable server instance with - /// default values. - /// - /// The builder to create the instance - public static IServerBuilder Create() - { return new ThreadedServerBuilder(); } - } - } diff --git a/Engine/Utilities/NumberStringCache.cs b/Engine/Utilities/NumberStringCache.cs index 8cbff22a..3d601bed 100644 --- a/Engine/Utilities/NumberStringCache.cs +++ b/Engine/Utilities/NumberStringCache.cs @@ -2,38 +2,34 @@ using System.Collections.Generic; using System.Linq; -namespace GenHTTP.Engine.Utilities +namespace GenHTTP.Engine.Utilities; + +/// +/// Caches the string representation of small numbers, +/// reducing the amount of string allocations needed +/// by the engine when writing HTTP responses. +/// +public static class NumberStringCache { - /// - /// Caches the string representation of small numbers, - /// reducing the amount of string allocations needed - /// by the engine when writing HTTP responses. - /// - public static class NumberStringCache - { - - private const int LIMIT = 1024; + private const int LIMIT = 1024; - private static readonly Dictionary _Cache = new - ( - Enumerable.Range(0, LIMIT + 1).Select(i => new KeyValuePair((ulong)i, $"{i}")) - ); + private static readonly Dictionary _Cache = new + ( + Enumerable.Range(0, LIMIT + 1).Select(i => new KeyValuePair((ulong)i, $"{i}")) + ); - #region Functionality + #region Functionality - public static string Convert(int number) - { + public static string Convert(int number) + { if (number < 0) throw new ArgumentOutOfRangeException(nameof(number), "Only positive numbers are supported"); return Convert((ulong)number); } - public static string Convert(ulong number) => (number <= LIMIT) ? _Cache[number] : $"{number}"; - - #endregion - - } + public static string Convert(ulong number) => (number <= LIMIT) ? _Cache[number] : $"{number}"; -} + #endregion +} \ No newline at end of file diff --git a/Engine/Utilities/PoolBufferedStream.cs b/Engine/Utilities/PoolBufferedStream.cs index 10b009c5..eceb94b7 100644 --- a/Engine/Utilities/PoolBufferedStream.cs +++ b/Engine/Utilities/PoolBufferedStream.cs @@ -4,81 +4,80 @@ using System.Threading; using System.Threading.Tasks; -namespace GenHTTP.Engine.Utilities +namespace GenHTTP.Engine.Utilities; + +/// +/// An output stream using a pooled array to buffer small writes. +/// +/// +/// Reduces write calls on underlying streams by collecting small writes +/// and flushing them only on request or if the internal buffer overflows. +/// Using a rented buffer from the array pool keeps allocations low. +/// +/// Decreases the overhead of response content that issues a lot of small +/// writes (such as serializers or template renderers). As the content +/// length for such responses is typically not known beforehand, this +/// would cause all of those small writes to be converted into chunks, adding +/// a lot of communication overhead to the client connection. +/// +public sealed class PoolBufferedStream : Stream { + private static readonly ArrayPool POOL = ArrayPool.Create(128 * 1024, 8192); // 1 GB - /// - /// An output stream using a pooled array to buffer small writes. - /// - /// - /// Reduces write calls on underlying streams by collecting small writes - /// and flushing them only on request or if the internal buffer overflows. - /// Using a rented buffer from the array pool keeps allocations low. - /// - /// Decreases the overhead of response content that issues a lot of small - /// writes (such as serializers or template renderers). As the content - /// length for such responses is typically not known beforehand, this - /// would cause all of those small writes to be converted into chunks, adding - /// a lot of communication overhead to the client connection. - /// - public sealed class PoolBufferedStream : Stream - { - private static readonly ArrayPool POOL = ArrayPool.Create(128 * 1024, 8192); // 1 GB - - #region Get-/Setters + #region Get-/Setters - public override bool CanRead => Stream.CanRead; + public override bool CanRead => Stream.CanRead; - public override bool CanSeek => Stream.CanSeek; + public override bool CanSeek => Stream.CanSeek; - public override bool CanWrite => Stream.CanWrite; + public override bool CanWrite => Stream.CanWrite; - public override long Length => Stream.Length; + public override long Length => Stream.Length; - public override long Position - { - get => Stream.Position; - set => Stream.Position = value; - } + public override long Position + { + get => Stream.Position; + set => Stream.Position = value; + } - private Stream Stream { get; } + private Stream Stream { get; } - private byte[] Buffer { get; } + private byte[] Buffer { get; } - private int Current { get; set; } + private int Current { get; set; } - #endregion + #endregion - #region Initialization + #region Initialization - public PoolBufferedStream(Stream stream, uint bufferSize) - { + public PoolBufferedStream(Stream stream, uint bufferSize) + { Stream = stream; Buffer = POOL.Rent((int)bufferSize); Current = 0; } - #endregion + #endregion - #region Functionality + #region Functionality - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => Stream.ReadAsync(buffer, offset, count, cancellationToken); + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => Stream.ReadAsync(buffer, offset, count, cancellationToken); - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => Stream.ReadAsync(buffer, cancellationToken); + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => Stream.ReadAsync(buffer, cancellationToken); - public override int Read(byte[] buffer, int offset, int count) => Stream.Read(buffer, offset, count); + public override int Read(byte[] buffer, int offset, int count) => Stream.Read(buffer, offset, count); - public override int Read(Span buffer) => Stream.Read(buffer); + public override int Read(Span buffer) => Stream.Read(buffer); - public override int ReadByte() => Stream.ReadByte(); + public override int ReadByte() => Stream.ReadByte(); - public override long Seek(long offset, SeekOrigin origin) => Stream.Seek(offset, origin); + public override long Seek(long offset, SeekOrigin origin) => Stream.Seek(offset, origin); - public override void SetLength(long value) => Stream.SetLength(value); + public override void SetLength(long value) => Stream.SetLength(value); - public override void Write(byte[] buffer, int offset, int count) - { + public override void Write(byte[] buffer, int offset, int count) + { if (count < (Buffer.Length - Current)) { System.Buffer.BlockCopy(buffer, offset, Buffer, Current, count); @@ -98,13 +97,13 @@ public override void Write(byte[] buffer, int offset, int count) } } - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { await WriteAsync(buffer.AsMemory(offset, count - offset), cancellationToken); } - public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { var count = buffer.Length; if (count < (Buffer.Length - Current)) @@ -126,22 +125,22 @@ public override async ValueTask WriteAsync(ReadOnlyMemory buffer, Cancella } } - public override void Flush() - { + public override void Flush() + { WriteBuffer(); Stream.Flush(); } - public override async Task FlushAsync(CancellationToken cancellationToken) - { + public override async Task FlushAsync(CancellationToken cancellationToken) + { await WriteBufferAsync(cancellationToken); await Stream.FlushAsync(cancellationToken); } - private void WriteBuffer() - { + private void WriteBuffer() + { if (Current > 0) { Stream.Write(Buffer, 0, Current); @@ -150,8 +149,8 @@ private void WriteBuffer() } } - private async ValueTask WriteBufferAsync(CancellationToken cancellationToken) - { + private async ValueTask WriteBufferAsync(CancellationToken cancellationToken) + { if (Current > 0) { await Stream.WriteAsync(Buffer.AsMemory(0, Current), cancellationToken); @@ -160,12 +159,12 @@ private async ValueTask WriteBufferAsync(CancellationToken cancellationToken) } } - #endregion + #endregion - #region Disposing + #region Disposing - protected override void Dispose(bool disposing) - { + protected override void Dispose(bool disposing) + { if (disposing) { try @@ -181,8 +180,6 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - #endregion - - } + #endregion } diff --git a/Engine/Utilities/PooledDictionary.cs b/Engine/Utilities/PooledDictionary.cs index 2b07629e..f70a463b 100644 --- a/Engine/Utilities/PooledDictionary.cs +++ b/Engine/Utilities/PooledDictionary.cs @@ -3,26 +3,25 @@ using System.Collections; using System.Collections.Generic; -namespace GenHTTP.Engine.Utilities -{ +namespace GenHTTP.Engine.Utilities; - internal class PooledDictionary : IDictionary, IReadOnlyDictionary, IEnumerator> where TKey : IEquatable - { - private static readonly ArrayPool> POOL = ArrayPool>.Shared; +internal class PooledDictionary : IDictionary, IReadOnlyDictionary, IEnumerator> where TKey : IEquatable +{ + private static readonly ArrayPool> POOL = ArrayPool>.Shared; - private short _Enumerator = -1; - private ushort _Index = 0; + private short _Enumerator = -1; + private ushort _Index = 0; - private KeyValuePair[]? _Entries; + private KeyValuePair[]? _Entries; - private readonly IEqualityComparer _Comparer; + private readonly IEqualityComparer _Comparer; - #region Get-/Setters + #region Get-/Setters - private KeyValuePair[] Entries + private KeyValuePair[] Entries + { + get { - get - { if (_Entries is null) { _Entries = POOL.Rent(Capacity); @@ -30,14 +29,14 @@ private KeyValuePair[] Entries return _Entries!; } - } + } - private bool HasEntries => _Entries is not null; + private bool HasEntries => _Entries is not null; - public virtual TValue this[TKey key] + public virtual TValue this[TKey key] + { + get { - get - { if (HasEntries) { for (int i = 0; i < _Index; i++) @@ -51,8 +50,8 @@ public virtual TValue this[TKey key] throw new KeyNotFoundException(); } - set - { + set + { if (HasEntries) { for (int i = 0; i < _Index; i++) @@ -67,12 +66,12 @@ public virtual TValue this[TKey key] Add(key, value); } - } + } - public ICollection Keys + public ICollection Keys + { + get { - get - { var result = new List(_Index); if (HasEntries) @@ -85,12 +84,12 @@ public ICollection Keys return result; } - } + } - public ICollection Values + public ICollection Values + { + get { - get - { var result = new List(_Index); if (HasEntries) @@ -103,61 +102,61 @@ public ICollection Values return result; } - } + } - public int Count => _Index; + public int Count => _Index; - public bool IsReadOnly => false; + public bool IsReadOnly => false; - IEnumerable IReadOnlyDictionary.Keys => Keys; + IEnumerable IReadOnlyDictionary.Keys => Keys; - IEnumerable IReadOnlyDictionary.Values => Values; + IEnumerable IReadOnlyDictionary.Values => Values; - public KeyValuePair Current => Entries[_Enumerator]; + public KeyValuePair Current => Entries[_Enumerator]; - object IEnumerator.Current => Entries[_Enumerator]; + object IEnumerator.Current => Entries[_Enumerator]; - public int Capacity { get; private set; } + public int Capacity { get; private set; } - #endregion + #endregion - #region Initialization + #region Initialization - internal PooledDictionary() : this(4, EqualityComparer.Default) - { + internal PooledDictionary() : this(4, EqualityComparer.Default) + { } - internal PooledDictionary(int initialCapacity, IEqualityComparer comparer) - { + internal PooledDictionary(int initialCapacity, IEqualityComparer comparer) + { Capacity = initialCapacity; _Comparer = comparer; } - #endregion + #endregion - #region Functionality + #region Functionality - public virtual void Add(TKey key, TValue value) - { + public virtual void Add(TKey key, TValue value) + { CheckResize(); Entries[_Index++] = new(key, value); } - public virtual void Add(KeyValuePair item) - { + public virtual void Add(KeyValuePair item) + { CheckResize(); Entries[_Index++] = item; } - public void Clear() - { + public void Clear() + { _Index = 0; } - public bool Contains(KeyValuePair item) - { + public bool Contains(KeyValuePair item) + { if (HasEntries) { for (int i = 0; i < _Index; i++) @@ -172,8 +171,8 @@ public bool Contains(KeyValuePair item) return false; } - public bool ContainsKey(TKey key) - { + public bool ContainsKey(TKey key) + { if (HasEntries) { for (int i = 0; i < _Index; i++) @@ -188,29 +187,29 @@ public bool ContainsKey(TKey key) return false; } - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { throw new NotSupportedException(); } - public IEnumerator> GetEnumerator() - { + public IEnumerator> GetEnumerator() + { _Enumerator = -1; return this; } - public bool Remove(TKey key) - { + public bool Remove(TKey key) + { throw new NotSupportedException(); } - public bool Remove(KeyValuePair item) - { + public bool Remove(KeyValuePair item) + { throw new NotSupportedException(); } - public bool TryGetValue(TKey key, out TValue value) - { + public bool TryGetValue(TKey key, out TValue value) + { if (ContainsKey(key)) { value = this[key]; @@ -224,24 +223,24 @@ public bool TryGetValue(TKey key, out TValue value) return false; } - IEnumerator IEnumerable.GetEnumerator() - { + IEnumerator IEnumerable.GetEnumerator() + { return this; } - public bool MoveNext() - { + public bool MoveNext() + { _Enumerator++; return (_Enumerator < _Index); } - public void Reset() - { + public void Reset() + { _Enumerator = -1; } - private void CheckResize() - { + private void CheckResize() + { if (_Index >= Entries.Length) { var oldEntries = Entries; @@ -271,14 +270,14 @@ private void CheckResize() } } - #endregion + #endregion - #region IDisposable Support + #region IDisposable Support - private bool disposedValue = false; + private bool disposedValue = false; - protected virtual void Dispose(bool disposing) - { + protected virtual void Dispose(bool disposing) + { if (!disposedValue) { if (disposing) @@ -293,13 +292,11 @@ protected virtual void Dispose(bool disposing) } } - public void Dispose() - { + public void Dispose() + { Dispose(true); } - #endregion - - } + #endregion } diff --git a/Modules/Authentication/ApiKey/ApiKeyConcern.cs b/Modules/Authentication/ApiKey/ApiKeyConcern.cs index 5eb25e53..eb837d50 100644 --- a/Modules/Authentication/ApiKey/ApiKeyConcern.cs +++ b/Modules/Authentication/ApiKey/ApiKeyConcern.cs @@ -5,28 +5,27 @@ using GenHTTP.Api.Content.Authentication; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Authentication.ApiKey -{ +namespace GenHTTP.Modules.Authentication.ApiKey; - public sealed class ApiKeyConcern : IConcern - { +public sealed class ApiKeyConcern : IConcern +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public IHandler Content { get; } + public IHandler Content { get; } - private Func KeyExtractor { get; } + private Func KeyExtractor { get; } - private Func> Authenticator { get; } + private Func> Authenticator { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ApiKeyConcern(IHandler parent, Func contentFactory, Func keyExtractor, Func> authenticator) - { + public ApiKeyConcern(IHandler parent, Func contentFactory, Func keyExtractor, Func> authenticator) + { Parent = parent; Content = contentFactory(this); @@ -34,12 +33,12 @@ public ApiKeyConcern(IHandler parent, Func contentFactory, F Authenticator = authenticator; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var key = KeyExtractor(request); if (key != null) @@ -65,10 +64,8 @@ public ApiKeyConcern(IHandler parent, Func contentFactory, F .Build(); } - public ValueTask PrepareAsync() => Content.PrepareAsync(); - - #endregion + public ValueTask PrepareAsync() => Content.PrepareAsync(); - } + #endregion -} +} \ No newline at end of file diff --git a/Modules/Authentication/ApiKey/ApiKeyConcernBuilder.cs b/Modules/Authentication/ApiKey/ApiKeyConcernBuilder.cs index 0ca4972d..5fc9f741 100644 --- a/Modules/Authentication/ApiKey/ApiKeyConcernBuilder.cs +++ b/Modules/Authentication/ApiKey/ApiKeyConcernBuilder.cs @@ -7,67 +7,66 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Authentication.ApiKey -{ +namespace GenHTTP.Modules.Authentication.ApiKey; - public sealed class ApiKeyConcernBuilder : IConcernBuilder - { - private Func? _KeyExtractor = (request) => request.Headers.TryGetValue("X-API-Key", out var key) ? key : null; +public sealed class ApiKeyConcernBuilder : IConcernBuilder +{ + private Func? _KeyExtractor = (request) => request.Headers.TryGetValue("X-API-Key", out var key) ? key : null; - private Func>? _Authenticator; + private Func>? _Authenticator; - #region Functionality + #region Functionality - /// - /// Configures the logic that is used to extract an API key from - /// an incoming request (e.g. by reading a header value). - /// - /// The logic to be executed to fetch an API key from a request - public ApiKeyConcernBuilder Extractor(Func keyExtractor) - { + /// + /// Configures the logic that is used to extract an API key from + /// an incoming request (e.g. by reading a header value). + /// + /// The logic to be executed to fetch an API key from a request + public ApiKeyConcernBuilder Extractor(Func keyExtractor) + { _KeyExtractor = keyExtractor; return this; } - /// - /// Configures the handler to read the API key from the - /// specified HTTP header. - /// - /// The name of the header to be read from the request - public ApiKeyConcernBuilder WithHeader(string headerName) - { + /// + /// Configures the handler to read the API key from the + /// specified HTTP header. + /// + /// The name of the header to be read from the request + public ApiKeyConcernBuilder WithHeader(string headerName) + { _KeyExtractor = (request) => request.Headers.TryGetValue(headerName, out var key) ? key : null; return this; } - /// - /// Configures the handler to read the API key from the - /// specified query parameter. - /// - /// The name of the query parameter to be read from the request - public ApiKeyConcernBuilder WithQueryParameter(string parameter) - { + /// + /// Configures the handler to read the API key from the + /// specified query parameter. + /// + /// The name of the query parameter to be read from the request + public ApiKeyConcernBuilder WithQueryParameter(string parameter) + { _KeyExtractor = (request) => request.Query.TryGetValue(parameter, out var key) ? key : null; return this; } - /// - /// Configures the logic that checks whether a given API key - /// is valid. - /// - /// The logic to be executed to authenticate a request - public ApiKeyConcernBuilder Authenticator(Func> authenticator) - { + /// + /// Configures the logic that checks whether a given API key + /// is valid. + /// + /// The logic to be executed to authenticate a request + public ApiKeyConcernBuilder Authenticator(Func> authenticator) + { _Authenticator = authenticator; return this; } - /// - /// Configures the handler to accept any of the given API keys. - /// - /// The keys which are allowed to access the content secured by the concern - public ApiKeyConcernBuilder Keys(params string[] allowedKeys) - { + /// + /// Configures the handler to accept any of the given API keys. + /// + /// The keys which are allowed to access the content secured by the concern + public ApiKeyConcernBuilder Keys(params string[] allowedKeys) + { var keySet = new HashSet(allowedKeys); _Authenticator = (r, key) => keySet.Contains(key) ? new ValueTask(new ApiKeyUser(key)) : new ValueTask(); @@ -75,8 +74,8 @@ public ApiKeyConcernBuilder Keys(params string[] allowedKeys) return this; } - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { var keyExtractor = _KeyExtractor ?? throw new BuilderMissingPropertyException("KeyExtractor"); var authenticator = _Authenticator ?? throw new BuilderMissingPropertyException("Authenticator"); @@ -84,8 +83,6 @@ public IConcern Build(IHandler parent, Func contentFactory) return new ApiKeyConcern(parent, contentFactory, keyExtractor, authenticator); } - #endregion - - } + #endregion } diff --git a/Modules/Authentication/ApiKey/ApiKeyUser.cs b/Modules/Authentication/ApiKey/ApiKeyUser.cs index 6a81b884..e12b1367 100644 --- a/Modules/Authentication/ApiKey/ApiKeyUser.cs +++ b/Modules/Authentication/ApiKey/ApiKeyUser.cs @@ -1,28 +1,25 @@ using GenHTTP.Api.Content.Authentication; -namespace GenHTTP.Modules.Authentication.ApiKey -{ +namespace GenHTTP.Modules.Authentication.ApiKey; - public class ApiKeyUser : IUser - { +public class ApiKeyUser : IUser +{ - #region Get-/Setters + #region Get-/Setters - public string DisplayName => Key; + public string DisplayName => Key; - public string Key { get; } + public string Key { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ApiKeyUser(string key) - { + public ApiKeyUser(string key) + { Key = key; } - #endregion - - } + #endregion } diff --git a/Modules/Authentication/ApiKeyAuthentication.cs b/Modules/Authentication/ApiKeyAuthentication.cs index 1028adfc..495e4aeb 100644 --- a/Modules/Authentication/ApiKeyAuthentication.cs +++ b/Modules/Authentication/ApiKeyAuthentication.cs @@ -7,52 +7,49 @@ using GenHTTP.Modules.Authentication.ApiKey; -namespace GenHTTP.Modules.Authentication +namespace GenHTTP.Modules.Authentication; + +/// +/// Allows API key authentication to be added to a handler instance +/// as a concern. +/// +public static class ApiKeyAuthentication { + #region Builder + /// - /// Allows API key authentication to be added to a handler instance - /// as a concern. + /// Creates a customizable API key authentication handler that will + /// read the key from the HTTP header named "X-API-Key". /// - public static class ApiKeyAuthentication - { + public static ApiKeyConcernBuilder Create() => new(); - #region Builder + #endregion - /// - /// Creates a customizable API key authentication handler that will - /// read the key from the HTTP header named "X-API-Key". - /// - public static ApiKeyConcernBuilder Create() => new(); + #region Extensions - #endregion - - #region Extensions - - /// - /// Adds API key authentication to the handler. - /// - /// The authentication concern to be added - public static T Authentication(this T builder, ApiKeyConcernBuilder apiKeyAuth) where T : IHandlerBuilder - { + /// + /// Adds API key authentication to the handler. + /// + /// The authentication concern to be added + public static T Authentication(this T builder, ApiKeyConcernBuilder apiKeyAuth) where T : IHandlerBuilder + { builder.Add(apiKeyAuth); return builder; } - /// - /// Adds API key authentication to the handler, using the - /// given function to check, whether a key passed by the - /// client is valid. - /// - /// The function to be invoked to determine, whether the given string key is valid - public static T Authentication(this T builder, Func> authenticator) where T : IHandlerBuilder - { + /// + /// Adds API key authentication to the handler, using the + /// given function to check, whether a key passed by the + /// client is valid. + /// + /// The function to be invoked to determine, whether the given string key is valid + public static T Authentication(this T builder, Func> authenticator) where T : IHandlerBuilder + { builder.Add(Create().Authenticator(authenticator)); return builder; } - #endregion - - } + #endregion } diff --git a/Modules/Authentication/Basic/BasicAuthenticationConcern.cs b/Modules/Authentication/Basic/BasicAuthenticationConcern.cs index f989395c..01b3a8ff 100644 --- a/Modules/Authentication/Basic/BasicAuthenticationConcern.cs +++ b/Modules/Authentication/Basic/BasicAuthenticationConcern.cs @@ -1,109 +1,107 @@ using System; using System.Text; using System.Threading.Tasks; - using GenHTTP.Api.Content; using GenHTTP.Api.Content.Authentication; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Authentication.Basic +namespace GenHTTP.Modules.Authentication.Basic; + +public sealed class BasicAuthenticationConcern : IConcern { - public sealed class BasicAuthenticationConcern : IConcern - { + #region Get-/Setters - #region Get-/Setters + public IHandler Content { get; } - public IHandler Content { get; } + public IHandler Parent { get; } - public IHandler Parent { get; } + private string Realm { get; } - private string Realm { get; } + private Func> Authenticator { get; } - private Func> Authenticator { get; } + #endregion - #endregion + #region Initialization - #region Initialization + public BasicAuthenticationConcern(IHandler parent, Func contentFactory, string realm, + Func> authenticator) + { + Parent = parent; + Content = contentFactory(this); - public BasicAuthenticationConcern(IHandler parent, Func contentFactory, string realm, Func> authenticator) - { - Parent = parent; - Content = contentFactory(this); + Realm = realm; + Authenticator = authenticator; + } - Realm = realm; - Authenticator = authenticator; - } + #endregion - #endregion + #region Functionality - #region Functionality - - public ValueTask PrepareAsync() => Content.PrepareAsync(); + public ValueTask PrepareAsync() => Content.PrepareAsync(); - public async ValueTask HandleAsync(IRequest request) + public async ValueTask HandleAsync(IRequest request) + { + if (!request.Headers.TryGetValue("Authorization", out var authHeader)) { - if (!request.Headers.TryGetValue("Authorization", out var authHeader)) - { - return GetChallenge(request); - } + return GetChallenge(request); + } - if (!authHeader.StartsWith("Basic ")) - { - return GetChallenge(request); - } + if (!authHeader.StartsWith("Basic ")) + { + return GetChallenge(request); + } - if (!TryDecode(authHeader[6..], out var credentials)) - { - return GetChallenge(request); - } + if (!TryDecode(authHeader[6..], out var credentials)) + { + return GetChallenge(request); + } - var user = await Authenticator(credentials.username, credentials.password); + var user = await Authenticator(credentials.username, credentials.password); - if (user is null) - { - return GetChallenge(request); - } + if (user is null) + { + return GetChallenge(request); + } - request.SetUser(user); + request.SetUser(user); - return await Content.HandleAsync(request); - } + return await Content.HandleAsync(request); + } - private IResponse GetChallenge(IRequest request) - { - return request.Respond() - .Status(ResponseStatus.Unauthorized) - .Header("WWW-Authenticate", $"Basic realm=\"{Realm}\", charset=\"UTF-8\"") - .Build(); - } + private IResponse GetChallenge(IRequest request) + { + return request.Respond() + .Status(ResponseStatus.Unauthorized) + .Header("WWW-Authenticate", $"Basic realm=\"{Realm}\", charset=\"UTF-8\"") + .Build(); + } - private static bool TryDecode(string header, out (string username, string password) credentials) + private static bool TryDecode(string header, out (string username, string password) credentials) + { + try { - try - { - var bytes = Convert.FromBase64String(header); - var str = Encoding.UTF8.GetString(bytes); + var bytes = Convert.FromBase64String(header); + var str = Encoding.UTF8.GetString(bytes); - var colon = str.IndexOf(':'); + var colon = str.IndexOf(':'); - if ((colon > -1) && (str.Length > colon)) - { - credentials = (str.Substring(0, colon), str[(colon + 1)..]); - return true; - } - } - catch (FormatException) + if ((colon > -1) && (str.Length > colon)) { - // invalid base 64 encoded string + credentials = (str.Substring(0, colon), str[(colon + 1)..]); + return true; } - credentials = (string.Empty, string.Empty); - return false; + } + catch (FormatException) + { + // invalid base 64 encoded string } - #endregion - + credentials = (string.Empty, string.Empty); + return false; } + #endregion + } diff --git a/Modules/Authentication/Basic/BasicAuthenticationConcernBuilder.cs b/Modules/Authentication/Basic/BasicAuthenticationConcernBuilder.cs index e268b7ee..c2fda718 100644 --- a/Modules/Authentication/Basic/BasicAuthenticationConcernBuilder.cs +++ b/Modules/Authentication/Basic/BasicAuthenticationConcernBuilder.cs @@ -5,31 +5,30 @@ using GenHTTP.Api.Content.Authentication; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.Authentication.Basic -{ +namespace GenHTTP.Modules.Authentication.Basic; - public sealed class BasicAuthenticationConcernBuilder : IConcernBuilder - { - private string? _Realm; +public sealed class BasicAuthenticationConcernBuilder : IConcernBuilder +{ + private string? _Realm; - private Func>? _Handler; + private Func>? _Handler; - #region Functionality + #region Functionality - public BasicAuthenticationConcernBuilder Realm(string realm) - { + public BasicAuthenticationConcernBuilder Realm(string realm) + { _Realm = realm; return this; } - public BasicAuthenticationConcernBuilder Handler(Func> handler) - { + public BasicAuthenticationConcernBuilder Handler(Func> handler) + { _Handler = handler; return this; } - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { var realm = _Realm ?? throw new BuilderMissingPropertyException("Realm"); var handler = _Handler ?? throw new BuilderMissingPropertyException("Handler"); @@ -37,8 +36,6 @@ public IConcern Build(IHandler parent, Func contentFactory) return new BasicAuthenticationConcern(parent, contentFactory, realm, handler); } - #endregion - - } + #endregion } diff --git a/Modules/Authentication/Basic/BasicAuthenticationKnownUsersBuilder.cs b/Modules/Authentication/Basic/BasicAuthenticationKnownUsersBuilder.cs index ea95d749..57aa3a13 100644 --- a/Modules/Authentication/Basic/BasicAuthenticationKnownUsersBuilder.cs +++ b/Modules/Authentication/Basic/BasicAuthenticationKnownUsersBuilder.cs @@ -7,31 +7,30 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.Authentication.Basic -{ +namespace GenHTTP.Modules.Authentication.Basic; - public sealed class BasicAuthenticationKnownUsersBuilder : IConcernBuilder - { - private readonly Dictionary _Users = new(StringComparer.OrdinalIgnoreCase); +public sealed class BasicAuthenticationKnownUsersBuilder : IConcernBuilder +{ + private readonly Dictionary _Users = new(StringComparer.OrdinalIgnoreCase); - private string? _Realm; + private string? _Realm; - #region Functionality + #region Functionality - public BasicAuthenticationKnownUsersBuilder Realm(string realm) - { + public BasicAuthenticationKnownUsersBuilder Realm(string realm) + { _Realm = realm; return this; } - public BasicAuthenticationKnownUsersBuilder Add(string user, string password) - { + public BasicAuthenticationKnownUsersBuilder Add(string user, string password) + { _Users.Add(user, password); return this; } - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { var realm = _Realm ?? throw new BuilderMissingPropertyException("Realm"); return new BasicAuthenticationConcern(parent, contentFactory, realm, (user, password) => @@ -48,8 +47,6 @@ public IConcern Build(IHandler parent, Func contentFactory) }); } - #endregion - - } + #endregion } diff --git a/Modules/Authentication/Basic/BasicAuthenticationUser.cs b/Modules/Authentication/Basic/BasicAuthenticationUser.cs index 2f862b86..aa6b451d 100644 --- a/Modules/Authentication/Basic/BasicAuthenticationUser.cs +++ b/Modules/Authentication/Basic/BasicAuthenticationUser.cs @@ -1,13 +1,10 @@ using GenHTTP.Api.Content.Authentication; -namespace GenHTTP.Modules.Authentication.Basic -{ - - public record BasicAuthenticationUser(string Name) : IUser - { +namespace GenHTTP.Modules.Authentication.Basic; - public string DisplayName => Name; +public record BasicAuthenticationUser(string Name) : IUser +{ - } + public string DisplayName => Name; } diff --git a/Modules/Authentication/BasicAuthentication.cs b/Modules/Authentication/BasicAuthentication.cs index a69e6a5f..20524b1d 100644 --- a/Modules/Authentication/BasicAuthentication.cs +++ b/Modules/Authentication/BasicAuthentication.cs @@ -6,82 +6,79 @@ using GenHTTP.Modules.Authentication.Basic; -namespace GenHTTP.Modules.Authentication +namespace GenHTTP.Modules.Authentication; + +/// +/// Allows basic authentication to be added to a handler instance +/// as a concern. +/// +public static class BasicAuthentication { - + private const string DEFAULT_REALM = "Restricted Area"; + + #region Builder + /// - /// Allows basic authentication to be added to a handler instance - /// as a concern. + /// Creates a basic authentication concern that will use the + /// given lambda to check, whether an user is allowed to + /// access the restricted area. /// - public static class BasicAuthentication + /// The lambda to be evaluated + /// The name of the realm returned to the client + /// The newly created basic authentication concern + public static BasicAuthenticationConcernBuilder Create(Func> authenticator, string realm = DEFAULT_REALM) { - private const string DEFAULT_REALM = "Restricted Area"; - - #region Builder - - /// - /// Creates a basic authentication concern that will use the - /// given lambda to check, whether an user is allowed to - /// access the restricted area. - /// - /// The lambda to be evaluated - /// The name of the realm returned to the client - /// The newly created basic authentication concern - public static BasicAuthenticationConcernBuilder Create(Func> authenticator, string realm = DEFAULT_REALM) - { return new BasicAuthenticationConcernBuilder().Handler(authenticator) .Realm(realm); } - /// - /// Creates a basic authentication concern that stores credentials in - /// memory and can be used for quick development purposes. - /// - /// The name of the realm returned to the client - /// The newly created basic authentication concern - public static BasicAuthenticationKnownUsersBuilder Create(string realm = DEFAULT_REALM) - { + /// + /// Creates a basic authentication concern that stores credentials in + /// memory and can be used for quick development purposes. + /// + /// The name of the realm returned to the client + /// The newly created basic authentication concern + public static BasicAuthenticationKnownUsersBuilder Create(string realm = DEFAULT_REALM) + { return new BasicAuthenticationKnownUsersBuilder().Realm(realm); } - #endregion + #endregion - #region Extensions + #region Extensions - /// - /// Adds basic authentication to the handler using the given lambda - /// to check, whether users are allowed to access the restricted area. - /// - /// The lambda to be evaluated on request - /// The name of the realm to be returned to the client - public static T Authentication(this T builder, Func> authenticator, string realm = DEFAULT_REALM) where T : IHandlerBuilder - { + /// + /// Adds basic authentication to the handler using the given lambda + /// to check, whether users are allowed to access the restricted area. + /// + /// The lambda to be evaluated on request + /// The name of the realm to be returned to the client + public static T Authentication(this T builder, Func> authenticator, string realm = DEFAULT_REALM) where T : IHandlerBuilder + { builder.Add(Create(authenticator, realm)); return builder; } - /// - /// Adds basic authentication to the handler using the specified concern instance. - /// - /// The pre-configured concern instance to be used - public static T Authentication(this T builder, BasicAuthenticationConcernBuilder basicAuth) where T : IHandlerBuilder - { + /// + /// Adds basic authentication to the handler using the specified concern instance. + /// + /// The pre-configured concern instance to be used + public static T Authentication(this T builder, BasicAuthenticationConcernBuilder basicAuth) where T : IHandlerBuilder + { builder.Add(basicAuth); return builder; } - /// - /// Adds basic authentication to the handler using the specified concern instance. - /// - /// The pre-configured concern instance to be used - public static T Authentication(this T builder, BasicAuthenticationKnownUsersBuilder basicAuth) where T : IHandlerBuilder - { + /// + /// Adds basic authentication to the handler using the specified concern instance. + /// + /// The pre-configured concern instance to be used + public static T Authentication(this T builder, BasicAuthenticationKnownUsersBuilder basicAuth) where T : IHandlerBuilder + { builder.Add(basicAuth); return builder; } - #endregion - - } + #endregion } diff --git a/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs b/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs index 28e87efb..af1150c4 100644 --- a/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs +++ b/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs @@ -12,53 +12,52 @@ using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; -namespace GenHTTP.Modules.Authentication.Bearer -{ +namespace GenHTTP.Modules.Authentication.Bearer; - #region Supporting data structures +#region Supporting data structures - internal class OpenIDConfiguration - { +internal class OpenIDConfiguration +{ - [JsonPropertyName("jwks_uri")] - public string? KeySetUrl { get; set; } + [JsonPropertyName("jwks_uri")] + public string? KeySetUrl { get; set; } - } +} - #endregion +#endregion - internal sealed class BearerAuthenticationConcern : IConcern - { - private ICollection? _IssuerKeys = null; +internal sealed class BearerAuthenticationConcern : IConcern +{ + private ICollection? _IssuerKeys = null; - #region Get-/Setters + #region Get-/Setters - public IHandler Content { get; } + public IHandler Content { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - private TokenValidationOptions ValidationOptions { get; } + private TokenValidationOptions ValidationOptions { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal BearerAuthenticationConcern(IHandler parent, Func contentFactory, TokenValidationOptions validationOptions) - { + internal BearerAuthenticationConcern(IHandler parent, Func contentFactory, TokenValidationOptions validationOptions) + { Parent = parent; Content = contentFactory(this); ValidationOptions = validationOptions; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask PrepareAsync() => Content.PrepareAsync(); + public ValueTask PrepareAsync() => Content.PrepareAsync(); - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { IdentityModelEventSource.LogCompleteSecurityArtifact = true; if (!request.Headers.TryGetValue("Authorization", out var authHeader)) @@ -133,8 +132,8 @@ internal BearerAuthenticationConcern(IHandler parent, Func c } } - private static async Task> FetchSigningKeys(string issuer) - { + private static async Task> FetchSigningKeys(string issuer) + { try { var configUrl = $"{issuer}/.well-known/openid-configuration"; @@ -158,8 +157,6 @@ private static async Task> FetchSigningKeys(string issu } } - } - - #endregion - } + +#endregion diff --git a/Modules/Authentication/Bearer/BearerAuthenticationConcernBuilder.cs b/Modules/Authentication/Bearer/BearerAuthenticationConcernBuilder.cs index cdb1b9ef..423e73a2 100644 --- a/Modules/Authentication/Bearer/BearerAuthenticationConcernBuilder.cs +++ b/Modules/Authentication/Bearer/BearerAuthenticationConcernBuilder.cs @@ -6,93 +6,90 @@ using GenHTTP.Api.Content.Authentication; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Authentication.Bearer -{ +namespace GenHTTP.Modules.Authentication.Bearer; - public sealed class BearerAuthenticationConcernBuilder : IConcernBuilder - { - private readonly TokenValidationOptions _Options = new(); +public sealed class BearerAuthenticationConcernBuilder : IConcernBuilder +{ + private readonly TokenValidationOptions _Options = new(); - #region Functionality + #region Functionality - /// - /// Sets the expected issuer. Tokens that are not issued by this - /// party will be declined. - /// - /// The URL of the exepcted issuer - /// - /// Setting the issuer will cause the concern to download and cache - /// the signing keys that are used to ensure that the party actually - /// issued a token. - /// - public BearerAuthenticationConcernBuilder Issuer(string issuer) - { + /// + /// Sets the expected issuer. Tokens that are not issued by this + /// party will be declined. + /// + /// The URL of the exepcted issuer + /// + /// Setting the issuer will cause the concern to download and cache + /// the signing keys that are used to ensure that the party actually + /// issued a token. + /// + public BearerAuthenticationConcernBuilder Issuer(string issuer) + { _Options.Issuer = issuer; return this; } - /// - /// Sets the expected audience that should be accepted. - /// - /// The audience to check for - public BearerAuthenticationConcernBuilder Audience(string audience) - { + /// + /// Sets the expected audience that should be accepted. + /// + /// The audience to check for + public BearerAuthenticationConcernBuilder Audience(string audience) + { _Options.Audience = audience; return this; } - /// - /// Adds a custom validator that can analyze the token read from the - /// request and can perform additional checks. - /// - /// The custom validator to be used - /// - /// This validator will be invoked after the regular checks (such as the - /// issuer) have been performed. - /// - /// If you would like to deny user access within a custom validator, - /// you can throw a . - /// - public BearerAuthenticationConcernBuilder Validation(Func validator) - { + /// + /// Adds a custom validator that can analyze the token read from the + /// request and can perform additional checks. + /// + /// The custom validator to be used + /// + /// This validator will be invoked after the regular checks (such as the + /// issuer) have been performed. + /// + /// If you would like to deny user access within a custom validator, + /// you can throw a . + /// + public BearerAuthenticationConcernBuilder Validation(Func validator) + { _Options.CustomValidator = validator; return this; } - /// - /// Optionally register a function that will compute the user that - /// should be set for the request. - /// - /// The user mapping to be used - /// - /// The usage of this mechanism allows to inject the user into - /// service methods via the user injector class. Returning null - /// within the delegate will not deny user access - if you would - /// like to prevent such user, you can throw a . - /// - public BearerAuthenticationConcernBuilder UserMapping(Func> userMapping) - { + /// + /// Optionally register a function that will compute the user that + /// should be set for the request. + /// + /// The user mapping to be used + /// + /// The usage of this mechanism allows to inject the user into + /// service methods via the user injector class. Returning null + /// within the delegate will not deny user access - if you would + /// like to prevent such user, you can throw a . + /// + public BearerAuthenticationConcernBuilder UserMapping(Func> userMapping) + { _Options.UserMapping = userMapping; return this; } - /// - /// If enabled, tokens that have expired or are not valid yet are - /// still accepted. This should be used for testing purposes only. - /// - public BearerAuthenticationConcernBuilder AllowExpired() - { + /// + /// If enabled, tokens that have expired or are not valid yet are + /// still accepted. This should be used for testing purposes only. + /// + public BearerAuthenticationConcernBuilder AllowExpired() + { _Options.Lifetime = false; return this; } - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { return new BearerAuthenticationConcern(parent, contentFactory, _Options); } - #endregion - - } + #endregion } diff --git a/Modules/Authentication/Bearer/TokenValidationOptions.cs b/Modules/Authentication/Bearer/TokenValidationOptions.cs index d1aab100..99015803 100644 --- a/Modules/Authentication/Bearer/TokenValidationOptions.cs +++ b/Modules/Authentication/Bearer/TokenValidationOptions.cs @@ -5,22 +5,19 @@ using GenHTTP.Api.Content.Authentication; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Authentication.Bearer -{ - - internal sealed class TokenValidationOptions - { +namespace GenHTTP.Modules.Authentication.Bearer; - internal string? Audience { get; set; } +internal sealed class TokenValidationOptions +{ - internal string? Issuer { get; set; } + internal string? Audience { get; set; } - internal bool Lifetime { get; set; } = true; + internal string? Issuer { get; set; } - internal Func? CustomValidator { get; set; } + internal bool Lifetime { get; set; } = true; - internal Func>? UserMapping { get; set; } + internal Func? CustomValidator { get; set; } - } + internal Func>? UserMapping { get; set; } -} +} \ No newline at end of file diff --git a/Modules/Authentication/BearerAuthentication.cs b/Modules/Authentication/BearerAuthentication.cs index 1a92a6dd..6c364378 100644 --- a/Modules/Authentication/BearerAuthentication.cs +++ b/Modules/Authentication/BearerAuthentication.cs @@ -1,19 +1,16 @@ using GenHTTP.Modules.Authentication.Bearer; -namespace GenHTTP.Modules.Authentication -{ - - public static class BearerAuthentication - { +namespace GenHTTP.Modules.Authentication; - /// - /// Creates a concern that will read an access token from - /// the authorization headers and validate it according to - /// its configuration. - /// - /// The newly created concern - public static BearerAuthenticationConcernBuilder Create() => new(); +public static class BearerAuthentication +{ - } + /// + /// Creates a concern that will read an access token from + /// the authorization headers and validate it according to + /// its configuration. + /// + /// The newly created concern + public static BearerAuthenticationConcernBuilder Create() => new(); } diff --git a/Modules/Authentication/Extensions.cs b/Modules/Authentication/Extensions.cs index 1964c2d2..b620044e 100644 --- a/Modules/Authentication/Extensions.cs +++ b/Modules/Authentication/Extensions.cs @@ -1,20 +1,19 @@ using GenHTTP.Api.Content.Authentication; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Authentication +namespace GenHTTP.Modules.Authentication; + +public static class Extensions { + private const string USER_PROPERTY = "__AUTH_USER"; - public static class Extensions + public static void SetUser(this IRequest request, IUser user) { - private const string USER_PROPERTY = "__AUTH_USER"; - - public static void SetUser(this IRequest request, IUser user) - { request.Properties[USER_PROPERTY] = user; } - public static T? GetUser(this IRequest request) where T : class, IUser - { + public static T? GetUser(this IRequest request) where T : class, IUser + { if (request.Properties.TryGet(USER_PROPERTY, out var user)) { return user; @@ -23,11 +22,9 @@ public static void SetUser(this IRequest request, IUser user) return null; } - public static void ClearUser(this IRequest request) - { + public static void ClearUser(this IRequest request) + { request.Properties.Clear(USER_PROPERTY); } - } - } diff --git a/Modules/Basics/CoreExtensions.cs b/Modules/Basics/CoreExtensions.cs index 0dae84fb..b3cceb22 100644 --- a/Modules/Basics/CoreExtensions.cs +++ b/Modules/Basics/CoreExtensions.cs @@ -8,16 +8,15 @@ using GenHTTP.Api.Protocol; using GenHTTP.Api.Routing; -namespace GenHTTP.Modules.Basics -{ +namespace GenHTTP.Modules.Basics; - public static class CoreExtensions - { +public static class CoreExtensions +{ - #region Request + #region Request - public static bool HasType(this IRequest request, params RequestMethod[] methods) - { + public static bool HasType(this IRequest request, params RequestMethod[] methods) + { foreach (var method in methods) { if (request.Method == method) @@ -29,8 +28,8 @@ public static bool HasType(this IRequest request, params RequestMethod[] methods return false; } - public static string? HostWithoutPort(this IRequest request) - { + public static string? HostWithoutPort(this IRequest request) + { var host = request.Host; if (host is not null) @@ -50,92 +49,92 @@ public static bool HasType(this IRequest request, params RequestMethod[] methods return null; } - #endregion - - #region Response builder - - /// - /// Specifies the content type of this response. - /// - /// The content type of this response - public static IResponseBuilder Type(this IResponseBuilder builder, ContentType contentType) => builder.Type(FlexibleContentType.Get(contentType)); - - /// - /// Specifies the content type of this response. - /// - /// The content type of this response - public static IResponseBuilder Type(this IResponseBuilder builder, string contentType) => builder.Type(FlexibleContentType.Parse(contentType)); - - #endregion - - #region Content types - - private static readonly Dictionary CONTENT_TYPES = new() { - // CSS - { "css", ContentType.TextCss }, - // HTML - { "html", ContentType.TextHtml }, - { "htm", ContentType.TextHtml }, - // Text files - { "txt", ContentType.TextPlain }, - { "conf", ContentType.TextPlain }, - { "config", ContentType.TextPlain }, - // Fonts - { "eot", ContentType.FontEmbeddedOpenTypeFont }, - { "ttf", ContentType.FontTrueTypeFont }, - { "otf", ContentType.FontOpenTypeFont }, - { "woff", ContentType.FontWoff }, - { "woff2", ContentType.FontWoff2 }, - // Scripts - { "js", ContentType.ApplicationJavaScript }, - { "mjs", ContentType.ApplicationJavaScript }, - // Images - { "ico", ContentType.ImageIcon }, - { "gif", ContentType.ImageGif }, - { "jpeg", ContentType.ImageJpg }, - { "jpg", ContentType.ImageJpg }, - { "png", ContentType.ImagePng }, - { "bmp", ContentType.ImageBmp }, - { "tiff", ContentType.ImageTiff }, - { "svg", ContentType.ImageScalableVectorGraphicsXml }, - { "svgz", ContentType.ImageScalableVectorGraphicsCompressed }, - // Audio - { "ogg", ContentType.AudioOgg }, - { "mp3", ContentType.AudioMpeg }, - { "wav", ContentType.AudioWav }, - // Video - { "avi", ContentType.VideoMpeg }, - { "3gp", ContentType.Video3Gpp }, - { "3g2", ContentType.Video3Gpp2 }, - { "av1", ContentType.VideoAV1 }, - { "avc", ContentType.VideoAvc }, - { "dv", ContentType.VideoDV }, - { "mkv", ContentType.VideoMatroska }, - { "mk3d", ContentType.VideoMatroska3D }, - { "mj2", ContentType.VideoMJ2 }, - { "mpg", ContentType.VideoMpeg }, - { "mp4", ContentType.VideoMP4 }, - { "mpeg", ContentType.VideoMpeg }, - { "mpv", ContentType.VideoMpv }, - { "mov", ContentType.VideoQuicktime }, - { "hdmov", ContentType.VideoQuicktime }, - { "vc1", ContentType.VideoVC1 }, - { "vc2", ContentType.VideoVC2 }, - { "webm", ContentType.VideoWebM }, - // Documents - { "csv", ContentType.TextCsv }, - { "rtf", ContentType.TextRichText }, - { "docx", ContentType.ApplicationOfficeDocumentWordProcessing }, - { "pptx", ContentType.ApplicationOfficeDocumentPresentation }, - { "ppsx", ContentType.ApplicationOfficeDocumentSlideshow }, - { "xslx", ContentType.ApplicationOfficeDocumentSheet }, - // Object models - { "json", ContentType.ApplicationJson }, - { "xml", ContentType.TextXml } - }; - - public static ContentType? GuessContentType(this string fileName) - { + #endregion + + #region Response builder + + /// + /// Specifies the content type of this response. + /// + /// The content type of this response + public static IResponseBuilder Type(this IResponseBuilder builder, ContentType contentType) => builder.Type(FlexibleContentType.Get(contentType)); + + /// + /// Specifies the content type of this response. + /// + /// The content type of this response + public static IResponseBuilder Type(this IResponseBuilder builder, string contentType) => builder.Type(FlexibleContentType.Parse(contentType)); + + #endregion + + #region Content types + + private static readonly Dictionary CONTENT_TYPES = new() { + // CSS + { "css", ContentType.TextCss }, + // HTML + { "html", ContentType.TextHtml }, + { "htm", ContentType.TextHtml }, + // Text files + { "txt", ContentType.TextPlain }, + { "conf", ContentType.TextPlain }, + { "config", ContentType.TextPlain }, + // Fonts + { "eot", ContentType.FontEmbeddedOpenTypeFont }, + { "ttf", ContentType.FontTrueTypeFont }, + { "otf", ContentType.FontOpenTypeFont }, + { "woff", ContentType.FontWoff }, + { "woff2", ContentType.FontWoff2 }, + // Scripts + { "js", ContentType.ApplicationJavaScript }, + { "mjs", ContentType.ApplicationJavaScript }, + // Images + { "ico", ContentType.ImageIcon }, + { "gif", ContentType.ImageGif }, + { "jpeg", ContentType.ImageJpg }, + { "jpg", ContentType.ImageJpg }, + { "png", ContentType.ImagePng }, + { "bmp", ContentType.ImageBmp }, + { "tiff", ContentType.ImageTiff }, + { "svg", ContentType.ImageScalableVectorGraphicsXml }, + { "svgz", ContentType.ImageScalableVectorGraphicsCompressed }, + // Audio + { "ogg", ContentType.AudioOgg }, + { "mp3", ContentType.AudioMpeg }, + { "wav", ContentType.AudioWav }, + // Video + { "avi", ContentType.VideoMpeg }, + { "3gp", ContentType.Video3Gpp }, + { "3g2", ContentType.Video3Gpp2 }, + { "av1", ContentType.VideoAV1 }, + { "avc", ContentType.VideoAvc }, + { "dv", ContentType.VideoDV }, + { "mkv", ContentType.VideoMatroska }, + { "mk3d", ContentType.VideoMatroska3D }, + { "mj2", ContentType.VideoMJ2 }, + { "mpg", ContentType.VideoMpeg }, + { "mp4", ContentType.VideoMP4 }, + { "mpeg", ContentType.VideoMpeg }, + { "mpv", ContentType.VideoMpv }, + { "mov", ContentType.VideoQuicktime }, + { "hdmov", ContentType.VideoQuicktime }, + { "vc1", ContentType.VideoVC1 }, + { "vc2", ContentType.VideoVC2 }, + { "webm", ContentType.VideoWebM }, + // Documents + { "csv", ContentType.TextCsv }, + { "rtf", ContentType.TextRichText }, + { "docx", ContentType.ApplicationOfficeDocumentWordProcessing }, + { "pptx", ContentType.ApplicationOfficeDocumentPresentation }, + { "ppsx", ContentType.ApplicationOfficeDocumentSlideshow }, + { "xslx", ContentType.ApplicationOfficeDocumentSheet }, + // Object models + { "json", ContentType.ApplicationJson }, + { "xml", ContentType.TextXml } + }; + + public static ContentType? GuessContentType(this string fileName) + { var extension = Path.GetExtension(fileName); if (extension is not null && extension.Length > 1) @@ -151,23 +150,23 @@ public static bool HasType(this IRequest request, params RequestMethod[] methods return null; } - #endregion + #endregion - #region Resource provider + #region Resource provider - public static async ValueTask GetResourceAsStringAsync(this IResource resourceProvider) - { + public static async ValueTask GetResourceAsStringAsync(this IResource resourceProvider) + { using var stream = await resourceProvider.GetContentAsync(); return await new StreamReader(stream).ReadToEndAsync(); } - #endregion + #endregion - #region Routing + #region Routing - public static string RelativeTo(this WebPath path, WebPath target) - { + public static string RelativeTo(this WebPath path, WebPath target) + { var common = CommonParts(path, target); var hops = path.Parts.Count - common + (path.TrailingSlash ? 1 : 0) - 1; @@ -199,8 +198,8 @@ public static string RelativeTo(this WebPath path, WebPath target) return new WebPath(relativeParts, trailing).ToString()[1..]; } - public static WebPath Combine(this WebPath path, WebPath target) - { + public static WebPath Combine(this WebPath path, WebPath target) + { var parts = new List(path.Parts); if (target.Parts.Count > 0) @@ -238,8 +237,8 @@ public static WebPath Combine(this WebPath path, WebPath target) return new WebPath(parts, target.TrailingSlash); } - private static int CommonParts(WebPath one, WebPath two) - { + private static int CommonParts(WebPath one, WebPath two) + { int common; for (common = 0; common < one.Parts.Count; common++) @@ -258,8 +257,6 @@ private static int CommonParts(WebPath one, WebPath two) return common; } - #endregion - - } + #endregion } diff --git a/Modules/Basics/Providers/RedirectProvider.cs b/Modules/Basics/Providers/RedirectProvider.cs index d14e91f7..2e567724 100644 --- a/Modules/Basics/Providers/RedirectProvider.cs +++ b/Modules/Basics/Providers/RedirectProvider.cs @@ -4,45 +4,44 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Basics.Providers -{ +namespace GenHTTP.Modules.Basics.Providers; - public sealed partial class RedirectProvider : IHandler - { - private static readonly Regex PROTOCOL_MATCHER = CreateProtocolMatcher(); +public sealed partial class RedirectProvider : IHandler +{ + private static readonly Regex PROTOCOL_MATCHER = CreateProtocolMatcher(); - #region Get-/Setters + #region Get-/Setters - public string Target { get; } + public string Target { get; } - public bool Temporary { get; } + public bool Temporary { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public RedirectProvider(IHandler parent, string location, bool temporary) - { + public RedirectProvider(IHandler parent, string location, bool temporary) + { Parent = parent; Target = location; Temporary = temporary; } - [GeneratedRegex("^[a-z_-]+://", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] - private static partial Regex CreateProtocolMatcher(); + [GeneratedRegex("^[a-z_-]+://", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] + private static partial Regex CreateProtocolMatcher(); - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { var resolved = ResolveRoute(request, Target); var response = request.Respond() @@ -53,8 +52,8 @@ public RedirectProvider(IHandler parent, string location, bool temporary) return new ValueTask(response.Status(status).Build()); } - private static string ResolveRoute(IRequest request, string route) - { + private static string ResolveRoute(IRequest request, string route) + { if (PROTOCOL_MATCHER.IsMatch(route)) { return route; @@ -65,8 +64,8 @@ private static string ResolveRoute(IRequest request, string route) return $"{protocol}{request.Host}{route}"; } - private static ResponseStatus MapStatus(IRequest request, bool temporary) - { + private static ResponseStatus MapStatus(IRequest request, bool temporary) + { if (request.HasType(RequestMethod.GET, RequestMethod.HEAD)) { return (temporary) ? ResponseStatus.TemporaryRedirect : ResponseStatus.MovedPermanently; @@ -77,8 +76,6 @@ private static ResponseStatus MapStatus(IRequest request, bool temporary) } } - #endregion - - } + #endregion } diff --git a/Modules/Basics/Providers/RedirectProviderBuilder.cs b/Modules/Basics/Providers/RedirectProviderBuilder.cs index b7ded303..261b9955 100644 --- a/Modules/Basics/Providers/RedirectProviderBuilder.cs +++ b/Modules/Basics/Providers/RedirectProviderBuilder.cs @@ -3,39 +3,38 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Content; -namespace GenHTTP.Modules.Basics.Providers -{ +namespace GenHTTP.Modules.Basics.Providers; - public sealed class RedirectProviderBuilder : IHandlerBuilder - { - private bool _Temporary = false; +public sealed class RedirectProviderBuilder : IHandlerBuilder +{ + private bool _Temporary = false; - private string? _Location; + private string? _Location; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - #region Functionality + #region Functionality - public RedirectProviderBuilder Location(string location) - { + public RedirectProviderBuilder Location(string location) + { _Location = location; return this; } - public RedirectProviderBuilder Mode(bool temporary) - { + public RedirectProviderBuilder Mode(bool temporary) + { _Temporary = temporary; return this; } - public RedirectProviderBuilder Add(IConcernBuilder concern) - { + public RedirectProviderBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { if (_Location is null) { throw new BuilderMissingPropertyException("Location"); @@ -44,8 +43,6 @@ public IHandler Build(IHandler parent) return Concerns.Chain(parent, _Concerns, (p) => new RedirectProvider(p, _Location, _Temporary)); } - #endregion - - } + #endregion } diff --git a/Modules/Basics/Redirect.cs b/Modules/Basics/Redirect.cs index 95b33e5d..e64f84b0 100644 --- a/Modules/Basics/Redirect.cs +++ b/Modules/Basics/Redirect.cs @@ -1,25 +1,22 @@ using GenHTTP.Modules.Basics.Providers; -namespace GenHTTP.Modules.Basics -{ - - public static class Redirect - { +namespace GenHTTP.Modules.Basics; - /// - /// Redirects the requesting client to the specified location. - /// - /// The location to redirect the client to (an absolute or relative route) - /// false, if the client should remember this redirection - /// - /// - /// Redirect.To("https://genhttp.org", true); - /// Redirect.To("some/other/site", false); - /// Redirect.To("{sitemap}", true); - /// - /// - public static RedirectProviderBuilder To(string location, bool temporary = false) => new RedirectProviderBuilder().Location(location) - .Mode(temporary); - } +public static class Redirect +{ + /// + /// Redirects the requesting client to the specified location. + /// + /// The location to redirect the client to (an absolute or relative route) + /// false, if the client should remember this redirection + /// + /// + /// Redirect.To("https://genhttp.org", true); + /// Redirect.To("some/other/site", false); + /// Redirect.To("{sitemap}", true); + /// + /// + public static RedirectProviderBuilder To(string location, bool temporary = false) => new RedirectProviderBuilder().Location(location) + .Mode(temporary); } diff --git a/Modules/Caching/Cache.cs b/Modules/Caching/Cache.cs index 09ecf044..d5eca70c 100644 --- a/Modules/Caching/Cache.cs +++ b/Modules/Caching/Cache.cs @@ -4,21 +4,20 @@ using GenHTTP.Modules.Caching.FileSystem; using GenHTTP.Modules.Caching.Memory; -namespace GenHTTP.Modules.Caching +namespace GenHTTP.Modules.Caching; + +/// +/// Creates caches with different kind of backends. +/// +public static class Cache { /// - /// Creates caches with different kind of backends. + /// A cache that will store all data in memory. /// - public static class Cache + /// The type of the entries to be stored + public static IMemoryCacheBuilder Memory() { - - /// - /// A cache that will store all data in memory. - /// - /// The type of the entries to be stored - public static IMemoryCacheBuilder Memory() - { if (typeof(Stream).IsAssignableFrom(typeof(T))) { return new StreamMemoryCacheBuilder(); @@ -29,30 +28,28 @@ public static IMemoryCacheBuilder Memory() } } - /// - /// A cache that will store all data in the specified directory. - /// - /// The type of the entries to be stored - /// The directory to store the entries in - public static FileSystemCacheBuilder FileSystem(DirectoryInfo directory) - => new FileSystemCacheBuilder().Directory(directory); - - /// - /// A cache that will store all data in the specified directory. - /// - /// The type of the entries to be stored - /// The directory to store the entries in - public static FileSystemCacheBuilder FileSystem(string directory) - => FileSystem(new DirectoryInfo(directory)); - - /// - /// A cache that will use files placed in the temp directory of the - /// operating system to store entries. - /// - /// The type of the entries to be stored - public static FileSystemCacheBuilder TemporaryFiles() - => FileSystem(new DirectoryInfo(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()))); - - } + /// + /// A cache that will store all data in the specified directory. + /// + /// The type of the entries to be stored + /// The directory to store the entries in + public static FileSystemCacheBuilder FileSystem(DirectoryInfo directory) + => new FileSystemCacheBuilder().Directory(directory); + + /// + /// A cache that will store all data in the specified directory. + /// + /// The type of the entries to be stored + /// The directory to store the entries in + public static FileSystemCacheBuilder FileSystem(string directory) + => FileSystem(new DirectoryInfo(directory)); + + /// + /// A cache that will use files placed in the temp directory of the + /// operating system to store entries. + /// + /// The type of the entries to be stored + public static FileSystemCacheBuilder TemporaryFiles() + => FileSystem(new DirectoryInfo(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()))); } diff --git a/Modules/Caching/FileSystem/FileSystemCache.cs b/Modules/Caching/FileSystem/FileSystemCache.cs index 2eadddbd..3b9a0200 100644 --- a/Modules/Caching/FileSystem/FileSystemCache.cs +++ b/Modules/Caching/FileSystem/FileSystemCache.cs @@ -8,48 +8,47 @@ using GenHTTP.Api.Content.Caching; -namespace GenHTTP.Modules.Caching.FileSystem -{ +namespace GenHTTP.Modules.Caching.FileSystem; - public sealed class FileSystemCache : ICache +public sealed class FileSystemCache : ICache +{ + private static readonly JsonSerializerOptions _Options = new() { - private static readonly JsonSerializerOptions _Options = new() - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; - private readonly SemaphoreSlim _Sync = new(1); + private readonly SemaphoreSlim _Sync = new(1); - #region Supporting data structures + #region Supporting data structures - internal record Index(Dictionary Entries, Dictionary Expiration); + internal record Index(Dictionary Entries, Dictionary Expiration); - #endregion + #endregion - #region Get-/Setters + #region Get-/Setters - public DirectoryInfo Directory { get; } + public DirectoryInfo Directory { get; } - public TimeSpan AccessExpiration { get; } + public TimeSpan AccessExpiration { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public FileSystemCache(DirectoryInfo directory, TimeSpan accessExpiration) - { + public FileSystemCache(DirectoryInfo directory, TimeSpan accessExpiration) + { Directory = directory; AccessExpiration = accessExpiration; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask GetEntriesAsync(string key) - { + public async ValueTask GetEntriesAsync(string key) + { await _Sync.WaitAsync(); try @@ -76,8 +75,8 @@ public async ValueTask GetEntriesAsync(string key) } } - public async ValueTask GetEntryAsync(string key, string variation) - { + public async ValueTask GetEntryAsync(string key, string variation) + { await _Sync.WaitAsync(); try @@ -97,8 +96,8 @@ public async ValueTask GetEntriesAsync(string key) } } - public async ValueTask StoreAsync(string key, string variation, T? entry) - { + public async ValueTask StoreAsync(string key, string variation, T? entry) + { await _Sync.WaitAsync(); try @@ -144,8 +143,8 @@ public async ValueTask StoreAsync(string key, string variation, T? entry) } } - public async ValueTask StoreDirectAsync(string key, string variation, Func asyncWriter) - { + public async ValueTask StoreDirectAsync(string key, string variation, Func asyncWriter) + { await _Sync.WaitAsync(); try @@ -193,8 +192,8 @@ public async ValueTask StoreDirectAsync(string key, string variation, Func GetValue(string key, string fileName) - { + private async ValueTask GetValue(string key, string fileName) + { var file = new FileInfo(Path.Combine(Directory.FullName, key, fileName)); if (file.Exists) @@ -214,12 +213,12 @@ public async ValueTask StoreDirectAsync(string key, string variation, Func GetIndex(string key) - { + private async ValueTask GetIndex(string key) + { var indexFile = new FileInfo(Path.Combine(Directory.FullName, key, "index.json")); if (indexFile.Exists) @@ -247,8 +246,8 @@ private async ValueTask GetIndex(string key) return new Index(new(), new()); } - private async ValueTask StoreIndex(string key, Index index) - { + private async ValueTask StoreIndex(string key, Index index) + { RunHouseKeeping(key, index); var indexFile = new FileInfo(Path.Combine(Directory.FullName, key, "index.json")); @@ -261,8 +260,8 @@ private async ValueTask StoreIndex(string key, Index index) indexFile.Refresh(); } - private void RunHouseKeeping(string key, Index index) - { + private void RunHouseKeeping(string key, Index index) + { var toDelete = new List(); foreach (var entry in index.Expiration) @@ -288,8 +287,8 @@ private void RunHouseKeeping(string key, Index index) } } - private void Remove(string key, string fileName) - { + private void Remove(string key, string fileName) + { var file = new FileInfo(Path.Combine(Directory.FullName, key, fileName)); if (file.Exists) @@ -298,8 +297,8 @@ private void Remove(string key, string fileName) } } - private void EnsureDirectory(string key) - { + private void EnsureDirectory(string key) + { var subPath = new DirectoryInfo(Path.Combine(Directory.FullName, key)); if (!subPath.Exists) @@ -308,8 +307,6 @@ private void EnsureDirectory(string key) } } - #endregion - - } + #endregion } diff --git a/Modules/Caching/FileSystem/FileSystemCacheBuilder.cs b/Modules/Caching/FileSystem/FileSystemCacheBuilder.cs index b421f185..f4e88e4b 100644 --- a/Modules/Caching/FileSystem/FileSystemCacheBuilder.cs +++ b/Modules/Caching/FileSystem/FileSystemCacheBuilder.cs @@ -3,41 +3,40 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.Caching.FileSystem -{ +namespace GenHTTP.Modules.Caching.FileSystem; - public class FileSystemCacheBuilder : IBuilder> - { - private DirectoryInfo? _Directory; +public class FileSystemCacheBuilder : IBuilder> +{ + private DirectoryInfo? _Directory; - private TimeSpan _AccessExpiration = TimeSpan.FromMinutes(30); + private TimeSpan _AccessExpiration = TimeSpan.FromMinutes(30); - #region Functionality + #region Functionality - public FileSystemCacheBuilder Directory(DirectoryInfo directory) - { + public FileSystemCacheBuilder Directory(DirectoryInfo directory) + { _Directory = directory; return this; } - /// - /// Sets the duration old files will be kept to allow clients to finish - /// their downloads. - /// - /// - /// - /// - /// Defaults to 30 minutes. If you serve very large files or your clients - /// download very slow, consider to increase this value.. - /// - public FileSystemCacheBuilder AccessExpiration(TimeSpan expiration) - { + /// + /// Sets the duration old files will be kept to allow clients to finish + /// their downloads. + /// + /// + /// + /// + /// Defaults to 30 minutes. If you serve very large files or your clients + /// download very slow, consider to increase this value.. + /// + public FileSystemCacheBuilder AccessExpiration(TimeSpan expiration) + { _AccessExpiration = expiration; return this; } - public FileSystemCache Build() - { + public FileSystemCache Build() + { var directory = _Directory ?? throw new BuilderMissingPropertyException("Directory"); if (!directory.Exists) @@ -48,8 +47,6 @@ public FileSystemCache Build() return new FileSystemCache(directory, _AccessExpiration); } - #endregion - - } + #endregion } diff --git a/Modules/Caching/Memory/IMemoryCacheBuilder.cs b/Modules/Caching/Memory/IMemoryCacheBuilder.cs index 3bfb273c..7b1d4a31 100644 --- a/Modules/Caching/Memory/IMemoryCacheBuilder.cs +++ b/Modules/Caching/Memory/IMemoryCacheBuilder.cs @@ -1,13 +1,10 @@ using GenHTTP.Api.Content.Caching; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.Caching.Memory -{ - - public interface IMemoryCacheBuilder : IBuilder> - { +namespace GenHTTP.Modules.Caching.Memory; +public interface IMemoryCacheBuilder : IBuilder> +{ - } } diff --git a/Modules/Caching/Memory/MemoryCache.cs b/Modules/Caching/Memory/MemoryCache.cs index a56f8dc6..46d2d557 100644 --- a/Modules/Caching/Memory/MemoryCache.cs +++ b/Modules/Caching/Memory/MemoryCache.cs @@ -7,19 +7,18 @@ using GenHTTP.Api.Content.Caching; -namespace GenHTTP.Modules.Caching.Memory -{ +namespace GenHTTP.Modules.Caching.Memory; - public sealed class MemoryCache : ICache - { - private readonly Dictionary> _Cache = new(); +public sealed class MemoryCache : ICache +{ + private readonly Dictionary> _Cache = new(); - private readonly SemaphoreSlim _Sync = new(1); + private readonly SemaphoreSlim _Sync = new(1); - #region Functionality + #region Functionality - public ValueTask GetEntriesAsync(string key) - { + public ValueTask GetEntriesAsync(string key) + { _Sync.Wait(); try @@ -37,8 +36,8 @@ public ValueTask GetEntriesAsync(string key) } } - public ValueTask GetEntryAsync(string key, string variation) - { + public ValueTask GetEntryAsync(string key, string variation) + { _Sync.Wait(); try @@ -59,8 +58,8 @@ public ValueTask GetEntriesAsync(string key) } } - public ValueTask StoreAsync(string key, string variation, T? entry) - { + public ValueTask StoreAsync(string key, string variation, T? entry) + { _Sync.Wait(); try @@ -87,13 +86,11 @@ public ValueTask StoreAsync(string key, string variation, T? entry) } } - public ValueTask StoreDirectAsync(string key, string variation, Func asyncWriter) - { + public ValueTask StoreDirectAsync(string key, string variation, Func asyncWriter) + { throw new NotSupportedException("Direct storage is not supported by the memory cache"); } - #endregion - - } + #endregion } diff --git a/Modules/Caching/Memory/MemoryCacheBuilder.cs b/Modules/Caching/Memory/MemoryCacheBuilder.cs index 28e0e148..b65bdbfe 100644 --- a/Modules/Caching/Memory/MemoryCacheBuilder.cs +++ b/Modules/Caching/Memory/MemoryCacheBuilder.cs @@ -1,16 +1,13 @@ using GenHTTP.Api.Content.Caching; -namespace GenHTTP.Modules.Caching.Memory +namespace GenHTTP.Modules.Caching.Memory; + +public sealed class MemoryCacheBuilder : IMemoryCacheBuilder { - public sealed class MemoryCacheBuilder : IMemoryCacheBuilder + public ICache Build() { - - public ICache Build() - { return new MemoryCache(); } - } - } diff --git a/Modules/Caching/Memory/StreamMemoryCache.cs b/Modules/Caching/Memory/StreamMemoryCache.cs index ffc27f53..609e92ee 100644 --- a/Modules/Caching/Memory/StreamMemoryCache.cs +++ b/Modules/Caching/Memory/StreamMemoryCache.cs @@ -5,17 +5,16 @@ using GenHTTP.Api.Content.Caching; -namespace GenHTTP.Modules.Caching.Memory -{ +namespace GenHTTP.Modules.Caching.Memory; - public sealed class StreamMemoryCache : ICache - { - private readonly MemoryCache _Cache = new(); +public sealed class StreamMemoryCache : ICache +{ + private readonly MemoryCache _Cache = new(); - #region Functionality + #region Functionality - public async ValueTask GetEntriesAsync(string key) - { + public async ValueTask GetEntriesAsync(string key) + { var entries = await _Cache.GetEntriesAsync(key); var result = new List(entries.Length); @@ -28,8 +27,8 @@ public async ValueTask GetEntriesAsync(string key) return result.ToArray(); } - public async ValueTask GetEntryAsync(string key, string variation) - { + public async ValueTask GetEntryAsync(string key, string variation) + { var entry = await _Cache.GetEntryAsync(key, variation); if (entry != null) @@ -40,8 +39,8 @@ public async ValueTask GetEntriesAsync(string key) return null; } - public async ValueTask StoreAsync(string key, string variation, Stream? entry) - { + public async ValueTask StoreAsync(string key, string variation, Stream? entry) + { if (entry != null) { using var memoryStream = new MemoryStream(); @@ -56,8 +55,8 @@ public async ValueTask StoreAsync(string key, string variation, Stream? entry) } } - public async ValueTask StoreDirectAsync(string key, string variation, Func asyncWriter) - { + public async ValueTask StoreDirectAsync(string key, string variation, Func asyncWriter) + { using var memoryStream = new MemoryStream(); await asyncWriter(memoryStream); @@ -65,8 +64,6 @@ public async ValueTask StoreDirectAsync(string key, string variation, Func : IMemoryCacheBuilder { - public sealed class StreamMemoryCacheBuilder : IMemoryCacheBuilder + public ICache Build() { - - public ICache Build() - { return (ICache)(ICache)new StreamMemoryCache(); } - } - } diff --git a/Modules/ClientCaching/ClientCache.cs b/Modules/ClientCaching/ClientCache.cs index d6bfc55b..24bfa360 100644 --- a/Modules/ClientCaching/ClientCache.cs +++ b/Modules/ClientCaching/ClientCache.cs @@ -1,25 +1,22 @@ using GenHTTP.Modules.ClientCaching.Policy; using GenHTTP.Modules.ClientCaching.Validation; -namespace GenHTTP.Modules.ClientCaching -{ - - public static class ClientCache - { +namespace GenHTTP.Modules.ClientCaching; - /// - /// Creates a concern that will tag content generated by the server - /// so the clients can check, whether new content is available - /// on the server. - /// - public static CacheValidationBuilder Validation() => new(); +public static class ClientCache +{ - /// - /// Creates a policy defining how the client should cache - /// the content generated by the server. - /// - public static CachePolicyBuilder Policy() => new(); + /// + /// Creates a concern that will tag content generated by the server + /// so the clients can check, whether new content is available + /// on the server. + /// + public static CacheValidationBuilder Validation() => new(); - } + /// + /// Creates a policy defining how the client should cache + /// the content generated by the server. + /// + public static CachePolicyBuilder Policy() => new(); } diff --git a/Modules/ClientCaching/Extensions.cs b/Modules/ClientCaching/Extensions.cs index ddba8572..721c1aed 100644 --- a/Modules/ClientCaching/Extensions.cs +++ b/Modules/ClientCaching/Extensions.cs @@ -1,17 +1,14 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.ClientCaching +namespace GenHTTP.Modules.ClientCaching; + +public static class Extensions { - public static class Extensions + public static IServerHost ClientCaching(this IServerHost host) { - - public static IServerHost ClientCaching(this IServerHost host) - { host.Add(ClientCache.Validation()); return host; } - } - } diff --git a/Modules/ClientCaching/Policy/CachePolicyBuilder.cs b/Modules/ClientCaching/Policy/CachePolicyBuilder.cs index 6d0b4120..9e28ba48 100644 --- a/Modules/ClientCaching/Policy/CachePolicyBuilder.cs +++ b/Modules/ClientCaching/Policy/CachePolicyBuilder.cs @@ -4,55 +4,52 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.ClientCaching.Policy -{ +namespace GenHTTP.Modules.ClientCaching.Policy; - public sealed class CachePolicyBuilder : IConcernBuilder - { - private TimeSpan? _Duration = null; +public sealed class CachePolicyBuilder : IConcernBuilder +{ + private TimeSpan? _Duration = null; - private Func? _Predicate = null; + private Func? _Predicate = null; - #region Functionality + #region Functionality - /// - /// Instructs the client to cache the content generated - /// by the server for the given duration. - /// - /// The duration the content should be cached on the client - public CachePolicyBuilder Duration(TimeSpan duration) - { + /// + /// Instructs the client to cache the content generated + /// by the server for the given duration. + /// + /// The duration the content should be cached on the client + public CachePolicyBuilder Duration(TimeSpan duration) + { _Duration = duration; return this; } - /// - /// Instructs the client to cache the content generated - /// by the server for the given number of days. - /// - /// The number of days the content should be cached on the client - public CachePolicyBuilder Duration(int days) => Duration(TimeSpan.FromDays(days)); - - /// - /// Allows to filter the responses which should be cached - /// by the client. - /// - /// The predicate to be evaluated to check, whether content should be cached - public CachePolicyBuilder Predicate(Func predicate) - { + /// + /// Instructs the client to cache the content generated + /// by the server for the given number of days. + /// + /// The number of days the content should be cached on the client + public CachePolicyBuilder Duration(int days) => Duration(TimeSpan.FromDays(days)); + + /// + /// Allows to filter the responses which should be cached + /// by the client. + /// + /// The predicate to be evaluated to check, whether content should be cached + public CachePolicyBuilder Predicate(Func predicate) + { _Predicate = predicate; return this; } - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { var duration = _Duration ?? throw new BuilderMissingPropertyException("Duration"); return new CachePolicyConcern(parent, contentFactory, duration, _Predicate); } - #endregion - - } + #endregion } diff --git a/Modules/ClientCaching/Policy/CachePolicyConcern.cs b/Modules/ClientCaching/Policy/CachePolicyConcern.cs index 76287833..6e9193fd 100644 --- a/Modules/ClientCaching/Policy/CachePolicyConcern.cs +++ b/Modules/ClientCaching/Policy/CachePolicyConcern.cs @@ -6,29 +6,28 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.ClientCaching.Policy -{ +namespace GenHTTP.Modules.ClientCaching.Policy; - public sealed class CachePolicyConcern : IConcern - { +public sealed class CachePolicyConcern : IConcern +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Content { get; } + public IHandler Content { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - private TimeSpan Duration { get; } + private TimeSpan Duration { get; } - private Func? Predicate { get; } + private Func? Predicate { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public CachePolicyConcern(IHandler parent, Func contentFactory, - TimeSpan duration, Func? predicate) - { + public CachePolicyConcern(IHandler parent, Func contentFactory, + TimeSpan duration, Func? predicate) + { Parent = parent; Content = contentFactory(this); @@ -36,12 +35,12 @@ public CachePolicyConcern(IHandler parent, Func contentFacto Predicate = predicate; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var response = await Content.HandleAsync(request); if (response != null) @@ -58,10 +57,8 @@ public CachePolicyConcern(IHandler parent, Func contentFacto return response; } - public ValueTask PrepareAsync() => Content.PrepareAsync(); - - #endregion + public ValueTask PrepareAsync() => Content.PrepareAsync(); - } + #endregion } diff --git a/Modules/ClientCaching/Validation/CacheValidationBuilder.cs b/Modules/ClientCaching/Validation/CacheValidationBuilder.cs index 669d36b3..6a33308f 100644 --- a/Modules/ClientCaching/Validation/CacheValidationBuilder.cs +++ b/Modules/ClientCaching/Validation/CacheValidationBuilder.cs @@ -2,17 +2,14 @@ using GenHTTP.Api.Content; -namespace GenHTTP.Modules.ClientCaching.Validation +namespace GenHTTP.Modules.ClientCaching.Validation; + +public sealed class CacheValidationBuilder : IConcernBuilder { - public sealed class CacheValidationBuilder : IConcernBuilder + public IConcern Build(IHandler parent, Func contentFactory) { - - public IConcern Build(IHandler parent, Func contentFactory) - { return new CacheValidationHandler(parent, contentFactory); } - } - } diff --git a/Modules/ClientCaching/Validation/CacheValidationHandler.cs b/Modules/ClientCaching/Validation/CacheValidationHandler.cs index 8b2be52d..b724d889 100644 --- a/Modules/ClientCaching/Validation/CacheValidationHandler.cs +++ b/Modules/ClientCaching/Validation/CacheValidationHandler.cs @@ -6,37 +6,36 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.ClientCaching.Validation -{ +namespace GenHTTP.Modules.ClientCaching.Validation; - public sealed class CacheValidationHandler : IConcern - { - private const string ETAG_HEADER = "ETag"; +public sealed class CacheValidationHandler : IConcern +{ + private const string ETAG_HEADER = "ETag"; - private static readonly RequestMethod[] _SupportedMethods = new[] { RequestMethod.GET, RequestMethod.HEAD }; + private static readonly RequestMethod[] _SupportedMethods = new[] { RequestMethod.GET, RequestMethod.HEAD }; - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public IHandler Content { get; } + public IHandler Content { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public CacheValidationHandler(IHandler parent, Func contentFactory) - { + public CacheValidationHandler(IHandler parent, Func contentFactory) + { Parent = parent; Content = contentFactory(this); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var response = await Content.HandleAsync(request); if (request.HasType(_SupportedMethods)) @@ -68,10 +67,10 @@ public CacheValidationHandler(IHandler parent, Func contentF return response; } - public ValueTask PrepareAsync() => Content.PrepareAsync(); + public ValueTask PrepareAsync() => Content.PrepareAsync(); - private static async ValueTask CalculateETag(IResponse response) - { + private static async ValueTask CalculateETag(IResponse response) + { if (response.Headers.TryGetValue(ETAG_HEADER, out var eTag)) { return eTag; @@ -90,8 +89,6 @@ public CacheValidationHandler(IHandler parent, Func contentF return null; } - #endregion - - } + #endregion } diff --git a/Modules/Compression/CompressedContent.cs b/Modules/Compression/CompressedContent.cs index 769572da..47c26ef1 100644 --- a/Modules/Compression/CompressedContent.cs +++ b/Modules/Compression/CompressedContent.cs @@ -2,60 +2,57 @@ using GenHTTP.Modules.Compression.Providers; -namespace GenHTTP.Modules.Compression +namespace GenHTTP.Modules.Compression; + +public static class CompressedContent { - public static class CompressedContent + #region Builder + + /// + /// Creates an empty builder that can be configured by adding + /// compression algorithms. + /// + /// The newly created builder + public static CompressionConcernBuilder Empty() => new(); + + /// + /// Creates a pre-configured builder which already supports + /// Zstandard, Brotli and Gzip compression. + /// + /// The newly created builder + public static CompressionConcernBuilder Default() { - - #region Builder - - /// - /// Creates an empty builder that can be configured by adding - /// compression algorithms. - /// - /// The newly created builder - public static CompressionConcernBuilder Empty() => new(); - - /// - /// Creates a pre-configured builder which already supports - /// Zstandard, Brotli and Gzip compression. - /// - /// The newly created builder - public static CompressionConcernBuilder Default() - { return new CompressionConcernBuilder().Add(new ZstdCompression()) .Add(new BrotliCompression()) .Add(new GzipAlgorithm()); } - #endregion + #endregion - #region Extensions + #region Extensions - /// - /// Configures the host to compress responses if the client supports - /// this feature. - /// - /// The host to be configured - /// The compression builder to use for compression - /// The configured server host for chaining - public static IServerHost Compression(this IServerHost host, CompressionConcernBuilder compression) - { + /// + /// Configures the host to compress responses if the client supports + /// this feature. + /// + /// The host to be configured + /// The compression builder to use for compression + /// The configured server host for chaining + public static IServerHost Compression(this IServerHost host, CompressionConcernBuilder compression) + { host.Add(compression); return host; } - /// - /// Configures the host to compress responses if the client supports - /// this feature. - /// - /// The host to be configured - /// The configured server host for chaining - public static IServerHost Compression(this IServerHost host) => host.Compression(Default()); - - #endregion + /// + /// Configures the host to compress responses if the client supports + /// this feature. + /// + /// The host to be configured + /// The configured server host for chaining + public static IServerHost Compression(this IServerHost host) => host.Compression(Default()); - } + #endregion } diff --git a/Modules/Compression/Providers/BrotliCompression.cs b/Modules/Compression/Providers/BrotliCompression.cs index de7cbd98..97125f08 100644 --- a/Modules/Compression/Providers/BrotliCompression.cs +++ b/Modules/Compression/Providers/BrotliCompression.cs @@ -3,21 +3,18 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Compression.Providers -{ +namespace GenHTTP.Modules.Compression.Providers; - public sealed class BrotliCompression : ICompressionAlgorithm - { +public sealed class BrotliCompression : ICompressionAlgorithm +{ - public string Name => "br"; + public string Name => "br"; - public Priority Priority => Priority.Medium; + public Priority Priority => Priority.Medium; - public IResponseContent Compress(IResponseContent content, CompressionLevel level) - { + public IResponseContent Compress(IResponseContent content, CompressionLevel level) + { return new CompressedResponseContent(content, (target) => new BrotliStream(target, level, false)); } - } - } diff --git a/Modules/Compression/Providers/CompressedResponseContent.cs b/Modules/Compression/Providers/CompressedResponseContent.cs index c3427921..4b1a6fd8 100644 --- a/Modules/Compression/Providers/CompressedResponseContent.cs +++ b/Modules/Compression/Providers/CompressedResponseContent.cs @@ -4,54 +4,53 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Compression.Providers -{ +namespace GenHTTP.Modules.Compression.Providers; - public class CompressedResponseContent : IResponseContent, IDisposable - { +public class CompressedResponseContent : IResponseContent, IDisposable +{ - #region Get-/Setters + #region Get-/Setters - public ulong? Length => null; + public ulong? Length => null; - private IResponseContent OriginalContent { get; } + private IResponseContent OriginalContent { get; } - private Func Generator { get; } + private Func Generator { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public CompressedResponseContent(IResponseContent originalContent, Func generator) - { + public CompressedResponseContent(IResponseContent originalContent, Func generator) + { OriginalContent = originalContent; Generator = generator; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask WriteAsync(Stream target, uint bufferSize) - { + public async ValueTask WriteAsync(Stream target, uint bufferSize) + { using var compressed = Generator(target); await OriginalContent.WriteAsync(compressed, bufferSize); } - public ValueTask CalculateChecksumAsync() - { + public ValueTask CalculateChecksumAsync() + { return OriginalContent.CalculateChecksumAsync(); } - #endregion + #endregion - #region IDisposable Support + #region IDisposable Support - private bool disposedValue = false; + private bool disposedValue = false; - protected virtual void Dispose(bool disposing) - { + protected virtual void Dispose(bool disposing) + { if (!disposedValue) { if (disposing) @@ -66,19 +65,17 @@ protected virtual void Dispose(bool disposing) } } - ~CompressedResponseContent() - { + ~CompressedResponseContent() + { Dispose(false); } - public void Dispose() - { + public void Dispose() + { Dispose(true); GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Modules/Compression/Providers/CompressionConcern.cs b/Modules/Compression/Providers/CompressionConcern.cs index 6e58db72..5a3999f3 100644 --- a/Modules/Compression/Providers/CompressionConcern.cs +++ b/Modules/Compression/Providers/CompressionConcern.cs @@ -10,33 +10,32 @@ using GenHTTP.Api.Protocol; using GenHTTP.Api.Routing; -namespace GenHTTP.Modules.Compression.Providers -{ +namespace GenHTTP.Modules.Compression.Providers; - public sealed class CompressionConcern : IConcern - { - private const string ACCEPT_ENCODING = "Accept-Encoding"; +public sealed class CompressionConcern : IConcern +{ + private const string ACCEPT_ENCODING = "Accept-Encoding"; - private const string VARY = "Vary"; + private const string VARY = "Vary"; - #region Get-/Setters + #region Get-/Setters - public IHandler Content { get; } + public IHandler Content { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - private IReadOnlyDictionary Algorithms { get; } + private IReadOnlyDictionary Algorithms { get; } - private CompressionLevel Level { get; } + private CompressionLevel Level { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public CompressionConcern(IHandler parent, Func contentFactory, - IReadOnlyDictionary algorithms, - CompressionLevel level) - { + public CompressionConcern(IHandler parent, Func contentFactory, + IReadOnlyDictionary algorithms, + CompressionLevel level) + { Parent = parent; Content = contentFactory(this); @@ -44,12 +43,12 @@ public CompressionConcern(IHandler parent, Func contentFacto Level = level; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var response = await Content.HandleAsync(request); if (response is not null) @@ -93,8 +92,8 @@ public CompressionConcern(IHandler parent, Func contentFacto return response; } - private static bool ShouldCompress(WebPath path, ContentType? type) - { + private static bool ShouldCompress(WebPath path, ContentType? type) + { if (type is not null) { switch (type) @@ -134,10 +133,8 @@ private static bool ShouldCompress(WebPath path, ContentType? type) return false; } - public ValueTask PrepareAsync() => Content.PrepareAsync(); - - #endregion + public ValueTask PrepareAsync() => Content.PrepareAsync(); - } + #endregion } diff --git a/Modules/Compression/Providers/CompressionConcernBuilder.cs b/Modules/Compression/Providers/CompressionConcernBuilder.cs index 35909154..793465fb 100644 --- a/Modules/Compression/Providers/CompressionConcernBuilder.cs +++ b/Modules/Compression/Providers/CompressionConcernBuilder.cs @@ -7,40 +7,37 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.Compression.Providers -{ +namespace GenHTTP.Modules.Compression.Providers; - public sealed class CompressionConcernBuilder : IConcernBuilder - { - private readonly List _Algorithms = new(); +public sealed class CompressionConcernBuilder : IConcernBuilder +{ + private readonly List _Algorithms = new(); - private CompressionLevel _Level = CompressionLevel.Fastest; + private CompressionLevel _Level = CompressionLevel.Fastest; - #region Functionality + #region Functionality - public CompressionConcernBuilder Add(IBuilder algorithm) => Add(algorithm.Build()); + public CompressionConcernBuilder Add(IBuilder algorithm) => Add(algorithm.Build()); - public CompressionConcernBuilder Add(ICompressionAlgorithm algorithm) - { + public CompressionConcernBuilder Add(ICompressionAlgorithm algorithm) + { _Algorithms.Add(algorithm); return this; } - public CompressionConcernBuilder Level(CompressionLevel level) - { + public CompressionConcernBuilder Level(CompressionLevel level) + { _Level = level; return this; } - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { var algorithms = _Algorithms.ToDictionary(a => a.Name); return new CompressionConcern(parent, contentFactory, algorithms, _Level); } - #endregion - - } + #endregion } diff --git a/Modules/Compression/Providers/GzipAlgorithm.cs b/Modules/Compression/Providers/GzipAlgorithm.cs index 172cb73d..658d8354 100644 --- a/Modules/Compression/Providers/GzipAlgorithm.cs +++ b/Modules/Compression/Providers/GzipAlgorithm.cs @@ -3,21 +3,18 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Compression.Providers -{ +namespace GenHTTP.Modules.Compression.Providers; - public sealed class GzipAlgorithm : ICompressionAlgorithm - { +public sealed class GzipAlgorithm : ICompressionAlgorithm +{ - public string Name => "gzip"; + public string Name => "gzip"; - public Priority Priority => Priority.Low; + public Priority Priority => Priority.Low; - public IResponseContent Compress(IResponseContent content, CompressionLevel level) - { + public IResponseContent Compress(IResponseContent content, CompressionLevel level) + { return new CompressedResponseContent(content, (target) => new GZipStream(target, level, false)); } - } - } diff --git a/Modules/Controllers/Controller.cs b/Modules/Controllers/Controller.cs index f78f04a1..3d919ad3 100644 --- a/Modules/Controllers/Controller.cs +++ b/Modules/Controllers/Controller.cs @@ -2,26 +2,23 @@ using GenHTTP.Modules.Controllers.Provider; -namespace GenHTTP.Modules.Controllers -{ - - public static class Controller - { +namespace GenHTTP.Modules.Controllers; - /// - /// Creates a handler that will use the given controller class to generate responses. - /// - /// The type of the controller to be used - /// The newly created request handler - public static ControllerBuilder From<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new() => new ControllerBuilder().Type(); +public static class Controller +{ - /// - /// Creates a handler that will use the given controller instance to generate responses. - /// - /// The instance to be used - /// The newly created request handler - public static ControllerBuilder From(object instance) => new ControllerBuilder().Instance(instance); + /// + /// Creates a handler that will use the given controller class to generate responses. + /// + /// The type of the controller to be used + /// The newly created request handler + public static ControllerBuilder From<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new() => new ControllerBuilder().Type(); - } + /// + /// Creates a handler that will use the given controller instance to generate responses. + /// + /// The instance to be used + /// The newly created request handler + public static ControllerBuilder From(object instance) => new ControllerBuilder().Instance(instance); } diff --git a/Modules/Controllers/ControllerActionAttribute.cs b/Modules/Controllers/ControllerActionAttribute.cs index 1d245f7d..f92c214b 100644 --- a/Modules/Controllers/ControllerActionAttribute.cs +++ b/Modules/Controllers/ControllerActionAttribute.cs @@ -2,24 +2,21 @@ using GenHTTP.Modules.Reflection; -namespace GenHTTP.Modules.Controllers -{ - - public class ControllerActionAttribute : MethodAttribute - { +namespace GenHTTP.Modules.Controllers; - /// - /// Configures the action to accept requests of the given kind. - /// - /// The request methods which are supported by this action - public ControllerActionAttribute(params RequestMethod[] methods) : base(methods) { } +public class ControllerActionAttribute : MethodAttribute +{ - /// - /// Configures the action to accept requests of the given kind. - /// - /// The request methods which are supported by this action - public ControllerActionAttribute(params FlexibleRequestMethod[] methods) : base(methods) { } + /// + /// Configures the action to accept requests of the given kind. + /// + /// The request methods which are supported by this action + public ControllerActionAttribute(params RequestMethod[] methods) : base(methods) { } - } + /// + /// Configures the action to accept requests of the given kind. + /// + /// The request methods which are supported by this action + public ControllerActionAttribute(params FlexibleRequestMethod[] methods) : base(methods) { } } diff --git a/Modules/Controllers/Extensions.cs b/Modules/Controllers/Extensions.cs index acfc266b..2b18c1da 100644 --- a/Modules/Controllers/Extensions.cs +++ b/Modules/Controllers/Extensions.cs @@ -4,52 +4,51 @@ using GenHTTP.Modules.Controllers.Provider; using GenHTTP.Modules.Conversion.Formatters; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.Layouting.Provider; using GenHTTP.Modules.Reflection.Injectors; -namespace GenHTTP.Modules.Controllers +namespace GenHTTP.Modules.Controllers; + +public static class Extensions { - public static class Extensions + /// + /// Causes all requests to the specified path to be handled by the + /// given controller class. + /// + /// The type of the controller used to handle requests + /// The layout the controller should be added to + /// The path that should be handled by the controller + /// Optionally the injectors to be used by this controller + /// Optionally the serializers to be used by this controller + /// Optionally the formatters to be used by this controller + public static LayoutBuilder AddController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder builder, string path, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : new() { - - /// - /// Causes all requests to the specified path to be handled by the - /// given controller class. - /// - /// The type of the controller used to handle requests - /// The layout the controller should be added to - /// The path that should be handled by the controller - /// Optionally the injectors to be used by this controller - /// Optionally the serializers to be used by this controller - /// Optionally the formatters to be used by this controller - public static LayoutBuilder AddController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder builder, string path, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : new() - { builder.Add(path, Controller.From().Configured(injectors, serializers, formatters)); return builder; } - /// - /// Causes the specified controller class to be used to handle the index of - /// this layout. - /// - /// The type of the controller used to handle requests - /// The layout the controller should be added to - /// Optionally the injectors to be used by this controller - /// Optionally the serializers to be used by this controller - /// Optionally the formatters to be used by this controller - public static LayoutBuilder IndexController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder builder, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : new() - { + /// + /// Causes the specified controller class to be used to handle the index of + /// this layout. + /// + /// The type of the controller used to handle requests + /// The layout the controller should be added to + /// Optionally the injectors to be used by this controller + /// Optionally the serializers to be used by this controller + /// Optionally the formatters to be used by this controller + public static LayoutBuilder IndexController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder builder, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : new() + { builder.Add(Controller.From().Configured(injectors, serializers, formatters)); return builder; } - private static ControllerBuilder Configured(this ControllerBuilder builder, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) - { + private static ControllerBuilder Configured(this ControllerBuilder builder, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) + { if (injectors != null) { - builder.Injectors(injectors); + builder.Injectors(injectors); } if (serializers != null) @@ -65,6 +64,4 @@ private static ControllerBuilder Configured(this ControllerBuilder builder, IBui return builder; } - } - } diff --git a/Modules/Controllers/FromPathAttribute.cs b/Modules/Controllers/FromPathAttribute.cs index 300aeba7..d972fff7 100644 --- a/Modules/Controllers/FromPathAttribute.cs +++ b/Modules/Controllers/FromPathAttribute.cs @@ -1,21 +1,18 @@ using System; -namespace GenHTTP.Modules.Controllers -{ - - /// - /// Marking an argument of a controller method with this attribute causes - /// the value of the argument to be read from the request path. - /// - /// - /// Specifying arguments annotated with this attribute will alter the path - /// the action matches (e.g. /action/argument/). You may specify as many - /// path arguments as you like, but they must not be optional. - /// - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class FromPathAttribute : Attribute - { +namespace GenHTTP.Modules.Controllers; - } +/// +/// Marking an argument of a controller method with this attribute causes +/// the value of the argument to be read from the request path. +/// +/// +/// Specifying arguments annotated with this attribute will alter the path +/// the action matches (e.g. /action/argument/). You may specify as many +/// path arguments as you like, but they must not be optional. +/// +[AttributeUsage(AttributeTargets.Parameter)] +public sealed class FromPathAttribute : Attribute +{ } diff --git a/Modules/Controllers/Provider/ControllerBuilder.cs b/Modules/Controllers/Provider/ControllerBuilder.cs index e80797d4..7dc2d519 100644 --- a/Modules/Controllers/Provider/ControllerBuilder.cs +++ b/Modules/Controllers/Provider/ControllerBuilder.cs @@ -6,65 +6,64 @@ using GenHTTP.Modules.Conversion; using GenHTTP.Modules.Conversion.Formatters; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.Reflection; using GenHTTP.Modules.Reflection.Injectors; -namespace GenHTTP.Modules.Controllers.Provider -{ +namespace GenHTTP.Modules.Controllers.Provider; - public sealed class ControllerBuilder : IHandlerBuilder - { - private IBuilder? _Serializers; +public sealed class ControllerBuilder : IHandlerBuilder +{ + private IBuilder? _Serializers; - private IBuilder? _Injection; + private IBuilder? _Injection; - private IBuilder? _Formatters; + private IBuilder? _Formatters; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - private object? _Instance; + private object? _Instance; - #region Functionality + #region Functionality - public ControllerBuilder Serializers(IBuilder registry) - { + public ControllerBuilder Serializers(IBuilder registry) + { _Serializers = registry; return this; } - public ControllerBuilder Injectors(IBuilder registry) - { + public ControllerBuilder Injectors(IBuilder registry) + { _Injection = registry; return this; } - public ControllerBuilder Formatters(IBuilder registry) - { + public ControllerBuilder Formatters(IBuilder registry) + { _Formatters = registry; return this; } - public ControllerBuilder Type<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new() - { + public ControllerBuilder Type<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new() + { _Instance = new T(); return this; } - public ControllerBuilder Instance(object instance) - { + public ControllerBuilder Instance(object instance) + { _Instance = instance; return this; } - public ControllerBuilder Add(IConcernBuilder concern) - { + public ControllerBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { var serializers = (_Serializers ?? Serialization.Default()).Build(); var injectors = (_Injection ?? Injection.Default()).Build(); @@ -76,8 +75,6 @@ public IHandler Build(IHandler parent) return Concerns.Chain(parent, _Concerns, (p) => new ControllerHandler(p, instance, serializers, injectors, formatters)); } - #endregion - - } + #endregion } diff --git a/Modules/Controllers/Provider/ControllerHandler.cs b/Modules/Controllers/Provider/ControllerHandler.cs index 7f23234e..c2c77d0c 100644 --- a/Modules/Controllers/Provider/ControllerHandler.cs +++ b/Modules/Controllers/Provider/ControllerHandler.cs @@ -9,37 +9,36 @@ using GenHTTP.Api.Protocol; using GenHTTP.Modules.Conversion.Formatters; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.Reflection; using GenHTTP.Modules.Reflection.Injectors; -namespace GenHTTP.Modules.Controllers.Provider -{ +namespace GenHTTP.Modules.Controllers.Provider; - public sealed partial class ControllerHandler : IHandler - { - private static readonly MethodRouting EMPTY = new("/", "^(/|)$", null, true, false); +public sealed partial class ControllerHandler : IHandler +{ + private static readonly MethodRouting EMPTY = new("/", "^(/|)$", null, true, false); - private static readonly Regex HYPEN_MATCHER = CreateHypenMatcher(); + private static readonly Regex HYPEN_MATCHER = CreateHypenMatcher(); - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private MethodCollection Provider { get; } + private MethodCollection Provider { get; } - private ResponseProvider ResponseProvider { get; } + private ResponseProvider ResponseProvider { get; } - private FormatterRegistry Formatting { get; } + private FormatterRegistry Formatting { get; } - private object Instance { get; } + private object Instance { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ControllerHandler(IHandler parent, object instance, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) - { + public ControllerHandler(IHandler parent, object instance, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) + { Parent = parent; Formatting = formatting; @@ -50,8 +49,8 @@ public ControllerHandler(IHandler parent, object instance, SerializationRegistry Provider = new(this, AnalyzeMethods(instance.GetType(), serialization, injection, formatting)); } - private IEnumerable> AnalyzeMethods(Type type, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) - { + private IEnumerable> AnalyzeMethods(Type type, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) + { foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { var annotation = method.GetCustomAttribute(true) ?? new MethodAttribute(); @@ -64,8 +63,8 @@ private IEnumerable> AnalyzeMethods(Type type, Ser } } - private static MethodRouting DeterminePath(MethodInfo method, List arguments) - { + private static MethodRouting DeterminePath(MethodInfo method, List arguments) + { var pathArgs = string.Join('/', arguments.Select(a => a.ToParameter())); var rawArgs = string.Join('/', arguments.Select(a => $":{a}")); @@ -85,8 +84,8 @@ private static MethodRouting DeterminePath(MethodInfo method, List argum } } - private List FindPathArguments(MethodInfo method) - { + private List FindPathArguments(MethodInfo method) + { var found = new List(); var parameters = method.GetParameters(); @@ -117,24 +116,22 @@ private List FindPathArguments(MethodInfo method) return found; } - private static string HypenCase(string input) - { + private static string HypenCase(string input) + { return HYPEN_MATCHER.Replace(input, "$1-$2").ToLowerInvariant(); } - [GeneratedRegex(@"([a-z])([A-Z0-9]+)")] - private static partial Regex CreateHypenMatcher(); + [GeneratedRegex(@"([a-z])([A-Z0-9]+)")] + private static partial Regex CreateHypenMatcher(); - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask PrepareAsync() => Provider.PrepareAsync(); + public ValueTask PrepareAsync() => Provider.PrepareAsync(); - public ValueTask HandleAsync(IRequest request) => Provider.HandleAsync(request); - - #endregion + public ValueTask HandleAsync(IRequest request) => Provider.HandleAsync(request); - } + #endregion } diff --git a/Modules/Conversion/Extensions.cs b/Modules/Conversion/Extensions.cs index c423fedc..2c92318f 100644 --- a/Modules/Conversion/Extensions.cs +++ b/Modules/Conversion/Extensions.cs @@ -5,21 +5,20 @@ using GenHTTP.Modules.Conversion.Formatters; -namespace GenHTTP.Modules.Conversion +namespace GenHTTP.Modules.Conversion; + +public static class Extensions { - public static class Extensions + /// + /// Attempts to convert the given string value to the specified type. + /// + /// The value to be converted + /// The target type to convert the value to + /// The formatting to be used to actually perform the conversion + /// The converted value + public static object? ConvertTo(this string? value, Type type, FormatterRegistry formatters) { - - /// - /// Attempts to convert the given string value to the specified type. - /// - /// The value to be converted - /// The target type to convert the value to - /// The formatting to be used to actually perform the conversion - /// The converted value - public static object? ConvertTo(this string? value, Type type, FormatterRegistry formatters) - { if (string.IsNullOrEmpty(value)) { if (Nullable.GetUnderlyingType(type) is not null) @@ -48,6 +47,4 @@ public static class Extensions } } - } - } diff --git a/Modules/Conversion/Formatters/BoolFormatter.cs b/Modules/Conversion/Formatters/BoolFormatter.cs index 9879c9a7..397dbd3a 100644 --- a/Modules/Conversion/Formatters/BoolFormatter.cs +++ b/Modules/Conversion/Formatters/BoolFormatter.cs @@ -1,15 +1,14 @@ using System; -namespace GenHTTP.Modules.Conversion.Formatters -{ +namespace GenHTTP.Modules.Conversion.Formatters; - public sealed class BoolFormatter : IFormatter - { +public sealed class BoolFormatter : IFormatter +{ - public bool CanHandle(Type type) => type == typeof(bool); + public bool CanHandle(Type type) => type == typeof(bool); - public object? Read(string value, Type type) - { + public object? Read(string value, Type type) + { if (value == "1" || Compare(value, "on") || Compare(value, "true")) { return true; @@ -22,10 +21,8 @@ public sealed class BoolFormatter : IFormatter return null; } - public string? Write(object value, Type type) => ((bool)value) ? "1" : "0"; - - private static bool Compare(string value, string expected) => string.Equals(value, expected, StringComparison.InvariantCultureIgnoreCase); + public string? Write(object value, Type type) => ((bool)value) ? "1" : "0"; - } + private static bool Compare(string value, string expected) => string.Equals(value, expected, StringComparison.InvariantCultureIgnoreCase); } diff --git a/Modules/Conversion/Formatters/DateOnlyFormatter.cs b/Modules/Conversion/Formatters/DateOnlyFormatter.cs index e286ec48..6e9b24f2 100644 --- a/Modules/Conversion/Formatters/DateOnlyFormatter.cs +++ b/Modules/Conversion/Formatters/DateOnlyFormatter.cs @@ -1,17 +1,16 @@ using System; using System.Text.RegularExpressions; -namespace GenHTTP.Modules.Conversion.Formatters -{ +namespace GenHTTP.Modules.Conversion.Formatters; - public sealed class DateOnlyFormatter : IFormatter - { - private static readonly Regex DATE_ONLY_PATTERN = new(@"^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$", RegexOptions.Compiled); +public sealed class DateOnlyFormatter : IFormatter +{ + private static readonly Regex DATE_ONLY_PATTERN = new(@"^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$", RegexOptions.Compiled); - public bool CanHandle(Type type) => type == typeof(DateOnly); + public bool CanHandle(Type type) => type == typeof(DateOnly); - public object? Read(string value, Type type) - { + public object? Read(string value, Type type) + { var match = DATE_ONLY_PATTERN.Match(value); if (match.Success) @@ -26,8 +25,6 @@ public sealed class DateOnlyFormatter : IFormatter throw new ArgumentException($"Input does not match the requested format (yyyy-mm-dd): {value}"); } - public string? Write(object value, Type type) => ((DateOnly)value).ToString("yyyy-MM-dd"); - - } + public string? Write(object value, Type type) => ((DateOnly)value).ToString("yyyy-MM-dd"); } diff --git a/Modules/Conversion/Formatters/EnumFormatter.cs b/Modules/Conversion/Formatters/EnumFormatter.cs index d254a886..caadb5c0 100644 --- a/Modules/Conversion/Formatters/EnumFormatter.cs +++ b/Modules/Conversion/Formatters/EnumFormatter.cs @@ -1,17 +1,14 @@ using System; -namespace GenHTTP.Modules.Conversion.Formatters -{ - - public sealed class EnumFormatter : IFormatter - { +namespace GenHTTP.Modules.Conversion.Formatters; - public bool CanHandle(Type type) => type.IsEnum; +public sealed class EnumFormatter : IFormatter +{ - public object? Read(string value, Type type) => Enum.Parse(type, value); + public bool CanHandle(Type type) => type.IsEnum; - public string? Write(object value, Type type) => value.ToString(); + public object? Read(string value, Type type) => Enum.Parse(type, value); - } + public string? Write(object value, Type type) => value.ToString(); } diff --git a/Modules/Conversion/Formatters/FormatterBuilder.cs b/Modules/Conversion/Formatters/FormatterBuilder.cs index e2803b9a..b59061c3 100644 --- a/Modules/Conversion/Formatters/FormatterBuilder.cs +++ b/Modules/Conversion/Formatters/FormatterBuilder.cs @@ -2,38 +2,35 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.Conversion.Formatters -{ +namespace GenHTTP.Modules.Conversion.Formatters; - public sealed class FormatterBuilder : IBuilder - { - private readonly List _Registry = new(); +public sealed class FormatterBuilder : IBuilder +{ + private readonly List _Registry = new(); - #region Functionality + #region Functionality - /// - /// Adds the given formatter to the registry. - /// - /// The formatter to be added - public FormatterBuilder Add(IFormatter formatter) - { + /// + /// Adds the given formatter to the registry. + /// + /// The formatter to be added + public FormatterBuilder Add(IFormatter formatter) + { _Registry.Add(formatter); return this; } - public FormatterBuilder Add() where T : IFormatter, new() => Add(new T()); + public FormatterBuilder Add() where T : IFormatter, new() => Add(new T()); - /// - /// Builds the formatter registry based on the configuration. - /// - /// The newly created formatter registry - public FormatterRegistry Build() - { + /// + /// Builds the formatter registry based on the configuration. + /// + /// The newly created formatter registry + public FormatterRegistry Build() + { return new FormatterRegistry(_Registry); } - #endregion - - } + #endregion } diff --git a/Modules/Conversion/Formatters/FormatterRegistry.cs b/Modules/Conversion/Formatters/FormatterRegistry.cs index f896e1eb..c0df76fe 100644 --- a/Modules/Conversion/Formatters/FormatterRegistry.cs +++ b/Modules/Conversion/Formatters/FormatterRegistry.cs @@ -1,31 +1,30 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Modules.Conversion.Formatters -{ +namespace GenHTTP.Modules.Conversion.Formatters; - public sealed class FormatterRegistry - { +public sealed class FormatterRegistry +{ - #region Get-/Setters + #region Get-/Setters - public IReadOnlyList Formatters { get; private set; } + public IReadOnlyList Formatters { get; private set; } - #endregion + #endregion - #region Initialization + #region Initialization - public FormatterRegistry(List formatters) - { + public FormatterRegistry(List formatters) + { Formatters = formatters; } - #endregion + #endregion - #region Functionality + #region Functionality - public bool CanHandle(Type type) - { + public bool CanHandle(Type type) + { for (int i = 0; i < Formatters.Count; i++) { if (Formatters[i].CanHandle(type)) @@ -37,8 +36,8 @@ public bool CanHandle(Type type) return false; } - public object? Read(string value, Type type) - { + public object? Read(string value, Type type) + { for (int i = 0; i < Formatters.Count; i++) { if (Formatters[i].CanHandle(type)) @@ -51,8 +50,8 @@ public bool CanHandle(Type type) } - public string? Write(object value, Type type) - { + public string? Write(object value, Type type) + { for (int i = 0; i < Formatters.Count; i++) { if (Formatters[i].CanHandle(type)) @@ -64,8 +63,6 @@ public bool CanHandle(Type type) return null; } - #endregion - - } + #endregion } diff --git a/Modules/Conversion/Formatters/GuidFormatter.cs b/Modules/Conversion/Formatters/GuidFormatter.cs index e8a7fa92..32e7f379 100644 --- a/Modules/Conversion/Formatters/GuidFormatter.cs +++ b/Modules/Conversion/Formatters/GuidFormatter.cs @@ -1,17 +1,14 @@ using System; -namespace GenHTTP.Modules.Conversion.Formatters -{ - - public sealed class GuidFormatter : IFormatter - { +namespace GenHTTP.Modules.Conversion.Formatters; - public bool CanHandle(Type type) => type == typeof(Guid); +public sealed class GuidFormatter : IFormatter +{ - public object? Read(string value, Type type) => Guid.Parse(value); + public bool CanHandle(Type type) => type == typeof(Guid); - public string? Write(object value, Type type) => value.ToString(); + public object? Read(string value, Type type) => Guid.Parse(value); - } + public string? Write(object value, Type type) => value.ToString(); } diff --git a/Modules/Conversion/Formatters/IFormatter.cs b/Modules/Conversion/Formatters/IFormatter.cs index a1778d6f..e7cfbdb5 100644 --- a/Modules/Conversion/Formatters/IFormatter.cs +++ b/Modules/Conversion/Formatters/IFormatter.cs @@ -1,45 +1,42 @@ using System; -namespace GenHTTP.Modules.Conversion.Formatters +namespace GenHTTP.Modules.Conversion.Formatters; + +/// +/// Allows to add support for a specific type to be used as a parameter +/// of a web service or controller as well as in form encoded data. +/// +public interface IFormatter { /// - /// Allows to add support for a specific type to be used as a parameter - /// of a web service or controller as well as in form encoded data. + /// Checks, whether the formatter is capable of handling the given type. /// - public interface IFormatter - { - - /// - /// Checks, whether the formatter is capable of handling the given type. - /// - /// The type to be checked - /// true, if the formatter can be used to read and write such types - bool CanHandle(Type type); + /// The type to be checked + /// true, if the formatter can be used to read and write such types + bool CanHandle(Type type); - /// - /// Converts the given string into the specified type. - /// - /// The value to be converted - /// The type to convert the value to - /// The value converted into the given type - /// - /// Used by the framework to read parameters to be passed to controllers or the like. - /// - object? Read(string value, Type type); - - /// - /// Converts the given object instance of the given type into a string representation. - /// - /// The value to be formatted - /// The declared type of the value - /// The string representation of the given value - /// - /// Used by the framework to serialize a single value into the response's body - /// or to generate form encoded data. - /// - string? Write(object value, Type type); + /// + /// Converts the given string into the specified type. + /// + /// The value to be converted + /// The type to convert the value to + /// The value converted into the given type + /// + /// Used by the framework to read parameters to be passed to controllers or the like. + /// + object? Read(string value, Type type); - } + /// + /// Converts the given object instance of the given type into a string representation. + /// + /// The value to be formatted + /// The declared type of the value + /// The string representation of the given value + /// + /// Used by the framework to serialize a single value into the response's body + /// or to generate form encoded data. + /// + string? Write(object value, Type type); } diff --git a/Modules/Conversion/Formatters/PrimitiveFormatter.cs b/Modules/Conversion/Formatters/PrimitiveFormatter.cs index c38936fe..619f645e 100644 --- a/Modules/Conversion/Formatters/PrimitiveFormatter.cs +++ b/Modules/Conversion/Formatters/PrimitiveFormatter.cs @@ -1,18 +1,15 @@ using System; using System.Globalization; -namespace GenHTTP.Modules.Conversion.Formatters -{ - - public sealed class PrimitiveFormatter : IFormatter - { +namespace GenHTTP.Modules.Conversion.Formatters; - public bool CanHandle(Type type) => type.IsPrimitive; +public sealed class PrimitiveFormatter : IFormatter +{ - public object? Read(string value, Type type) => Convert.ChangeType(value, type, CultureInfo.InvariantCulture); + public bool CanHandle(Type type) => type.IsPrimitive; - public string? Write(object value, Type type) => Convert.ChangeType(value, typeof(string), CultureInfo.InvariantCulture) as string; + public object? Read(string value, Type type) => Convert.ChangeType(value, type, CultureInfo.InvariantCulture); - } + public string? Write(object value, Type type) => Convert.ChangeType(value, typeof(string), CultureInfo.InvariantCulture) as string; } diff --git a/Modules/Conversion/Formatters/StringFormatter.cs b/Modules/Conversion/Formatters/StringFormatter.cs index f08dd3da..9bb315af 100644 --- a/Modules/Conversion/Formatters/StringFormatter.cs +++ b/Modules/Conversion/Formatters/StringFormatter.cs @@ -1,17 +1,14 @@ using System; -namespace GenHTTP.Modules.Conversion.Formatters -{ - - public sealed class StringFormatter : IFormatter - { +namespace GenHTTP.Modules.Conversion.Formatters; - public bool CanHandle(Type type) => type == typeof(string); +public sealed class StringFormatter : IFormatter +{ - public object? Read(string value, Type type) => value; + public bool CanHandle(Type type) => type == typeof(string); - public string? Write(object value, Type type) => (string)value; + public object? Read(string value, Type type) => value; - } + public string? Write(object value, Type type) => (string)value; } diff --git a/Modules/Conversion/Formatting.cs b/Modules/Conversion/Formatting.cs index 64e1c982..d425e7d3 100644 --- a/Modules/Conversion/Formatting.cs +++ b/Modules/Conversion/Formatting.cs @@ -1,22 +1,21 @@ using GenHTTP.Modules.Conversion.Formatters; -namespace GenHTTP.Modules.Conversion +namespace GenHTTP.Modules.Conversion; + +/// +/// Entry point to customize the string representation generated for +/// various types that can be returned or read by services. +/// +public static class Formatting { /// - /// Entry point to customize the string representation generated for - /// various types that can be returned or read by services. + /// The default formatters to be used with support for enums, GUIDs, + /// primitive types and strings. /// - public static class Formatting + /// The default formatters + public static FormatterBuilder Default() { - - /// - /// The default formatters to be used with support for enums, GUIDs, - /// primitive types and strings. - /// - /// The default formatters - public static FormatterBuilder Default() - { return new FormatterBuilder().Add() .Add() .Add() @@ -25,13 +24,11 @@ public static FormatterBuilder Default() .Add(); } - /// - /// Creates an empty formatter registry that can be extended - /// as needed. - /// - /// An empty formatter registry - public static FormatterBuilder Empty() => new FormatterBuilder(); - - } + /// + /// Creates an empty formatter registry that can be extended + /// as needed. + /// + /// An empty formatter registry + public static FormatterBuilder Empty() => new FormatterBuilder(); } diff --git a/Modules/Conversion/Serialization.cs b/Modules/Conversion/Serialization.cs index 9600d43c..d16d8f2e 100644 --- a/Modules/Conversion/Serialization.cs +++ b/Modules/Conversion/Serialization.cs @@ -1,36 +1,33 @@ using GenHTTP.Api.Protocol; -using GenHTTP.Modules.Conversion.Providers; -using GenHTTP.Modules.Conversion.Providers.Forms; -using GenHTTP.Modules.Conversion.Providers.Json; -using GenHTTP.Modules.Conversion.Providers.Xml; +using GenHTTP.Modules.Conversion.Serializers; +using GenHTTP.Modules.Conversion.Serializers.Forms; +using GenHTTP.Modules.Conversion.Serializers.Json; +using GenHTTP.Modules.Conversion.Serializers.Xml; -namespace GenHTTP.Modules.Conversion +namespace GenHTTP.Modules.Conversion; + +/// +/// Entry point to configure the formats supported by a webservice +/// resource. +/// +public static class Serialization { /// - /// Entry point to configure the formats supported by a webservice - /// resource. + /// Returns a registry that will support JSON and XML serialization + /// and will use JSON as a default format. /// - public static class Serialization + public static SerializationBuilder Default() { - - /// - /// Returns a registry that will support JSON and XML serialization - /// and will use JSON as a default format. - /// - public static SerializationBuilder Default() - { return new SerializationBuilder().Default(ContentType.ApplicationJson) .Add(ContentType.ApplicationJson, new JsonFormat()) .Add(ContentType.ApplicationWwwFormUrlEncoded, new FormFormat()) .Add(ContentType.TextXml, new XmlFormat()); } - /// - /// Returns an empty registry to be customized. - /// - public static SerializationBuilder Empty() => new(); - - } + /// + /// Returns an empty registry to be customized. + /// + public static SerializationBuilder Empty() => new(); } diff --git a/Modules/Conversion/Serializers/Forms/FormContent.cs b/Modules/Conversion/Serializers/Forms/FormContent.cs index 09933f76..3bdb8120 100644 --- a/Modules/Conversion/Serializers/Forms/FormContent.cs +++ b/Modules/Conversion/Serializers/Forms/FormContent.cs @@ -5,49 +5,47 @@ using System.Text; using System.Threading.Tasks; using System.Web; - using GenHTTP.Api.Protocol; using GenHTTP.Modules.Conversion.Formatters; -namespace GenHTTP.Modules.Conversion.Providers.Forms -{ +namespace GenHTTP.Modules.Conversion.Serializers.Forms; - public sealed class FormContent : IResponseContent - { +public sealed class FormContent : IResponseContent +{ - #region Get-/Setters + #region Get-/Setters - public ulong? Length => null; + public ulong? Length => null; - private Type Type { get; } + private Type Type { get; } - private object Data { get; } + private object Data { get; } - private FormatterRegistry Formatters { get; } + private FormatterRegistry Formatters { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public FormContent(Type type, object data, FormatterRegistry formatters) - { + public FormContent(Type type, object data, FormatterRegistry formatters) + { Type = type; Data = data; Formatters = formatters; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask CalculateChecksumAsync() - { + public ValueTask CalculateChecksumAsync() + { return new ValueTask((ulong)Data.GetHashCode()); } - public async ValueTask WriteAsync(Stream target, uint bufferSize) - { + public async ValueTask WriteAsync(Stream target, uint bufferSize) + { using var writer = new StreamWriter(target, Encoding.UTF8, (int)bufferSize, true); var query = HttpUtility.ParseQueryString(string.Empty); @@ -69,8 +67,8 @@ public async ValueTask WriteAsync(Stream target, uint bufferSize) await writer.WriteAsync(replaced); } - private void SetValue(NameValueCollection query, string field, object? value, Type type) - { + private void SetValue(NameValueCollection query, string field, object? value, Type type) + { if (value != null) { var formatted = Formatters.Write(value, type); @@ -82,8 +80,6 @@ private void SetValue(NameValueCollection query, string field, object? value, Ty } } - #endregion - - } + #endregion } diff --git a/Modules/Conversion/Serializers/Forms/FormFormat.cs b/Modules/Conversion/Serializers/Forms/FormFormat.cs index e8b34ef5..922418a4 100644 --- a/Modules/Conversion/Serializers/Forms/FormFormat.cs +++ b/Modules/Conversion/Serializers/Forms/FormFormat.cs @@ -5,43 +5,40 @@ using System.Text; using System.Threading.Tasks; using System.Web; - using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; - using GenHTTP.Modules.Basics; using GenHTTP.Modules.Conversion.Formatters; -namespace GenHTTP.Modules.Conversion.Providers.Forms -{ +namespace GenHTTP.Modules.Conversion.Serializers.Forms; - public sealed class FormFormat : ISerializationFormat - { - private static readonly Type[] EMPTY_CONSTRUCTOR = Array.Empty(); +public sealed class FormFormat : ISerializationFormat +{ + private static readonly Type[] EMPTY_CONSTRUCTOR = Array.Empty(); - private static readonly object[] EMPTY_ARGS = Array.Empty(); + private static readonly object[] EMPTY_ARGS = Array.Empty(); - #region Get-/Setters + #region Get-/Setters - private FormatterRegistry Formatters { get; } + private FormatterRegistry Formatters { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public FormFormat(FormatterRegistry formatters) - { + public FormFormat(FormatterRegistry formatters) + { Formatters = formatters; } - public FormFormat() : this(Formatting.Default().Build()) { } + public FormFormat() : this(Formatting.Default().Build()) { } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask DeserializeAsync(Stream stream, Type type) - { + public async ValueTask DeserializeAsync(Stream stream, Type type) + { using var reader = new StreamReader(stream); var content = await reader.ReadToEndAsync(); @@ -87,8 +84,8 @@ public FormFormat() : this(Formatting.Default().Build()) { } return result; } - public ValueTask SerializeAsync(IRequest request, object response) - { + public ValueTask SerializeAsync(IRequest request, object response) + { var result = request.Respond() .Content(new FormContent(response.GetType(), response, Formatters)) .Type(ContentType.ApplicationWwwFormUrlEncoded); @@ -96,8 +93,8 @@ public ValueTask SerializeAsync(IRequest request, object respo return new ValueTask(result); } - public static Dictionary? GetContent(IRequest request) - { + public static Dictionary? GetContent(IRequest request) + { if ((request.Content is not null) && (request.ContentType is not null)) { if (request.ContentType == ContentType.ApplicationWwwFormUrlEncoded) @@ -125,8 +122,8 @@ public ValueTask SerializeAsync(IRequest request, object respo return null; } - private static string GetRequestContent(IRequest request) - { + private static string GetRequestContent(IRequest request) + { var requestContent = request.Content; if (requestContent is null) @@ -143,8 +140,6 @@ private static string GetRequestContent(IRequest request) return content; } - #endregion - - } + #endregion } diff --git a/Modules/Conversion/Serializers/ISerializationFormat.cs b/Modules/Conversion/Serializers/ISerializationFormat.cs index 642a6f7a..24b9d25e 100644 --- a/Modules/Conversion/Serializers/ISerializationFormat.cs +++ b/Modules/Conversion/Serializers/ISerializationFormat.cs @@ -2,41 +2,37 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; - using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Conversion.Providers +namespace GenHTTP.Modules.Conversion.Serializers; + +/// +/// Implements a specific format that can be used to serialize +/// and deserialize data within service oriented handler. +/// +/// +/// Instances implementing this interface can be registered at a +/// to support additional +/// transport formats. +/// +public interface ISerializationFormat { /// - /// Implements a specific format that can be used to serialize - /// and deserialize data within service oriented handler. + /// Deserializes the given stream into an object of the given type. /// - /// - /// Instances implementing this interface can be registered at a - /// to support additional - /// transport formats. - /// - public interface ISerializationFormat - { - - /// - /// Deserializes the given stream into an object of the given type. - /// - /// The stream providing the data to be deserialized - /// The type to be deserialized - /// The object deserialized from the given stream - ValueTask DeserializeAsync(Stream stream, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type); + /// The stream providing the data to be deserialized + /// The type to be deserialized + /// The object deserialized from the given stream + ValueTask DeserializeAsync(Stream stream, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type); - /// - /// Serializes the given response into a new response for the - /// given request. - /// - /// The request to generate a response for - /// The object to be serialized - /// The response representing the serialized object - ValueTask SerializeAsync(IRequest request, object response); - - } + /// + /// Serializes the given response into a new response for the + /// given request. + /// + /// The request to generate a response for + /// The object to be serialized + /// The response representing the serialized object + ValueTask SerializeAsync(IRequest request, object response); } diff --git a/Modules/Conversion/Serializers/Json/JsonContent.cs b/Modules/Conversion/Serializers/Json/JsonContent.cs index 179af1ae..20aeab46 100644 --- a/Modules/Conversion/Serializers/Json/JsonContent.cs +++ b/Modules/Conversion/Serializers/Json/JsonContent.cs @@ -1,49 +1,45 @@ using System.IO; using System.Text.Json; using System.Threading.Tasks; - using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Conversion.Providers.Json -{ +namespace GenHTTP.Modules.Conversion.Serializers.Json; - public sealed class JsonContent : IResponseContent - { +public sealed class JsonContent : IResponseContent +{ - #region Get-/Setters + #region Get-/Setters - public ulong? Length => null; + public ulong? Length => null; - private object Data { get; } + private object Data { get; } - private JsonSerializerOptions Options { get; } + private JsonSerializerOptions Options { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public JsonContent(object data, JsonSerializerOptions options) - { + public JsonContent(object data, JsonSerializerOptions options) + { Data = data; Options = options; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask CalculateChecksumAsync() - { + public ValueTask CalculateChecksumAsync() + { return new ValueTask((ulong)Data.GetHashCode()); } - public async ValueTask WriteAsync(Stream target, uint bufferSize) - { + public async ValueTask WriteAsync(Stream target, uint bufferSize) + { await JsonSerializer.SerializeAsync(target, Data, Data.GetType(), Options); } - #endregion - - } + #endregion } diff --git a/Modules/Conversion/Serializers/Json/JsonFormat.cs b/Modules/Conversion/Serializers/Json/JsonFormat.cs index 7b1e4e98..9485e5f6 100644 --- a/Modules/Conversion/Serializers/Json/JsonFormat.cs +++ b/Modules/Conversion/Serializers/Json/JsonFormat.cs @@ -3,30 +3,27 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; - using GenHTTP.Api.Protocol; - using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.Conversion.Providers.Json -{ +namespace GenHTTP.Modules.Conversion.Serializers.Json; - public sealed class JsonFormat : ISerializationFormat +public sealed class JsonFormat : ISerializationFormat +{ + private static readonly JsonSerializerOptions OPTIONS = new() { - private static readonly JsonSerializerOptions OPTIONS = new() - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; - public ValueTask DeserializeAsync(Stream stream, Type type) - { + public ValueTask DeserializeAsync(Stream stream, Type type) + { return JsonSerializer.DeserializeAsync(stream, type, OPTIONS); } - public ValueTask SerializeAsync(IRequest request, object response) - { + public ValueTask SerializeAsync(IRequest request, object response) + { var result = request.Respond() .Content(new JsonContent(response, OPTIONS)) .Type(ContentType.ApplicationJson); @@ -34,6 +31,4 @@ public ValueTask SerializeAsync(IRequest request, object respo return new ValueTask(result); } - } - } diff --git a/Modules/Conversion/Serializers/SerializationBuilder.cs b/Modules/Conversion/Serializers/SerializationBuilder.cs index 2b545f9d..03b658e6 100644 --- a/Modules/Conversion/Serializers/SerializationBuilder.cs +++ b/Modules/Conversion/Serializers/SerializationBuilder.cs @@ -1,42 +1,38 @@ using System.Collections.Generic; - using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Conversion.Providers -{ +namespace GenHTTP.Modules.Conversion.Serializers; - public sealed class SerializationBuilder : IBuilder - { - private FlexibleContentType? _Default = null; +public sealed class SerializationBuilder : IBuilder +{ + private FlexibleContentType? _Default = null; - private readonly Dictionary _Registry = new(); + private readonly Dictionary _Registry = new(); - #region Functionality + #region Functionality - public SerializationBuilder Default(ContentType contentType) => Default(FlexibleContentType.Get(contentType)); + public SerializationBuilder Default(ContentType contentType) => Default(FlexibleContentType.Get(contentType)); - public SerializationBuilder Default(FlexibleContentType contentType) - { + public SerializationBuilder Default(FlexibleContentType contentType) + { _Default = contentType; return this; } - public SerializationBuilder Add(ContentType contentType, ISerializationFormat format) => Add(FlexibleContentType.Get(contentType), format); + public SerializationBuilder Add(ContentType contentType, ISerializationFormat format) => Add(FlexibleContentType.Get(contentType), format); - public SerializationBuilder Add(FlexibleContentType contentType, ISerializationFormat format) - { + public SerializationBuilder Add(FlexibleContentType contentType, ISerializationFormat format) + { _Registry[contentType] = format; return this; } - public SerializationRegistry Build() - { + public SerializationRegistry Build() + { return new SerializationRegistry(_Default ?? throw new BuilderMissingPropertyException("default"), _Registry); } - #endregion - - } + #endregion } diff --git a/Modules/Conversion/Serializers/SerializationRegistry.cs b/Modules/Conversion/Serializers/SerializationRegistry.cs index ab5dd9c4..0474298b 100644 --- a/Modules/Conversion/Serializers/SerializationRegistry.cs +++ b/Modules/Conversion/Serializers/SerializationRegistry.cs @@ -1,41 +1,39 @@ using System.Collections.Generic; - using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Conversion.Providers -{ +namespace GenHTTP.Modules.Conversion.Serializers; - /// - /// Registers formats that can be used to serialize and - /// deserialize objects sent to or received from a - /// service oriented handler. - /// - public sealed class SerializationRegistry - { +/// +/// Registers formats that can be used to serialize and +/// deserialize objects sent to or received from a +/// service oriented handler. +/// +public sealed class SerializationRegistry +{ - #region Get-/Setters + #region Get-/Setters - private FlexibleContentType Default { get; } + private FlexibleContentType Default { get; } - private Dictionary Formats { get; } + private Dictionary Formats { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public SerializationRegistry(FlexibleContentType defaultType, - Dictionary formats) - { + public SerializationRegistry(FlexibleContentType defaultType, + Dictionary formats) + { Default = defaultType; Formats = formats; } - #endregion + #endregion - #region Functionality + #region Functionality - public ISerializationFormat? GetDeserialization(IRequest request) - { + public ISerializationFormat? GetDeserialization(IRequest request) + { if (request.Headers.TryGetValue("Content-Type", out string? requested)) { return GetFormat(FlexibleContentType.Parse(requested)); @@ -44,8 +42,8 @@ public SerializationRegistry(FlexibleContentType defaultType, return GetFormat(Default); } - public ISerializationFormat? GetSerialization(IRequest request) - { + public ISerializationFormat? GetSerialization(IRequest request) + { if (request.Headers.TryGetValue("Accept", out string? accepted)) { return GetFormat(FlexibleContentType.Parse(accepted)) ?? GetFormat(Default); @@ -54,18 +52,18 @@ public SerializationRegistry(FlexibleContentType defaultType, return GetFormat(Default); } - public ISerializationFormat? GetFormat(string? contentType) - { + public ISerializationFormat? GetFormat(string? contentType) + { if (contentType != null) { return GetFormat(FlexibleContentType.Parse(contentType)); } - + return GetFormat(Default); } - private ISerializationFormat? GetFormat(FlexibleContentType contentType) - { + private ISerializationFormat? GetFormat(FlexibleContentType contentType) + { if (Formats.TryGetValue(contentType, out var format)) { return format; @@ -74,8 +72,6 @@ public SerializationRegistry(FlexibleContentType defaultType, return null; } - #endregion - - } + #endregion } diff --git a/Modules/Conversion/Serializers/Xml/XmlContent.cs b/Modules/Conversion/Serializers/Xml/XmlContent.cs index 7073262c..8ea4a90e 100644 --- a/Modules/Conversion/Serializers/Xml/XmlContent.cs +++ b/Modules/Conversion/Serializers/Xml/XmlContent.cs @@ -1,49 +1,45 @@ using System.IO; using System.Threading.Tasks; using System.Xml.Serialization; - using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Conversion.Providers.Xml -{ +namespace GenHTTP.Modules.Conversion.Serializers.Xml; - public sealed class XmlContent : IResponseContent - { +public sealed class XmlContent : IResponseContent +{ - #region Get-/Setters + #region Get-/Setters - public ulong? Length => null; + public ulong? Length => null; - private object Data { get; } + private object Data { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public XmlContent(object data) - { + public XmlContent(object data) + { Data = data; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask CalculateChecksumAsync() - { + public ValueTask CalculateChecksumAsync() + { return new ValueTask((ulong)Data.GetHashCode()); } - public ValueTask WriteAsync(Stream target, uint bufferSize) - { + public ValueTask WriteAsync(Stream target, uint bufferSize) + { var serializer = new XmlSerializer(Data.GetType()); serializer.Serialize(target, Data); return new ValueTask(); } - #endregion - - } + #endregion } diff --git a/Modules/Conversion/Serializers/Xml/XmlFormat.cs b/Modules/Conversion/Serializers/Xml/XmlFormat.cs index e66f9efa..fdf35768 100644 --- a/Modules/Conversion/Serializers/Xml/XmlFormat.cs +++ b/Modules/Conversion/Serializers/Xml/XmlFormat.cs @@ -2,26 +2,23 @@ using System.IO; using System.Threading.Tasks; using System.Xml.Serialization; - using GenHTTP.Api.Protocol; - using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.Conversion.Providers.Xml +namespace GenHTTP.Modules.Conversion.Serializers.Xml; + +public sealed class XmlFormat : ISerializationFormat { - public sealed class XmlFormat : ISerializationFormat + public ValueTask DeserializeAsync(Stream stream, Type type) { - - public ValueTask DeserializeAsync(Stream stream, Type type) - { var result = new XmlSerializer(type).Deserialize(stream); return new ValueTask(result); } - public ValueTask SerializeAsync(IRequest request, object response) - { + public ValueTask SerializeAsync(IRequest request, object response) + { var result = request.Respond() .Content(new XmlContent(response)) .Type(ContentType.TextXml); @@ -29,6 +26,4 @@ public ValueTask SerializeAsync(IRequest request, object respo return new ValueTask(result); } - } - } diff --git a/Modules/DirectoryBrowsing/Listing.cs b/Modules/DirectoryBrowsing/Listing.cs index 140f440e..d2c21eb5 100644 --- a/Modules/DirectoryBrowsing/Listing.cs +++ b/Modules/DirectoryBrowsing/Listing.cs @@ -3,16 +3,13 @@ using GenHTTP.Modules.DirectoryBrowsing.Provider; -namespace GenHTTP.Modules.DirectoryBrowsing -{ - - public static class Listing - { +namespace GenHTTP.Modules.DirectoryBrowsing; - public static ListingRouterBuilder From(IBuilder tree) => From(tree.Build()); +public static class Listing +{ - public static ListingRouterBuilder From(IResourceTree tree) => new ListingRouterBuilder().Tree(tree); + public static ListingRouterBuilder From(IBuilder tree) => From(tree.Build()); - } + public static ListingRouterBuilder From(IResourceTree tree) => new ListingRouterBuilder().Tree(tree); } diff --git a/Modules/DirectoryBrowsing/Provider/FileSizeFormatter.cs b/Modules/DirectoryBrowsing/Provider/FileSizeFormatter.cs index 47f0dcd8..3a55ff08 100644 --- a/Modules/DirectoryBrowsing/Provider/FileSizeFormatter.cs +++ b/Modules/DirectoryBrowsing/Provider/FileSizeFormatter.cs @@ -1,22 +1,21 @@ using System; using System.Globalization; -namespace GenHTTP.Modules.DirectoryBrowsing.Provider -{ +namespace GenHTTP.Modules.DirectoryBrowsing.Provider; - public static class FileSizeFormatter - { +public static class FileSizeFormatter +{ - private const double KILOBYTES = 1024.0; + private const double KILOBYTES = 1024.0; - private const double MEGABYTES = KILOBYTES * 1024; + private const double MEGABYTES = KILOBYTES * 1024; - private const double GIGABYTES = MEGABYTES * 1024; + private const double GIGABYTES = MEGABYTES * 1024; - private const double TERABYTES = GIGABYTES * 1024; + private const double TERABYTES = GIGABYTES * 1024; - public static string Format(ulong? bytes) - { + public static string Format(ulong? bytes) + { if (bytes is not null) { var b = (long)bytes; @@ -44,6 +43,4 @@ public static string Format(ulong? bytes) return "-"; } - } - } diff --git a/Modules/DirectoryBrowsing/Provider/ListingModel.cs b/Modules/DirectoryBrowsing/Provider/ListingModel.cs index 9d86c4e2..5de4e9ef 100644 --- a/Modules/DirectoryBrowsing/Provider/ListingModel.cs +++ b/Modules/DirectoryBrowsing/Provider/ListingModel.cs @@ -1,8 +1,5 @@ using GenHTTP.Api.Content.IO; -namespace GenHTTP.Modules.DirectoryBrowsing.Provider -{ +namespace GenHTTP.Modules.DirectoryBrowsing.Provider; - public record class ListingModel(IResourceContainer Container, bool HasParent); - -} \ No newline at end of file +public record class ListingModel(IResourceContainer Container, bool HasParent); diff --git a/Modules/DirectoryBrowsing/Provider/ListingProvider.cs b/Modules/DirectoryBrowsing/Provider/ListingProvider.cs index 9e260d03..595fe930 100644 --- a/Modules/DirectoryBrowsing/Provider/ListingProvider.cs +++ b/Modules/DirectoryBrowsing/Provider/ListingProvider.cs @@ -6,34 +6,33 @@ using GenHTTP.Modules.Pages; -namespace GenHTTP.Modules.DirectoryBrowsing.Provider -{ +namespace GenHTTP.Modules.DirectoryBrowsing.Provider; - public sealed class ListingProvider : IHandler - { +public sealed class ListingProvider : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public IResourceContainer Container { get; } + public IResourceContainer Container { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ListingProvider(IHandler parent, IResourceContainer container) - { + public ListingProvider(IHandler parent, IResourceContainer container) + { Parent = parent; Container = container; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var model = new ListingModel(Container, !request.Target.Ended); var content = await ListingRenderer.RenderAsync(model); @@ -43,10 +42,8 @@ public ListingProvider(IHandler parent, IResourceContainer container) return request.GetPage(page).Build(); } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; - - #endregion + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - } + #endregion } diff --git a/Modules/DirectoryBrowsing/Provider/ListingRenderer.cs b/Modules/DirectoryBrowsing/Provider/ListingRenderer.cs index 339c1878..b4d703f3 100644 --- a/Modules/DirectoryBrowsing/Provider/ListingRenderer.cs +++ b/Modules/DirectoryBrowsing/Provider/ListingRenderer.cs @@ -4,14 +4,13 @@ using System.Text; using System.Threading.Tasks; -namespace GenHTTP.Modules.DirectoryBrowsing.Provider +namespace GenHTTP.Modules.DirectoryBrowsing.Provider; + +public static class ListingRenderer { - public static class ListingRenderer + public static async ValueTask RenderAsync(ListingModel model) { - - public static async ValueTask RenderAsync(ListingModel model) - { var content = new StringBuilder(); content.AppendLine(""); @@ -44,8 +43,8 @@ public static async ValueTask RenderAsync(ListingModel model) return content.ToString(); } - private static void Append(StringBuilder builder, string path, string name, ulong? size, DateTime? modified) - { + private static void Append(StringBuilder builder, string path, string name, ulong? size, DateTime? modified) + { builder.AppendLine(""); builder.AppendLine($" "); builder.AppendLine($" "); @@ -62,6 +61,4 @@ private static void Append(StringBuilder builder, string path, string name, ulon builder.AppendLine(""); } - } - } diff --git a/Modules/DirectoryBrowsing/Provider/ListingRouter.cs b/Modules/DirectoryBrowsing/Provider/ListingRouter.cs index eeaf1a8c..5c6bb665 100644 --- a/Modules/DirectoryBrowsing/Provider/ListingRouter.cs +++ b/Modules/DirectoryBrowsing/Provider/ListingRouter.cs @@ -6,34 +6,33 @@ using GenHTTP.Modules.IO; -namespace GenHTTP.Modules.DirectoryBrowsing.Provider -{ +namespace GenHTTP.Modules.DirectoryBrowsing.Provider; - public sealed class ListingRouter : IHandler - { +public sealed class ListingRouter : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private IResourceTree Tree { get; } + private IResourceTree Tree { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ListingRouter(IHandler parent, IResourceTree tree) - { + public ListingRouter(IHandler parent, IResourceTree tree) + { Parent = parent; Tree = tree; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var (node, resource) = await Tree.Find(request.Target); if (resource is not null) @@ -49,11 +48,9 @@ public ListingRouter(IHandler parent, IResourceTree tree) return null; } - - public ValueTask PrepareAsync() => ValueTask.CompletedTask; - #endregion + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - } + #endregion } diff --git a/Modules/DirectoryBrowsing/Provider/ListingRouterBuilder.cs b/Modules/DirectoryBrowsing/Provider/ListingRouterBuilder.cs index 9f1e9e47..b3450952 100644 --- a/Modules/DirectoryBrowsing/Provider/ListingRouterBuilder.cs +++ b/Modules/DirectoryBrowsing/Provider/ListingRouterBuilder.cs @@ -5,40 +5,37 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.DirectoryBrowsing.Provider -{ +namespace GenHTTP.Modules.DirectoryBrowsing.Provider; - public sealed class ListingRouterBuilder : IHandlerBuilder - { - private IResourceTree? _Tree; +public sealed class ListingRouterBuilder : IHandlerBuilder +{ + private IResourceTree? _Tree; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - #region Functionality + #region Functionality - public ListingRouterBuilder Tree(IResourceTree tree) - { + public ListingRouterBuilder Tree(IResourceTree tree) + { _Tree = tree; return this; } - public ListingRouterBuilder Tree(IBuilder tree) => Tree(tree.Build()); + public ListingRouterBuilder Tree(IBuilder tree) => Tree(tree.Build()); - public ListingRouterBuilder Add(IConcernBuilder concern) - { + public ListingRouterBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { var tree = _Tree ?? throw new BuilderMissingPropertyException("tree"); return Concerns.Chain(parent, _Concerns, (p) => new ListingRouter(p, tree)); } - #endregion - - } + #endregion } diff --git a/Modules/ErrorHandling/ErrorHandler.cs b/Modules/ErrorHandling/ErrorHandler.cs index e3ca8746..d33bdcc4 100644 --- a/Modules/ErrorHandling/ErrorHandler.cs +++ b/Modules/ErrorHandling/ErrorHandler.cs @@ -2,52 +2,49 @@ using GenHTTP.Api.Content; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.ErrorHandling.Provider; -namespace GenHTTP.Modules.ErrorHandling +namespace GenHTTP.Modules.ErrorHandling; + +public static class ErrorHandler { - public static class ErrorHandler - { - - /// - /// The default error handler used by the server to - /// render error pages. - /// - /// - /// By default, server errors will be rendered into - /// structured responses. - /// - /// The default error handler - public static ErrorSentryBuilder Default() => Structured(); - - /// - /// Ans error handler which will render exceptions into - /// structured error objects serialized to the format - /// requested by the client (e.g. JSON or XML). - /// - /// The serialization configuration to be used - /// A structured error handler - public static ErrorSentryBuilder Structured(SerializationBuilder? serialization = null) => From(new StructuredErrorMapper(serialization?.Build())); - - /// - /// An error handler which will render exceptions into - /// HTML using the current template and IErrorRenderer. - /// - /// An HTML error handler - public static ErrorSentryBuilder Html() => From(new HtmlErrorMapper()); - - /// - /// Creates an error handling concern which will use - /// the specified error mapper to convert exceptions - /// to HTTP responses. - /// - /// The type of exceptions to be catched - /// The mapper to use for exception mapping - /// The newly generated concern - public static ErrorSentryBuilder From(IErrorMapper mapper) where T : Exception => new(mapper); - - } + /// + /// The default error handler used by the server to + /// render error pages. + /// + /// + /// By default, server errors will be rendered into + /// structured responses. + /// + /// The default error handler + public static ErrorSentryBuilder Default() => Structured(); + + /// + /// Ans error handler which will render exceptions into + /// structured error objects serialized to the format + /// requested by the client (e.g. JSON or XML). + /// + /// The serialization configuration to be used + /// A structured error handler + public static ErrorSentryBuilder Structured(SerializationBuilder? serialization = null) => From(new StructuredErrorMapper(serialization?.Build())); + + /// + /// An error handler which will render exceptions into + /// HTML using the current template and IErrorRenderer. + /// + /// An HTML error handler + public static ErrorSentryBuilder Html() => From(new HtmlErrorMapper()); + + /// + /// Creates an error handling concern which will use + /// the specified error mapper to convert exceptions + /// to HTTP responses. + /// + /// The type of exceptions to be catched + /// The mapper to use for exception mapping + /// The newly generated concern + public static ErrorSentryBuilder From(IErrorMapper mapper) where T : Exception => new(mapper); } diff --git a/Modules/ErrorHandling/HtmlErrorMapper.cs b/Modules/ErrorHandling/HtmlErrorMapper.cs index b4d637da..301b328a 100644 --- a/Modules/ErrorHandling/HtmlErrorMapper.cs +++ b/Modules/ErrorHandling/HtmlErrorMapper.cs @@ -6,14 +6,13 @@ using GenHTTP.Modules.Pages; -namespace GenHTTP.Modules.ErrorHandling +namespace GenHTTP.Modules.ErrorHandling; + +public class HtmlErrorMapper : IErrorMapper { - public class HtmlErrorMapper : IErrorMapper + public async ValueTask Map(IRequest request, IHandler handler, Exception error) { - - public async ValueTask Map(IRequest request, IHandler handler, Exception error) - { var developmentMode = request.Server.Development; if (error is ProviderException e) @@ -36,8 +35,8 @@ public class HtmlErrorMapper : IErrorMapper } } - public async ValueTask GetNotFound(IRequest request, IHandler handler) - { + public async ValueTask GetNotFound(IRequest request, IHandler handler) + { var content = await Renderer.Server.RenderAsync("Not Found", "The specified content was not found on this server."); return request.GetPage(content) @@ -45,6 +44,4 @@ public class HtmlErrorMapper : IErrorMapper .Build(); } - } - } diff --git a/Modules/ErrorHandling/Provider/ErrorSentry.cs b/Modules/ErrorHandling/Provider/ErrorSentry.cs index 83d5a78e..0be74997 100644 --- a/Modules/ErrorHandling/Provider/ErrorSentry.cs +++ b/Modules/ErrorHandling/Provider/ErrorSentry.cs @@ -4,40 +4,39 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.ErrorHandling.Provider -{ +namespace GenHTTP.Modules.ErrorHandling.Provider; - public sealed class ErrorSentry : IConcern where T : Exception - { +public sealed class ErrorSentry : IConcern where T : Exception +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Content { get; } + public IHandler Content { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - private IErrorMapper ErrorHandler { get; } + private IErrorMapper ErrorHandler { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ErrorSentry(IHandler parent, Func contentFactory, IErrorMapper errorHandler) - { + public ErrorSentry(IHandler parent, Func contentFactory, IErrorMapper errorHandler) + { Parent = parent; Content = contentFactory(this); ErrorHandler = errorHandler; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask PrepareAsync() => Content.PrepareAsync(); + public ValueTask PrepareAsync() => Content.PrepareAsync(); - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { try { var response = await Content.HandleAsync(request); @@ -55,8 +54,6 @@ public ErrorSentry(IHandler parent, Func contentFactory, IEr } } - #endregion - - } + #endregion } diff --git a/Modules/ErrorHandling/Provider/ErrorSentryBuilder.cs b/Modules/ErrorHandling/Provider/ErrorSentryBuilder.cs index 76e41369..51d4e4ef 100644 --- a/Modules/ErrorHandling/Provider/ErrorSentryBuilder.cs +++ b/Modules/ErrorHandling/Provider/ErrorSentryBuilder.cs @@ -2,36 +2,33 @@ using GenHTTP.Api.Content; -namespace GenHTTP.Modules.ErrorHandling.Provider -{ +namespace GenHTTP.Modules.ErrorHandling.Provider; - public sealed class ErrorSentryBuilder : IConcernBuilder where T : Exception - { +public sealed class ErrorSentryBuilder : IConcernBuilder where T : Exception +{ - #region Get-/Setters + #region Get-/Setters - private IErrorMapper Handler { get; } + private IErrorMapper Handler { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ErrorSentryBuilder(IErrorMapper handler) - { + public ErrorSentryBuilder(IErrorMapper handler) + { Handler = handler; } - #endregion + #endregion - #region Functionality + #region Functionality - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { return new ErrorSentry(parent, contentFactory, Handler); } - #endregion - - } + #endregion } diff --git a/Modules/ErrorHandling/StructuredErrorMapper.cs b/Modules/ErrorHandling/StructuredErrorMapper.cs index 1cd3cdce..a8d2b7bf 100644 --- a/Modules/ErrorHandling/StructuredErrorMapper.cs +++ b/Modules/ErrorHandling/StructuredErrorMapper.cs @@ -5,81 +5,78 @@ using GenHTTP.Api.Protocol; using GenHTTP.Modules.Conversion; -using GenHTTP.Modules.Conversion.Providers; -using GenHTTP.Modules.Conversion.Providers.Json; +using GenHTTP.Modules.Conversion.Serializers; +using GenHTTP.Modules.Conversion.Serializers.Json; -namespace GenHTTP.Modules.ErrorHandling +namespace GenHTTP.Modules.ErrorHandling; + +public sealed class StructuredErrorMapper : IErrorMapper { - public sealed class StructuredErrorMapper : IErrorMapper - { + #region Supporting data structures - #region Supporting data structures + public record ErrorModel(ResponseStatus Status, string Message, string? StackTrace = null); - public record class ErrorModel(ResponseStatus Status, string Message, string? StackTrace = null); + #endregion - #endregion + #region Get-/Setters - #region Get-/Setters + public SerializationRegistry Registry { get; } - public SerializationRegistry Registry { get; } + #endregion - #endregion + #region Initialization - #region Initialization + public StructuredErrorMapper(SerializationRegistry? registry) + { + Registry = registry ?? Serialization.Default().Build(); + } - public StructuredErrorMapper(SerializationRegistry? registry) - { - Registry = registry ?? Serialization.Default().Build(); - } + #endregion - #endregion + #region Functionality - #region Functionality + public async ValueTask Map(IRequest request, IHandler handler, Exception error) + { + string? stackTrace = null; - public async ValueTask Map(IRequest request, IHandler handler, Exception error) + if (request.Server.Development) { - string? stackTrace = null; - - if (request.Server.Development) - { - stackTrace = error.StackTrace; - } - - if (error is ProviderException e) - { - var model = new ErrorModel(e.Status, error.Message, stackTrace); + stackTrace = error.StackTrace; + } - return await RenderAsync(request, model); - } - else - { - var model = new ErrorModel(ResponseStatus.InternalServerError, error.Message, stackTrace); + if (error is ProviderException e) + { + var model = new ErrorModel(e.Status, error.Message, stackTrace); - return await RenderAsync(request, model); - } + return await RenderAsync(request, model); } - - public async ValueTask GetNotFound(IRequest request, IHandler handler) + else { - var model = new ErrorModel(ResponseStatus.NotFound, "The requested resource does not exist on this server"); + var model = new ErrorModel(ResponseStatus.InternalServerError, error.Message, stackTrace); return await RenderAsync(request, model); } + } - private async ValueTask RenderAsync(IRequest request, ErrorModel model) - { - var format = Registry.GetSerialization(request) ?? new JsonFormat(); + public async ValueTask GetNotFound(IRequest request, IHandler handler) + { + var model = new ErrorModel(ResponseStatus.NotFound, "The requested resource does not exist on this server"); - var response = await format.SerializeAsync(request, model); + return await RenderAsync(request, model); + } - response.Status(model.Status); + private async ValueTask RenderAsync(IRequest request, ErrorModel model) + { + var format = Registry.GetSerialization(request) ?? new JsonFormat(); - return response.Build(); - } + var response = await format.SerializeAsync(request, model); - #endregion + response.Status(model.Status); + return response.Build(); } + #endregion + } diff --git a/Modules/Functional/Inline.cs b/Modules/Functional/Inline.cs index 92f45f13..8b07860d 100644 --- a/Modules/Functional/Inline.cs +++ b/Modules/Functional/Inline.cs @@ -1,17 +1,14 @@ using GenHTTP.Modules.Functional.Provider; -namespace GenHTTP.Modules.Functional -{ - - public static class Inline - { +namespace GenHTTP.Modules.Functional; - /// - /// Creates a functional handler that accepts delegates - /// which are executed to respond to incoming requests. - /// - public static InlineBuilder Create() => new(); +public static class Inline +{ - } + /// + /// Creates a functional handler that accepts delegates + /// which are executed to respond to incoming requests. + /// + public static InlineBuilder Create() => new(); } diff --git a/Modules/Functional/Provider/InlineBuilder.cs b/Modules/Functional/Provider/InlineBuilder.cs index 67ea69ad..fd9711bd 100644 --- a/Modules/Functional/Provider/InlineBuilder.cs +++ b/Modules/Functional/Provider/InlineBuilder.cs @@ -7,203 +7,200 @@ using GenHTTP.Api.Protocol; using GenHTTP.Modules.Conversion; -using GenHTTP.Modules.Conversion.Providers; using GenHTTP.Modules.Conversion.Formatters; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.Reflection.Injectors; using GenHTTP.Modules.Reflection; -namespace GenHTTP.Modules.Functional.Provider +namespace GenHTTP.Modules.Functional.Provider; + +public class InlineBuilder : IHandlerBuilder { + private static readonly HashSet ALL_METHODS = new(Enum.GetValues().Select(m => FlexibleRequestMethod.Get(m))); - public class InlineBuilder : IHandlerBuilder - { - private static readonly HashSet ALL_METHODS = new(Enum.GetValues().Select(m => FlexibleRequestMethod.Get(m))); + private readonly List _Concerns = new(); - private readonly List _Concerns = new(); + private readonly List _Functions = new(); - private readonly List _Functions = new(); + private IBuilder? _Serializers; - private IBuilder? _Serializers; + private IBuilder? _Injectors; - private IBuilder? _Injectors; + private IBuilder? _Formatters; - private IBuilder? _Formatters; + #region Functionality - #region Functionality + /// + /// Configures the serialization registry to be used by this handler. Allows + /// to add support for additional formats such as protobuf. + /// + /// The registry to be used by the handler + public InlineBuilder Serializers(IBuilder registry) + { + _Serializers = registry; + return this; + } - /// - /// Configures the serialization registry to be used by this handler. Allows - /// to add support for additional formats such as protobuf. - /// - /// The registry to be used by the handler - public InlineBuilder Serializers(IBuilder registry) - { - _Serializers = registry; - return this; - } + /// + /// Configures the injectors to be used to extract complex parameter values. + /// + /// The registry to be used by the handler + public InlineBuilder Injectors(IBuilder registry) + { + _Injectors = registry; + return this; + } - /// - /// Configures the injectors to be used to extract complex parameter values. - /// - /// The registry to be used by the handler - public InlineBuilder Injectors(IBuilder registry) - { - _Injectors = registry; - return this; - } + /// + /// Configures the formatters to be used to extract path values. + /// + /// The registry to be used by the handler + public InlineBuilder Formatters(IBuilder registry) + { + _Formatters = registry; + return this; + } - /// - /// Configures the formatters to be used to extract path values. - /// - /// The registry to be used by the handler - public InlineBuilder Formatters(IBuilder registry) - { - _Formatters = registry; - return this; - } + /// + /// Adds a route for a request of any type to the root of the handler. + /// + /// The logic to be executed + /// True to exclude the content from sitemaps etc. + /// A type implementing IContentHints to allow content discovery + public InlineBuilder Any(Delegate function, bool ignoreContent = false, Type? contentHints = null) + { + return On(function, ALL_METHODS, null, ignoreContent, contentHints); + } - /// - /// Adds a route for a request of any type to the root of the handler. - /// - /// The logic to be executed - /// True to exclude the content from sitemaps etc. - /// A type implementing IContentHints to allow content discovery - public InlineBuilder Any(Delegate function, bool ignoreContent = false, Type? contentHints = null) - { - return On(function, ALL_METHODS, null, ignoreContent, contentHints); - } + /// + /// Adds a route for a request of any type to the specified path. + /// + /// The path of the request to handle (e.g. "/my-method") + /// The logic to be executed + /// True to exclude the content from sitemaps etc. + /// A type implementing IContentHints to allow content discovery + public InlineBuilder Any(string path, Delegate function, bool ignoreContent = false, Type? contentHints = null) + { + return On(function, ALL_METHODS, path, ignoreContent, contentHints); + } - /// - /// Adds a route for a request of any type to the specified path. - /// - /// The path of the request to handle (e.g. "/my-method") - /// The logic to be executed - /// True to exclude the content from sitemaps etc. - /// A type implementing IContentHints to allow content discovery - public InlineBuilder Any(string path, Delegate function, bool ignoreContent = false, Type? contentHints = null) - { - return On(function, ALL_METHODS, path, ignoreContent, contentHints); - } + /// + /// Adds a route for a GET request to the root of the handler. + /// + /// The logic to be executed + /// True to exclude the content from sitemaps etc. + /// A type implementing IContentHints to allow content discovery + public InlineBuilder Get(Delegate function, bool ignoreContent = false, Type? contentHints = null) + { + return On(function, new() { FlexibleRequestMethod.Get(RequestMethod.GET) }, null, ignoreContent, contentHints); + } - /// - /// Adds a route for a GET request to the root of the handler. - /// - /// The logic to be executed - /// True to exclude the content from sitemaps etc. - /// A type implementing IContentHints to allow content discovery - public InlineBuilder Get(Delegate function, bool ignoreContent = false, Type? contentHints = null) - { - return On(function, new() { FlexibleRequestMethod.Get(RequestMethod.GET) }, null, ignoreContent, contentHints); - } + /// + /// Adds a route for a GET request to the specified path. + /// + /// The path of the request to handle (e.g. "/my-method") + /// The logic to be executed + /// True to exclude the content from sitemaps etc. + /// A type implementing IContentHints to allow content discovery + public InlineBuilder Get(string path, Delegate function, bool ignoreContent = false, Type? contentHints = null) + { + return On(function, new() { FlexibleRequestMethod.Get(RequestMethod.GET) }, path, ignoreContent, contentHints); + } - /// - /// Adds a route for a GET request to the specified path. - /// - /// The path of the request to handle (e.g. "/my-method") - /// The logic to be executed - /// True to exclude the content from sitemaps etc. - /// A type implementing IContentHints to allow content discovery - public InlineBuilder Get(string path, Delegate function, bool ignoreContent = false, Type? contentHints = null) - { - return On(function, new() { FlexibleRequestMethod.Get(RequestMethod.GET) }, path, ignoreContent, contentHints); - } + /// + /// Adds a route for a HEAD request to the root of the handler. + /// + /// The logic to be executed + public InlineBuilder Head(Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.HEAD) }); + + /// + /// Adds a route for a HEAD request to the specified path. + /// + /// The path of the request to handle (e.g. "/my-method") + public InlineBuilder Head(string path, Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.HEAD) }, path); + + /// + /// Adds a route for a POST request to the root of the handler. + /// + /// The logic to be executed + public InlineBuilder Post(Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.POST) }); + + /// + /// Adds a route for a POST request to the specified path. + /// + /// The path of the request to handle (e.g. "/my-method") + public InlineBuilder Post(string path, Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.POST) }, path); + + /// + /// Adds a route for a PUT request to the root of the handler. + /// + /// The logic to be executed + public InlineBuilder Put(Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.PUT) }); + + /// + /// Adds a route for a PUT request to the specified path. + /// + /// The path of the request to handle (e.g. "/my-method") + public InlineBuilder Put(string path, Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.PUT) }, path); + + /// + /// Adds a route for a DELETE request to the root of the handler. + /// + /// The logic to be executed + public InlineBuilder Delete(Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.DELETE) }); + + /// + /// Adds a route for a DELETE request to the specified path. + /// + /// The path of the request to handle (e.g. "/my-method") + public InlineBuilder Delete(string path, Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.DELETE) }, path); + + /// + /// Executes the given function for the specified path and method. + /// + /// The logic to be executed + /// The HTTP verbs to respond to + /// The path which needs to be specified by the client to call this logic + /// True to exclude the content from sitemaps etc. + /// A type implementing IContentHints to allow content discovery + public InlineBuilder On(Delegate function, HashSet? methods = null, string? path = null, bool ignoreContent = false, Type? contentHints = null) + { + var requestMethods = methods ?? new HashSet(); - /// - /// Adds a route for a HEAD request to the root of the handler. - /// - /// The logic to be executed - public InlineBuilder Head(Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.HEAD) }); - - /// - /// Adds a route for a HEAD request to the specified path. - /// - /// The path of the request to handle (e.g. "/my-method") - public InlineBuilder Head(string path, Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.HEAD) }, path); - - /// - /// Adds a route for a POST request to the root of the handler. - /// - /// The logic to be executed - public InlineBuilder Post(Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.POST) }); - - /// - /// Adds a route for a POST request to the specified path. - /// - /// The path of the request to handle (e.g. "/my-method") - public InlineBuilder Post(string path, Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.POST) }, path); - - /// - /// Adds a route for a PUT request to the root of the handler. - /// - /// The logic to be executed - public InlineBuilder Put(Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.PUT) }); - - /// - /// Adds a route for a PUT request to the specified path. - /// - /// The path of the request to handle (e.g. "/my-method") - public InlineBuilder Put(string path, Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.PUT) }, path); - - /// - /// Adds a route for a DELETE request to the root of the handler. - /// - /// The logic to be executed - public InlineBuilder Delete(Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.DELETE) }); - - /// - /// Adds a route for a DELETE request to the specified path. - /// - /// The path of the request to handle (e.g. "/my-method") - public InlineBuilder Delete(string path, Delegate function) => On(function, new() { FlexibleRequestMethod.Get(RequestMethod.DELETE) }, path); - - /// - /// Executes the given function for the specified path and method. - /// - /// The logic to be executed - /// The HTTP verbs to respond to - /// The path which needs to be specified by the client to call this logic - /// True to exclude the content from sitemaps etc. - /// A type implementing IContentHints to allow content discovery - public InlineBuilder On(Delegate function, HashSet? methods = null, string? path = null, bool ignoreContent = false, Type? contentHints = null) + if (requestMethods.Count == 1 && requestMethods.Contains(FlexibleRequestMethod.Get(RequestMethod.GET))) { - var requestMethods = methods ?? new HashSet(); - - if (requestMethods.Count == 1 && requestMethods.Contains(FlexibleRequestMethod.Get(RequestMethod.GET))) - { - requestMethods.Add(FlexibleRequestMethod.Get(RequestMethod.HEAD)); - } - - if (path?.StartsWith('/') == true) - { - path = path[1..]; - } - - var config = new MethodConfiguration(requestMethods, ignoreContent, contentHints); - - _Functions.Add(new InlineFunction(path, config, function)); - - return this; + requestMethods.Add(FlexibleRequestMethod.Get(RequestMethod.HEAD)); } - public InlineBuilder Add(IConcernBuilder concern) + if (path?.StartsWith('/') == true) { - _Concerns.Add(concern); - return this; + path = path[1..]; } - public IHandler Build(IHandler parent) - { - var serializers = (_Serializers ?? Serialization.Default()).Build(); + var config = new MethodConfiguration(requestMethods, ignoreContent, contentHints); - var injectors = (_Injectors ?? Injection.Default()).Build(); + _Functions.Add(new InlineFunction(path, config, function)); - var formatters = (_Formatters ?? Formatting.Default()).Build(); + return this; + } - return Concerns.Chain(parent, _Concerns, (p) => new InlineHandler(p, _Functions, serializers, injectors, formatters)); - } + public InlineBuilder Add(IConcernBuilder concern) + { + _Concerns.Add(concern); + return this; + } + + public IHandler Build(IHandler parent) + { + var serializers = (_Serializers ?? Serialization.Default()).Build(); - #endregion + var injectors = (_Injectors ?? Injection.Default()).Build(); + var formatters = (_Formatters ?? Formatting.Default()).Build(); + + return Concerns.Chain(parent, _Concerns, (p) => new InlineHandler(p, _Functions, serializers, injectors, formatters)); } + #endregion + } diff --git a/Modules/Functional/Provider/InlineFunction.cs b/Modules/Functional/Provider/InlineFunction.cs index 38638308..32cbbee0 100644 --- a/Modules/Functional/Provider/InlineFunction.cs +++ b/Modules/Functional/Provider/InlineFunction.cs @@ -2,9 +2,6 @@ using GenHTTP.Modules.Reflection; -namespace GenHTTP.Modules.Functional.Provider -{ +namespace GenHTTP.Modules.Functional.Provider; - public record InlineFunction(string? Path, IMethodConfiguration Configuration, Delegate Delegate); - -} +public record InlineFunction(string? Path, IMethodConfiguration Configuration, Delegate Delegate); diff --git a/Modules/Functional/Provider/InlineHandler.cs b/Modules/Functional/Provider/InlineHandler.cs index f7bf3969..4d59eaf0 100644 --- a/Modules/Functional/Provider/InlineHandler.cs +++ b/Modules/Functional/Provider/InlineHandler.cs @@ -6,63 +6,60 @@ using GenHTTP.Api.Protocol; using GenHTTP.Modules.Conversion.Formatters; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.Reflection; using GenHTTP.Modules.Reflection.Injectors; -namespace GenHTTP.Modules.Functional.Provider -{ +namespace GenHTTP.Modules.Functional.Provider; - public class InlineHandler : IHandler - { +public class InlineHandler : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private MethodCollection Methods { get; } + private MethodCollection Methods { get; } - private ResponseProvider ResponseProvider { get; } + private ResponseProvider ResponseProvider { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public InlineHandler(IHandler parent, List functions, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) - { - Parent = parent; + public InlineHandler(IHandler parent, List functions, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) + { + Parent = parent; - ResponseProvider = new(serialization, formatting); + ResponseProvider = new(serialization, formatting); - Methods = new(this, AnalyzeMethods(functions, serialization, injection, formatting)); - } + Methods = new(this, AnalyzeMethods(functions, serialization, injection, formatting)); + } - private IEnumerable> AnalyzeMethods(List functions, SerializationRegistry formats, InjectionRegistry injection, FormatterRegistry formatting) + private IEnumerable> AnalyzeMethods(List functions, SerializationRegistry formats, InjectionRegistry injection, FormatterRegistry formatting) + { + foreach (var function in functions) { - foreach (var function in functions) - { - var method = function.Delegate.Method; + var method = function.Delegate.Method; - var wildcardRoute = PathArguments.CheckWildcardRoute(method.ReturnType); + var wildcardRoute = PathArguments.CheckWildcardRoute(method.ReturnType); - var path = PathArguments.Route(function.Path, wildcardRoute); + var path = PathArguments.Route(function.Path, wildcardRoute); - var target = function.Delegate.Target ?? throw new InvalidOperationException("Delegate target must not be null"); + var target = function.Delegate.Target ?? throw new InvalidOperationException("Delegate target must not be null"); - yield return (parent) => new MethodHandler(parent, method, path, () => target, function.Configuration, ResponseProvider.GetResponseAsync, formats, injection, formatting); - } + yield return (parent) => new MethodHandler(parent, method, path, () => target, function.Configuration, ResponseProvider.GetResponseAsync, formats, injection, formatting); } + } - #endregion - - #region Functionality + #endregion - public ValueTask PrepareAsync() => Methods.PrepareAsync(); + #region Functionality - public ValueTask HandleAsync(IRequest request) => Methods.HandleAsync(request); + public ValueTask PrepareAsync() => Methods.PrepareAsync(); - #endregion + public ValueTask HandleAsync(IRequest request) => Methods.HandleAsync(request); - } + #endregion } diff --git a/Modules/Functional/Provider/MethodConfiguration.cs b/Modules/Functional/Provider/MethodConfiguration.cs index daa67143..b53a4cf2 100644 --- a/Modules/Functional/Provider/MethodConfiguration.cs +++ b/Modules/Functional/Provider/MethodConfiguration.cs @@ -4,9 +4,6 @@ using GenHTTP.Api.Protocol; using GenHTTP.Modules.Reflection; -namespace GenHTTP.Modules.Functional.Provider -{ +namespace GenHTTP.Modules.Functional.Provider; - public record MethodConfiguration(HashSet SupportedMethods, bool IgnoreContent, Type? ContentHints) : IMethodConfiguration; - -} +public record MethodConfiguration(HashSet SupportedMethods, bool IgnoreContent, Type? ContentHints) : IMethodConfiguration; diff --git a/Modules/IO/ChangeTracking.cs b/Modules/IO/ChangeTracking.cs index 4496d4f0..70246dbb 100644 --- a/Modules/IO/ChangeTracking.cs +++ b/Modules/IO/ChangeTracking.cs @@ -2,40 +2,37 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Modules.IO.Tracking; -namespace GenHTTP.Modules.IO -{ - - public static class ChangeTracking - { +namespace GenHTTP.Modules.IO; - /// - /// Wraps the given resource into a change tracker that can - /// tell, whether the content of the resource has changed - /// since it's content was read the last time. - /// - /// The resource to be tracked - /// The given resource with change tracking attached - /// - /// Use this mechanism for functionality that needs to - /// perform computation heavy work such as parsing - /// or interpreting the content of the resource. - /// - public static ChangeTrackingResource Track(this IResource resource) => new(resource); +public static class ChangeTracking +{ - /// - /// Builds the resource and wraps it into a change tracker that can - /// tell, whether the content of the resource has changed - /// since it's content was read the last time. - /// - /// The builder of the resource to be tracked - /// The newly created resource with change tracking attached - /// - /// Use this mechanism for functionality that needs to - /// perform computation heavy work such as parsing - /// or interpreting the content of the resource. - /// - public static ChangeTrackingResource BuildWithTracking(this IBuilder resourceBuilder) => new(resourceBuilder.Build()); + /// + /// Wraps the given resource into a change tracker that can + /// tell, whether the content of the resource has changed + /// since it's content was read the last time. + /// + /// The resource to be tracked + /// The given resource with change tracking attached + /// + /// Use this mechanism for functionality that needs to + /// perform computation heavy work such as parsing + /// or interpreting the content of the resource. + /// + public static ChangeTrackingResource Track(this IResource resource) => new(resource); - } + /// + /// Builds the resource and wraps it into a change tracker that can + /// tell, whether the content of the resource has changed + /// since it's content was read the last time. + /// + /// The builder of the resource to be tracked + /// The newly created resource with change tracking attached + /// + /// Use this mechanism for functionality that needs to + /// perform computation heavy work such as parsing + /// or interpreting the content of the resource. + /// + public static ChangeTrackingResource BuildWithTracking(this IBuilder resourceBuilder) => new(resourceBuilder.Build()); } diff --git a/Modules/IO/Content.cs b/Modules/IO/Content.cs index 95ff3804..c281d2b6 100644 --- a/Modules/IO/Content.cs +++ b/Modules/IO/Content.cs @@ -3,30 +3,27 @@ using GenHTTP.Modules.IO.Providers; -namespace GenHTTP.Modules.IO +namespace GenHTTP.Modules.IO; + +/// +/// Creates a handler that will generate a response from the content +/// of the given resource. +/// +public static class Content { /// - /// Creates a handler that will generate a response from the content + /// Creates a new handler that returns the content /// of the given resource. /// - public static class Content - { - - /// - /// Creates a new handler that returns the content - /// of the given resource. - /// - /// The resource to be served - public static ContentProviderBuilder From(IBuilder resource) => From(resource.Build()); - - /// - /// Creates a new handler that returns the content - /// of the given resource. - /// - /// The resource to be served - public static ContentProviderBuilder From(IResource resource) => new ContentProviderBuilder().Resource(resource); + /// The resource to be served + public static ContentProviderBuilder From(IBuilder resource) => From(resource.Build()); - } + /// + /// Creates a new handler that returns the content + /// of the given resource. + /// + /// The resource to be served + public static ContentProviderBuilder From(IResource resource) => new ContentProviderBuilder().Resource(resource); } diff --git a/Modules/IO/Download.cs b/Modules/IO/Download.cs index f7fca38e..394652d9 100644 --- a/Modules/IO/Download.cs +++ b/Modules/IO/Download.cs @@ -3,27 +3,24 @@ using GenHTTP.Modules.IO.Providers; -namespace GenHTTP.Modules.IO +namespace GenHTTP.Modules.IO; + +/// +/// Generates a file download response for a given resource. +/// +public static class Download { /// - /// Generates a file download response for a given resource. + /// Creates a new download handler for the given resource. /// - public static class Download - { - - /// - /// Creates a new download handler for the given resource. - /// - /// The resource to be provided - public static DownloadProviderBuilder From(IBuilder resource) => From(resource.Build()); - - /// - /// Creates a new download handler for the given resource. - /// - /// The resource to be provided - public static DownloadProviderBuilder From(IResource resource) => new DownloadProviderBuilder().Resource(resource); + /// The resource to be provided + public static DownloadProviderBuilder From(IBuilder resource) => From(resource.Build()); - } + /// + /// Creates a new download handler for the given resource. + /// + /// The resource to be provided + public static DownloadProviderBuilder From(IResource resource) => new DownloadProviderBuilder().Resource(resource); } diff --git a/Modules/IO/Embedded/EmbeddedResource.cs b/Modules/IO/Embedded/EmbeddedResource.cs index a1d31c19..d2e5d5f9 100644 --- a/Modules/IO/Embedded/EmbeddedResource.cs +++ b/Modules/IO/Embedded/EmbeddedResource.cs @@ -9,33 +9,32 @@ using GenHTTP.Modules.IO.Streaming; -namespace GenHTTP.Modules.IO.Embedded -{ +namespace GenHTTP.Modules.IO.Embedded; - public sealed class EmbeddedResource : IResource - { - private ulong? _Checksum; +public sealed class EmbeddedResource : IResource +{ + private ulong? _Checksum; - #region Get-/Setters + #region Get-/Setters - public Assembly Source { get; } + public Assembly Source { get; } - public string QualifiedName { get; } + public string QualifiedName { get; } - public string? Name { get; } + public string? Name { get; } - public DateTime? Modified { get; } + public DateTime? Modified { get; } - public FlexibleContentType? ContentType { get; } + public FlexibleContentType? ContentType { get; } - public ulong? Length { get; } + public ulong? Length { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public EmbeddedResource(Assembly source, string path, string? name, FlexibleContentType? contentType, DateTime? modified) - { + public EmbeddedResource(Assembly source, string path, string? name, FlexibleContentType? contentType, DateTime? modified) + { var fqn = source.GetManifestResourceNames() .FirstOrDefault(n => (n == path) || n.EndsWith($".{path}")); @@ -51,14 +50,14 @@ public EmbeddedResource(Assembly source, string path, string? name, FlexibleCont Length = (ulong)content.Length; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask GetContentAsync() => new(TryGetStream()); + public ValueTask GetContentAsync() => new(TryGetStream()); - public async ValueTask CalculateChecksumAsync() - { + public async ValueTask CalculateChecksumAsync() + { if (_Checksum is null) { using var stream = TryGetStream(); @@ -69,17 +68,15 @@ public async ValueTask CalculateChecksumAsync() return _Checksum.Value; } - public async ValueTask WriteAsync(Stream target, uint bufferSize) - { + public async ValueTask WriteAsync(Stream target, uint bufferSize) + { using var content = TryGetStream(); await content.CopyPooledAsync(target, bufferSize); } - private Stream TryGetStream() => Source.GetManifestResourceStream(QualifiedName) ?? throw new InvalidOperationException($"Unable to resolve resource '{QualifiedName}' in assembly '{Source}'"); - - #endregion + private Stream TryGetStream() => Source.GetManifestResourceStream(QualifiedName) ?? throw new InvalidOperationException($"Unable to resolve resource '{QualifiedName}' in assembly '{Source}'"); - } + #endregion } diff --git a/Modules/IO/Embedded/EmbeddedResourceBuilder.cs b/Modules/IO/Embedded/EmbeddedResourceBuilder.cs index 40da3e3e..4939d67b 100644 --- a/Modules/IO/Embedded/EmbeddedResourceBuilder.cs +++ b/Modules/IO/Embedded/EmbeddedResourceBuilder.cs @@ -7,53 +7,52 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.IO.Embedded -{ +namespace GenHTTP.Modules.IO.Embedded; - public sealed class EmbeddedResourceBuilder : IResourceBuilder - { - private Assembly? _Assembly; +public sealed class EmbeddedResourceBuilder : IResourceBuilder +{ + private Assembly? _Assembly; - private string? _Path, _Name; + private string? _Path, _Name; - private FlexibleContentType? _Type; + private FlexibleContentType? _Type; - private DateTime? _Modified; + private DateTime? _Modified; - #region Functionality + #region Functionality - public EmbeddedResourceBuilder Assembly(Assembly assembly) - { + public EmbeddedResourceBuilder Assembly(Assembly assembly) + { _Assembly = assembly; return this; } - public EmbeddedResourceBuilder Path(string name) - { + public EmbeddedResourceBuilder Path(string name) + { _Path = name; return this; } - public EmbeddedResourceBuilder Name(string name) - { + public EmbeddedResourceBuilder Name(string name) + { _Name = name; return this; } - public EmbeddedResourceBuilder Type(FlexibleContentType contentType) - { + public EmbeddedResourceBuilder Type(FlexibleContentType contentType) + { _Type = contentType; return this; } - public EmbeddedResourceBuilder Modified(DateTime modified) - { + public EmbeddedResourceBuilder Modified(DateTime modified) + { _Modified = modified; return this; } - public IResource Build() - { + public IResource Build() + { var path = _Path ?? throw new BuilderMissingPropertyException("path"); var assembly = _Assembly ?? System.Reflection.Assembly.GetCallingAssembly(); @@ -65,8 +64,6 @@ public IResource Build() return new EmbeddedResource(assembly, path, _Name, type, modified); } - #endregion - - } + #endregion } diff --git a/Modules/IO/Embedded/EmbeddedResourceContainer.cs b/Modules/IO/Embedded/EmbeddedResourceContainer.cs index ba082c1c..2f0d6bd5 100644 --- a/Modules/IO/Embedded/EmbeddedResourceContainer.cs +++ b/Modules/IO/Embedded/EmbeddedResourceContainer.cs @@ -5,25 +5,24 @@ using GenHTTP.Api.Content.IO; -namespace GenHTTP.Modules.IO.Embedded -{ +namespace GenHTTP.Modules.IO.Embedded; - internal class EmbeddedResourceContainer : IResourceContainer - { - private readonly Dictionary _Nodes = new(); +internal class EmbeddedResourceContainer : IResourceContainer +{ + private readonly Dictionary _Nodes = new(); - private readonly Dictionary _Resources = new(); + private readonly Dictionary _Resources = new(); - #region Get-/Setters + #region Get-/Setters - public DateTime? Modified { get; } + public DateTime? Modified { get; } - #endregion + #endregion - #region Initialization + #region Initialization - protected EmbeddedResourceContainer(Assembly source, string prefix) - { + protected EmbeddedResourceContainer(Assembly source, string prefix) + { Modified = source.GetModificationDate(); foreach (var resource in source.GetManifestResourceNames()) @@ -62,20 +61,18 @@ protected EmbeddedResourceContainer(Assembly source, string prefix) } - #endregion - - #region Functionality + #endregion - public ValueTask> GetNodes() => new(_Nodes.Values); + #region Functionality - public ValueTask> GetResources() => new(_Resources.Values); + public ValueTask> GetNodes() => new(_Nodes.Values); - public ValueTask TryGetNodeAsync(string name) => new(_Nodes.GetValueOrDefault(name)); + public ValueTask> GetResources() => new(_Resources.Values); - public ValueTask TryGetResourceAsync(string name) => new(_Resources.GetValueOrDefault(name)); + public ValueTask TryGetNodeAsync(string name) => new(_Nodes.GetValueOrDefault(name)); - #endregion + public ValueTask TryGetResourceAsync(string name) => new(_Resources.GetValueOrDefault(name)); - } + #endregion } diff --git a/Modules/IO/Embedded/EmbeddedResourceNode.cs b/Modules/IO/Embedded/EmbeddedResourceNode.cs index f3df69f1..633b62e5 100644 --- a/Modules/IO/Embedded/EmbeddedResourceNode.cs +++ b/Modules/IO/Embedded/EmbeddedResourceNode.cs @@ -1,30 +1,27 @@ using GenHTTP.Api.Content.IO; using System.Reflection; -namespace GenHTTP.Modules.IO.Embedded -{ +namespace GenHTTP.Modules.IO.Embedded; - internal class EmbeddedResourceNode : EmbeddedResourceContainer, IResourceNode - { +internal class EmbeddedResourceNode : EmbeddedResourceContainer, IResourceNode +{ - #region Get-/Setters + #region Get-/Setters - public string Name { get; } + public string Name { get; } - public IResourceContainer Parent { get; } + public IResourceContainer Parent { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal EmbeddedResourceNode(Assembly source, string prefix, IResourceContainer parent, string name) : base(source, prefix) - { + internal EmbeddedResourceNode(Assembly source, string prefix, IResourceContainer parent, string name) : base(source, prefix) + { Name = name; Parent = parent; } - #endregion - - } + #endregion } diff --git a/Modules/IO/Embedded/EmbeddedResourceTree.cs b/Modules/IO/Embedded/EmbeddedResourceTree.cs index 66f3382f..06b2f98e 100644 --- a/Modules/IO/Embedded/EmbeddedResourceTree.cs +++ b/Modules/IO/Embedded/EmbeddedResourceTree.cs @@ -2,14 +2,11 @@ using GenHTTP.Api.Content.IO; -namespace GenHTTP.Modules.IO.Embedded -{ - - internal class EmbeddedResourceTree : EmbeddedResourceContainer, IResourceTree - { +namespace GenHTTP.Modules.IO.Embedded; - internal EmbeddedResourceTree(Assembly source, string root) : base(source, root) { } +internal class EmbeddedResourceTree : EmbeddedResourceContainer, IResourceTree +{ - } + internal EmbeddedResourceTree(Assembly source, string root) : base(source, root) { } } diff --git a/Modules/IO/Embedded/EmbeddedResourceTreeBuilder.cs b/Modules/IO/Embedded/EmbeddedResourceTreeBuilder.cs index 56cca9cf..60cb0ebb 100644 --- a/Modules/IO/Embedded/EmbeddedResourceTreeBuilder.cs +++ b/Modules/IO/Embedded/EmbeddedResourceTreeBuilder.cs @@ -3,31 +3,30 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.IO.Embedded -{ +namespace GenHTTP.Modules.IO.Embedded; - public sealed class EmbeddedResourceTreeBuilder : IBuilder - { - private string? _Root; +public sealed class EmbeddedResourceTreeBuilder : IBuilder +{ + private string? _Root; - private Assembly? _Source; + private Assembly? _Source; - #region Functionality + #region Functionality - public EmbeddedResourceTreeBuilder Source(Assembly source) - { + public EmbeddedResourceTreeBuilder Source(Assembly source) + { _Source = source; return this; } - public EmbeddedResourceTreeBuilder Root(string root) - { + public EmbeddedResourceTreeBuilder Root(string root) + { _Root = root; return this; } - public IResourceTree Build() - { + public IResourceTree Build() + { var source = _Source ?? throw new BuilderMissingPropertyException("source"); var root = _Root ?? throw new BuilderMissingPropertyException("root"); @@ -35,8 +34,6 @@ public IResourceTree Build() return new EmbeddedResourceTree(source, root); } - #endregion - - } + #endregion } diff --git a/Modules/IO/Embedded/Extensions.cs b/Modules/IO/Embedded/Extensions.cs index 86539917..35a3fc50 100644 --- a/Modules/IO/Embedded/Extensions.cs +++ b/Modules/IO/Embedded/Extensions.cs @@ -2,14 +2,13 @@ using System.IO; using System.Reflection; -namespace GenHTTP.Modules.IO.Embedded +namespace GenHTTP.Modules.IO.Embedded; + +internal static class Extensions { - internal static class Extensions + internal static DateTime GetModificationDate(this Assembly assembly) { - - internal static DateTime GetModificationDate(this Assembly assembly) - { #pragma warning disable IL3000 var location = assembly.Location; @@ -24,6 +23,4 @@ internal static DateTime GetModificationDate(this Assembly assembly) #pragma warning restore IL3000 } - } - } diff --git a/Modules/IO/Extensions.cs b/Modules/IO/Extensions.cs index 5edffaa2..d4e3bf10 100644 --- a/Modules/IO/Extensions.cs +++ b/Modules/IO/Extensions.cs @@ -1,22 +1,19 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.IO +namespace GenHTTP.Modules.IO; + +public static class Extensions { - public static class Extensions + /// + /// Configures the server to respond with partial responses if + /// requested by the client. + /// + /// The host to add the feature to + public static IServerHost RangeSupport(this IServerHost host) { - - /// - /// Configures the server to respond with partial responses if - /// requested by the client. - /// - /// The host to add the feature to - public static IServerHost RangeSupport(this IServerHost host) - { host.Add(IO.RangeSupport.Create()); return host; } - } - } diff --git a/Modules/IO/FileSystem/DirectoryContainer.cs b/Modules/IO/FileSystem/DirectoryContainer.cs index 1a7d5abd..9251dd6b 100644 --- a/Modules/IO/FileSystem/DirectoryContainer.cs +++ b/Modules/IO/FileSystem/DirectoryContainer.cs @@ -5,33 +5,32 @@ using GenHTTP.Api.Content.IO; -namespace GenHTTP.Modules.IO.FileSystem -{ +namespace GenHTTP.Modules.IO.FileSystem; - internal class DirectoryContainer : IResourceContainer - { +internal class DirectoryContainer : IResourceContainer +{ - #region Get-/Setters + #region Get-/Setters - protected DirectoryInfo Directory { get; } + protected DirectoryInfo Directory { get; } - public DateTime? Modified => Directory.LastWriteTimeUtc; + public DateTime? Modified => Directory.LastWriteTimeUtc; - #endregion + #endregion - #region Initialization + #region Initialization - protected DirectoryContainer(DirectoryInfo directory) - { + protected DirectoryContainer(DirectoryInfo directory) + { Directory = directory; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask> GetNodes() - { + public ValueTask> GetNodes() + { var result = new List(); foreach (var directory in Directory.EnumerateDirectories()) @@ -42,8 +41,8 @@ public ValueTask> GetNodes() return new(result); } - public ValueTask> GetResources() - { + public ValueTask> GetResources() + { var result = new List(); foreach (var file in Directory.EnumerateFiles()) @@ -54,8 +53,8 @@ public ValueTask> GetResources() return new(result); } - public ValueTask TryGetNodeAsync(string name) - { + public ValueTask TryGetNodeAsync(string name) + { var path = Path.Combine(Directory.FullName, name); var directory = new DirectoryInfo(path); @@ -68,8 +67,8 @@ public ValueTask> GetResources() return new(); } - public ValueTask TryGetResourceAsync(string name) - { + public ValueTask TryGetResourceAsync(string name) + { var path = Path.Combine(Directory.FullName, name); var file = new FileInfo(path); @@ -82,8 +81,6 @@ public ValueTask> GetResources() return new(); } - #endregion - - } + #endregion } diff --git a/Modules/IO/FileSystem/DirectoryNode.cs b/Modules/IO/FileSystem/DirectoryNode.cs index 7521a457..09bf5599 100644 --- a/Modules/IO/FileSystem/DirectoryNode.cs +++ b/Modules/IO/FileSystem/DirectoryNode.cs @@ -2,29 +2,26 @@ using GenHTTP.Api.Content.IO; -namespace GenHTTP.Modules.IO.FileSystem -{ +namespace GenHTTP.Modules.IO.FileSystem; - internal class DirectoryNode : DirectoryContainer, IResourceNode - { +internal class DirectoryNode : DirectoryContainer, IResourceNode +{ - #region Get-/Setters + #region Get-/Setters - public string Name => Directory.Name; + public string Name => Directory.Name; - public IResourceContainer Parent { get; } + public IResourceContainer Parent { get; } - #endregion + #endregion - #region Initialization + #region Initialization - internal DirectoryNode(DirectoryInfo directory, IResourceContainer parent) : base(directory) - { + internal DirectoryNode(DirectoryInfo directory, IResourceContainer parent) : base(directory) + { Parent = parent; } - #endregion - - } + #endregion } diff --git a/Modules/IO/FileSystem/DirectoryTree.cs b/Modules/IO/FileSystem/DirectoryTree.cs index ccf3891a..f4d9a958 100644 --- a/Modules/IO/FileSystem/DirectoryTree.cs +++ b/Modules/IO/FileSystem/DirectoryTree.cs @@ -2,18 +2,15 @@ using GenHTTP.Api.Content.IO; -namespace GenHTTP.Modules.IO.FileSystem -{ - - internal class DirectoryTree : DirectoryContainer, IResourceTree - { +namespace GenHTTP.Modules.IO.FileSystem; - #region Initialization +internal class DirectoryTree : DirectoryContainer, IResourceTree +{ - internal DirectoryTree(DirectoryInfo directory) : base(directory) { } + #region Initialization - #endregion + internal DirectoryTree(DirectoryInfo directory) : base(directory) { } - } + #endregion } diff --git a/Modules/IO/FileSystem/DirectoryTreeBuilder.cs b/Modules/IO/FileSystem/DirectoryTreeBuilder.cs index 782d0f1b..93dff4ec 100644 --- a/Modules/IO/FileSystem/DirectoryTreeBuilder.cs +++ b/Modules/IO/FileSystem/DirectoryTreeBuilder.cs @@ -3,23 +3,22 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.IO.FileSystem -{ +namespace GenHTTP.Modules.IO.FileSystem; - public sealed class DirectoryTreeBuilder : IBuilder - { - private DirectoryInfo? _Directory; +public sealed class DirectoryTreeBuilder : IBuilder +{ + private DirectoryInfo? _Directory; - #region Functionality + #region Functionality - public DirectoryTreeBuilder Directory(DirectoryInfo directory) - { + public DirectoryTreeBuilder Directory(DirectoryInfo directory) + { _Directory = directory; return this; } - public IResourceTree Build() - { + public IResourceTree Build() + { var directory = _Directory ?? throw new BuilderMissingPropertyException("directory"); if (!directory.Exists) @@ -30,8 +29,6 @@ public IResourceTree Build() return new DirectoryTree(directory); } - #endregion - - } + #endregion } diff --git a/Modules/IO/FileSystem/FileResource.cs b/Modules/IO/FileSystem/FileResource.cs index a8e03c4e..8d19660b 100644 --- a/Modules/IO/FileSystem/FileResource.cs +++ b/Modules/IO/FileSystem/FileResource.cs @@ -9,44 +9,43 @@ using GenHTTP.Modules.IO.Streaming; -namespace GenHTTP.Modules.IO.FileSystem -{ +namespace GenHTTP.Modules.IO.FileSystem; - public sealed class FileResource : IResource - { +public sealed class FileResource : IResource +{ - #region Get-/Setters + #region Get-/Setters - public FileInfo File { get; } + public FileInfo File { get; } - public string? Name { get; } + public string? Name { get; } - public DateTime? Modified + public DateTime? Modified + { + get { - get - { File.Refresh(); return File.LastWriteTimeUtc; } - } + } - public FlexibleContentType? ContentType { get; } + public FlexibleContentType? ContentType { get; } - public ulong? Length + public ulong? Length + { + get { - get - { File.Refresh(); return (ulong)File.Length; } - } + } - #endregion + #endregion - #region Initialization + #region Initialization - public FileResource(FileInfo file, string? name, FlexibleContentType? contentType) - { + public FileResource(FileInfo file, string? name, FlexibleContentType? contentType) + { if (!file.Exists) { throw new FileNotFoundException("File does not exist", file.FullName); @@ -59,17 +58,17 @@ public FileResource(FileInfo file, string? name, FlexibleContentType? contentTyp ContentType = contentType ?? FlexibleContentType.Get(Name.GuessContentType() ?? Api.Protocol.ContentType.ApplicationForceDownload); } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask GetContentAsync() - { + public ValueTask GetContentAsync() + { return new ValueTask(File.OpenRead()); } - public ValueTask CalculateChecksumAsync() - { + public ValueTask CalculateChecksumAsync() + { unchecked { ulong hash = 17; @@ -83,15 +82,13 @@ public ValueTask CalculateChecksumAsync() } } - public async ValueTask WriteAsync(Stream target, uint bufferSize) - { + public async ValueTask WriteAsync(Stream target, uint bufferSize) + { using var content = File.OpenRead(); await content.CopyPooledAsync(target, bufferSize); } - #endregion - - } + #endregion } diff --git a/Modules/IO/FileSystem/FileResourceBuilder.cs b/Modules/IO/FileSystem/FileResourceBuilder.cs index 1294f53f..bd68aec9 100644 --- a/Modules/IO/FileSystem/FileResourceBuilder.cs +++ b/Modules/IO/FileSystem/FileResourceBuilder.cs @@ -5,44 +5,43 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.FileSystem -{ +namespace GenHTTP.Modules.IO.FileSystem; - public sealed class FileResourceBuilder : IResourceBuilder - { - private FileInfo? _File; +public sealed class FileResourceBuilder : IResourceBuilder +{ + private FileInfo? _File; - private string? _Name; + private string? _Name; - private FlexibleContentType? _Type; + private FlexibleContentType? _Type; - #region Functionality + #region Functionality - public FileResourceBuilder File(FileInfo file) - { + public FileResourceBuilder File(FileInfo file) + { _File = file; return this; } - public FileResourceBuilder Name(string name) - { + public FileResourceBuilder Name(string name) + { _Name = name; return this; } - public FileResourceBuilder Type(FlexibleContentType contentType) - { + public FileResourceBuilder Type(FlexibleContentType contentType) + { _Type = contentType; return this; } - public FileResourceBuilder Modified(DateTime modified) - { + public FileResourceBuilder Modified(DateTime modified) + { throw new NotSupportedException("Modification date of file resources cannot be changed"); } - public IResource Build() - { + public IResource Build() + { var file = _File ?? throw new BuilderMissingPropertyException("file"); if (!file.Exists) @@ -53,8 +52,6 @@ public IResource Build() return new FileResource(file, _Name, _Type); } - #endregion - - } + #endregion } diff --git a/Modules/IO/Providers/ContentProvider.cs b/Modules/IO/Providers/ContentProvider.cs index 740fabbc..37e53ab3 100644 --- a/Modules/IO/Providers/ContentProvider.cs +++ b/Modules/IO/Providers/ContentProvider.cs @@ -7,28 +7,27 @@ using GenHTTP.Modules.Basics; using GenHTTP.Modules.IO.Streaming; -namespace GenHTTP.Modules.IO.Providers -{ +namespace GenHTTP.Modules.IO.Providers; - public sealed class ContentProvider : IHandler - { +public sealed class ContentProvider : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public IResource Resource { get; } + public IResource Resource { get; } - private IResponseContent Content { get; } + private IResponseContent Content { get; } - private FlexibleContentType ContentType { get; } + private FlexibleContentType ContentType { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ContentProvider(IHandler parent, IResource resourceProvider) - { + public ContentProvider(IHandler parent, IResource resourceProvider) + { Parent = parent; Resource = resourceProvider; @@ -36,22 +35,20 @@ public ContentProvider(IHandler parent, IResource resourceProvider) ContentType = Resource.ContentType ?? FlexibleContentType.Get(Resource.Name?.GuessContentType() ?? Api.Protocol.ContentType.ApplicationForceDownload); } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { return request.Respond() .Content(Content) .Type(ContentType) .BuildTask(); } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; - - #endregion + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - } + #endregion } diff --git a/Modules/IO/Providers/ContentProviderBuilder.cs b/Modules/IO/Providers/ContentProviderBuilder.cs index e08dafb7..dbbb15a1 100644 --- a/Modules/IO/Providers/ContentProviderBuilder.cs +++ b/Modules/IO/Providers/ContentProviderBuilder.cs @@ -4,38 +4,35 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Content.IO; -namespace GenHTTP.Modules.IO.Providers -{ +namespace GenHTTP.Modules.IO.Providers; - public sealed class ContentProviderBuilder : IHandlerBuilder - { - private IResource? _ResourceProvider; +public sealed class ContentProviderBuilder : IHandlerBuilder +{ + private IResource? _ResourceProvider; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - #region Functionality + #region Functionality - public ContentProviderBuilder Resource(IResource resource) - { + public ContentProviderBuilder Resource(IResource resource) + { _ResourceProvider = resource; return this; } - public ContentProviderBuilder Add(IConcernBuilder concern) - { + public ContentProviderBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { var resource = _ResourceProvider ?? throw new BuilderMissingPropertyException("resourceProvider"); return Concerns.Chain(parent, _Concerns, (p) => new ContentProvider(p, resource)); } - #endregion - - } + #endregion } diff --git a/Modules/IO/Providers/DownloadProvider.cs b/Modules/IO/Providers/DownloadProvider.cs index ef434727..237f4326 100644 --- a/Modules/IO/Providers/DownloadProvider.cs +++ b/Modules/IO/Providers/DownloadProvider.cs @@ -6,28 +6,27 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.IO.Providers +namespace GenHTTP.Modules.IO.Providers; + +public sealed class DownloadProvider : IHandler { - - public sealed class DownloadProvider : IHandler - { - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public IResource Resource { get; } + public IResource Resource { get; } - public string? FileName { get; } + public string? FileName { get; } - private FlexibleContentType ContentType { get; } + private FlexibleContentType ContentType { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public DownloadProvider(IHandler parent, IResource resourceProvider, string? fileName, FlexibleContentType? contentType) - { + public DownloadProvider(IHandler parent, IResource resourceProvider, string? fileName, FlexibleContentType? contentType) + { Parent = parent; Resource = resourceProvider; @@ -37,12 +36,12 @@ public DownloadProvider(IHandler parent, IResource resourceProvider, string? fil ContentType = contentType ?? Resource.ContentType ?? FlexibleContentType.Get(FileName?.GuessContentType() ?? Api.Protocol.ContentType.ApplicationForceDownload); } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { if (!request.Target.Ended) { return new ValueTask(); @@ -71,10 +70,8 @@ public DownloadProvider(IHandler parent, IResource resourceProvider, string? fil return new ValueTask(response.Build()); } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; - - #endregion + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - } + #endregion } diff --git a/Modules/IO/Providers/DownloadProviderBuilder.cs b/Modules/IO/Providers/DownloadProviderBuilder.cs index 163baa91..9236263b 100644 --- a/Modules/IO/Providers/DownloadProviderBuilder.cs +++ b/Modules/IO/Providers/DownloadProviderBuilder.cs @@ -5,56 +5,53 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.Providers -{ +namespace GenHTTP.Modules.IO.Providers; - public sealed class DownloadProviderBuilder : IHandlerBuilder - { - private IResource? _ResourceProvider; +public sealed class DownloadProviderBuilder : IHandlerBuilder +{ + private IResource? _ResourceProvider; - private string? _FileName; + private string? _FileName; - private FlexibleContentType? _ContentType; + private FlexibleContentType? _ContentType; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - #region Functionality + #region Functionality - public DownloadProviderBuilder Resource(IResource resource) - { + public DownloadProviderBuilder Resource(IResource resource) + { _ResourceProvider = resource; return this; } - public DownloadProviderBuilder Type(ContentType contentType, string? charset = null) => Type(FlexibleContentType.Get(contentType, charset)); + public DownloadProviderBuilder Type(ContentType contentType, string? charset = null) => Type(FlexibleContentType.Get(contentType, charset)); - public DownloadProviderBuilder Type(FlexibleContentType contentType) - { + public DownloadProviderBuilder Type(FlexibleContentType contentType) + { _ContentType = contentType; return this; } - public DownloadProviderBuilder FileName(string fileName) - { + public DownloadProviderBuilder FileName(string fileName) + { _FileName = fileName; return this; } - public DownloadProviderBuilder Add(IConcernBuilder concern) - { + public DownloadProviderBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { var resource = _ResourceProvider ?? throw new BuilderMissingPropertyException("resourceProvider"); return Concerns.Chain(parent, _Concerns, (p) => new DownloadProvider(p, resource, _FileName, _ContentType)); } - #endregion - - } + #endregion -} +} \ No newline at end of file diff --git a/Modules/IO/Providers/ResourceHandler.cs b/Modules/IO/Providers/ResourceHandler.cs index 020c704c..97e4d433 100644 --- a/Modules/IO/Providers/ResourceHandler.cs +++ b/Modules/IO/Providers/ResourceHandler.cs @@ -6,36 +6,35 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.IO.Providers -{ +namespace GenHTTP.Modules.IO.Providers; - public sealed class ResourceHandler : IHandler - { +public sealed class ResourceHandler : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private IResourceTree Tree { get; } + private IResourceTree Tree { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ResourceHandler(IHandler parent, IResourceTree tree) - { + public ResourceHandler(IHandler parent, IResourceTree tree) + { Parent = parent; Tree = tree; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var (_, resource) = await Tree.Find(request.Target); if (resource is not null) @@ -51,8 +50,6 @@ public ResourceHandler(IHandler parent, IResourceTree tree) return null; } - #endregion - - } + #endregion } diff --git a/Modules/IO/Providers/ResourceHandlerBuilder.cs b/Modules/IO/Providers/ResourceHandlerBuilder.cs index 5a728fcc..b51d3807 100644 --- a/Modules/IO/Providers/ResourceHandlerBuilder.cs +++ b/Modules/IO/Providers/ResourceHandlerBuilder.cs @@ -4,40 +4,37 @@ using System.Collections.Generic; -namespace GenHTTP.Modules.IO.Providers -{ +namespace GenHTTP.Modules.IO.Providers; - public sealed class ResourceHandlerBuilder : IHandlerBuilder - { - private readonly List _Concerns = new(); +public sealed class ResourceHandlerBuilder : IHandlerBuilder +{ + private readonly List _Concerns = new(); - private IResourceTree? _Tree; + private IResourceTree? _Tree; - #region Functionality + #region Functionality - public ResourceHandlerBuilder Tree(IBuilder tree) => Tree(tree.Build()); + public ResourceHandlerBuilder Tree(IBuilder tree) => Tree(tree.Build()); - public ResourceHandlerBuilder Tree(IResourceTree tree) - { + public ResourceHandlerBuilder Tree(IResourceTree tree) + { _Tree = tree; return this; } - public ResourceHandlerBuilder Add(IConcernBuilder concern) - { + public ResourceHandlerBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { var tree = _Tree ?? throw new BuilderMissingPropertyException("tree"); return Concerns.Chain(parent, _Concerns, (p) => new ResourceHandler(p, tree)); } - #endregion - - } + #endregion } diff --git a/Modules/IO/RangeSupport.cs b/Modules/IO/RangeSupport.cs index b6789086..63c563bb 100644 --- a/Modules/IO/RangeSupport.cs +++ b/Modules/IO/RangeSupport.cs @@ -2,27 +2,24 @@ using GenHTTP.Modules.IO.Ranges; -namespace GenHTTP.Modules.IO -{ +namespace GenHTTP.Modules.IO; - public static class RangeSupport - { +public static class RangeSupport +{ - public static RangeSupportConcernBuilder Create() => new(); + public static RangeSupportConcernBuilder Create() => new(); - #region Extensions + #region Extensions - /// - /// Adds range support as a concern to the given builder. - /// - public static T AddRangeSupport(this T builder) where T : IHandlerBuilder - { + /// + /// Adds range support as a concern to the given builder. + /// + public static T AddRangeSupport(this T builder) where T : IHandlerBuilder + { builder.Add(Create()); return builder; } - #endregion - - } + #endregion } diff --git a/Modules/IO/Ranges/RangeSupportConcern.cs b/Modules/IO/Ranges/RangeSupportConcern.cs index f62f9439..54e5a0c2 100644 --- a/Modules/IO/Ranges/RangeSupportConcern.cs +++ b/Modules/IO/Ranges/RangeSupportConcern.cs @@ -5,40 +5,39 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.Ranges -{ +namespace GenHTTP.Modules.IO.Ranges; - public partial class RangeSupportConcern : IConcern - { - private static readonly Regex _PATTERN = CreatePattern(); +public partial class RangeSupportConcern : IConcern +{ + private static readonly Regex _PATTERN = CreatePattern(); - #region Get-/Setters + #region Get-/Setters - public IHandler Content { get; } + public IHandler Content { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public RangeSupportConcern(IHandler parent, Func contentFactory) - { + public RangeSupportConcern(IHandler parent, Func contentFactory) + { Parent = parent; Content = contentFactory(this); } - [GeneratedRegex(@"^\s*bytes\s*=\s*([0-9]*)-([0-9]*)\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] - private static partial Regex CreatePattern(); + [GeneratedRegex(@"^\s*bytes\s*=\s*([0-9]*)-([0-9]*)\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] + private static partial Regex CreatePattern(); - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { if (request.Method == RequestMethod.GET || request.Method == RequestMethod.HEAD) { var response = await Content.HandleAsync(request); @@ -98,29 +97,29 @@ public RangeSupportConcern(IHandler parent, Func contentFact } } - private static IResponse GetRangeFromStart(IRequest request, IResponse response, ulong length, ulong start) - { + private static IResponse GetRangeFromStart(IRequest request, IResponse response, ulong length, ulong start) + { if (start > length) return NotSatisfiable(request, length); return GetRange(response, start, length - 1, length); } - private static IResponse GetRangeFromEnd(IRequest request, IResponse response, ulong length, ulong end) - { + private static IResponse GetRangeFromEnd(IRequest request, IResponse response, ulong length, ulong end) + { if (end > length) return NotSatisfiable(request, length); return GetRange(response, length - end, length - 1, length); } - private static IResponse GetFullRange(IRequest request, IResponse response, ulong length, ulong start, ulong end) - { + private static IResponse GetFullRange(IRequest request, IResponse response, ulong length, ulong start, ulong end) + { if (start > end || end >= length) return NotSatisfiable(request, length); return GetRange(response, start, end, length); } - private static IResponse GetRange(IResponse response, ulong actualStart, ulong actualEnd, ulong totalLength) - { + private static IResponse GetRange(IResponse response, ulong actualStart, ulong actualEnd, ulong totalLength) + { response.Status = new(ResponseStatus.PartialContent); response["Content-Range"] = $"bytes {actualStart}-{actualEnd}/{totalLength}"; @@ -131,8 +130,8 @@ private static IResponse GetRange(IResponse response, ulong actualStart, ulong a return response; } - private static IResponse NotSatisfiable(IRequest request, ulong totalLength) - { + private static IResponse NotSatisfiable(IRequest request, ulong totalLength) + { var content = Resource.FromString($"Requested length cannot be satisfied (available = {totalLength} bytes)") .Build(); @@ -143,8 +142,6 @@ private static IResponse NotSatisfiable(IRequest request, ulong totalLength) .Build(); } - #endregion - - } + #endregion -} +} \ No newline at end of file diff --git a/Modules/IO/Ranges/RangeSupportConcernBuilder.cs b/Modules/IO/Ranges/RangeSupportConcernBuilder.cs index 778318aa..a8c53dcc 100644 --- a/Modules/IO/Ranges/RangeSupportConcernBuilder.cs +++ b/Modules/IO/Ranges/RangeSupportConcernBuilder.cs @@ -2,21 +2,18 @@ using GenHTTP.Api.Content; -namespace GenHTTP.Modules.IO.Ranges -{ +namespace GenHTTP.Modules.IO.Ranges; - public class RangeSupportConcernBuilder : IConcernBuilder - { +public class RangeSupportConcernBuilder : IConcernBuilder +{ - #region Functionality + #region Functionality - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { return new RangeSupportConcern(parent, contentFactory); } - #endregion - - } + #endregion } diff --git a/Modules/IO/Ranges/RangedContent.cs b/Modules/IO/Ranges/RangedContent.cs index 4fc639d6..e27f2c86 100644 --- a/Modules/IO/Ranges/RangedContent.cs +++ b/Modules/IO/Ranges/RangedContent.cs @@ -3,28 +3,27 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.Ranges -{ +namespace GenHTTP.Modules.IO.Ranges; - public class RangedContent : IResponseContent - { +public class RangedContent : IResponseContent +{ - #region Get-/Setters + #region Get-/Setters - public ulong? Length { get; } + public ulong? Length { get; } - private IResponseContent Source { get; } + private IResponseContent Source { get; } - private ulong Start { get; } + private ulong Start { get; } - private ulong End { get; } + private ulong End { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public RangedContent(IResponseContent source, ulong start, ulong end) - { + public RangedContent(IResponseContent source, ulong start, ulong end) + { Source = source; Start = start; @@ -33,12 +32,12 @@ public RangedContent(IResponseContent source, ulong start, ulong end) Length = (End - Start); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask CalculateChecksumAsync() - { + public async ValueTask CalculateChecksumAsync() + { var checksum = await Source.CalculateChecksumAsync(); if (checksum != null) @@ -53,10 +52,8 @@ public RangedContent(IResponseContent source, ulong start, ulong end) return checksum; } - public ValueTask WriteAsync(Stream target, uint bufferSize) => Source.WriteAsync(new RangedStream(target, Start, End), bufferSize); - - #endregion + public ValueTask WriteAsync(Stream target, uint bufferSize) => Source.WriteAsync(new RangedStream(target, Start, End), bufferSize); - } + #endregion } diff --git a/Modules/IO/Ranges/RangedStream.cs b/Modules/IO/Ranges/RangedStream.cs index 41ce8baf..9a49c9f2 100644 --- a/Modules/IO/Ranges/RangedStream.cs +++ b/Modules/IO/Ranges/RangedStream.cs @@ -1,61 +1,60 @@ using System; using System.IO; -namespace GenHTTP.Modules.IO.Ranges -{ +namespace GenHTTP.Modules.IO.Ranges; - /// - /// A stream that can be configured to just write a specified - /// portion of the incoming data into the underlying stream. - /// - public class RangedStream : Stream - { +/// +/// A stream that can be configured to just write a specified +/// portion of the incoming data into the underlying stream. +/// +public class RangedStream : Stream +{ - #region Get-/Setters + #region Get-/Setters - private Stream Target { get; } + private Stream Target { get; } - private long Start { get; } + private long Start { get; } - private long End { get; } + private long End { get; } - public override bool CanRead => false; + public override bool CanRead => false; - public override bool CanSeek => false; + public override bool CanSeek => false; - public override bool CanWrite => true; + public override bool CanWrite => true; - public override long Length => (End - Start); + public override long Length => (End - Start); - public override long Position { get; set; } + public override long Position { get; set; } - #endregion + #endregion - #region Initialization + #region Initialization - /// - /// Creates a ranged stream that writes the specified portion of - /// data to the given target stream. - /// - /// The stream to write to - /// The zero based index of the starting position - /// The zero based index of the inclusive end position - public RangedStream(Stream target, ulong start, ulong end) - { + /// + /// Creates a ranged stream that writes the specified portion of + /// data to the given target stream. + /// + /// The stream to write to + /// The zero based index of the starting position + /// The zero based index of the inclusive end position + public RangedStream(Stream target, ulong start, ulong end) + { Target = target; Start = (long)start; End = (long)end; } - #endregion + #endregion - #region Functionality + #region Functionality - public override void Flush() => Target.Flush(); + public override void Flush() => Target.Flush(); - public override void Write(byte[] buffer, int offset, int count) - { + public override void Write(byte[] buffer, int offset, int count) + { if (Position > End) return; long actualOffset = offset; @@ -82,14 +81,12 @@ public override void Write(byte[] buffer, int offset, int count) Position += count; } - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - public override void SetLength(long value) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - #endregion + public override void SetLength(long value) => throw new NotSupportedException(); - } + #endregion } diff --git a/Modules/IO/Resource.cs b/Modules/IO/Resource.cs index 68f03d70..5d4f791c 100644 --- a/Modules/IO/Resource.cs +++ b/Modules/IO/Resource.cs @@ -5,49 +5,46 @@ using GenHTTP.Modules.IO.FileSystem; using GenHTTP.Modules.IO.Strings; -namespace GenHTTP.Modules.IO +namespace GenHTTP.Modules.IO; + +/// +/// Allows to build resource instances which can be used by other +/// handlers to generate their content. +/// +public static class Resource { /// - /// Allows to build resource instances which can be used by other - /// handlers to generate their content. + /// Generates a resource from the given string. + /// + /// The string that will be returned by the resource (UTF-8-encoded) + public static StringResourceBuilder FromString(string data) => new StringResourceBuilder().Content(data); + + /// + /// Searches within the current assembly for an embedded resource + /// with the given name and generates a resource from it. + /// + /// The name of the resource to search for (may be fully qualified or not, e.g. "File.txt") + public static EmbeddedResourceBuilder FromAssembly(string name) => new EmbeddedResourceBuilder().Path(name).Assembly(Assembly.GetCallingAssembly()); + + /// + /// Searches within the given assembly for an embedded resource + /// with the specified name and generates a resource from it. + /// + /// The assembly to search the embedded resource in + /// The name of the resource to search for (may be fully qualified or not, e.g. "File.txt") + public static EmbeddedResourceBuilder FromAssembly(Assembly assembly, string name) => new EmbeddedResourceBuilder().Assembly(assembly).Path(name); + + /// + /// Generates a resource from the given file. + /// + /// The path of the file to be provided + public static FileResourceBuilder FromFile(string file) => FromFile(new FileInfo(file)); + + /// + /// Generates a resource from the given file. /// - public static class Resource - { - - /// - /// Generates a resource from the given string. - /// - /// The string that will be returned by the resource (UTF-8-encoded) - public static StringResourceBuilder FromString(string data) => new StringResourceBuilder().Content(data); - - /// - /// Searches within the current assembly for an embedded resource - /// with the given name and generates a resource from it. - /// - /// The name of the resource to search for (may be fully qualified or not, e.g. "File.txt") - public static EmbeddedResourceBuilder FromAssembly(string name) => new EmbeddedResourceBuilder().Path(name).Assembly(Assembly.GetCallingAssembly()); - - /// - /// Searches within the given assembly for an embedded resource - /// with the specified name and generates a resource from it. - /// - /// The assembly to search the embedded resource in - /// The name of the resource to search for (may be fully qualified or not, e.g. "File.txt") - public static EmbeddedResourceBuilder FromAssembly(Assembly assembly, string name) => new EmbeddedResourceBuilder().Assembly(assembly).Path(name); - - /// - /// Generates a resource from the given file. - /// - /// The path of the file to be provided - public static FileResourceBuilder FromFile(string file) => FromFile(new FileInfo(file)); - - /// - /// Generates a resource from the given file. - /// - /// The file to be provided - public static FileResourceBuilder FromFile(FileInfo file) => new FileResourceBuilder().File(file); - - } + /// The file to be provided + public static FileResourceBuilder FromFile(FileInfo file) => new FileResourceBuilder().File(file); } diff --git a/Modules/IO/ResourceTree.cs b/Modules/IO/ResourceTree.cs index b8d5e498..e7811de5 100644 --- a/Modules/IO/ResourceTree.cs +++ b/Modules/IO/ResourceTree.cs @@ -5,21 +5,20 @@ using GenHTTP.Modules.IO.Embedded; using GenHTTP.Modules.IO.FileSystem; -namespace GenHTTP.Modules.IO +namespace GenHTTP.Modules.IO; + +/// +/// Provides a folder-like structure that can be used to generate responses. +/// +public static class ResourceTree { /// - /// Provides a folder-like structure that can be used to generate responses. + /// Creates a resource tree that will provide all embedded + /// resources provided by the currently executing assembly. /// - public static class ResourceTree + public static EmbeddedResourceTreeBuilder FromAssembly() { - - /// - /// Creates a resource tree that will provide all embedded - /// resources provided by the currently executing assembly. - /// - public static EmbeddedResourceTreeBuilder FromAssembly() - { var assembly = Assembly.GetCallingAssembly(); var name = assembly.GetName().Name ?? throw new InvalidOperationException($"Unable to determine root namespace for assembly '{assembly}'"); @@ -27,44 +26,42 @@ public static EmbeddedResourceTreeBuilder FromAssembly() return FromAssembly(assembly, name); } - /// - /// Creates a resource tree that will provide all embedded - /// resources provided by the given assembly. - /// - public static EmbeddedResourceTreeBuilder FromAssembly(Assembly source) - { + /// + /// Creates a resource tree that will provide all embedded + /// resources provided by the given assembly. + /// + public static EmbeddedResourceTreeBuilder FromAssembly(Assembly source) + { var name = source.GetName().Name ?? throw new InvalidOperationException($"Unable to determine root namespace for assembly '{source}'"); return new EmbeddedResourceTreeBuilder().Source(source) .Root(name); } - /// - /// Creates a resource tree that will provide all embedded - /// resources provided by the executing assembly and starting - /// with the given prefix (e.g. "My.Namespace.Folder"). - /// - public static EmbeddedResourceTreeBuilder FromAssembly(string root) => FromAssembly(Assembly.GetCallingAssembly(), root); - - /// - /// Creates a resource tree that will provide all embedded - /// resources provided by the specified assembly and starting - /// with the given prefix (e.g. "My.Namespace.Folder"). - /// - public static EmbeddedResourceTreeBuilder FromAssembly(Assembly source, string root) => new EmbeddedResourceTreeBuilder().Source(source).Root(root); + /// + /// Creates a resource tree that will provide all embedded + /// resources provided by the executing assembly and starting + /// with the given prefix (e.g. "My.Namespace.Folder"). + /// + public static EmbeddedResourceTreeBuilder FromAssembly(string root) => FromAssembly(Assembly.GetCallingAssembly(), root); - /// - /// Creates a resource tree from the given directory. - /// - /// The full path of the directory to be provided - public static DirectoryTreeBuilder FromDirectory(string directory) => FromDirectory(new DirectoryInfo(directory)); + /// + /// Creates a resource tree that will provide all embedded + /// resources provided by the specified assembly and starting + /// with the given prefix (e.g. "My.Namespace.Folder"). + /// + public static EmbeddedResourceTreeBuilder FromAssembly(Assembly source, string root) => new EmbeddedResourceTreeBuilder().Source(source).Root(root); - /// - /// Creates a resource tree from the given directory. - /// - /// The directory to be provided - public static DirectoryTreeBuilder FromDirectory(DirectoryInfo directory) => new DirectoryTreeBuilder().Directory(directory); + /// + /// Creates a resource tree from the given directory. + /// + /// The full path of the directory to be provided + public static DirectoryTreeBuilder FromDirectory(string directory) => FromDirectory(new DirectoryInfo(directory)); - } + /// + /// Creates a resource tree from the given directory. + /// + /// The directory to be provided + public static DirectoryTreeBuilder FromDirectory(DirectoryInfo directory) => new DirectoryTreeBuilder().Directory(directory); } diff --git a/Modules/IO/ResourceTreeExtensions.cs b/Modules/IO/ResourceTreeExtensions.cs index 87b4618e..7c024ad9 100644 --- a/Modules/IO/ResourceTreeExtensions.cs +++ b/Modules/IO/ResourceTreeExtensions.cs @@ -3,21 +3,20 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Routing; -namespace GenHTTP.Modules.IO +namespace GenHTTP.Modules.IO; + +public static class ResourceTreeExtensions { - public static class ResourceTreeExtensions + /// + /// Attempts to resolve the requested node and/or resource from + /// the given container, according to the specified routing target. + /// + /// The node used to resolve the target + /// The target to be resolved + /// A tuple of the node and resource resolved from the container (or both null, if they could not be resolved) + public async static ValueTask<(IResourceContainer? node, IResource? resource)> Find(this IResourceContainer node, RoutingTarget target) { - - /// - /// Attempts to resolve the requested node and/or resource from - /// the given container, according to the specified routing target. - /// - /// The node used to resolve the target - /// The target to be resolved - /// A tuple of the node and resource resolved from the container (or both null, if they could not be resolved) - public async static ValueTask<(IResourceContainer? node, IResource? resource)> Find(this IResourceContainer node, RoutingTarget target) - { var current = target.Current; if (current is not null) @@ -52,6 +51,4 @@ public static class ResourceTreeExtensions return new(node, null); } - } - } diff --git a/Modules/IO/Resources.cs b/Modules/IO/Resources.cs index 46bd74c8..c077d3dc 100644 --- a/Modules/IO/Resources.cs +++ b/Modules/IO/Resources.cs @@ -3,34 +3,31 @@ using GenHTTP.Modules.IO.Providers; -namespace GenHTTP.Modules.IO +namespace GenHTTP.Modules.IO; + +/// +/// Provides a folder structure (provided by a resource tree) to +/// requesting clients. +/// +/// +/// This handler can be used to serve "static" resources alongside +/// with your application. +/// +public static class Resources { /// - /// Provides a folder structure (provided by a resource tree) to - /// requesting clients. + /// Creates a new handler that will serve the resources provided + /// by the given tree. /// - /// - /// This handler can be used to serve "static" resources alongside - /// with your application. - /// - public static class Resources - { - - /// - /// Creates a new handler that will serve the resources provided - /// by the given tree. - /// - /// The resource tree to read resourced from - public static ResourceHandlerBuilder From(IBuilder tree) => From(tree.Build()); - - /// - /// Creates a new handler that will serve the resources provided - /// by the given tree. - /// - /// The resource tree to read resourced from - public static ResourceHandlerBuilder From(IResourceTree tree) => new ResourceHandlerBuilder().Tree(tree); + /// The resource tree to read resourced from + public static ResourceHandlerBuilder From(IBuilder tree) => From(tree.Build()); - } + /// + /// Creates a new handler that will serve the resources provided + /// by the given tree. + /// + /// The resource tree to read resourced from + public static ResourceHandlerBuilder From(IResourceTree tree) => new ResourceHandlerBuilder().Tree(tree); } diff --git a/Modules/IO/ResponseBuilderExtensions.cs b/Modules/IO/ResponseBuilderExtensions.cs index f5e0f3b9..3764abdf 100644 --- a/Modules/IO/ResponseBuilderExtensions.cs +++ b/Modules/IO/ResponseBuilderExtensions.cs @@ -7,46 +7,43 @@ using GenHTTP.Modules.IO.Streaming; -namespace GenHTTP.Modules.IO -{ +namespace GenHTTP.Modules.IO; - public static class ResponseBuilderExtensions - { - private static readonly FlexibleContentType _TextPlainType = new(ContentType.TextPlain, "UTF-8"); - - /// - /// Sends the given string to the client. - /// - /// The string to be sent - public static IResponseBuilder Content(this IResponseBuilder builder, string text) => builder.Content(Resource.FromString(text).Type(_TextPlainType).Build()); - - /// - /// Sends the given resource to the client. - /// - /// The resource to be sent - /// - /// This method will set the content, but not the content - /// type of the response. - /// - public static IResponseBuilder Content(this IResponseBuilder builder, IResource resource) => builder.Content(new ResourceContent(resource)).Type(resource.ContentType ?? FlexibleContentType.Get(ContentType.ApplicationOctetStream)); - - /// - /// Sends the given stream to the client. - /// - /// The stream to be sent - /// The known length of the stream (if the stream does not propagate this information) - /// The logic to efficiently calculate checksums - public static IResponseBuilder Content(this IResponseBuilder builder, Stream stream, ulong? knownLength, Func> checksumProvider) => builder.Content(new StreamContent(stream, knownLength, checksumProvider)); - - /// - /// Sends the given stream to the client. - /// - /// The stream to be sent - /// The logic to efficiently calculate checksums - public static IResponseBuilder Content(this IResponseBuilder builder, Stream stream, Func> checksumProvider) => builder.Content(stream, null, checksumProvider); - - public static ValueTask BuildTask(this IResponseBuilder builder) => new(builder.Build()); - - } +public static class ResponseBuilderExtensions +{ + private static readonly FlexibleContentType _TextPlainType = new(ContentType.TextPlain, "UTF-8"); + + /// + /// Sends the given string to the client. + /// + /// The string to be sent + public static IResponseBuilder Content(this IResponseBuilder builder, string text) => builder.Content(Resource.FromString(text).Type(_TextPlainType).Build()); + + /// + /// Sends the given resource to the client. + /// + /// The resource to be sent + /// + /// This method will set the content, but not the content + /// type of the response. + /// + public static IResponseBuilder Content(this IResponseBuilder builder, IResource resource) => builder.Content(new ResourceContent(resource)).Type(resource.ContentType ?? FlexibleContentType.Get(ContentType.ApplicationOctetStream)); + + /// + /// Sends the given stream to the client. + /// + /// The stream to be sent + /// The known length of the stream (if the stream does not propagate this information) + /// The logic to efficiently calculate checksums + public static IResponseBuilder Content(this IResponseBuilder builder, Stream stream, ulong? knownLength, Func> checksumProvider) => builder.Content(new StreamContent(stream, knownLength, checksumProvider)); + + /// + /// Sends the given stream to the client. + /// + /// The stream to be sent + /// The logic to efficiently calculate checksums + public static IResponseBuilder Content(this IResponseBuilder builder, Stream stream, Func> checksumProvider) => builder.Content(stream, null, checksumProvider); + + public static ValueTask BuildTask(this IResponseBuilder builder) => new(builder.Build()); } diff --git a/Modules/IO/Streaming/ResourceContent.cs b/Modules/IO/Streaming/ResourceContent.cs index bf0da685..ed44729b 100644 --- a/Modules/IO/Streaming/ResourceContent.cs +++ b/Modules/IO/Streaming/ResourceContent.cs @@ -4,37 +4,34 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.Streaming -{ +namespace GenHTTP.Modules.IO.Streaming; - public sealed class ResourceContent : IResponseContent - { +public sealed class ResourceContent : IResponseContent +{ - #region Get-/Setters + #region Get-/Setters - public ulong? Length => Resource.Length; + public ulong? Length => Resource.Length; - private IResource Resource { get; } + private IResource Resource { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ResourceContent(IResource resource) - { + public ResourceContent(IResource resource) + { Resource = resource; } - #endregion - - #region Functionality + #endregion - public async ValueTask CalculateChecksumAsync() => await Resource.CalculateChecksumAsync(); + #region Functionality - public ValueTask WriteAsync(Stream target, uint bufferSize) => Resource.WriteAsync(target, bufferSize); + public async ValueTask CalculateChecksumAsync() => await Resource.CalculateChecksumAsync(); - #endregion + public ValueTask WriteAsync(Stream target, uint bufferSize) => Resource.WriteAsync(target, bufferSize); - } + #endregion } diff --git a/Modules/IO/Streaming/StreamContent.cs b/Modules/IO/Streaming/StreamContent.cs index 8926d7e2..592a27b1 100644 --- a/Modules/IO/Streaming/StreamContent.cs +++ b/Modules/IO/Streaming/StreamContent.cs @@ -4,23 +4,22 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.Streaming -{ +namespace GenHTTP.Modules.IO.Streaming; - public class StreamContent : IResponseContent, IDisposable - { - private readonly Func> _ChecksumProvider; +public class StreamContent : IResponseContent, IDisposable +{ + private readonly Func> _ChecksumProvider; - private readonly ulong? _KnownLengh; + private readonly ulong? _KnownLengh; - #region Get-/Setters + #region Get-/Setters - private Stream Content { get; } + private Stream Content { get; } - public ulong? Length + public ulong? Length + { + get { - get - { if (_KnownLengh != null) { return _KnownLengh; @@ -33,39 +32,39 @@ public ulong? Length return null; } - } + } - #endregion + #endregion - #region Initialization + #region Initialization - public StreamContent(Stream content, ulong? knownLength, Func> checksumProvider) - { + public StreamContent(Stream content, ulong? knownLength, Func> checksumProvider) + { Content = content; _KnownLengh = knownLength; _ChecksumProvider = checksumProvider; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask CalculateChecksumAsync() => _ChecksumProvider(); + public ValueTask CalculateChecksumAsync() => _ChecksumProvider(); - public ValueTask WriteAsync(Stream target, uint bufferSize) - { + public ValueTask WriteAsync(Stream target, uint bufferSize) + { return Content.CopyPooledAsync(target, bufferSize); } - #endregion + #endregion - #region IDisposable Support + #region IDisposable Support - private bool disposedValue = false; + private bool disposedValue = false; - protected virtual void Dispose(bool disposing) - { + protected virtual void Dispose(bool disposing) + { if (!disposedValue) { if (disposing) @@ -77,19 +76,17 @@ protected virtual void Dispose(bool disposing) } } - ~StreamContent() - { + ~StreamContent() + { Dispose(false); } - public void Dispose() - { + public void Dispose() + { Dispose(true); GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Modules/IO/Streaming/StreamExtensions.cs b/Modules/IO/Streaming/StreamExtensions.cs index a4f09c81..9483398b 100644 --- a/Modules/IO/Streaming/StreamExtensions.cs +++ b/Modules/IO/Streaming/StreamExtensions.cs @@ -4,19 +4,18 @@ using System.Text; using System.Threading.Tasks; -namespace GenHTTP.Modules.IO.Streaming -{ +namespace GenHTTP.Modules.IO.Streaming; - public static class StreamExtensions - { - private static readonly ArrayPool POOL = ArrayPool.Shared; +public static class StreamExtensions +{ + private static readonly ArrayPool POOL = ArrayPool.Shared; - private static readonly Encoding UTF8 = Encoding.UTF8; + private static readonly Encoding UTF8 = Encoding.UTF8; - private static readonly Encoder ENCODER = UTF8.GetEncoder(); + private static readonly Encoder ENCODER = UTF8.GetEncoder(); - public static async ValueTask CopyPooledAsync(this Stream source, Stream target, uint bufferSize) - { + public static async ValueTask CopyPooledAsync(this Stream source, Stream target, uint bufferSize) + { if (source.CanSeek && source.Position != 0) { source.Seek(0, SeekOrigin.Begin); @@ -47,8 +46,8 @@ public static async ValueTask CopyPooledAsync(this Stream source, Stream target, } } - public static async ValueTask WriteAsync(this string content, Stream target) - { + public static async ValueTask WriteAsync(this string content, Stream target) + { var bytes = ENCODER.GetByteCount(content, false); var buffer = POOL.Rent(bytes); @@ -65,8 +64,8 @@ public static async ValueTask WriteAsync(this string content, Stream target) } } - public static void Write(this string content, Stream target) - { + public static void Write(this string content, Stream target) + { var length = ENCODER.GetByteCount(content, false); var buffer = POOL.Rent(length); @@ -83,14 +82,14 @@ public static void Write(this string content, Stream target) } } - /// - /// Efficiently calculates the checksum of the stream, beginning - /// from the current position. Resets the position to the previous - /// one. - /// - /// The checksum of the stream - public static async ValueTask CalculateChecksumAsync(this Stream stream) - { + /// + /// Efficiently calculates the checksum of the stream, beginning + /// from the current position. Resets the position to the previous + /// one. + /// + /// The checksum of the stream + public static async ValueTask CalculateChecksumAsync(this Stream stream) + { if (stream.CanSeek) { var position = stream.Position; @@ -135,6 +134,4 @@ public static void Write(this string content, Stream target) return null; } - } - } diff --git a/Modules/IO/Strings/StringContent.cs b/Modules/IO/Strings/StringContent.cs index 68b0605d..1c02a4d7 100644 --- a/Modules/IO/Strings/StringContent.cs +++ b/Modules/IO/Strings/StringContent.cs @@ -5,27 +5,26 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.Strings -{ +namespace GenHTTP.Modules.IO.Strings; - public sealed class StringContent : IResponseContent - { - private static readonly Encoding UTF8 = Encoding.UTF8; +public sealed class StringContent : IResponseContent +{ + private static readonly Encoding UTF8 = Encoding.UTF8; - private readonly byte[] _Content; + private readonly byte[] _Content; - private readonly ulong _Checksum; + private readonly ulong _Checksum; - #region Get-/Setters + #region Get-/Setters - public ulong? Length { get; } + public ulong? Length { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public StringContent(string content) - { + public StringContent(string content) + { _Content = UTF8.GetBytes(content); Length = (ulong)_Content.Length; @@ -33,19 +32,17 @@ public StringContent(string content) _Checksum = (ulong)content.GetHashCode(); } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask CalculateChecksumAsync() => new(_Checksum); + public ValueTask CalculateChecksumAsync() => new(_Checksum); - public async ValueTask WriteAsync(Stream target, uint bufferSize) - { + public async ValueTask WriteAsync(Stream target, uint bufferSize) + { await target.WriteAsync(_Content.AsMemory()); } - #endregion - - } + #endregion } diff --git a/Modules/IO/Strings/StringResource.cs b/Modules/IO/Strings/StringResource.cs index cafcb4e3..8dedf561 100644 --- a/Modules/IO/Strings/StringResource.cs +++ b/Modules/IO/Strings/StringResource.cs @@ -6,33 +6,32 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.Strings -{ +namespace GenHTTP.Modules.IO.Strings; - public sealed class StringResource : IResource - { - private static readonly Encoding UTF8 = Encoding.UTF8; +public sealed class StringResource : IResource +{ + private static readonly Encoding UTF8 = Encoding.UTF8; - #region Get-/Setters + #region Get-/Setters - public byte[] Content { get; } + public byte[] Content { get; } - public string? Name { get; } + public string? Name { get; } - public DateTime? Modified { get; } + public DateTime? Modified { get; } - public FlexibleContentType? ContentType { get; } + public FlexibleContentType? ContentType { get; } - public ulong? Length => (ulong)Content.Length; + public ulong? Length => (ulong)Content.Length; - private ulong Checksum { get; } + private ulong Checksum { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public StringResource(string content, string? name, FlexibleContentType? contentType, DateTime? modified) - { + public StringResource(string content, string? name, FlexibleContentType? contentType, DateTime? modified) + { Content = UTF8.GetBytes(content); Checksum = (ulong)content.GetHashCode(); @@ -41,21 +40,19 @@ public StringResource(string content, string? name, FlexibleContentType? content Modified = modified; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask GetContentAsync() => new(new MemoryStream(Content, false)); + public ValueTask GetContentAsync() => new(new MemoryStream(Content, false)); - public ValueTask CalculateChecksumAsync() - { + public ValueTask CalculateChecksumAsync() + { return new ValueTask(Checksum); } - public ValueTask WriteAsync(Stream target, uint bufferSize) => target.WriteAsync(Content.AsMemory()); - - #endregion + public ValueTask WriteAsync(Stream target, uint bufferSize) => target.WriteAsync(Content.AsMemory()); - } + #endregion } diff --git a/Modules/IO/Strings/StringResourceBuilder.cs b/Modules/IO/Strings/StringResourceBuilder.cs index d33fce6f..d0e9302d 100644 --- a/Modules/IO/Strings/StringResourceBuilder.cs +++ b/Modules/IO/Strings/StringResourceBuilder.cs @@ -4,52 +4,49 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.Strings -{ +namespace GenHTTP.Modules.IO.Strings; - public sealed class StringResourceBuilder : IResourceBuilder - { - private string? _Content, _Name; +public sealed class StringResourceBuilder : IResourceBuilder +{ + private string? _Content, _Name; - private FlexibleContentType? _ContentType; + private FlexibleContentType? _ContentType; - private DateTime? _Modified; + private DateTime? _Modified; - #region Functionality + #region Functionality - public StringResourceBuilder Content(string content) - { + public StringResourceBuilder Content(string content) + { _Content = content; return this; } - public StringResourceBuilder Name(string name) - { + public StringResourceBuilder Name(string name) + { _Name = name; return this; } - public StringResourceBuilder Type(FlexibleContentType contentType) - { + public StringResourceBuilder Type(FlexibleContentType contentType) + { _ContentType = contentType; return this; } - public StringResourceBuilder Modified(DateTime modified) - { + public StringResourceBuilder Modified(DateTime modified) + { _Modified = modified; return this; } - public IResource Build() - { + public IResource Build() + { var content = _Content ?? throw new BuilderMissingPropertyException("content"); return new StringResource(content, _Name, _ContentType, _Modified); } - #endregion - - } + #endregion } diff --git a/Modules/IO/Tracking/ChangeTrackingResource.cs b/Modules/IO/Tracking/ChangeTrackingResource.cs index 1754e6fc..057c555d 100644 --- a/Modules/IO/Tracking/ChangeTrackingResource.cs +++ b/Modules/IO/Tracking/ChangeTrackingResource.cs @@ -5,69 +5,66 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.IO.Tracking -{ +namespace GenHTTP.Modules.IO.Tracking; - public sealed class ChangeTrackingResource : IResource - { - private ulong? _LastChecksum; +public sealed class ChangeTrackingResource : IResource +{ + private ulong? _LastChecksum; - #region Get-/Setters + #region Get-/Setters - private IResource Source { get; } + private IResource Source { get; } - public string? Name => Source.Name; + public string? Name => Source.Name; - public DateTime? Modified => Source.Modified; + public DateTime? Modified => Source.Modified; - public FlexibleContentType? ContentType => Source.ContentType; + public FlexibleContentType? ContentType => Source.ContentType; - public ulong? Length => Source.Length; + public ulong? Length => Source.Length; - #endregion + #endregion - #region Initialization + #region Initialization - public ChangeTrackingResource(IResource source) - { + public ChangeTrackingResource(IResource source) + { Source = source; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask GetContentAsync() - { + public async ValueTask GetContentAsync() + { _LastChecksum = await CalculateChecksumAsync(); return await Source.GetContentAsync(); } - public async ValueTask WriteAsync(Stream target, uint bufferSize) - { + public async ValueTask WriteAsync(Stream target, uint bufferSize) + { _LastChecksum = await CalculateChecksumAsync(); await Source.WriteAsync(target, bufferSize); } - public ValueTask CalculateChecksumAsync() - { + public ValueTask CalculateChecksumAsync() + { return Source.CalculateChecksumAsync(); } - /// - /// True, if the content of the resource has changed - /// since has been called - /// the last time. - /// - public async ValueTask HasChanged() - { + /// + /// True, if the content of the resource has changed + /// since has been called + /// the last time. + /// + public async ValueTask HasChanged() + { return await CalculateChecksumAsync() != _LastChecksum; } - #endregion - - } + #endregion } diff --git a/Modules/IO/VirtualTree.cs b/Modules/IO/VirtualTree.cs index 4e6182aa..32828cf2 100644 --- a/Modules/IO/VirtualTree.cs +++ b/Modules/IO/VirtualTree.cs @@ -1,17 +1,14 @@ using GenHTTP.Modules.IO.VirtualTrees; -namespace GenHTTP.Modules.IO -{ - - public static class VirtualTree - { +namespace GenHTTP.Modules.IO; - /// - /// Creates a virtual tree that may contain any other kind - /// of tree or resource and allows to combine them. - /// - public static VirtualTreeBuilder Create() => new(); +public static class VirtualTree +{ - } + /// + /// Creates a virtual tree that may contain any other kind + /// of tree or resource and allows to combine them. + /// + public static VirtualTreeBuilder Create() => new(); } diff --git a/Modules/IO/VirtualTrees/VirtualNode.cs b/Modules/IO/VirtualTrees/VirtualNode.cs index bd38e9f7..09b60cf3 100644 --- a/Modules/IO/VirtualTrees/VirtualNode.cs +++ b/Modules/IO/VirtualTrees/VirtualNode.cs @@ -4,48 +4,45 @@ using GenHTTP.Api.Content.IO; -namespace GenHTTP.Modules.IO.VirtualTrees -{ +namespace GenHTTP.Modules.IO.VirtualTrees; - public sealed class VirtualNode : IResourceNode - { +public sealed class VirtualNode : IResourceNode +{ - #region Get-/Setters + #region Get-/Setters - public string Name { get; } + public string Name { get; } - public IResourceContainer Parent { get; } + public IResourceContainer Parent { get; } - public DateTime? Modified => Container.Modified; + public DateTime? Modified => Container.Modified; - private IResourceContainer Container { get; } + private IResourceContainer Container { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public VirtualNode(IResourceContainer parent, string name, IResourceContainer container) - { + public VirtualNode(IResourceContainer parent, string name, IResourceContainer container) + { Parent = parent; Name = name; Container = container; } - #endregion - - #region Functionaliy + #endregion - public ValueTask> GetNodes() => Container.GetNodes(); + #region Functionaliy - public ValueTask> GetResources() => Container.GetResources(); + public ValueTask> GetNodes() => Container.GetNodes(); - public ValueTask TryGetNodeAsync(string name) => Container.TryGetNodeAsync(name); + public ValueTask> GetResources() => Container.GetResources(); - public ValueTask TryGetResourceAsync(string name) => Container.TryGetResourceAsync(name); + public ValueTask TryGetNodeAsync(string name) => Container.TryGetNodeAsync(name); - #endregion + public ValueTask TryGetResourceAsync(string name) => Container.TryGetResourceAsync(name); - } + #endregion } diff --git a/Modules/IO/VirtualTrees/VirtualTree.cs b/Modules/IO/VirtualTrees/VirtualTree.cs index 779b5b55..97ae1950 100644 --- a/Modules/IO/VirtualTrees/VirtualTree.cs +++ b/Modules/IO/VirtualTrees/VirtualTree.cs @@ -5,22 +5,21 @@ using GenHTTP.Api.Content.IO; -namespace GenHTTP.Modules.IO.VirtualTrees -{ +namespace GenHTTP.Modules.IO.VirtualTrees; - public sealed class VirtualTree : IResourceTree - { +public sealed class VirtualTree : IResourceTree +{ - #region Get-/Setters + #region Get-/Setters - private Dictionary Nodes { get; } + private Dictionary Nodes { get; } - private Dictionary Resources { get; } + private Dictionary Resources { get; } - public DateTime? Modified + public DateTime? Modified + { + get { - get - { return Nodes.Select(n => n.Value.Modified) .Where(n => n != null) .Union @@ -31,14 +30,14 @@ public DateTime? Modified .DefaultIfEmpty(null) .Max(); } - } + } - #endregion + #endregion - #region Initialization + #region Initialization - public VirtualTree(Dictionary> nodes, Dictionary resources) - { + public VirtualTree(Dictionary> nodes, Dictionary resources) + { var built = new Dictionary(nodes.Count); foreach (var node in nodes) @@ -50,20 +49,18 @@ public VirtualTree(Dictionary> n Resources = resources; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask> GetNodes() => new(Nodes.Values); + public ValueTask> GetNodes() => new(Nodes.Values); - public ValueTask> GetResources() => new(Resources.Values); + public ValueTask> GetResources() => new(Resources.Values); - public ValueTask TryGetNodeAsync(string name) => new(Nodes.GetValueOrDefault(name)); + public ValueTask TryGetNodeAsync(string name) => new(Nodes.GetValueOrDefault(name)); - public ValueTask TryGetResourceAsync(string name) => new(Resources.GetValueOrDefault(name)); + public ValueTask TryGetResourceAsync(string name) => new(Resources.GetValueOrDefault(name)); - #endregion - - } + #endregion } diff --git a/Modules/IO/VirtualTrees/VirtualTreeBuilder.cs b/Modules/IO/VirtualTrees/VirtualTreeBuilder.cs index 76d487fb..5037df4c 100644 --- a/Modules/IO/VirtualTrees/VirtualTreeBuilder.cs +++ b/Modules/IO/VirtualTrees/VirtualTreeBuilder.cs @@ -4,61 +4,58 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.IO.VirtualTrees -{ +namespace GenHTTP.Modules.IO.VirtualTrees; - public sealed class VirtualTreeBuilder : IBuilder - { - private readonly Dictionary> _Nodes = new(); +public sealed class VirtualTreeBuilder : IBuilder +{ + private readonly Dictionary> _Nodes = new(); - private readonly Dictionary _Resources = new(); + private readonly Dictionary _Resources = new(); - #region Functionality + #region Functionality - /// - /// Adds the given container with the specified name to the tree. - /// - /// The name of the node to be added - /// The container to be added - public VirtualTreeBuilder Add(string name, IResourceContainer container) - { + /// + /// Adds the given container with the specified name to the tree. + /// + /// The name of the node to be added + /// The container to be added + public VirtualTreeBuilder Add(string name, IResourceContainer container) + { _Nodes.Add(name, (p) => new VirtualNode(p, name, container)); return this; } - /// - /// Adds the given container with the specified name to the tree. - /// - /// The name of the node to be added - /// The container to be added - public VirtualTreeBuilder Add(string name, IBuilder tree) => Add(name, tree.Build()); - - /// - /// Adds the given resource with the specified name to the tree. - /// - /// The name of the resource to be added - /// The resource to be added - public VirtualTreeBuilder Add(string name, IResource resource) - { + /// + /// Adds the given container with the specified name to the tree. + /// + /// The name of the node to be added + /// The container to be added + public VirtualTreeBuilder Add(string name, IBuilder tree) => Add(name, tree.Build()); + + /// + /// Adds the given resource with the specified name to the tree. + /// + /// The name of the resource to be added + /// The resource to be added + public VirtualTreeBuilder Add(string name, IResource resource) + { _Resources.Add(name, resource); return this; } - /// - /// Adds the given resource with the specified name to the tree. - /// - /// The name of the resource to be added - /// The resource to be added - public VirtualTreeBuilder Add(string name, IBuilder resource) - { + /// + /// Adds the given resource with the specified name to the tree. + /// + /// The name of the resource to be added + /// The resource to be added + public VirtualTreeBuilder Add(string name, IBuilder resource) + { _Resources.Add(name, resource.Build()); return this; } - public IResourceTree Build() => new VirtualTree(_Nodes, _Resources); - - #endregion + public IResourceTree Build() => new VirtualTree(_Nodes, _Resources); - } + #endregion } diff --git a/Modules/Layouting/Layout.cs b/Modules/Layouting/Layout.cs index 534aa3c3..1649f95d 100644 --- a/Modules/Layouting/Layout.cs +++ b/Modules/Layouting/Layout.cs @@ -1,20 +1,17 @@ using GenHTTP.Modules.Layouting.Provider; -namespace GenHTTP.Modules.Layouting +namespace GenHTTP.Modules.Layouting; + +public static class Layout { - public static class Layout + /// + /// Creates a new layout that can be used to route requests. + /// + /// The newly created layout builder + public static LayoutBuilder Create() { - - /// - /// Creates a new layout that can be used to route requests. - /// - /// The newly created layout builder - public static LayoutBuilder Create() - { return new LayoutBuilder(); } - } - } diff --git a/Modules/Layouting/Provider/LayoutBuilder.cs b/Modules/Layouting/Provider/LayoutBuilder.cs index 44093ccd..ec30f63f 100644 --- a/Modules/Layouting/Provider/LayoutBuilder.cs +++ b/Modules/Layouting/Provider/LayoutBuilder.cs @@ -3,57 +3,56 @@ using GenHTTP.Api.Content; -namespace GenHTTP.Modules.Layouting.Provider -{ +namespace GenHTTP.Modules.Layouting.Provider; - public sealed class LayoutBuilder : IHandlerBuilder - { - private IHandlerBuilder? _Index; +public sealed class LayoutBuilder : IHandlerBuilder +{ + private IHandlerBuilder? _Index; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - #region Get-/Setters + #region Get-/Setters - private Dictionary RoutedHandlers { get; } + private Dictionary RoutedHandlers { get; } - private List RootHandlers { get; } + private List RootHandlers { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public LayoutBuilder() - { + public LayoutBuilder() + { RoutedHandlers = new(); RootHandlers = new(); } - #endregion + #endregion - #region Functionality + #region Functionality - /// - /// Sets the handler which should be invoked to provide - /// the index of the layout. - /// - /// The handler used for the index of the layout - public LayoutBuilder Index(IHandlerBuilder handler) - { + /// + /// Sets the handler which should be invoked to provide + /// the index of the layout. + /// + /// The handler used for the index of the layout + public LayoutBuilder Index(IHandlerBuilder handler) + { _Index = handler; return this; } - [Obsolete("Deprecated, use Add() instead")] - public LayoutBuilder Fallback(IHandlerBuilder handler) => Add(handler); - - /// - /// Adds a handler that will be invoked for all URLs below - /// the specified path segment. - /// - /// The name of the path segment to be handled - /// The handler which will handle the segment - public LayoutBuilder Add(string name, IHandlerBuilder handler) - { + [Obsolete("Deprecated, use Add() instead")] + public LayoutBuilder Fallback(IHandlerBuilder handler) => Add(handler); + + /// + /// Adds a handler that will be invoked for all URLs below + /// the specified path segment. + /// + /// The name of the path segment to be handled + /// The handler which will handle the segment + public LayoutBuilder Add(string name, IHandlerBuilder handler) + { if (name.Contains('/')) { throw new ArgumentException("Path seperators are not allowed in the name of the segment.", nameof(name)); @@ -63,35 +62,33 @@ public LayoutBuilder Add(string name, IHandlerBuilder handler) return this; } - /// - /// Adds a handler on root level that will be invoked if neither a - /// path segment has been detected nor the index has been invoked. - /// - /// The root level handler to be added - /// - /// Can be used to provide one or multiple fallback handlers for the layout. - /// Fallback handlers will be executed in the order they have been added - /// to the layout. - /// - public LayoutBuilder Add(IHandlerBuilder handler) - { + /// + /// Adds a handler on root level that will be invoked if neither a + /// path segment has been detected nor the index has been invoked. + /// + /// The root level handler to be added + /// + /// Can be used to provide one or multiple fallback handlers for the layout. + /// Fallback handlers will be executed in the order they have been added + /// to the layout. + /// + public LayoutBuilder Add(IHandlerBuilder handler) + { RootHandlers.Add(handler); return this; } - public LayoutBuilder Add(IConcernBuilder concern) - { + public LayoutBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { return Concerns.Chain(parent, _Concerns, (p) => new LayoutRouter(p, RoutedHandlers, RootHandlers, _Index)); } - #endregion - - } + #endregion -} +} \ No newline at end of file diff --git a/Modules/Layouting/Provider/LayoutRouter.cs b/Modules/Layouting/Provider/LayoutRouter.cs index 60dad382..8556601a 100644 --- a/Modules/Layouting/Provider/LayoutRouter.cs +++ b/Modules/Layouting/Provider/LayoutRouter.cs @@ -8,31 +8,30 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.Layouting.Provider -{ +namespace GenHTTP.Modules.Layouting.Provider; - public sealed class LayoutRouter : IHandler - { +public sealed class LayoutRouter : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private Dictionary RoutedHandlers { get; } + private Dictionary RoutedHandlers { get; } - private List RootHandlers { get; } + private List RootHandlers { get; } - private IHandler? Index { get; } + private IHandler? Index { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public LayoutRouter(IHandler parent, - Dictionary routedHandlers, - List rootHandlers, - IHandlerBuilder? index) - { + public LayoutRouter(IHandler parent, + Dictionary routedHandlers, + List rootHandlers, + IHandlerBuilder? index) + { Parent = parent; RoutedHandlers = routedHandlers.ToDictionary(kv => kv.Key, kv => kv.Value.Build(this)); @@ -42,12 +41,12 @@ public LayoutRouter(IHandler parent, Index = index?.Build(this); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var current = request.Target.Current; if (current is not null) @@ -88,8 +87,8 @@ public LayoutRouter(IHandler parent, return null; } - public async ValueTask PrepareAsync() - { + public async ValueTask PrepareAsync() + { if (Index != null) { await Index.PrepareAsync(); @@ -106,8 +105,6 @@ public async ValueTask PrepareAsync() } } - #endregion - - } + #endregion } diff --git a/Modules/LoadBalancing/LoadBalancer.cs b/Modules/LoadBalancing/LoadBalancer.cs index a332f9c1..cc284a24 100644 --- a/Modules/LoadBalancing/LoadBalancer.cs +++ b/Modules/LoadBalancing/LoadBalancer.cs @@ -1,13 +1,10 @@ using GenHTTP.Modules.LoadBalancing.Provider; -namespace GenHTTP.Modules.LoadBalancing -{ - - public static class LoadBalancer - { +namespace GenHTTP.Modules.LoadBalancing; - public static LoadBalancerBuilder Create() => new(); +public static class LoadBalancer +{ - } + public static LoadBalancerBuilder Create() => new(); } diff --git a/Modules/LoadBalancing/Provider/LoadBalancerBuilder.cs b/Modules/LoadBalancing/Provider/LoadBalancerBuilder.cs index f6a7ac3f..a2ac722c 100644 --- a/Modules/LoadBalancing/Provider/LoadBalancerBuilder.cs +++ b/Modules/LoadBalancing/Provider/LoadBalancerBuilder.cs @@ -3,42 +3,39 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.LoadBalancing.Provider -{ +namespace GenHTTP.Modules.LoadBalancing.Provider; - public sealed class LoadBalancerBuilder : IHandlerBuilder - { - private readonly List _Concerns = new(); +public sealed class LoadBalancerBuilder : IHandlerBuilder +{ + private readonly List _Concerns = new(); - private readonly List<(IHandlerBuilder, PriorityEvaluation)> _Nodes = new(); + private readonly List<(IHandlerBuilder, PriorityEvaluation)> _Nodes = new(); - private static readonly PriorityEvaluation DEFAULT_PRIORITY = (_) => Priority.Medium; + private static readonly PriorityEvaluation DEFAULT_PRIORITY = (_) => Priority.Medium; - #region Functionality + #region Functionality - public LoadBalancerBuilder Add(IConcernBuilder concern) - { + public LoadBalancerBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public LoadBalancerBuilder Add(IHandlerBuilder handler, PriorityEvaluation? priority = null) - { + public LoadBalancerBuilder Add(IHandlerBuilder handler, PriorityEvaluation? priority = null) + { _Nodes.Add((handler, priority ?? DEFAULT_PRIORITY)); return this; } - public LoadBalancerBuilder Redirect(string node, PriorityEvaluation? priority = null) => Add(new LoadBalancerRedirectionBuilder().Root(node), priority); + public LoadBalancerBuilder Redirect(string node, PriorityEvaluation? priority = null) => Add(new LoadBalancerRedirectionBuilder().Root(node), priority); - public LoadBalancerBuilder Proxy(string node, PriorityEvaluation? priority = null) => Add(ReverseProxy.Proxy.Create().Upstream(node), priority); + public LoadBalancerBuilder Proxy(string node, PriorityEvaluation? priority = null) => Add(ReverseProxy.Proxy.Create().Upstream(node), priority); - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { return Concerns.Chain(parent, _Concerns, (p) => new LoadBalancerHandler(p, _Nodes)); } - #endregion - - } + #endregion } diff --git a/Modules/LoadBalancing/Provider/LoadBalancerHandler.cs b/Modules/LoadBalancing/Provider/LoadBalancerHandler.cs index b9bc9b1e..b70e6715 100644 --- a/Modules/LoadBalancing/Provider/LoadBalancerHandler.cs +++ b/Modules/LoadBalancing/Provider/LoadBalancerHandler.cs @@ -7,45 +7,44 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.LoadBalancing.Provider -{ +namespace GenHTTP.Modules.LoadBalancing.Provider; - public sealed class LoadBalancerHandler : IHandler - { +public sealed class LoadBalancerHandler : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private readonly List<(IHandler, PriorityEvaluation)> _Nodes; + private readonly List<(IHandler, PriorityEvaluation)> _Nodes; - private static readonly Random _Random = new(); + private static readonly Random _Random = new(); - #endregion + #endregion - #region Initialization + #region Initialization - public LoadBalancerHandler(IHandler parent, List<(IHandlerBuilder, PriorityEvaluation)> nodes) - { + public LoadBalancerHandler(IHandler parent, List<(IHandlerBuilder, PriorityEvaluation)> nodes) + { Parent = parent; _Nodes = nodes.Select(n => (n.Item1.Build(this), n.Item2)).ToList(); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask PrepareAsync() - { + public async ValueTask PrepareAsync() + { foreach (var entry in _Nodes) { await entry.Item1.PrepareAsync(); } } - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { // get the handlers that share the highest priority var priorityGroup = _Nodes.GroupBy(n => n.Item2(request)) .OrderByDescending(n => n.Key) @@ -72,8 +71,6 @@ public async ValueTask PrepareAsync() return new ValueTask(); } - #endregion - - } + #endregion } diff --git a/Modules/LoadBalancing/Provider/LoadBalancerRedirectionBuilder.cs b/Modules/LoadBalancing/Provider/LoadBalancerRedirectionBuilder.cs index 9091fbbb..f862e58d 100644 --- a/Modules/LoadBalancing/Provider/LoadBalancerRedirectionBuilder.cs +++ b/Modules/LoadBalancing/Provider/LoadBalancerRedirectionBuilder.cs @@ -3,38 +3,35 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.LoadBalancing.Provider -{ +namespace GenHTTP.Modules.LoadBalancing.Provider; - public sealed class LoadBalancerRedirectionBuilder : IHandlerBuilder - { - private readonly List _Concerns = new(); +public sealed class LoadBalancerRedirectionBuilder : IHandlerBuilder +{ + private readonly List _Concerns = new(); - private string? _Root; + private string? _Root; - #region Functionality + #region Functionality - public LoadBalancerRedirectionBuilder Add(IConcernBuilder concern) - { + public LoadBalancerRedirectionBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public LoadBalancerRedirectionBuilder Root(string node) - { + public LoadBalancerRedirectionBuilder Root(string node) + { _Root = node; return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { var root = _Root ?? throw new BuilderMissingPropertyException("root"); return Concerns.Chain(parent, _Concerns, (p) => new LoadBalancerRedirectionHandler(p, root)); } - #endregion - - } + #endregion } diff --git a/Modules/LoadBalancing/Provider/LoadBalancerRedirectionHandler.cs b/Modules/LoadBalancing/Provider/LoadBalancerRedirectionHandler.cs index fd487843..bd5d8cbc 100644 --- a/Modules/LoadBalancing/Provider/LoadBalancerRedirectionHandler.cs +++ b/Modules/LoadBalancing/Provider/LoadBalancerRedirectionHandler.cs @@ -5,44 +5,41 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.LoadBalancing.Provider -{ +namespace GenHTTP.Modules.LoadBalancing.Provider; - public sealed class LoadBalancerRedirectionHandler : IHandler - { +public sealed class LoadBalancerRedirectionHandler : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private string Root { get; } + private string Root { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public LoadBalancerRedirectionHandler(IHandler parent, string root) - { + public LoadBalancerRedirectionHandler(IHandler parent, string root) + { Parent = parent; Root = root.EndsWith('/') ? root : $"{root}/"; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { return Redirect.To(Root + request.Target.Current, true) .Build(this) .HandleAsync(request); } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; - - #endregion + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - } + #endregion } diff --git a/Modules/Pages/Extensions.cs b/Modules/Pages/Extensions.cs index d6323125..407aac03 100644 --- a/Modules/Pages/Extensions.cs +++ b/Modules/Pages/Extensions.cs @@ -4,34 +4,31 @@ using GenHTTP.Modules.IO.Strings; -namespace GenHTTP.Modules.Pages -{ +namespace GenHTTP.Modules.Pages; - public static class Extensions +public static class Extensions +{ + private static readonly FlexibleContentType _ContentType = new(ContentType.TextHtml, "utf-8"); + + /// + /// Creates a response that can be returned by a handler to serve + /// a HTML page. + /// + /// The request to be responded to + /// The HTML page to be served + /// The HTML page response + public static IResponseBuilder GetPage(this IRequest request, string content) { - private static readonly FlexibleContentType _ContentType = new(ContentType.TextHtml, "utf-8"); - - /// - /// Creates a response that can be returned by a handler to serve - /// a HTML page. - /// - /// The request to be responded to - /// The HTML page to be served - /// The HTML page response - public static IResponseBuilder GetPage(this IRequest request, string content) - { return request.Respond() .Content(new StringContent(content)) .Type(_ContentType); } - /// - /// Escapes the given string so it can safely be used in HTML. - /// - /// The content to be escaped - /// The escaped version of the string - public static string Escaped(this string content) => HttpUtility.HtmlEncode(content); - - } + /// + /// Escapes the given string so it can safely be used in HTML. + /// + /// The content to be escaped + /// The escaped version of the string + public static string Escaped(this string content) => HttpUtility.HtmlEncode(content); } diff --git a/Modules/Pages/Renderer.cs b/Modules/Pages/Renderer.cs index 3f897f6f..1dbfc561 100644 --- a/Modules/Pages/Renderer.cs +++ b/Modules/Pages/Renderer.cs @@ -3,32 +3,29 @@ using GenHTTP.Modules.IO; using GenHTTP.Modules.Pages.Rendering; -namespace GenHTTP.Modules.Pages +namespace GenHTTP.Modules.Pages; + +/// +/// Thin wrappers to render HTML pages using the Cottle framework. +/// +/// +/// To serve a page, you might want to use . +/// +public static class Renderer { + private static readonly ServerRenderer _ServerRenderer = new(); /// - /// Thin wrappers to render HTML pages using the Cottle framework. + /// A renderer which can be used to generate a HTML page in the + /// server theme passing just title and content. /// - /// - /// To serve a page, you might want to use . - /// - public static class Renderer - { - private static readonly ServerRenderer _ServerRenderer = new(); - - /// - /// A renderer which can be used to generate a HTML page in the - /// server theme passing just title and content. - /// - public static ServerRenderer Server { get => _ServerRenderer; } - - /// - /// Creates a new renderer for the given template file. - /// - /// The template to be used for rendering - /// The newly created renderer - public static TemplateRenderer From(IResource template) => new(template.Track()); + public static ServerRenderer Server { get => _ServerRenderer; } - } + /// + /// Creates a new renderer for the given template file. + /// + /// The template to be used for rendering + /// The newly created renderer + public static TemplateRenderer From(IResource template) => new(template.Track()); } diff --git a/Modules/Pages/Rendering/ServerRenderer.cs b/Modules/Pages/Rendering/ServerRenderer.cs index b22032f7..c3e197ad 100644 --- a/Modules/Pages/Rendering/ServerRenderer.cs +++ b/Modules/Pages/Rendering/ServerRenderer.cs @@ -8,31 +8,30 @@ using Cottle; -namespace GenHTTP.Modules.Pages.Rendering -{ +namespace GenHTTP.Modules.Pages.Rendering; - public sealed class ServerRenderer - { - private static readonly IResource _ServerTemplate = Resource.FromAssembly("ServerPage.html").Build(); +public sealed class ServerRenderer +{ + private static readonly IResource _ServerTemplate = Resource.FromAssembly("ServerPage.html").Build(); - private readonly TemplateRenderer _TemplateRender; + private readonly TemplateRenderer _TemplateRender; - public ServerRenderer() - { + public ServerRenderer() + { _TemplateRender = Renderer.From(_ServerTemplate); } - /// - /// Renders a server-styled HTML page for the given title and content. - /// - /// The title of the page to be rendered - /// The HTML content of the page - /// The generated HTML page - /// - /// This method will not escape the given title or content. - /// - public async ValueTask RenderAsync(string title, string content) - { + /// + /// Renders a server-styled HTML page for the given title and content. + /// + /// The title of the page to be rendered + /// The HTML content of the page + /// The generated HTML page + /// + /// This method will not escape the given title or content. + /// + public async ValueTask RenderAsync(string title, string content) + { return await _TemplateRender.RenderAsync(new Dictionary() { ["title"] = title, @@ -40,16 +39,14 @@ public async ValueTask RenderAsync(string title, string content) }); } - /// - /// Renders a server-styled HTML error page for the given exception. - /// - /// The title of the page to be rendered - /// The error which has ocurred - /// Whether additional error information should be printed - /// The generated HTML error page - public ValueTask RenderAsync(string title, Exception error, bool developmentMode) - => RenderAsync(title, developmentMode ? error.ToString().Escaped() : error.Message.Escaped()); - - } + /// + /// Renders a server-styled HTML error page for the given exception. + /// + /// The title of the page to be rendered + /// The error which has ocurred + /// Whether additional error information should be printed + /// The generated HTML error page + public ValueTask RenderAsync(string title, Exception error, bool developmentMode) + => RenderAsync(title, developmentMode ? error.ToString().Escaped() : error.Message.Escaped()); } diff --git a/Modules/Pages/Rendering/TemplateRenderer.cs b/Modules/Pages/Rendering/TemplateRenderer.cs index 81b4d7f2..5bed6ecc 100644 --- a/Modules/Pages/Rendering/TemplateRenderer.cs +++ b/Modules/Pages/Rendering/TemplateRenderer.cs @@ -6,27 +6,26 @@ using Cottle; -namespace GenHTTP.Modules.Pages.Rendering -{ +namespace GenHTTP.Modules.Pages.Rendering; - public class TemplateRenderer - { - private IDocument? _Document = null; +public class TemplateRenderer +{ + private IDocument? _Document = null; - public ChangeTrackingResource Template { get; } + public ChangeTrackingResource Template { get; } - public TemplateRenderer(ChangeTrackingResource template) - { + public TemplateRenderer(ChangeTrackingResource template) + { Template = template; } - /// - /// Renders the template with the given model. - /// - /// The model to be used for rendering - /// The generated response - public async ValueTask RenderAsync(IReadOnlyDictionary model) - { + /// + /// Renders the template with the given model. + /// + /// The model to be used for rendering + /// The generated response + public async ValueTask RenderAsync(IReadOnlyDictionary model) + { if ((_Document == null) || await Template.HasChanged()) { using var reader = new StreamReader(await Template.GetContentAsync()); @@ -37,6 +36,4 @@ public async ValueTask RenderAsync(IReadOnlyDictionary mod return _Document.Render(Context.CreateBuiltin(model)); } - } - } diff --git a/Modules/Practices/Extensions.cs b/Modules/Practices/Extensions.cs index 8765d73f..95dd7387 100644 --- a/Modules/Practices/Extensions.cs +++ b/Modules/Practices/Extensions.cs @@ -8,30 +8,29 @@ using GenHTTP.Modules.Security; using GenHTTP.Modules.Security.Providers; -namespace GenHTTP.Modules.Practices +namespace GenHTTP.Modules.Practices; + +public static class Extensions { - public static class Extensions + /// + /// Configures the server host with default policies for compression and security. + /// + /// The host to be configured + /// Whether responses sent by the server should automatically be compressed + /// Whether the server should automatically upgrade insecure requests + /// Whether the server should send a strict transport policy + /// Validates the cached entries of the client by sending an ETag header and evaluating it when a request is processed (returning HTTP 304 if the content did not change) + /// Enables partial responses if requested by the client + /// Instructs clients not to guess the MIME type of the served content + public static IServerHost Defaults(this IServerHost host, + bool compression = true, + bool secureUpgrade = true, + bool strictTransport = true, + bool clientCaching = true, + bool rangeSupport = false, + bool preventSniffing = false) { - - /// - /// Configures the server host with default policies for compression and security. - /// - /// The host to be configured - /// Whether responses sent by the server should automatically be compressed - /// Whether the server should automatically upgrade insecure requests - /// Whether the server should send a strict transport policy - /// Validates the cached entries of the client by sending an ETag header and evaluating it when a request is processed (returning HTTP 304 if the content did not change) - /// Enables partial responses if requested by the client - /// Instructs clients not to guess the MIME type of the served content - public static IServerHost Defaults(this IServerHost host, - bool compression = true, - bool secureUpgrade = true, - bool strictTransport = true, - bool clientCaching = true, - bool rangeSupport = false, - bool preventSniffing = false) - { if (strictTransport) { host.StrictTransport(new StrictTransportPolicy(TimeSpan.FromDays(365), true, true)); @@ -65,6 +64,4 @@ public static IServerHost Defaults(this IServerHost host, return host; } - } - } diff --git a/Modules/Protobuf/Providers/ProtobufContent.cs b/Modules/Protobuf/Providers/ProtobufContent.cs index f1a343b5..6dc2b616 100644 --- a/Modules/Protobuf/Providers/ProtobufContent.cs +++ b/Modules/Protobuf/Providers/ProtobufContent.cs @@ -3,41 +3,40 @@ using System.IO; using System.Threading.Tasks; -namespace GenHTTP.Modules.Protobuf.Providers +namespace GenHTTP.Modules.Protobuf.Providers; + +public sealed class ProtobufContent : IResponseContent { - public sealed class ProtobufContent : IResponseContent - { - #region Get-/Setters + #region Get-/Setters - public ulong? Length => null; + public ulong? Length => null; - private object Data { get; } + private object Data { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ProtobufContent(object data) - { + public ProtobufContent(object data) + { Data = data; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask CalculateChecksumAsync() - { + public ValueTask CalculateChecksumAsync() + { return new ValueTask((ulong)Data.GetHashCode()); } - public ValueTask WriteAsync(Stream target, uint bufferSize) - { + public ValueTask WriteAsync(Stream target, uint bufferSize) + { Serializer.Serialize(target, Data); return new ValueTask(); } - #endregion - } + #endregion } diff --git a/Modules/Protobuf/Providers/ProtobufFormat.cs b/Modules/Protobuf/Providers/ProtobufFormat.cs index 3f32d123..68e3015c 100644 --- a/Modules/Protobuf/Providers/ProtobufFormat.cs +++ b/Modules/Protobuf/Providers/ProtobufFormat.cs @@ -1,29 +1,28 @@ using GenHTTP.Api.Protocol; using GenHTTP.Modules.Basics; -using GenHTTP.Modules.Conversion.Providers; using ProtoBuf; using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; +using GenHTTP.Modules.Conversion.Serializers; -namespace GenHTTP.Modules.Protobuf.Providers +namespace GenHTTP.Modules.Protobuf.Providers; + +public sealed class ProtobufFormat : ISerializationFormat { - public sealed class ProtobufFormat : ISerializationFormat + public ValueTask DeserializeAsync(Stream stream, [DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes)(-1))] Type type) { - public ValueTask DeserializeAsync(Stream stream, [DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes)(-1))] Type type) - { object deserializedObject = Serializer.Deserialize(type, stream); return new ValueTask(deserializedObject); } - public ValueTask SerializeAsync(IRequest request, object response) - { + public ValueTask SerializeAsync(IRequest request, object response) + { var result = request.Respond() .Content(new ProtobufContent(response)) .Type(ContentType.ApplicationProtobuf); return new ValueTask(result); } - } } diff --git a/Modules/Protobuf/SerializationExtensions.cs b/Modules/Protobuf/SerializationExtensions.cs index 310eac21..e9a36d8d 100644 --- a/Modules/Protobuf/SerializationExtensions.cs +++ b/Modules/Protobuf/SerializationExtensions.cs @@ -1,14 +1,13 @@ using GenHTTP.Api.Protocol; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.Protobuf.Providers; -namespace GenHTTP.Modules.Protobuf +namespace GenHTTP.Modules.Protobuf; + +public static class SerializationExtensions { - public static class SerializationExtensions + public static SerializationBuilder AddProtobuf(this SerializationBuilder serializationBuilder) { - public static SerializationBuilder AddProtobuf(this SerializationBuilder serializationBuilder) - { return serializationBuilder.Add(ContentType.ApplicationProtobuf, new ProtobufFormat()); } - } } diff --git a/Modules/Reflection/Adjustments.cs b/Modules/Reflection/Adjustments.cs index a91a7fc9..e5c38dd9 100644 --- a/Modules/Reflection/Adjustments.cs +++ b/Modules/Reflection/Adjustments.cs @@ -2,26 +2,23 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection +namespace GenHTTP.Modules.Reflection; + +internal static class Adjustments { - internal static class Adjustments + /// + /// Allows to chain the execution of the given adjustments into + /// the given response builder. + /// + /// The response builder to be adjusted + /// The adjustments to be executed (if any) + /// The response builder to be chained + internal static IResponseBuilder Adjust(this IResponseBuilder builder, Action? adjustments) { - - /// - /// Allows to chain the execution of the given adjustments into - /// the given response builder. - /// - /// The response builder to be adjusted - /// The adjustments to be executed (if any) - /// The response builder to be chained - internal static IResponseBuilder Adjust(this IResponseBuilder builder, Action? adjustments) - { adjustments?.Invoke(builder); return builder; } - } - } diff --git a/Modules/Reflection/Extensions.cs b/Modules/Reflection/Extensions.cs index 86cfe8c7..2aec6c30 100644 --- a/Modules/Reflection/Extensions.cs +++ b/Modules/Reflection/Extensions.cs @@ -4,50 +4,49 @@ using GenHTTP.Modules.Conversion.Formatters; -namespace GenHTTP.Modules.Reflection +namespace GenHTTP.Modules.Reflection; + +public static class Extensions { - public static class Extensions + /// + /// Checks, whether the given parameter can be passed via the URL. + /// + /// The parameter to be analyzes + /// true, if the given parameter can be passed via the URL + public static bool CanFormat(this ParameterInfo info, FormatterRegistry formatters) { - - /// - /// Checks, whether the given parameter can be passed via the URL. - /// - /// The parameter to be analyzes - /// true, if the given parameter can be passed via the URL - public static bool CanFormat(this ParameterInfo info, FormatterRegistry formatters) - { return info.CheckNullable() || formatters.CanHandle(info.ParameterType); } - /// - /// Checks, whether the given parameter is a nullable value type. - /// - /// The parameter to be analyzes - /// true, if the given parameter is a nullable value type - public static bool CheckNullable(this ParameterInfo info) - { + /// + /// Checks, whether the given parameter is a nullable value type. + /// + /// The parameter to be analyzes + /// true, if the given parameter is a nullable value type + public static bool CheckNullable(this ParameterInfo info) + { return Nullable.GetUnderlyingType(info.ParameterType) is not null; } - /// - /// Creates a regular expression that will match the name of the - /// given parameter. - /// - /// The name to generate the expression from - /// The newly created expression - public static string ToParameter(this string name) - { + /// + /// Creates a regular expression that will match the name of the + /// given parameter. + /// + /// The name to generate the expression from + /// The newly created expression + public static string ToParameter(this string name) + { return @$"(?<{name}>[^/]+)"; } - public static bool IsAsyncGeneric(this Type resultType) - { + public static bool IsAsyncGeneric(this Type resultType) + { return resultType.IsAssignableToGenericType(typeof(ValueTask<>)) || resultType.IsAssignableToGenericType(typeof(Task<>)); } - public static bool IsAssignableToGenericType(this Type givenType, Type genericType) - { + public static bool IsAssignableToGenericType(this Type givenType, Type genericType) + { var interfaceTypes = givenType.GetInterfaces(); foreach (var it in interfaceTypes) @@ -73,6 +72,4 @@ public static bool IsAssignableToGenericType(this Type givenType, Type genericTy return IsAssignableToGenericType(baseType, genericType); } - } - } diff --git a/Modules/Reflection/FromBodyAttribute.cs b/Modules/Reflection/FromBodyAttribute.cs index 8b614d42..c53f1a15 100644 --- a/Modules/Reflection/FromBodyAttribute.cs +++ b/Modules/Reflection/FromBodyAttribute.cs @@ -1,21 +1,18 @@ using System; -namespace GenHTTP.Modules.Reflection -{ - - /// - /// Marking an argument of a service method with this attribute will - /// cause the parameter to be read from the body of the request. - /// - /// - /// This attribute can be used on all parameters with simple types - /// (such as string or int). Complex types will always be deserialized - /// from the body without the need of marking it explicitly. - /// - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class FromBodyAttribute : Attribute - { +namespace GenHTTP.Modules.Reflection; - } +/// +/// Marking an argument of a service method with this attribute will +/// cause the parameter to be read from the body of the request. +/// +/// +/// This attribute can be used on all parameters with simple types +/// (such as string or int). Complex types will always be deserialized +/// from the body without the need of marking it explicitly. +/// +[AttributeUsage(AttributeTargets.Parameter)] +public sealed class FromBodyAttribute : Attribute +{ } diff --git a/Modules/Reflection/IMethodConfiguration.cs b/Modules/Reflection/IMethodConfiguration.cs index b08aafc0..4a756f3c 100644 --- a/Modules/Reflection/IMethodConfiguration.cs +++ b/Modules/Reflection/IMethodConfiguration.cs @@ -2,28 +2,25 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection +namespace GenHTTP.Modules.Reflection; + +/// +/// Configures the method provider which invokes functionality +/// provided via reflection. +/// +public interface IMethodConfiguration { /// - /// Configures the method provider which invokes functionality - /// provided via reflection. + /// The HTTP verbs which are supported by this method. /// - public interface IMethodConfiguration - { - - /// - /// The HTTP verbs which are supported by this method. - /// - public HashSet SupportedMethods { get; } - - /// - /// If specified, the content generated by this method - /// will not be declared and therefore not be listed - /// in sitemaps or other content dependent functionality. - /// - public bool IgnoreContent { get; } + public HashSet SupportedMethods { get; } - } + /// + /// If specified, the content generated by this method + /// will not be declared and therefore not be listed + /// in sitemaps or other content dependent functionality. + /// + public bool IgnoreContent { get; } } diff --git a/Modules/Reflection/IResultWrapper.cs b/Modules/Reflection/IResultWrapper.cs index d8d646b9..c5eca7dc 100644 --- a/Modules/Reflection/IResultWrapper.cs +++ b/Modules/Reflection/IResultWrapper.cs @@ -1,27 +1,24 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection +namespace GenHTTP.Modules.Reflection; + +/// +/// Allows the framework to unwrap +/// instances. +/// +internal interface IResultWrapper { /// - /// Allows the framework to unwrap - /// instances. + /// The actual result to be returned. /// - internal interface IResultWrapper - { - - /// - /// The actual result to be returned. - /// - object? Payload { get; } - - /// - /// Performs the configured modifications to the response - /// on the given builder. - /// - /// The response builder to manipulate - void Apply(IResponseBuilder builder); + object? Payload { get; } - } + /// + /// Performs the configured modifications to the response + /// on the given builder. + /// + /// The response builder to manipulate + void Apply(IResponseBuilder builder); } diff --git a/Modules/Reflection/Injection.cs b/Modules/Reflection/Injection.cs index 5fecacd0..c8560d43 100644 --- a/Modules/Reflection/Injection.cs +++ b/Modules/Reflection/Injection.cs @@ -1,20 +1,17 @@ using GenHTTP.Modules.Reflection.Injectors; -namespace GenHTTP.Modules.Reflection -{ +namespace GenHTTP.Modules.Reflection; - public static class Injection - { +public static class Injection +{ - public static InjectionRegistryBuilder Empty() => new InjectionRegistryBuilder(); + public static InjectionRegistryBuilder Empty() => new InjectionRegistryBuilder(); - public static InjectionRegistryBuilder Default() - { + public static InjectionRegistryBuilder Default() + { return new InjectionRegistryBuilder().Add(new RequestInjector()) .Add(new RequestBodyInjector()) .Add(new HandlerInjector()); } - } - } diff --git a/Modules/Reflection/Injectors/HandlerInjector.cs b/Modules/Reflection/Injectors/HandlerInjector.cs index 40fa84c6..bb94ec93 100644 --- a/Modules/Reflection/Injectors/HandlerInjector.cs +++ b/Modules/Reflection/Injectors/HandlerInjector.cs @@ -3,16 +3,13 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection.Injectors -{ - - public class HandlerInjector : IParameterInjector - { +namespace GenHTTP.Modules.Reflection.Injectors; - public bool Supports(Type type) => type == typeof(IHandler); +public class HandlerInjector : IParameterInjector +{ - public object? GetValue(IHandler handler, IRequest request, Type targetType) => handler; + public bool Supports(Type type) => type == typeof(IHandler); - } + public object? GetValue(IHandler handler, IRequest request, Type targetType) => handler; } diff --git a/Modules/Reflection/Injectors/IParameterInjector.cs b/Modules/Reflection/Injectors/IParameterInjector.cs index f4948bb6..35f0a28e 100644 --- a/Modules/Reflection/Injectors/IParameterInjector.cs +++ b/Modules/Reflection/Injectors/IParameterInjector.cs @@ -3,16 +3,13 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection.Injectors -{ - - public interface IParameterInjector - { +namespace GenHTTP.Modules.Reflection.Injectors; - bool Supports(Type type); +public interface IParameterInjector +{ - object? GetValue(IHandler handler, IRequest request, Type targetType); + bool Supports(Type type); - } + object? GetValue(IHandler handler, IRequest request, Type targetType); } diff --git a/Modules/Reflection/Injectors/InjectionRegistry.cs b/Modules/Reflection/Injectors/InjectionRegistry.cs index 73b6a7fa..7b979cca 100644 --- a/Modules/Reflection/Injectors/InjectionRegistry.cs +++ b/Modules/Reflection/Injectors/InjectionRegistry.cs @@ -1,20 +1,17 @@ using System.Collections.Generic; -namespace GenHTTP.Modules.Reflection.Injectors -{ +namespace GenHTTP.Modules.Reflection.Injectors; - public class InjectionRegistry : List - { +public class InjectionRegistry : List +{ - #region Initialization + #region Initialization - public InjectionRegistry(IEnumerable injectors) : base(injectors) - { + public InjectionRegistry(IEnumerable injectors) : base(injectors) + { } - #endregion - - } + #endregion } diff --git a/Modules/Reflection/Injectors/InjectionRegistryBuilder.cs b/Modules/Reflection/Injectors/InjectionRegistryBuilder.cs index 915ba1b0..839a90ed 100644 --- a/Modules/Reflection/Injectors/InjectionRegistryBuilder.cs +++ b/Modules/Reflection/Injectors/InjectionRegistryBuilder.cs @@ -2,25 +2,22 @@ using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.Reflection.Injectors -{ +namespace GenHTTP.Modules.Reflection.Injectors; - public class InjectionRegistryBuilder : IBuilder - { - private readonly List _Injectors = new(); +public class InjectionRegistryBuilder : IBuilder +{ + private readonly List _Injectors = new(); - #region Functionality + #region Functionality - public InjectionRegistryBuilder Add(IParameterInjector injector) - { + public InjectionRegistryBuilder Add(IParameterInjector injector) + { _Injectors.Add(injector); return this; } - public InjectionRegistry Build() => new(_Injectors); - - #endregion + public InjectionRegistry Build() => new(_Injectors); - } + #endregion } diff --git a/Modules/Reflection/Injectors/RequestBodyInjector.cs b/Modules/Reflection/Injectors/RequestBodyInjector.cs index e413d76e..5d500153 100644 --- a/Modules/Reflection/Injectors/RequestBodyInjector.cs +++ b/Modules/Reflection/Injectors/RequestBodyInjector.cs @@ -4,16 +4,15 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection.Injectors -{ +namespace GenHTTP.Modules.Reflection.Injectors; - public class RequestBodyInjector : IParameterInjector - { +public class RequestBodyInjector : IParameterInjector +{ - public bool Supports(Type type) => type == typeof(Stream); + public bool Supports(Type type) => type == typeof(Stream); - public object? GetValue(IHandler handler, IRequest request, Type targetType) - { + public object? GetValue(IHandler handler, IRequest request, Type targetType) + { if (request.Content is null) { throw new ProviderException(ResponseStatus.BadRequest, "Request body expected"); @@ -22,6 +21,4 @@ public class RequestBodyInjector : IParameterInjector return request.Content; } - } - } diff --git a/Modules/Reflection/Injectors/RequestInjector.cs b/Modules/Reflection/Injectors/RequestInjector.cs index e18c5bae..f9aa233f 100644 --- a/Modules/Reflection/Injectors/RequestInjector.cs +++ b/Modules/Reflection/Injectors/RequestInjector.cs @@ -3,16 +3,13 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection.Injectors -{ - - public class RequestInjector : IParameterInjector - { +namespace GenHTTP.Modules.Reflection.Injectors; - public bool Supports(Type type) => type == typeof(IRequest); +public class RequestInjector : IParameterInjector +{ - public object? GetValue(IHandler handler, IRequest request, Type targetType) => request; + public bool Supports(Type type) => type == typeof(IRequest); - } + public object? GetValue(IHandler handler, IRequest request, Type targetType) => request; } diff --git a/Modules/Reflection/Injectors/UserInjector.cs b/Modules/Reflection/Injectors/UserInjector.cs index 2b02cae1..0cc7af4e 100644 --- a/Modules/Reflection/Injectors/UserInjector.cs +++ b/Modules/Reflection/Injectors/UserInjector.cs @@ -4,22 +4,21 @@ using GenHTTP.Api.Content.Authentication; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection.Injectors -{ +namespace GenHTTP.Modules.Reflection.Injectors; - public class UserInjector : IParameterInjector where T : IUser - { +public class UserInjector : IParameterInjector where T : IUser +{ - #region Get-/Setters + #region Get-/Setters - public bool Supports(Type type) => type == typeof(T); + public bool Supports(Type type) => type == typeof(T); - #endregion + #endregion - #region Functionality + #region Functionality - public object? GetValue(IHandler handler, IRequest request, Type targetType) - { + public object? GetValue(IHandler handler, IRequest request, Type targetType) + { if (request.Properties.TryGet("__AUTH_USER", out var user)) { return user; @@ -28,8 +27,6 @@ public class UserInjector : IParameterInjector where T : IUser throw new ProviderException(ResponseStatus.Unauthorized, "Authentication required to invoke this endpoint"); } - #endregion - - } + #endregion } diff --git a/Modules/Reflection/MethodAttribute.cs b/Modules/Reflection/MethodAttribute.cs index 2f367d2c..a5b8d377 100644 --- a/Modules/Reflection/MethodAttribute.cs +++ b/Modules/Reflection/MethodAttribute.cs @@ -4,66 +4,63 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection +namespace GenHTTP.Modules.Reflection; + +/// +/// Attribute indicating that this method can be invoked +/// via reflection. +/// +[AttributeUsage(AttributeTargets.Method)] +public class MethodAttribute : Attribute, IMethodConfiguration { + #region Get-/Setters + /// - /// Attribute indicating that this method can be invoked - /// via reflection. + /// The HTTP verbs which are supported by this method. /// - [AttributeUsage(AttributeTargets.Method)] - public class MethodAttribute : Attribute, IMethodConfiguration - { - - #region Get-/Setters - - /// - /// The HTTP verbs which are supported by this method. - /// - public HashSet SupportedMethods { get; set; } + public HashSet SupportedMethods { get; set; } - /// - /// If specified, the content generated by this method - /// will not be declared and therefore not be listed - /// in sitemaps or other content dependent functionality. - /// - public bool IgnoreContent { get; set; } + /// + /// If specified, the content generated by this method + /// will not be declared and therefore not be listed + /// in sitemaps or other content dependent functionality. + /// + public bool IgnoreContent { get; set; } - #endregion + #endregion - #region Initialization + #region Initialization - /// - /// Marks the method as a invokable function. - /// - public MethodAttribute() - { + /// + /// Marks the method as a invokable function. + /// + public MethodAttribute() + { SupportedMethods = new(2) { FlexibleRequestMethod.Get(RequestMethod.GET), FlexibleRequestMethod.Get(RequestMethod.HEAD) }; } - /// - /// Marks the method as a invokable function for the specified HTTP verbs. - /// - /// The HTTP verbs supported by this method - public MethodAttribute(params RequestMethod[] methods) - { + /// + /// Marks the method as a invokable function for the specified HTTP verbs. + /// + /// The HTTP verbs supported by this method + public MethodAttribute(params RequestMethod[] methods) + { SupportedMethods = new(methods.Select(m => FlexibleRequestMethod.Get(m))); } - /// - /// Marks the method as a invokable function for the specified HTTP verbs. - /// - /// The HTTP verbs supported by this method - public MethodAttribute(params FlexibleRequestMethod[] methods) - { + /// + /// Marks the method as a invokable function for the specified HTTP verbs. + /// + /// The HTTP verbs supported by this method + public MethodAttribute(params FlexibleRequestMethod[] methods) + { SupportedMethods = new(methods); } - #endregion - - } + #endregion } diff --git a/Modules/Reflection/MethodCollection.cs b/Modules/Reflection/MethodCollection.cs index af4537e3..75b1388f 100644 --- a/Modules/Reflection/MethodCollection.cs +++ b/Modules/Reflection/MethodCollection.cs @@ -6,36 +6,35 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection -{ +namespace GenHTTP.Modules.Reflection; - public sealed class MethodCollection : IHandler - { +public sealed class MethodCollection : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public List Methods { get; } + public List Methods { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public MethodCollection(IHandler parent, IEnumerable> methodFactories) - { + public MethodCollection(IHandler parent, IEnumerable> methodFactories) + { Parent = parent; Methods = methodFactories.Select(factory => factory(this)) .ToList(); } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { var methods = FindProviders(request.Target.GetRemaining().ToString(), request.Method, out var foundOthers); if (methods.Count == 1) @@ -65,16 +64,16 @@ public MethodCollection(IHandler parent, IEnumerable FindProviders(string path, FlexibleRequestMethod requestedMethod, out bool foundOthers) - { + private List FindProviders(string path, FlexibleRequestMethod requestedMethod, out bool foundOthers) + { foundOthers = false; var result = new List(2); @@ -111,8 +110,6 @@ private List FindProviders(string path, FlexibleRequestMethod req return result; } - #endregion - - } + #endregion } diff --git a/Modules/Reflection/MethodHandler.cs b/Modules/Reflection/MethodHandler.cs index 6375c4c4..890fa842 100644 --- a/Modules/Reflection/MethodHandler.cs +++ b/Modules/Reflection/MethodHandler.cs @@ -11,57 +11,56 @@ using GenHTTP.Modules.Conversion; using GenHTTP.Modules.Conversion.Formatters; -using GenHTTP.Modules.Conversion.Providers; -using GenHTTP.Modules.Conversion.Providers.Forms; +using GenHTTP.Modules.Conversion.Serializers; +using GenHTTP.Modules.Conversion.Serializers.Forms; using GenHTTP.Modules.Reflection.Injectors; -namespace GenHTTP.Modules.Reflection +namespace GenHTTP.Modules.Reflection; + +/// +/// Allows to invoke a function on a service oriented resource. +/// +/// +/// This provider analyzes the target method to be invoked and supplies +/// the required arguments. The result of the method is analyzed and +/// converted into a HTTP response. +/// +public sealed class MethodHandler : IHandler { + private static readonly object?[] NO_ARGUMENTS = Array.Empty(); - /// - /// Allows to invoke a function on a service oriented resource. - /// - /// - /// This provider analyzes the target method to be invoked and supplies - /// the required arguments. The result of the method is analyzed and - /// converted into a HTTP response. - /// - public sealed class MethodHandler : IHandler - { - private static readonly object?[] NO_ARGUMENTS = Array.Empty(); - - private static readonly Type? VOID_TASK_RESULT = Type.GetType("System.Threading.Tasks.VoidTaskResult"); + private static readonly Type? VOID_TASK_RESULT = Type.GetType("System.Threading.Tasks.VoidTaskResult"); - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public MethodRouting Routing { get; } + public MethodRouting Routing { get; } - public IMethodConfiguration Configuration { get; } + public IMethodConfiguration Configuration { get; } - public MethodInfo Method { get; } + public MethodInfo Method { get; } - private Guid ID { get; } + private Guid ID { get; } - private Func InstanceProvider { get; } + private Func InstanceProvider { get; } - private Func?, ValueTask> ResponseProvider { get; } + private Func?, ValueTask> ResponseProvider { get; } - private SerializationRegistry Serialization { get; } + private SerializationRegistry Serialization { get; } - private InjectionRegistry Injection { get; } + private InjectionRegistry Injection { get; } - private FormatterRegistry Formatting { get; } + private FormatterRegistry Formatting { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, Func instanceProvider, IMethodConfiguration metaData, - Func?, ValueTask> responseProvider, SerializationRegistry serialization, - InjectionRegistry injection, FormatterRegistry formatting) - { + public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, Func instanceProvider, IMethodConfiguration metaData, + Func?, ValueTask> responseProvider, SerializationRegistry serialization, + InjectionRegistry injection, FormatterRegistry formatting) + { Parent = parent; Method = method; @@ -79,12 +78,12 @@ public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, ID = Guid.NewGuid(); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var arguments = await GetArguments(request); var result = Invoke(arguments); @@ -92,8 +91,8 @@ public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, return await ResponseProvider(request, this, await UnwrapAsync(result), null); } - private async ValueTask GetArguments(IRequest request) - { + private async ValueTask GetArguments(IRequest request) + { var targetParameters = Method.GetParameters(); Match? sourceParameters = null; @@ -143,7 +142,7 @@ public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, if (request.Content != null) { using var reader = new StreamReader(request.Content, leaveOpen: true); - + var body = await reader.ReadToEndAsync(); if (!string.IsNullOrWhiteSpace(body)) @@ -223,10 +222,10 @@ public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, return NO_ARGUMENTS; } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - private object? Invoke(object?[] arguments) - { + private object? Invoke(object?[] arguments) + { try { return Method.Invoke(InstanceProvider(), arguments); @@ -243,8 +242,8 @@ public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, } } - private static async ValueTask UnwrapAsync(object? result) - { + private static async ValueTask UnwrapAsync(object? result) + { if (result == null) { return null; @@ -277,8 +276,6 @@ public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, return result; } - #endregion - - } + #endregion } diff --git a/Modules/Reflection/MethodRouting.cs b/Modules/Reflection/MethodRouting.cs index f994cab4..40ab4ab6 100644 --- a/Modules/Reflection/MethodRouting.cs +++ b/Modules/Reflection/MethodRouting.cs @@ -2,70 +2,67 @@ using GenHTTP.Api.Routing; -namespace GenHTTP.Modules.Reflection -{ +namespace GenHTTP.Modules.Reflection; - public sealed class MethodRouting +public sealed class MethodRouting +{ + private readonly string _PathExpression; + + private Regex? _ParsedPath; + + #region Get-/Setters + + /// + /// The path of the method, with placeholders for + /// path variables (":var"). + /// + public WebPath Path { get; } + + /// + /// The path of the method, converted into a regular + /// expression to be evaluated at runtime. + /// + public Regex ParsedPath => _ParsedPath ??= new(_PathExpression, RegexOptions.Compiled); + + /// + /// The first segment of the raw path, if any. + /// + public string? Segment { get; } + + /// + /// True, if this route matches the index of the + /// scoped segment. + /// + public bool IsIndex { get; } + + /// + /// True, if this is a wildcard route that is created + /// when returning a handler or handler builder from + /// a method. + /// + /// + /// Wildcard routes have a lower priority compared to + /// non-wildcard routes and will not be considered + /// ambiguous. + /// + public bool IsWildcard { get; } + + #endregion + + #region Initialization + + public MethodRouting(string path, string pathExpression, string? segment, bool isIndex, bool isWildcard) { - private readonly string _PathExpression; - - private Regex? _ParsedPath; - - #region Get-/Setters - - /// - /// The path of the method, with placeholders for - /// path variables (":var"). - /// - public WebPath Path { get; } - - /// - /// The path of the method, converted into a regular - /// expression to be evaluated at runtime. - /// - public Regex ParsedPath => _ParsedPath ??= new(_PathExpression, RegexOptions.Compiled); - - /// - /// The first segment of the raw path, if any. - /// - public string? Segment { get; } - - /// - /// True, if this route matches the index of the - /// scoped segment. - /// - public bool IsIndex { get; } - - /// - /// True, if this is a wildcard route that is created - /// when returning a handler or handler builder from - /// a method. - /// - /// - /// Wildcard routes have a lower priority compared to - /// non-wildcard routes and will not be considered - /// ambiguous. - /// - public bool IsWildcard { get; } - - #endregion - - #region Initialization - - public MethodRouting(string path, string pathExpression, string? segment, bool isIndex, bool isWildcard) - { Path = new PathBuilder(path).Build(); _PathExpression = pathExpression; - + Segment = segment; IsIndex = isIndex; IsWildcard = isWildcard; } - #endregion - - } + #endregion } diff --git a/Modules/Reflection/PathArguments.cs b/Modules/Reflection/PathArguments.cs index 2d3d8181..e98be0d8 100644 --- a/Modules/Reflection/PathArguments.cs +++ b/Modules/Reflection/PathArguments.cs @@ -4,27 +4,26 @@ using GenHTTP.Api.Content; -namespace GenHTTP.Modules.Reflection +namespace GenHTTP.Modules.Reflection; + +public static class PathArguments { + private static readonly MethodRouting EMPTY = new("/", "^(/|)$", null, true, false); + + private static readonly MethodRouting EMPTY_WILDCARD = new("/", "^.*", null, true, true); + + private static readonly Regex VAR_PATTERN = new(@"\:([a-z]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static class PathArguments + /// + /// Parses the given path and returns a routing structure + /// expected by the method provider to check which logic + /// to be executed on request. + /// + /// The path to be analyzed + /// If true, a route will be created that matches any sub path + /// The routing information to be used by the method provider + public static MethodRouting Route(string? path, bool wildcard = false) { - private static readonly MethodRouting EMPTY = new("/", "^(/|)$", null, true, false); - - private static readonly MethodRouting EMPTY_WILDCARD = new("/", "^.*", null, true, true); - - private static readonly Regex VAR_PATTERN = new(@"\:([a-z]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); - - /// - /// Parses the given path and returns a routing structure - /// expected by the method provider to check which logic - /// to be executed on request. - /// - /// The path to be analyzed - /// If true, a route will be created that matches any sub path - /// The routing information to be used by the method provider - public static MethodRouting Route(string? path, bool wildcard = false) - { if (path is not null) { var builder = new StringBuilder(path); @@ -55,15 +54,15 @@ public static MethodRouting Route(string? path, bool wildcard = false) return (wildcard) ? EMPTY_WILDCARD : EMPTY; } - /// - /// Checks, whether the given type ultimately returns a handler or handler builder, - /// so requests should passed to this handler which means that we allow any sub - /// routes here. - /// - /// The return type to be checked - /// true, if the given type will ultimately create an IHandler instance that should handle the request - public static bool CheckWildcardRoute(Type returnType) - { + /// + /// Checks, whether the given type ultimately returns a handler or handler builder, + /// so requests should passed to this handler which means that we allow any sub + /// routes here. + /// + /// The return type to be checked + /// true, if the given type will ultimately create an IHandler instance that should handle the request + public static bool CheckWildcardRoute(Type returnType) + { if (IsHandlerType(returnType)) { return true; @@ -80,11 +79,9 @@ public static bool CheckWildcardRoute(Type returnType) return false; } - private static bool IsHandlerType(Type returnType) - { + private static bool IsHandlerType(Type returnType) + { return typeof(IHandlerBuilder).IsAssignableFrom(returnType) || typeof(IHandler).IsAssignableFrom(returnType); } - } - } diff --git a/Modules/Reflection/ResponseProvider.cs b/Modules/Reflection/ResponseProvider.cs index a2a7c57b..9c58d803 100644 --- a/Modules/Reflection/ResponseProvider.cs +++ b/Modules/Reflection/ResponseProvider.cs @@ -7,42 +7,41 @@ using GenHTTP.Modules.Basics; using GenHTTP.Modules.Conversion.Formatters; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.IO; using GenHTTP.Modules.IO.Streaming; -namespace GenHTTP.Modules.Reflection -{ +namespace GenHTTP.Modules.Reflection; - /// - /// Converts the result fetched from an invocation using reflection - /// into a HTTP response. - /// - public class ResponseProvider - { +/// +/// Converts the result fetched from an invocation using reflection +/// into a HTTP response. +/// +public class ResponseProvider +{ - #region Get-/Setters + #region Get-/Setters - private SerializationRegistry Serialization { get; } + private SerializationRegistry Serialization { get; } - private FormatterRegistry Formatting { get; } + private FormatterRegistry Formatting { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ResponseProvider(SerializationRegistry serialization, FormatterRegistry formatting) - { + public ResponseProvider(SerializationRegistry serialization, FormatterRegistry formatting) + { Serialization = serialization; Formatting = formatting; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask GetResponseAsync(IRequest request, IHandler handler, object? result, Action? adjustments = null) - { + public async ValueTask GetResponseAsync(IRequest request, IHandler handler, object? result, Action? adjustments = null) + { // no result = 204 if (result is null) { @@ -129,8 +128,6 @@ public ResponseProvider(SerializationRegistry serialization, FormatterRegistry f throw new ProviderException(ResponseStatus.InternalServerError, "Result type must be one of: IHandlerBuilder, IHandler, IResponseBuilder, IResponse, Stream"); } - #endregion - - } + #endregion } diff --git a/Modules/Reflection/Result.cs b/Modules/Reflection/Result.cs index 960aec73..8c184030 100644 --- a/Modules/Reflection/Result.cs +++ b/Modules/Reflection/Result.cs @@ -3,79 +3,78 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Reflection +namespace GenHTTP.Modules.Reflection; + +/// +/// A result of a service invocation that wraps a given payload and +/// still allows to modify the response generated by the server. +/// +/// The type of payload wrapped by this result +/// +/// Useful to change response properties (such as the status code or headers) +/// while still keeping the content in place. Note that returning a result +/// will not change the serialized outcome of the service method, it will be +/// the same as it would be when you would just return the payload itself. +/// +public class Result : IResultWrapper, IResponseModification> { + private FlexibleResponseStatus? _Status; - /// - /// A result of a service invocation that wraps a given payload and - /// still allows to modify the response generated by the server. - /// - /// The type of payload wrapped by this result - /// - /// Useful to change response properties (such as the status code or headers) - /// while still keeping the content in place. Note that returning a result - /// will not change the serialized outcome of the service method, it will be - /// the same as it would be when you would just return the payload itself. - /// - public class Result : IResultWrapper, IResponseModification> - { - private FlexibleResponseStatus? _Status; + private Dictionary? _Headers; - private Dictionary? _Headers; + private DateTime? _Expires; - private DateTime? _Expires; + private DateTime? _Modified; - private DateTime? _Modified; + private List? _Cookies; - private List? _Cookies; + private FlexibleContentType? _ContentType; - private FlexibleContentType? _ContentType; + private string? _Encoding; - private string? _Encoding; + #region Get-/Setters - #region Get-/Setters - - /// - /// The actual data to be returned to the client. - /// - public T? Payload { get; } + /// + /// The actual data to be returned to the client. + /// + public T? Payload { get; } - object? IResultWrapper.Payload => Payload; + object? IResultWrapper.Payload => Payload; - #endregion + #endregion - #region Initialization + #region Initialization - /// - /// Creates a new result with the given payload. - /// - /// The payload to be returned to the client - public Result(T? payload) - { + /// + /// Creates a new result with the given payload. + /// + /// The payload to be returned to the client + public Result(T? payload) + { Payload = payload; } - #endregion + #endregion - #region Functionality + #region Functionality - /// - public Result Status(ResponseStatus status) - { + /// + public Result Status(ResponseStatus status) + { _Status = new(status); return this; } - /// - public Result Status(int status, string reason) - { + /// + public Result Status(int status, string reason) + { _Status = new FlexibleResponseStatus(status, reason); return this; } - /// - public Result Header(string key, string value) - { + /// + public Result Header(string key, string value) + { if (_Headers == null) { _Headers = new(); @@ -86,23 +85,23 @@ public Result Header(string key, string value) return this; } - /// - public Result Expires(DateTime expiryDate) - { + /// + public Result Expires(DateTime expiryDate) + { _Expires = expiryDate; return this; } - /// - public Result Modified(DateTime modificationDate) - { + /// + public Result Modified(DateTime modificationDate) + { _Modified = modificationDate; return this; } - /// - public Result Cookie(Cookie cookie) - { + /// + public Result Cookie(Cookie cookie) + { if (_Cookies == null) { _Cookies = new(); @@ -113,22 +112,22 @@ public Result Cookie(Cookie cookie) return this; } - /// - public Result Type(FlexibleContentType contentType) - { + /// + public Result Type(FlexibleContentType contentType) + { _ContentType = contentType; return this; } - /// - public Result Encoding(string encoding) - { + /// + public Result Encoding(string encoding) + { _Encoding = encoding; return this; } - void IResultWrapper.Apply(IResponseBuilder builder) - { + void IResultWrapper.Apply(IResponseBuilder builder) + { if (_Status != null) { var value = _Status.Value; @@ -173,8 +172,6 @@ void IResultWrapper.Apply(IResponseBuilder builder) } } - #endregion - - } + #endregion } diff --git a/Modules/ReverseProxy/Provider/ClientResponseContent.cs b/Modules/ReverseProxy/Provider/ClientResponseContent.cs index ebcc49de..2be3516d 100644 --- a/Modules/ReverseProxy/Provider/ClientResponseContent.cs +++ b/Modules/ReverseProxy/Provider/ClientResponseContent.cs @@ -7,21 +7,20 @@ using GenHTTP.Modules.IO.Streaming; -namespace GenHTTP.Modules.ReverseProxy.Provider -{ +namespace GenHTTP.Modules.ReverseProxy.Provider; - internal sealed class ClientResponseContent : IResponseContent, IDisposable - { - private bool _Disposed; +internal sealed class ClientResponseContent : IResponseContent, IDisposable +{ + private bool _Disposed; - #region Get/Setters + #region Get/Setters - private HttpResponseMessage Message { get; } + private HttpResponseMessage Message { get; } - public ulong? Length + public ulong? Length + { + get { - get - { var length = Message.Content.Headers.ContentLength; if (length != null) @@ -31,36 +30,36 @@ public ulong? Length return null; } - } + } - #endregion + #endregion - #region Initialization + #region Initialization - public ClientResponseContent(HttpResponseMessage message) - { + public ClientResponseContent(HttpResponseMessage message) + { Message = message; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask CalculateChecksumAsync() => new(); + public ValueTask CalculateChecksumAsync() => new(); - public async ValueTask WriteAsync(Stream target, uint bufferSize) - { + public async ValueTask WriteAsync(Stream target, uint bufferSize) + { using var source = await Message.Content.ReadAsStreamAsync(); await source.CopyPooledAsync(target, bufferSize); } - #endregion + #endregion - #region Disposal + #region Disposal - private void Dispose(bool disposing) - { + private void Dispose(bool disposing) + { if (!_Disposed) { if (disposing) @@ -72,14 +71,12 @@ private void Dispose(bool disposing) } } - public void Dispose() - { + public void Dispose() + { Dispose(disposing: true); GC.SuppressFinalize(this); } - #endregion - - } + #endregion } diff --git a/Modules/ReverseProxy/Provider/ReverseProxyBuilder.cs b/Modules/ReverseProxy/Provider/ReverseProxyBuilder.cs index 35e6fd70..18901820 100644 --- a/Modules/ReverseProxy/Provider/ReverseProxyBuilder.cs +++ b/Modules/ReverseProxy/Provider/ReverseProxyBuilder.cs @@ -4,22 +4,21 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.ReverseProxy.Provider -{ +namespace GenHTTP.Modules.ReverseProxy.Provider; - public sealed class ReverseProxyBuilder : IHandlerBuilder - { - private string? _Upstream; +public sealed class ReverseProxyBuilder : IHandlerBuilder +{ + private string? _Upstream; - private TimeSpan _ConnectTimeout = TimeSpan.FromSeconds(10); - private TimeSpan _ReadTimeout = TimeSpan.FromSeconds(60); + private TimeSpan _ConnectTimeout = TimeSpan.FromSeconds(10); + private TimeSpan _ReadTimeout = TimeSpan.FromSeconds(60); - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - #region Functionality + #region Functionality - public ReverseProxyBuilder Upstream(string upstream) - { + public ReverseProxyBuilder Upstream(string upstream) + { _Upstream = upstream; if (_Upstream.EndsWith('/')) @@ -30,26 +29,26 @@ public ReverseProxyBuilder Upstream(string upstream) return this; } - public ReverseProxyBuilder ConnectTimeout(TimeSpan connectTimeout) - { + public ReverseProxyBuilder ConnectTimeout(TimeSpan connectTimeout) + { _ConnectTimeout = connectTimeout; return this; } - public ReverseProxyBuilder ReadTimeout(TimeSpan readTimeout) - { + public ReverseProxyBuilder ReadTimeout(TimeSpan readTimeout) + { _ReadTimeout = readTimeout; return this; } - public ReverseProxyBuilder Add(IConcernBuilder concern) - { + public ReverseProxyBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { if (_Upstream is null) { throw new BuilderMissingPropertyException("Upstream"); @@ -58,8 +57,6 @@ public IHandler Build(IHandler parent) return Concerns.Chain(parent, _Concerns, (p) => new ReverseProxyProvider(p, _Upstream, _ConnectTimeout, _ReadTimeout)); } - #endregion - - } + #endregion } diff --git a/Modules/ReverseProxy/Provider/ReverseProxyProvider.cs b/Modules/ReverseProxy/Provider/ReverseProxyProvider.cs index 5e0d7b2a..26ee73c1 100644 --- a/Modules/ReverseProxy/Provider/ReverseProxyProvider.cs +++ b/Modules/ReverseProxy/Provider/ReverseProxyProvider.cs @@ -13,45 +13,44 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.ReverseProxy.Provider -{ +namespace GenHTTP.Modules.ReverseProxy.Provider; - public sealed class ReverseProxyProvider : IHandler +public sealed class ReverseProxyProvider : IHandler +{ + private static readonly HashSet RESERVED_RESPONSE_HEADERS = new(StringComparer.OrdinalIgnoreCase) { - private static readonly HashSet RESERVED_RESPONSE_HEADERS = new(StringComparer.OrdinalIgnoreCase) - { - "Server", - "Date", - "Content-Encoding", - "Transfer-Encoding", - "Content-Type", - "Connection", - "Content-Length", - "Keep-Alive" - }; - - private static readonly HashSet RESERVED_REQUEST_HEADERS = new(StringComparer.OrdinalIgnoreCase) - { - "Host", - "Connection", - "Forwarded", - "Upgrade-Insecure-Requests" - }; + "Server", + "Date", + "Content-Encoding", + "Transfer-Encoding", + "Content-Type", + "Connection", + "Content-Length", + "Keep-Alive" + }; + + private static readonly HashSet RESERVED_REQUEST_HEADERS = new(StringComparer.OrdinalIgnoreCase) + { + "Host", + "Connection", + "Forwarded", + "Upgrade-Insecure-Requests" + }; - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public string Upstream { get; } + public string Upstream { get; } - private HttpClient Client { get; } + private HttpClient Client { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ReverseProxyProvider(IHandler parent, string upstream, TimeSpan connectTimeout, TimeSpan readTimeout) - { + public ReverseProxyProvider(IHandler parent, string upstream, TimeSpan connectTimeout, TimeSpan readTimeout) + { Parent = parent; Upstream = upstream; @@ -68,12 +67,12 @@ public ReverseProxyProvider(IHandler parent, string upstream, TimeSpan connectTi }; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { try { var req = ConfigureRequest(request); @@ -92,8 +91,8 @@ public ReverseProxyProvider(IHandler parent, string upstream, TimeSpan connectTi } } - private HttpRequestMessage ConfigureRequest(IRequest request) - { + private HttpRequestMessage ConfigureRequest(IRequest request) + { var req = new HttpRequestMessage(new(request.Method.RawMethod), GetRequestUri(request)); if (request.Content is not null && CanSendBody(request)) @@ -128,13 +127,13 @@ private HttpRequestMessage ConfigureRequest(IRequest request) return req; } - private string GetRequestUri(IRequest request) - { + private string GetRequestUri(IRequest request) + { return Upstream + request.Target.GetRemaining().ToString(true) + GetQueryString(request); } - private static string GetQueryString(IRequest request) - { + private static string GetQueryString(IRequest request) + { if (request.Query.Count > 0) { var query = HttpUtility.ParseQueryString(string.Empty); @@ -152,8 +151,8 @@ private static string GetQueryString(IRequest request) return string.Empty; } - private IResponseBuilder GetResponse(HttpResponseMessage response, IRequest request) - { + private IResponseBuilder GetResponse(HttpResponseMessage response, IRequest request) + { var builder = request.Respond(); builder.Status((int)response.StatusCode, response.ReasonPhrase ?? "No Reason"); @@ -189,8 +188,8 @@ private IResponseBuilder GetResponse(HttpResponseMessage response, IRequest requ return builder; } - private void SetHeaders(IResponseBuilder builder, IRequest request, HttpHeaders headers) - { + private void SetHeaders(IResponseBuilder builder, IRequest request, HttpHeaders headers) + { foreach (var kv in headers) { var key = kv.Key; @@ -235,18 +234,18 @@ private void SetHeaders(IResponseBuilder builder, IRequest request, HttpHeaders } } - private static bool CanSendBody(IRequest request) - { + private static bool CanSendBody(IRequest request) + { return !request.HasType(RequestMethod.GET, RequestMethod.HEAD, RequestMethod.OPTIONS); } - private static bool HasBody(IRequest request, HttpResponseMessage response) - { + private static bool HasBody(IRequest request, HttpResponseMessage response) + { return !request.HasType(RequestMethod.HEAD) && response.Content.Headers.ContentType is not null; } - private string RewriteLocation(string location, IRequest request) - { + private string RewriteLocation(string location, IRequest request) + { if (location.StartsWith(Upstream)) { var path = request.Target.Path.ToString(); @@ -271,15 +270,15 @@ private string RewriteLocation(string location, IRequest request) return location; } - private static string GetForwardings(IRequest request) - { + private static string GetForwardings(IRequest request) + { return string.Join(", ", request.Forwardings .Union(new[] { new Forwarding(request.LocalClient.IPAddress, request.LocalClient.Host, request.LocalClient.Protocol) }) .Select(f => GetForwarding(f))); } - private static string GetForwarding(Forwarding forwarding) - { + private static string GetForwarding(Forwarding forwarding) + { var result = new List(2); if (forwarding.For is not null) @@ -300,15 +299,13 @@ private static string GetForwarding(Forwarding forwarding) return string.Join("; ", result); } - private static bool TryParseDate(string value, out DateTime parsedValue) - { + private static bool TryParseDate(string value, out DateTime parsedValue) + { return DateTime.TryParseExact(value, CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal, out parsedValue); } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; - - #endregion + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - } + #endregion } diff --git a/Modules/ReverseProxy/Proxy.cs b/Modules/ReverseProxy/Proxy.cs index 6e02885c..7581b82c 100644 --- a/Modules/ReverseProxy/Proxy.cs +++ b/Modules/ReverseProxy/Proxy.cs @@ -1,16 +1,13 @@ using GenHTTP.Modules.ReverseProxy.Provider; -namespace GenHTTP.Modules.ReverseProxy +namespace GenHTTP.Modules.ReverseProxy; + +public static class Proxy { - public static class Proxy + public static ReverseProxyBuilder Create() { - - public static ReverseProxyBuilder Create() - { return new ReverseProxyBuilder(); } - } - } diff --git a/Modules/Security/Cors/CorsPolicyBuilder.cs b/Modules/Security/Cors/CorsPolicyBuilder.cs index 2fdda680..1301da8f 100644 --- a/Modules/Security/Cors/CorsPolicyBuilder.cs +++ b/Modules/Security/Cors/CorsPolicyBuilder.cs @@ -4,62 +4,59 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Security.Cors -{ +namespace GenHTTP.Modules.Security.Cors; - public sealed class CorsPolicyBuilder : IConcernBuilder - { - private readonly Dictionary _AdditionalPolicies = new(4); +public sealed class CorsPolicyBuilder : IConcernBuilder +{ + private readonly Dictionary _AdditionalPolicies = new(4); - private OriginPolicy? _DefaultPolicy; + private OriginPolicy? _DefaultPolicy; - #region Functionality + #region Functionality - /// - /// Sets the default policy to be applied, if no origin is given or - /// - /// - /// - /// - public CorsPolicyBuilder Default(OriginPolicy? policy) - { + /// + /// Sets the default policy to be applied, if no origin is given or + /// + /// + /// + /// + public CorsPolicyBuilder Default(OriginPolicy? policy) + { _DefaultPolicy = policy; return this; } - /// - /// Adds a custom policy for the specified origin. - /// - /// The origin the policy applies to (e.g. https://example.com) - /// The policy to be applied (if not set, access will be denied) - public CorsPolicyBuilder Add(string origin, OriginPolicy? policy) - { + /// + /// Adds a custom policy for the specified origin. + /// + /// The origin the policy applies to (e.g. https://example.com) + /// The policy to be applied (if not set, access will be denied) + public CorsPolicyBuilder Add(string origin, OriginPolicy? policy) + { _AdditionalPolicies[origin] = policy; return this; } - /// - /// Adds a custom policy for the specified origin. - /// - /// The origin the policy applies to (e.g. https://example.com) - /// The HTTP methods the client is allowed to access (any, if not given) - /// The headers a client may send to the server (any, if not given) - /// The headers that will be accessible by the client (any, if not given) - /// Whether the client is allowed to read credentials from the request - /// The duration in seconds this policy is valid for - public CorsPolicyBuilder Add(string origin, List? allowedMethods, List? allowedHeaders, - List? exposedHeaders, bool allowCredentials, uint maxAge = 86400) - { + /// + /// Adds a custom policy for the specified origin. + /// + /// The origin the policy applies to (e.g. https://example.com) + /// The HTTP methods the client is allowed to access (any, if not given) + /// The headers a client may send to the server (any, if not given) + /// The headers that will be accessible by the client (any, if not given) + /// Whether the client is allowed to read credentials from the request + /// The duration in seconds this policy is valid for + public CorsPolicyBuilder Add(string origin, List? allowedMethods, List? allowedHeaders, + List? exposedHeaders, bool allowCredentials, uint maxAge = 86400) + { return Add(origin, new OriginPolicy(allowedMethods, allowedHeaders, exposedHeaders, allowCredentials, maxAge)); } - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { return new CorsPolicyHandler(parent, contentFactory, _DefaultPolicy, _AdditionalPolicies); } - #endregion - - } + #endregion } diff --git a/Modules/Security/Cors/CorsPolicyHandler.cs b/Modules/Security/Cors/CorsPolicyHandler.cs index 01801dcd..9b0f2655 100644 --- a/Modules/Security/Cors/CorsPolicyHandler.cs +++ b/Modules/Security/Cors/CorsPolicyHandler.cs @@ -8,30 +8,29 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.Security.Cors -{ +namespace GenHTTP.Modules.Security.Cors; - public sealed class CorsPolicyHandler : IConcern - { - public const string ALLOW_ANY = "*"; +public sealed class CorsPolicyHandler : IConcern +{ + public const string ALLOW_ANY = "*"; - #region Get-/Setters + #region Get-/Setters - public IHandler Content { get; } + public IHandler Content { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - public OriginPolicy? DefaultPolicy { get; } + public OriginPolicy? DefaultPolicy { get; } - public IDictionary AdditionalPolicies { get; } + public IDictionary AdditionalPolicies { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public CorsPolicyHandler(IHandler parent, Func contentFactory, - OriginPolicy? defaultPolicy, IDictionary additionalPolicies) - { + public CorsPolicyHandler(IHandler parent, Func contentFactory, + OriginPolicy? defaultPolicy, IDictionary additionalPolicies) + { Parent = parent; Content = contentFactory(this); @@ -39,14 +38,14 @@ public CorsPolicyHandler(IHandler parent, Func contentFactor AdditionalPolicies = additionalPolicies; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask PrepareAsync() => Content.PrepareAsync(); + public ValueTask PrepareAsync() => Content.PrepareAsync(); - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var (origin, policy) = GetPolicy(request); IResponse? response; @@ -70,8 +69,8 @@ public CorsPolicyHandler(IHandler parent, Func contentFactor return response; } - private static void ConfigureResponse(IResponse response, string origin, OriginPolicy policy) - { + private static void ConfigureResponse(IResponse response, string origin, OriginPolicy policy) + { response.Headers["Access-Control-Allow-Origin"] = origin; if (HasValue(policy.AllowedMethods)) @@ -102,8 +101,8 @@ private static void ConfigureResponse(IResponse response, string origin, OriginP } } - private (string origin, OriginPolicy? policy) GetPolicy(IRequest request) - { + private (string origin, OriginPolicy? policy) GetPolicy(IRequest request) + { var origin = request["Origin"]; if (origin is not null) @@ -117,8 +116,8 @@ private static void ConfigureResponse(IResponse response, string origin, OriginP return (origin ?? ALLOW_ANY, DefaultPolicy); } - private static string GetListOrWildcard(List? values) - { + private static string GetListOrWildcard(List? values) + { if (values is not null) { return string.Join(", ", values); @@ -127,8 +126,8 @@ private static string GetListOrWildcard(List? values) return ALLOW_ANY; } - private static string GetListOrWildcard(List? values) - { + private static string GetListOrWildcard(List? values) + { if (values is not null) { return string.Join(", ", values.Select(v => v.RawMethod)); @@ -137,10 +136,8 @@ private static string GetListOrWildcard(List? values) return ALLOW_ANY; } - private static bool HasValue(List? list) => (list is null) || (list.Count > 0); - - #endregion + private static bool HasValue(List? list) => (list is null) || (list.Count > 0); - } + #endregion } diff --git a/Modules/Security/Cors/OriginPolicy.cs b/Modules/Security/Cors/OriginPolicy.cs index bd76450a..5cc1a236 100644 --- a/Modules/Security/Cors/OriginPolicy.cs +++ b/Modules/Security/Cors/OriginPolicy.cs @@ -2,10 +2,7 @@ using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Security.Cors -{ +namespace GenHTTP.Modules.Security.Cors; - public record OriginPolicy(List? AllowedMethods, List? AllowedHeaders, - List? ExposedHeaders, bool AllowCredentials, uint MaxAge); - -} +public record OriginPolicy(List? AllowedMethods, List? AllowedHeaders, + List? ExposedHeaders, bool AllowCredentials, uint MaxAge); diff --git a/Modules/Security/CorsPolicy.cs b/Modules/Security/CorsPolicy.cs index 9469ee20..25d00d46 100644 --- a/Modules/Security/CorsPolicy.cs +++ b/Modules/Security/CorsPolicy.cs @@ -1,36 +1,33 @@ using GenHTTP.Modules.Security.Cors; -namespace GenHTTP.Modules.Security -{ +namespace GenHTTP.Modules.Security; - public static class CorsPolicy - { +public static class CorsPolicy +{ - /// - /// Creates a policy that does not restrict browsers from interacting - /// with the requested resources. - /// - /// Indicate if the header Authorization should be in 'Access-Control-Allow-Headers'. Default value=true - public static CorsPolicyBuilder Permissive(bool allowAuthorization = true) - => new CorsPolicyBuilder().Default( - new OriginPolicy( - null, - allowAuthorization ? new() { "*", "Authorization" } : null, - null, - true, - 86400)); + /// + /// Creates a policy that does not restrict browsers from interacting + /// with the requested resources. + /// + /// Indicate if the header Authorization should be in 'Access-Control-Allow-Headers'. Default value=true + public static CorsPolicyBuilder Permissive(bool allowAuthorization = true) + => new CorsPolicyBuilder().Default( + new OriginPolicy( + null, + allowAuthorization ? new() { "*", "Authorization" } : null, + null, + true, + 86400)); - /// - /// Creates a policy that denies access to resources by browsers. - /// - /// - /// You may add more permissive policies for specific origins. - /// - public static CorsPolicyBuilder Restrictive() - { + /// + /// Creates a policy that denies access to resources by browsers. + /// + /// + /// You may add more permissive policies for specific origins. + /// + public static CorsPolicyBuilder Restrictive() + { return new CorsPolicyBuilder(); } - } - } diff --git a/Modules/Security/Extensions.cs b/Modules/Security/Extensions.cs index 5387de43..49623191 100644 --- a/Modules/Security/Extensions.cs +++ b/Modules/Security/Extensions.cs @@ -4,18 +4,17 @@ using GenHTTP.Modules.Security.Providers; -namespace GenHTTP.Modules.Security -{ +namespace GenHTTP.Modules.Security; - public static class Extensions - { +public static class Extensions +{ - public static IServerHost Harden(this IServerHost host, - bool secureUpgrade = true, - bool strictTransport = true, - bool preventSniffing = true) - { + public static IServerHost Harden(this IServerHost host, + bool secureUpgrade = true, + bool strictTransport = true, + bool preventSniffing = true) + { if (secureUpgrade) { host.SecureUpgrade(Api.Infrastructure.SecureUpgrade.Force); @@ -34,8 +33,8 @@ public static IServerHost Harden(this IServerHost host, return host; } - public static IServerHost SecureUpgrade(this IServerHost host, SecureUpgrade mode) - { + public static IServerHost SecureUpgrade(this IServerHost host, SecureUpgrade mode) + { if (mode != Api.Infrastructure.SecureUpgrade.None) { host.Add(new SecureUpgradeConcernBuilder().Mode(mode)); @@ -44,18 +43,16 @@ public static IServerHost SecureUpgrade(this IServerHost host, SecureUpgrade mod return host; } - public static IServerHost StrictTransport(this IServerHost host, StrictTransportPolicy policy) - { + public static IServerHost StrictTransport(this IServerHost host, StrictTransportPolicy policy) + { host.Add(new StrictTransportConcernBuilder().Policy(policy)); return host; } - public static IServerHost PreventSniffing(this IServerHost host) - { + public static IServerHost PreventSniffing(this IServerHost host) + { host.Add(new SnifferPreventionConcernBuilder()); return host; } - } - } diff --git a/Modules/Security/Providers/SecureUpgradeConcern.cs b/Modules/Security/Providers/SecureUpgradeConcern.cs index 79e9adc1..72c11ab1 100644 --- a/Modules/Security/Providers/SecureUpgradeConcern.cs +++ b/Modules/Security/Providers/SecureUpgradeConcern.cs @@ -9,38 +9,37 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.Security.Providers -{ +namespace GenHTTP.Modules.Security.Providers; - public sealed class SecureUpgradeConcern : IConcern - { +public sealed class SecureUpgradeConcern : IConcern +{ - #region Get-/Setters + #region Get-/Setters - public SecureUpgrade Mode { get; } + public SecureUpgrade Mode { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - public IHandler Content { get; } + public IHandler Content { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public SecureUpgradeConcern(IHandler parent, Func contentFactory, SecureUpgrade mode) - { + public SecureUpgradeConcern(IHandler parent, Func contentFactory, SecureUpgrade mode) + { Parent = parent; Content = contentFactory(this); Mode = mode; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { if (!request.EndPoint.Secure) { if (request.Server.EndPoints.Any(e => e.Secure)) @@ -84,8 +83,8 @@ public SecureUpgradeConcern(IHandler parent, Func contentFac return await Content.HandleAsync(request); } - private static string GetRedirectLocation(IRequest request, List endPoints) - { + private static string GetRedirectLocation(IRequest request, List endPoints) + { var targetPort = GetTargetPort(request, endPoints); var port = targetPort == 443 ? string.Empty : $":{targetPort}"; @@ -93,8 +92,8 @@ private static string GetRedirectLocation(IRequest request, List endP return $"https://{request.HostWithoutPort()}{port}{request.Target.Path}"; } - private static ushort GetTargetPort(IRequest request, List endPoints) - { + private static ushort GetTargetPort(IRequest request, List endPoints) + { // this extension can only be added if there are secure endpoints available if (endPoints.Count == 0) { @@ -119,10 +118,8 @@ private static ushort GetTargetPort(IRequest request, List endPoints) return endPoints.First().Port; } - public ValueTask PrepareAsync() => Content.PrepareAsync(); - - #endregion + public ValueTask PrepareAsync() => Content.PrepareAsync(); - } + #endregion -} +} \ No newline at end of file diff --git a/Modules/Security/Providers/SecureUpgradeConcernBuilder.cs b/Modules/Security/Providers/SecureUpgradeConcernBuilder.cs index 55d00996..5d5da6d1 100644 --- a/Modules/Security/Providers/SecureUpgradeConcernBuilder.cs +++ b/Modules/Security/Providers/SecureUpgradeConcernBuilder.cs @@ -3,28 +3,25 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.Security.Providers -{ +namespace GenHTTP.Modules.Security.Providers; - public sealed class SecureUpgradeConcernBuilder : IConcernBuilder - { - private SecureUpgrade _Mode = SecureUpgrade.Force; +public sealed class SecureUpgradeConcernBuilder : IConcernBuilder +{ + private SecureUpgrade _Mode = SecureUpgrade.Force; - #region Functionality + #region Functionality - public SecureUpgradeConcernBuilder Mode(SecureUpgrade mode) - { + public SecureUpgradeConcernBuilder Mode(SecureUpgrade mode) + { _Mode = mode; return this; } - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { return new SecureUpgradeConcern(parent, contentFactory, _Mode); } - #endregion - - } + #endregion -} +} \ No newline at end of file diff --git a/Modules/Security/Providers/SnifferPreventionConcern.cs b/Modules/Security/Providers/SnifferPreventionConcern.cs index d455cbee..92629615 100644 --- a/Modules/Security/Providers/SnifferPreventionConcern.cs +++ b/Modules/Security/Providers/SnifferPreventionConcern.cs @@ -4,34 +4,33 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Security.Providers -{ +namespace GenHTTP.Modules.Security.Providers; - public class SnifferPreventionConcern : IConcern - { +public class SnifferPreventionConcern : IConcern +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public IHandler Content { get; } + public IHandler Content { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public SnifferPreventionConcern(IHandler parent, Func contentFactory) - { + public SnifferPreventionConcern(IHandler parent, Func contentFactory) + { Parent = parent; Content = contentFactory(this); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var content = await Content.HandleAsync(request); if (content != null) @@ -42,10 +41,8 @@ public SnifferPreventionConcern(IHandler parent, Func conten return content; } - public ValueTask PrepareAsync() => Content.PrepareAsync(); - - #endregion + public ValueTask PrepareAsync() => Content.PrepareAsync(); - } + #endregion } diff --git a/Modules/Security/Providers/SnifferPreventionConcernBuilder.cs b/Modules/Security/Providers/SnifferPreventionConcernBuilder.cs index 7dbe8d09..73197296 100644 --- a/Modules/Security/Providers/SnifferPreventionConcernBuilder.cs +++ b/Modules/Security/Providers/SnifferPreventionConcernBuilder.cs @@ -2,21 +2,18 @@ using GenHTTP.Api.Content; -namespace GenHTTP.Modules.Security.Providers -{ +namespace GenHTTP.Modules.Security.Providers; - public class SnifferPreventionConcernBuilder : IConcernBuilder - { +public class SnifferPreventionConcernBuilder : IConcernBuilder +{ - #region Functionality + #region Functionality - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { return new SnifferPreventionConcern(parent, contentFactory); } - #endregion - - } + #endregion } diff --git a/Modules/Security/Providers/StrictTransportConcern.cs b/Modules/Security/Providers/StrictTransportConcern.cs index 95375abd..f4a9b7a8 100644 --- a/Modules/Security/Providers/StrictTransportConcern.cs +++ b/Modules/Security/Providers/StrictTransportConcern.cs @@ -4,29 +4,28 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.Security.Providers -{ +namespace GenHTTP.Modules.Security.Providers; - public sealed class StrictTransportConcern : IConcern - { - private const string HEADER = "Strict-Transport-Security"; +public sealed class StrictTransportConcern : IConcern +{ + private const string HEADER = "Strict-Transport-Security"; - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - public IHandler Content { get; } + public IHandler Content { get; } - public StrictTransportPolicy Policy { get; } + public StrictTransportPolicy Policy { get; } - private string HeaderValue { get; } + private string HeaderValue { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public StrictTransportConcern(IHandler parent, Func contentFactory, StrictTransportPolicy policy) - { + public StrictTransportConcern(IHandler parent, Func contentFactory, StrictTransportPolicy policy) + { Parent = parent; Content = contentFactory(this); @@ -34,12 +33,12 @@ public StrictTransportConcern(IHandler parent, Func contentF HeaderValue = GetPolicyHeader(); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { var response = await Content.HandleAsync(request); if (response is not null) @@ -56,8 +55,8 @@ public StrictTransportConcern(IHandler parent, Func contentF return response; } - private string GetPolicyHeader() - { + private string GetPolicyHeader() + { var seconds = (int)Policy.MaximumAge.TotalSeconds; var result = $"max-age={seconds}"; @@ -75,10 +74,8 @@ private string GetPolicyHeader() return result; } - public ValueTask PrepareAsync() => Content.PrepareAsync(); - - #endregion + public ValueTask PrepareAsync() => Content.PrepareAsync(); - } + #endregion } diff --git a/Modules/Security/Providers/StrictTransportConcernBuilder.cs b/Modules/Security/Providers/StrictTransportConcernBuilder.cs index 2d2a37ae..64677f3a 100644 --- a/Modules/Security/Providers/StrictTransportConcernBuilder.cs +++ b/Modules/Security/Providers/StrictTransportConcernBuilder.cs @@ -3,30 +3,27 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.Security.Providers -{ +namespace GenHTTP.Modules.Security.Providers; - public sealed class StrictTransportConcernBuilder : IConcernBuilder - { - private StrictTransportPolicy? _Policy; +public sealed class StrictTransportConcernBuilder : IConcernBuilder +{ + private StrictTransportPolicy? _Policy; - #region Functionality + #region Functionality - public StrictTransportConcernBuilder Policy(StrictTransportPolicy policy) - { + public StrictTransportConcernBuilder Policy(StrictTransportPolicy policy) + { _Policy = policy; return this; } - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { var policy = _Policy ?? throw new BuilderMissingPropertyException("policy"); return new StrictTransportConcern(parent, contentFactory, policy); } - #endregion - - } + #endregion } diff --git a/Modules/Security/Providers/StrictTransportPolicy.cs b/Modules/Security/Providers/StrictTransportPolicy.cs index aebb5adf..7ddf6455 100644 --- a/Modules/Security/Providers/StrictTransportPolicy.cs +++ b/Modules/Security/Providers/StrictTransportPolicy.cs @@ -1,32 +1,29 @@ using System; -namespace GenHTTP.Modules.Security.Providers -{ +namespace GenHTTP.Modules.Security.Providers; - public sealed class StrictTransportPolicy - { +public sealed class StrictTransportPolicy +{ - #region Get-/Setters + #region Get-/Setters - public TimeSpan MaximumAge { get; } + public TimeSpan MaximumAge { get; } - public bool IncludeSubdomains { get; } + public bool IncludeSubdomains { get; } - public bool Preload { get; } + public bool Preload { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public StrictTransportPolicy(TimeSpan maximumAge, bool includeSubdomains, bool preload) - { + public StrictTransportPolicy(TimeSpan maximumAge, bool includeSubdomains, bool preload) + { MaximumAge = maximumAge; IncludeSubdomains = includeSubdomains; Preload = preload; } - #endregion - - } + #endregion } diff --git a/Modules/ServerCaching/CachedResponse.cs b/Modules/ServerCaching/CachedResponse.cs index ee87d25d..3684eb85 100644 --- a/Modules/ServerCaching/CachedResponse.cs +++ b/Modules/ServerCaching/CachedResponse.cs @@ -2,12 +2,9 @@ using System; using System.Collections.Generic; -namespace GenHTTP.Modules.ServerCaching -{ - - public record CachedResponse(int StatusCode, string StatusPhrase, DateTime? Expires, DateTime? Modified, - Dictionary? Variations, Dictionary Headers, - Dictionary? Cookies, string? ContentType, - string? ContentEncoding, ulong? ContentLength, ulong? ContentChecksum); +namespace GenHTTP.Modules.ServerCaching; -} +public record CachedResponse(int StatusCode, string StatusPhrase, DateTime? Expires, DateTime? Modified, + Dictionary? Variations, Dictionary Headers, + Dictionary? Cookies, string? ContentType, + string? ContentEncoding, ulong? ContentLength, ulong? ContentChecksum); diff --git a/Modules/ServerCaching/Provider/CacheKey.cs b/Modules/ServerCaching/Provider/CacheKey.cs index 0e4d4c5c..02ba4eec 100644 --- a/Modules/ServerCaching/Provider/CacheKey.cs +++ b/Modules/ServerCaching/Provider/CacheKey.cs @@ -1,14 +1,13 @@ using GenHTTP.Api.Protocol; using System.Collections.Generic; -namespace GenHTTP.Modules.ServerCaching.Provider +namespace GenHTTP.Modules.ServerCaching.Provider; + +public static class CacheKey { - public static class CacheKey + public static string GetKey(this IRequest request) { - - public static string GetKey(this IRequest request) - { unchecked { ulong hash = 17; @@ -30,8 +29,8 @@ public static string GetKey(this IRequest request) } } - public static string GetVariationKey(this Dictionary? variations) - { + public static string GetVariationKey(this Dictionary? variations) + { unchecked { ulong hash = 17; @@ -49,6 +48,4 @@ public static string GetVariationKey(this Dictionary? variations } } - } - } diff --git a/Modules/ServerCaching/Provider/ServerCacheHandler.cs b/Modules/ServerCaching/Provider/ServerCacheHandler.cs index 4ffeb8bb..0773c00e 100644 --- a/Modules/ServerCaching/Provider/ServerCacheHandler.cs +++ b/Modules/ServerCaching/Provider/ServerCacheHandler.cs @@ -11,34 +11,33 @@ using GenHTTP.Modules.Basics; using GenHTTP.Modules.IO.Streaming; -namespace GenHTTP.Modules.ServerCaching.Provider -{ +namespace GenHTTP.Modules.ServerCaching.Provider; - public sealed class ServerCacheHandler : IConcern - { +public sealed class ServerCacheHandler : IConcern +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Content { get; } + public IHandler Content { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - private ICache Meta { get; } + private ICache Meta { get; } - private ICache Data { get; } + private ICache Data { get; } - private bool Invalidate { get; } + private bool Invalidate { get; } - private Func? Predicate { get; } + private Func? Predicate { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ServerCacheHandler(IHandler parent, Func contentFactory, - ICache meta, ICache data, - Func? predicate, bool invalidate) - { + public ServerCacheHandler(IHandler parent, Func contentFactory, + ICache meta, ICache data, + Func? predicate, bool invalidate) + { Parent = parent; Content = contentFactory(this); @@ -49,12 +48,12 @@ public ServerCacheHandler(IHandler parent, Func contentFacto Invalidate = invalidate; } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { if (request.HasType(RequestMethod.GET, RequestMethod.HEAD)) { var response = (Invalidate) ? await Content.HandleAsync(request) : null; @@ -106,10 +105,10 @@ public ServerCacheHandler(IHandler parent, Func contentFacto return await Content.HandleAsync(request); } - public ValueTask PrepareAsync() => Content.PrepareAsync(); + public ValueTask PrepareAsync() => Content.PrepareAsync(); - private static bool TryFindMatching(CachedResponse[] list, IRequest request, out CachedResponse? response) - { + private static bool TryFindMatching(CachedResponse[] list, IRequest request, out CachedResponse? response) + { response = null; foreach (var variant in list) @@ -137,8 +136,8 @@ private static bool TryFindMatching(CachedResponse[] list, IRequest request, out return (response != null); } - private static IResponse GetResponse(IRequest request, CachedResponse cached, Stream? content) - { + private static IResponse GetResponse(IRequest request, CachedResponse cached, Stream? content) + { var response = request.Respond() .Status(cached.StatusCode, cached.StatusPhrase); @@ -188,8 +187,8 @@ private static IResponse GetResponse(IRequest request, CachedResponse cached, St return response.Build(); } - private async static ValueTask GetResponse(IResponse response, IRequest request) - { + private async static ValueTask GetResponse(IResponse response, IRequest request) + { var headers = new Dictionary(response.Headers); var cookies = new Dictionary(response.Cookies); @@ -222,8 +221,8 @@ private async static ValueTask GetResponse(IResponse response, I checksum); } - private async static ValueTask HasChanged(IResponse current, CachedResponse cached) - { + private async static ValueTask HasChanged(IResponse current, CachedResponse cached) + { var checksum = (current.Content != null) ? await current.Content.CalculateChecksumAsync() : null; return (cached.ContentChecksum != checksum) @@ -236,8 +235,6 @@ private async static ValueTask HasChanged(IResponse current, CachedRespons || (current.Cookies.Count != cached.Cookies?.Count || current.Cookies.Except(cached.Cookies).Any()); } - #endregion - - } + #endregion } diff --git a/Modules/ServerCaching/Provider/ServerCacheHandlerBuilder.cs b/Modules/ServerCaching/Provider/ServerCacheHandlerBuilder.cs index 47f94087..a29a65ca 100644 --- a/Modules/ServerCaching/Provider/ServerCacheHandlerBuilder.cs +++ b/Modules/ServerCaching/Provider/ServerCacheHandlerBuilder.cs @@ -6,62 +6,61 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; -namespace GenHTTP.Modules.ServerCaching.Provider -{ +namespace GenHTTP.Modules.ServerCaching.Provider; - public class ServerCacheHandlerBuilder : IConcernBuilder - { - private ICache? _Meta; +public class ServerCacheHandlerBuilder : IConcernBuilder +{ + private ICache? _Meta; - private ICache? _Data; + private ICache? _Data; - private bool _Invalidate = true; + private bool _Invalidate = true; - private Func? _Predicate = null; + private Func? _Predicate = null; - #region Functionality + #region Functionality - /// - /// If disabled, the concern will not invalidate the cache by invoking - /// the handler to check for content changes. Therefore, it will always just - /// cache the first response. - /// - /// false, if the cache should not be invalidated automatically - public ServerCacheHandlerBuilder Invalidate(bool invalidate) - { + /// + /// If disabled, the concern will not invalidate the cache by invoking + /// the handler to check for content changes. Therefore, it will always just + /// cache the first response. + /// + /// false, if the cache should not be invalidated automatically + public ServerCacheHandlerBuilder Invalidate(bool invalidate) + { _Invalidate = invalidate; return this; } - /// - /// A predicate that will be invoked to check, whether the given response - /// should be cached or not. - /// - /// The predicate to evaluate before a response is cached - public ServerCacheHandlerBuilder Predicate(Func predicate) - { + /// + /// A predicate that will be invoked to check, whether the given response + /// should be cached or not. + /// + /// The predicate to evaluate before a response is cached + public ServerCacheHandlerBuilder Predicate(Func predicate) + { _Predicate = predicate; return this; } - public ServerCacheHandlerBuilder MetaStore(IBuilder> cache) => MetaStore(cache.Build()); + public ServerCacheHandlerBuilder MetaStore(IBuilder> cache) => MetaStore(cache.Build()); - public ServerCacheHandlerBuilder MetaStore(ICache cache) - { + public ServerCacheHandlerBuilder MetaStore(ICache cache) + { _Meta = cache; return this; } - public ServerCacheHandlerBuilder DataStore(ICache cache) - { + public ServerCacheHandlerBuilder DataStore(ICache cache) + { _Data = cache; return this; } - public ServerCacheHandlerBuilder DataStore(IBuilder> cache) => DataStore(cache.Build()); + public ServerCacheHandlerBuilder DataStore(IBuilder> cache) => DataStore(cache.Build()); - public IConcern Build(IHandler parent, Func contentFactory) - { + public IConcern Build(IHandler parent, Func contentFactory) + { var meta = _Meta ?? throw new BuilderMissingPropertyException("MetaStore"); var data = _Data ?? throw new BuilderMissingPropertyException("DataStore"); @@ -69,8 +68,6 @@ public IConcern Build(IHandler parent, Func contentFactory) return new ServerCacheHandler(parent, contentFactory, meta, data, _Predicate, _Invalidate); } - #endregion - - } + #endregion } diff --git a/Modules/ServerCaching/ServerCache.cs b/Modules/ServerCaching/ServerCache.cs index e818101d..dce53600 100644 --- a/Modules/ServerCaching/ServerCache.cs +++ b/Modules/ServerCaching/ServerCache.cs @@ -6,64 +6,61 @@ using GenHTTP.Modules.Caching; using GenHTTP.Modules.ServerCaching.Provider; -namespace GenHTTP.Modules.ServerCaching +namespace GenHTTP.Modules.ServerCaching; + +/// +/// Allows to cache generated responses so they can be served much +/// faster when requested again. +/// +public static class ServerCache { /// - /// Allows to cache generated responses so they can be served much - /// faster when requested again. + /// Creates a concern that will cache generated responses in + /// the specified caches. /// - public static class ServerCache - { - - /// - /// Creates a concern that will cache generated responses in - /// the specified caches. - /// - /// The cache to be used for meta data - /// The cache to be used for binary data - public static ServerCacheHandlerBuilder Create(ICache metaStore, ICache dataStore) - => new ServerCacheHandlerBuilder().MetaStore(metaStore).DataStore(dataStore); + /// The cache to be used for meta data + /// The cache to be used for binary data + public static ServerCacheHandlerBuilder Create(ICache metaStore, ICache dataStore) + => new ServerCacheHandlerBuilder().MetaStore(metaStore).DataStore(dataStore); - /// - /// Creates a concern that will cache generated responses in - /// the specified caches. - /// - /// The cache to be used for meta data - /// The cache to be used for binary data - public static ServerCacheHandlerBuilder Create(IBuilder> metaStore, IBuilder> dataStore) - => Create(metaStore.Build(), dataStore.Build()); - - /// - /// Creates a concern that will cache generated responses in memory. - /// - public static ServerCacheHandlerBuilder Memory() - => Create(Cache.Memory(), Cache.Memory()); + /// + /// Creates a concern that will cache generated responses in + /// the specified caches. + /// + /// The cache to be used for meta data + /// The cache to be used for binary data + public static ServerCacheHandlerBuilder Create(IBuilder> metaStore, IBuilder> dataStore) + => Create(metaStore.Build(), dataStore.Build()); - /// - /// Creates a concern that will cache generated responses in a - /// temporary folder on the file system. Uses a memory cache to - /// store meta data. - /// - public static ServerCacheHandlerBuilder TemporaryFiles() - => Create(Cache.Memory(), Cache.TemporaryFiles()); + /// + /// Creates a concern that will cache generated responses in memory. + /// + public static ServerCacheHandlerBuilder Memory() + => Create(Cache.Memory(), Cache.Memory()); - /// - /// Creates a concern that will cache generated responses in the specified - /// folder. Persists the cache accross server restarts. - /// - /// The directory to store the cached responses in - public static ServerCacheHandlerBuilder Persistent(string directory) - => Create(Cache.FileSystem(Path.Combine(directory, "meta")), Cache.FileSystem(Path.Combine(directory, "data"))); + /// + /// Creates a concern that will cache generated responses in a + /// temporary folder on the file system. Uses a memory cache to + /// store meta data. + /// + public static ServerCacheHandlerBuilder TemporaryFiles() + => Create(Cache.Memory(), Cache.TemporaryFiles()); - /// - /// Creates a concern that will cache generated responses in the specified - /// folder. Persists the cache accross server restarts. - /// - /// The directory to store the cached responses in - public static ServerCacheHandlerBuilder Persistent(DirectoryInfo directory) - => Persistent(directory.FullName); + /// + /// Creates a concern that will cache generated responses in the specified + /// folder. Persists the cache accross server restarts. + /// + /// The directory to store the cached responses in + public static ServerCacheHandlerBuilder Persistent(string directory) + => Create(Cache.FileSystem(Path.Combine(directory, "meta")), Cache.FileSystem(Path.Combine(directory, "data"))); - } + /// + /// Creates a concern that will cache generated responses in the specified + /// folder. Persists the cache accross server restarts. + /// + /// The directory to store the cached responses in + public static ServerCacheHandlerBuilder Persistent(DirectoryInfo directory) + => Persistent(directory.FullName); } diff --git a/Modules/SinglePageApplications/Provider/SinglePageBuilder.cs b/Modules/SinglePageApplications/Provider/SinglePageBuilder.cs index 33f72d47..233efd60 100644 --- a/Modules/SinglePageApplications/Provider/SinglePageBuilder.cs +++ b/Modules/SinglePageApplications/Provider/SinglePageBuilder.cs @@ -4,52 +4,49 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.SinglePageApplications.Provider -{ +namespace GenHTTP.Modules.SinglePageApplications.Provider; - public sealed class SinglePageBuilder : IHandlerBuilder - { - private IResourceTree? _Tree; +public sealed class SinglePageBuilder : IHandlerBuilder +{ + private IResourceTree? _Tree; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - private bool _ServerSideRouting = false; + private bool _ServerSideRouting = false; - #region Functionality + #region Functionality - public SinglePageBuilder Add(IConcernBuilder concern) - { + public SinglePageBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public SinglePageBuilder Tree(IResourceTree tree) - { + public SinglePageBuilder Tree(IResourceTree tree) + { _Tree = tree; return this; } - public SinglePageBuilder Tree(IBuilder tree) => Tree(tree.Build()); + public SinglePageBuilder Tree(IBuilder tree) => Tree(tree.Build()); - /// - /// If enabled, the server will serve the SPA index for - /// every unknown route, which enables path based routing. - /// - public SinglePageBuilder ServerSideRouting() - { + /// + /// If enabled, the server will serve the SPA index for + /// every unknown route, which enables path based routing. + /// + public SinglePageBuilder ServerSideRouting() + { _ServerSideRouting = true; return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { var tree = _Tree ?? throw new BuilderMissingPropertyException("tree"); return Concerns.Chain(parent, _Concerns, (p) => new SinglePageProvider(p, tree, _ServerSideRouting)); } - #endregion - - } + #endregion } diff --git a/Modules/SinglePageApplications/Provider/SinglePageProvider.cs b/Modules/SinglePageApplications/Provider/SinglePageProvider.cs index 24c01070..a3838136 100644 --- a/Modules/SinglePageApplications/Provider/SinglePageProvider.cs +++ b/Modules/SinglePageApplications/Provider/SinglePageProvider.cs @@ -8,34 +8,33 @@ using GenHTTP.Modules.IO; -namespace GenHTTP.Modules.SinglePageApplications.Provider -{ +namespace GenHTTP.Modules.SinglePageApplications.Provider; - public sealed class SinglePageProvider : IHandler +public sealed class SinglePageProvider : IHandler +{ + private static readonly HashSet INDEX_FILES = new(StringComparer.InvariantCultureIgnoreCase) { - private static readonly HashSet INDEX_FILES = new(StringComparer.InvariantCultureIgnoreCase) - { - "index.html", "index.htm" - }; + "index.html", "index.htm" + }; - private IHandler? _Index; + private IHandler? _Index; - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private IResourceTree Tree { get; } + private IResourceTree Tree { get; } - private IHandler Resources { get; } + private IHandler Resources { get; } - private bool ServerSideRouting { get; } + private bool ServerSideRouting { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public SinglePageProvider(IHandler parent, IResourceTree tree, bool serverSideRouting) - { + public SinglePageProvider(IHandler parent, IResourceTree tree, bool serverSideRouting) + { Parent = parent; Tree = tree; @@ -45,12 +44,12 @@ public SinglePageProvider(IHandler parent, IResourceTree tree, bool serverSideRo .Build(this); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { if (request.Target.Ended) { var index = await GetIndex(); @@ -80,8 +79,8 @@ public SinglePageProvider(IHandler parent, IResourceTree tree, bool serverSideRo return null; } - private async ValueTask GetIndex() - { + private async ValueTask GetIndex() + { if (_Index == null) { foreach (var index in INDEX_FILES) @@ -101,10 +100,8 @@ public SinglePageProvider(IHandler parent, IResourceTree tree, bool serverSideRo return _Index; } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; - - #endregion + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - } + #endregion } diff --git a/Modules/SinglePageApplications/SinglePageApplication.cs b/Modules/SinglePageApplications/SinglePageApplication.cs index 937e3351..9e424f6e 100644 --- a/Modules/SinglePageApplications/SinglePageApplication.cs +++ b/Modules/SinglePageApplications/SinglePageApplication.cs @@ -3,16 +3,13 @@ using GenHTTP.Modules.SinglePageApplications.Provider; -namespace GenHTTP.Modules.SinglePageApplications -{ - - public static class SinglePageApplication - { +namespace GenHTTP.Modules.SinglePageApplications; - public static SinglePageBuilder From(IBuilder tree) => From(tree.Build()); +public static class SinglePageApplication +{ - public static SinglePageBuilder From(IResourceTree tree) => new SinglePageBuilder().Tree(tree); + public static SinglePageBuilder From(IBuilder tree) => From(tree.Build()); - } + public static SinglePageBuilder From(IResourceTree tree) => new SinglePageBuilder().Tree(tree); -} +} \ No newline at end of file diff --git a/Modules/StaticWebsites/Provider/StaticWebsiteBuilder.cs b/Modules/StaticWebsites/Provider/StaticWebsiteBuilder.cs index 83d9ac45..71ee7fff 100644 --- a/Modules/StaticWebsites/Provider/StaticWebsiteBuilder.cs +++ b/Modules/StaticWebsites/Provider/StaticWebsiteBuilder.cs @@ -4,38 +4,35 @@ using GenHTTP.Api.Content.IO; using GenHTTP.Api.Infrastructure; -namespace GenHTTP.Modules.StaticWebsites.Provider -{ +namespace GenHTTP.Modules.StaticWebsites.Provider; - public class StaticWebsiteBuilder : IHandlerBuilder - { - private IResourceTree? _Tree; +public class StaticWebsiteBuilder : IHandlerBuilder +{ + private IResourceTree? _Tree; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - #region Functionality + #region Functionality - public StaticWebsiteBuilder Tree(IResourceTree tree) - { + public StaticWebsiteBuilder Tree(IResourceTree tree) + { _Tree = tree; return this; } - public StaticWebsiteBuilder Add(IConcernBuilder concern) - { + public StaticWebsiteBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { var tree = _Tree ?? throw new BuilderMissingPropertyException("tree"); return Concerns.Chain(parent, _Concerns, (p) => new StaticWebsiteHandler(p, tree)); } - #endregion - - } + #endregion } diff --git a/Modules/StaticWebsites/Provider/StaticWebsiteHandler.cs b/Modules/StaticWebsites/Provider/StaticWebsiteHandler.cs index 48973bfa..547df02d 100644 --- a/Modules/StaticWebsites/Provider/StaticWebsiteHandler.cs +++ b/Modules/StaticWebsites/Provider/StaticWebsiteHandler.cs @@ -6,27 +6,26 @@ using GenHTTP.Modules.IO; -namespace GenHTTP.Modules.StaticWebsites.Provider -{ +namespace GenHTTP.Modules.StaticWebsites.Provider; - public sealed class StaticWebsiteHandler : IHandler - { - private static readonly string[] INDEX_FILES = new[] { "index.html", "index.htm" }; +public sealed class StaticWebsiteHandler : IHandler +{ + private static readonly string[] INDEX_FILES = new[] { "index.html", "index.htm" }; - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private IResourceTree Tree { get; } + private IResourceTree Tree { get; } - private IHandler Resources { get; } + private IHandler Resources { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public StaticWebsiteHandler(IHandler parent, IResourceTree tree) - { + public StaticWebsiteHandler(IHandler parent, IResourceTree tree) + { Parent = parent; Tree = tree; @@ -34,12 +33,12 @@ public StaticWebsiteHandler(IHandler parent, IResourceTree tree) .Build(this); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask HandleAsync(IRequest request) - { + public async ValueTask HandleAsync(IRequest request) + { if (request.Target.Path.TrailingSlash) { var (node, _) = await Tree.Find(request.Target); @@ -67,13 +66,11 @@ public StaticWebsiteHandler(IHandler parent, IResourceTree tree) } } - public async ValueTask PrepareAsync() - { + public async ValueTask PrepareAsync() + { await Resources.PrepareAsync(); } - #endregion - - } + #endregion } diff --git a/Modules/StaticWebsites/StaticWebsite.cs b/Modules/StaticWebsites/StaticWebsite.cs index 26b8fe6d..dfc77d89 100644 --- a/Modules/StaticWebsites/StaticWebsite.cs +++ b/Modules/StaticWebsites/StaticWebsite.cs @@ -3,28 +3,25 @@ using GenHTTP.Modules.StaticWebsites.Provider; -namespace GenHTTP.Modules.StaticWebsites -{ - - public static class StaticWebsite - { +namespace GenHTTP.Modules.StaticWebsites; - /// - /// Creates a static website from the given resource tree which - /// will automatically serve index files (index.html, index.htm) - /// and provide a sitemap and robots instruction file. - /// - /// The resource to generate the application from - public static StaticWebsiteBuilder From(IBuilder tree) => From(tree.Build()); +public static class StaticWebsite +{ - /// - /// Creates a static website from the given resource tree which - /// will automatically serve index files (index.html, index.htm) - /// and provide a sitemap and robots instruction file. - /// - /// The resource to generate the application from - public static StaticWebsiteBuilder From(IResourceTree tree) => new StaticWebsiteBuilder().Tree(tree); + /// + /// Creates a static website from the given resource tree which + /// will automatically serve index files (index.html, index.htm) + /// and provide a sitemap and robots instruction file. + /// + /// The resource to generate the application from + public static StaticWebsiteBuilder From(IBuilder tree) => From(tree.Build()); - } + /// + /// Creates a static website from the given resource tree which + /// will automatically serve index files (index.html, index.htm) + /// and provide a sitemap and robots instruction file. + /// + /// The resource to generate the application from + public static StaticWebsiteBuilder From(IResourceTree tree) => new StaticWebsiteBuilder().Tree(tree); } diff --git a/Modules/VirtualHosting/Provider/VirtualHostRouter.cs b/Modules/VirtualHosting/Provider/VirtualHostRouter.cs index 1ee6141f..f221365e 100644 --- a/Modules/VirtualHosting/Provider/VirtualHostRouter.cs +++ b/Modules/VirtualHosting/Provider/VirtualHostRouter.cs @@ -7,40 +7,39 @@ using GenHTTP.Modules.Basics; -namespace GenHTTP.Modules.VirtualHosting.Provider -{ +namespace GenHTTP.Modules.VirtualHosting.Provider; - public sealed class VirtualHostRouter : IHandler - { +public sealed class VirtualHostRouter : IHandler +{ - #region Get-/Setters + #region Get-/Setters - public IHandler Parent { get; } + public IHandler Parent { get; } - private Dictionary Hosts { get; } + private Dictionary Hosts { get; } - private IHandler? DefaultRoute { get; } + private IHandler? DefaultRoute { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public VirtualHostRouter(IHandler parent, - Dictionary hosts, - IHandlerBuilder? defaultRoute) - { + public VirtualHostRouter(IHandler parent, + Dictionary hosts, + IHandlerBuilder? defaultRoute) + { Parent = parent; Hosts = hosts.ToDictionary(kv => kv.Key, kv => kv.Value.Build(this)); DefaultRoute = defaultRoute?.Build(this); } - #endregion + #endregion - #region Functionality + #region Functionality - public async ValueTask PrepareAsync() - { + public async ValueTask PrepareAsync() + { foreach (var host in Hosts.Values) { await host.PrepareAsync(); @@ -52,13 +51,13 @@ public async ValueTask PrepareAsync() } } - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { return GetHandler(request)?.HandleAsync(request) ?? new ValueTask(); } - private IHandler? GetHandler(IRequest request) - { + private IHandler? GetHandler(IRequest request) + { var host = request.HostWithoutPort(); // try to find a regular route @@ -74,8 +73,6 @@ public async ValueTask PrepareAsync() return DefaultRoute; } - #endregion - - } + #endregion } diff --git a/Modules/VirtualHosting/Provider/VirtualHostRouterBuilder.cs b/Modules/VirtualHosting/Provider/VirtualHostRouterBuilder.cs index 3ce5f835..537184dc 100644 --- a/Modules/VirtualHosting/Provider/VirtualHostRouterBuilder.cs +++ b/Modules/VirtualHosting/Provider/VirtualHostRouterBuilder.cs @@ -3,21 +3,20 @@ using GenHTTP.Api.Content; -namespace GenHTTP.Modules.VirtualHosting.Provider -{ +namespace GenHTTP.Modules.VirtualHosting.Provider; - public sealed class VirtualHostRouterBuilder : IHandlerBuilder - { - private readonly Dictionary _Hosts = new(); +public sealed class VirtualHostRouterBuilder : IHandlerBuilder +{ + private readonly Dictionary _Hosts = new(); - private IHandlerBuilder? _DefaultRoute; + private IHandlerBuilder? _DefaultRoute; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - #region Functionality + #region Functionality - public VirtualHostRouterBuilder Add(string host, IHandlerBuilder handler) - { + public VirtualHostRouterBuilder Add(string host, IHandlerBuilder handler) + { if (_Hosts.ContainsKey(host)) { throw new InvalidOperationException("A host with this name has already been added"); @@ -27,25 +26,23 @@ public VirtualHostRouterBuilder Add(string host, IHandlerBuilder handler) return this; } - public VirtualHostRouterBuilder Add(IConcernBuilder concern) - { + public VirtualHostRouterBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public VirtualHostRouterBuilder Default(IHandlerBuilder handler) - { + public VirtualHostRouterBuilder Default(IHandlerBuilder handler) + { _DefaultRoute = handler; return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { return Concerns.Chain(parent, _Concerns, (p) => new VirtualHostRouter(p, _Hosts, _DefaultRoute)); } - #endregion - - } + #endregion } diff --git a/Modules/VirtualHosting/VirtualHosts.cs b/Modules/VirtualHosting/VirtualHosts.cs index 1b54f809..383ccfd2 100644 --- a/Modules/VirtualHosting/VirtualHosts.cs +++ b/Modules/VirtualHosting/VirtualHosts.cs @@ -1,16 +1,13 @@ using GenHTTP.Modules.VirtualHosting.Provider; -namespace GenHTTP.Modules.VirtualHosting +namespace GenHTTP.Modules.VirtualHosting; + +public static class VirtualHosts { - public static class VirtualHosts + public static VirtualHostRouterBuilder Create() { - - public static VirtualHostRouterBuilder Create() - { return new VirtualHostRouterBuilder(); } - } - } diff --git a/Modules/Webservices/Extensions.cs b/Modules/Webservices/Extensions.cs index c097078e..02dc9e2f 100644 --- a/Modules/Webservices/Extensions.cs +++ b/Modules/Webservices/Extensions.cs @@ -3,68 +3,65 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Modules.Conversion.Formatters; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.Layouting.Provider; using GenHTTP.Modules.Reflection.Injectors; using GenHTTP.Modules.Webservices.Provider; -namespace GenHTTP.Modules.Webservices +namespace GenHTTP.Modules.Webservices; + +/// +/// Extensions to simplify handling of service resources. +/// +public static class Extensions { /// - /// Extensions to simplify handling of service resources. + /// Adds the given webservice resource to the layout, accessible using + /// the specified path. /// - public static class Extensions + /// The type of the resource to be added + /// The path the resource should be available at + /// Optionally the injectors to be used by this service + /// Optionally the formats to be used by this service + /// Optionally the formatters to be used by this service + public static LayoutBuilder AddService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder layout, string path, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : new() { + return layout.Add(path, ServiceResource.From().Configured(injectors, serializers, formatters)); + } - /// - /// Adds the given webservice resource to the layout, accessible using - /// the specified path. - /// - /// The type of the resource to be added - /// The path the resource should be available at - /// Optionally the injectors to be used by this service - /// Optionally the formats to be used by this service - /// Optionally the formatters to be used by this service - public static LayoutBuilder AddService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder layout, string path, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : new() + /// + /// Adds the given webservice resource to the layout, accessible using + /// the specified path. + /// + /// The path the resource should be available at + /// The webservice resource instance + /// Optionally the injectors to be used by this service + /// Optionally the formats to be used by this service + /// Optionally the formatters to be used by this service + public static LayoutBuilder AddService(this LayoutBuilder layout, string path, object instance, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) + { + return layout.Add(path, ServiceResource.From(instance).Configured(injectors, serializers, formatters)); + } + + private static ServiceResourceBuilder Configured(this ServiceResourceBuilder builder, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) + { + if (injectors != null) { - return layout.Add(path, ServiceResource.From().Configured(injectors, serializers, formatters)); + builder.Injectors(injectors); } - /// - /// Adds the given webservice resource to the layout, accessible using - /// the specified path. - /// - /// The path the resource should be available at - /// The webservice resource instance - /// Optionally the injectors to be used by this service - /// Optionally the formats to be used by this service - /// Optionally the formatters to be used by this service - public static LayoutBuilder AddService(this LayoutBuilder layout, string path, object instance, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) + if (serializers != null) { - return layout.Add(path, ServiceResource.From(instance).Configured(injectors, serializers, formatters)); + builder.Serializers(serializers); } - private static ServiceResourceBuilder Configured(this ServiceResourceBuilder builder, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) + if (formatters != null) { - if (injectors != null) - { - builder.Injectors(injectors); - } - - if (serializers != null) - { - builder.Serializers(serializers); - } - - if (formatters != null) - { - builder.Formatters(formatters); - } - - return builder; + builder.Formatters(formatters); } + return builder; } } diff --git a/Modules/Webservices/Provider/ServiceResourceBuilder.cs b/Modules/Webservices/Provider/ServiceResourceBuilder.cs index a692e988..691767d2 100644 --- a/Modules/Webservices/Provider/ServiceResourceBuilder.cs +++ b/Modules/Webservices/Provider/ServiceResourceBuilder.cs @@ -6,74 +6,71 @@ using GenHTTP.Modules.Conversion; using GenHTTP.Modules.Conversion.Formatters; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.Reflection; using GenHTTP.Modules.Reflection.Injectors; -namespace GenHTTP.Modules.Webservices.Provider -{ - - public sealed class ServiceResourceBuilder : IHandlerBuilder - { - private object? _Instance; +namespace GenHTTP.Modules.Webservices.Provider; - private IBuilder? _Serializers; - - private IBuilder? _Injectors; +public sealed class ServiceResourceBuilder : IHandlerBuilder +{ + private object? _Instance; - private IBuilder? _Formatters; + private IBuilder? _Serializers; - private readonly List _Concerns = new(); + private IBuilder? _Injectors; - #region Functionality + private IBuilder? _Formatters; - public ServiceResourceBuilder Type<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new() => Instance(new T()); + private readonly List _Concerns = new(); - public ServiceResourceBuilder Instance(object instance) - { - _Instance = instance; - return this; - } + #region Functionality - public ServiceResourceBuilder Serializers(IBuilder registry) - { - _Serializers = registry; - return this; - } + public ServiceResourceBuilder Type<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new() => Instance(new T()); - public ServiceResourceBuilder Injectors(IBuilder registry) - { - _Injectors = registry; - return this; - } + public ServiceResourceBuilder Instance(object instance) + { + _Instance = instance; + return this; + } - public ServiceResourceBuilder Formatters(IBuilder registry) - { - _Formatters = registry; - return this; - } + public ServiceResourceBuilder Serializers(IBuilder registry) + { + _Serializers = registry; + return this; + } - public ServiceResourceBuilder Add(IConcernBuilder concern) - { - _Concerns.Add(concern); - return this; - } + public ServiceResourceBuilder Injectors(IBuilder registry) + { + _Injectors = registry; + return this; + } - public IHandler Build(IHandler parent) - { - var serializers = (_Serializers ?? Serialization.Default()).Build(); + public ServiceResourceBuilder Formatters(IBuilder registry) + { + _Formatters = registry; + return this; + } - var injectors = (_Injectors ?? Injection.Default()).Build(); + public ServiceResourceBuilder Add(IConcernBuilder concern) + { + _Concerns.Add(concern); + return this; + } - var formatters = (_Formatters ?? Formatting.Default()).Build(); + public IHandler Build(IHandler parent) + { + var serializers = (_Serializers ?? Serialization.Default()).Build(); - var instance = _Instance ?? throw new BuilderMissingPropertyException("instance"); + var injectors = (_Injectors ?? Injection.Default()).Build(); - return Concerns.Chain(parent, _Concerns, (p) => new ServiceResourceRouter(p, instance, serializers, injectors, formatters)); - } + var formatters = (_Formatters ?? Formatting.Default()).Build(); - #endregion + var instance = _Instance ?? throw new BuilderMissingPropertyException("instance"); + return Concerns.Chain(parent, _Concerns, (p) => new ServiceResourceRouter(p, instance, serializers, injectors, formatters)); } + #endregion + } diff --git a/Modules/Webservices/Provider/ServiceResourceRouter.cs b/Modules/Webservices/Provider/ServiceResourceRouter.cs index 74269808..f75aa849 100644 --- a/Modules/Webservices/Provider/ServiceResourceRouter.cs +++ b/Modules/Webservices/Provider/ServiceResourceRouter.cs @@ -7,68 +7,65 @@ using GenHTTP.Api.Protocol; using GenHTTP.Modules.Conversion.Formatters; -using GenHTTP.Modules.Conversion.Providers; +using GenHTTP.Modules.Conversion.Serializers; using GenHTTP.Modules.Reflection; using GenHTTP.Modules.Reflection.Injectors; -namespace GenHTTP.Modules.Webservices.Provider -{ +namespace GenHTTP.Modules.Webservices.Provider; - public sealed class ServiceResourceRouter : IHandler - { +public sealed class ServiceResourceRouter : IHandler +{ - #region Get-/Setters + #region Get-/Setters - private MethodCollection Methods { get; } + private MethodCollection Methods { get; } - public IHandler Parent { get; } + public IHandler Parent { get; } - public ResponseProvider ResponseProvider { get; } + public ResponseProvider ResponseProvider { get; } - public object Instance { get; } + public object Instance { get; } - #endregion + #endregion - #region Initialization + #region Initialization - public ServiceResourceRouter(IHandler parent, object instance, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) - { - Parent = parent; + public ServiceResourceRouter(IHandler parent, object instance, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) + { + Parent = parent; - Instance = instance; + Instance = instance; - ResponseProvider = new(serialization, formatting); + ResponseProvider = new(serialization, formatting); - Methods = new(this, AnalyzeMethods(instance.GetType(), serialization, injection, formatting)); - } + Methods = new(this, AnalyzeMethods(instance.GetType(), serialization, injection, formatting)); + } - private IEnumerable> AnalyzeMethods(Type type, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) + private IEnumerable> AnalyzeMethods(Type type, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting) + { + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { - foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) - { - var attribute = method.GetCustomAttribute(true); + var attribute = method.GetCustomAttribute(true); - if (attribute is not null) - { - var wildcardRoute = PathArguments.CheckWildcardRoute(method.ReturnType); + if (attribute is not null) + { + var wildcardRoute = PathArguments.CheckWildcardRoute(method.ReturnType); - var path = PathArguments.Route(attribute.Path, wildcardRoute); + var path = PathArguments.Route(attribute.Path, wildcardRoute); - yield return (parent) => new MethodHandler(parent, method, path, () => Instance, attribute, ResponseProvider.GetResponseAsync, serialization, injection, formatting); - } + yield return (parent) => new MethodHandler(parent, method, path, () => Instance, attribute, ResponseProvider.GetResponseAsync, serialization, injection, formatting); } } + } - #endregion - - #region Functionality + #endregion - public ValueTask PrepareAsync() => Methods.PrepareAsync(); + #region Functionality - public ValueTask HandleAsync(IRequest request) => Methods.HandleAsync(request); + public ValueTask PrepareAsync() => Methods.PrepareAsync(); - #endregion + public ValueTask HandleAsync(IRequest request) => Methods.HandleAsync(request); - } + #endregion } diff --git a/Modules/Webservices/ResourceMethodAttribute.cs b/Modules/Webservices/ResourceMethodAttribute.cs index 0821a27a..2c049002 100644 --- a/Modules/Webservices/ResourceMethodAttribute.cs +++ b/Modules/Webservices/ResourceMethodAttribute.cs @@ -1,44 +1,41 @@ using GenHTTP.Api.Protocol; using GenHTTP.Modules.Reflection; -namespace GenHTTP.Modules.Webservices -{ +namespace GenHTTP.Modules.Webservices; - public class ResourceMethodAttribute : MethodAttribute - { +public class ResourceMethodAttribute : MethodAttribute +{ - #region Get-/Setters + #region Get-/Setters - /// - /// The path this method is availabe at. - /// - public string? Path { get; set; } + /// + /// The path this method is availabe at. + /// + public string? Path { get; set; } - #endregion + #endregion - #region Initialization + #region Initialization - /// - /// Marks the method as a webservice method. - /// - /// The HTTP verb used to invoke the method - /// The path the method should be available at - public ResourceMethodAttribute(RequestMethod requestMethod = RequestMethod.GET, string? path = null) : base(requestMethod) - { + /// + /// Marks the method as a webservice method. + /// + /// The HTTP verb used to invoke the method + /// The path the method should be available at + public ResourceMethodAttribute(RequestMethod requestMethod = RequestMethod.GET, string? path = null) : base(requestMethod) + { Path = path; } - /// - /// Configures the method to be invoked via GET at the given path. - /// - /// The path the method should be available at - public ResourceMethodAttribute(string path) : this(RequestMethod.GET, path) - { + /// + /// Configures the method to be invoked via GET at the given path. + /// + /// The path the method should be available at + public ResourceMethodAttribute(string path) : this(RequestMethod.GET, path) + { } - #endregion - - } + #endregion } diff --git a/Modules/Webservices/ServiceResource.cs b/Modules/Webservices/ServiceResource.cs index 198079d3..498d37ba 100644 --- a/Modules/Webservices/ServiceResource.cs +++ b/Modules/Webservices/ServiceResource.cs @@ -2,29 +2,26 @@ using GenHTTP.Modules.Webservices.Provider; -namespace GenHTTP.Modules.Webservices +namespace GenHTTP.Modules.Webservices; + +/// +/// Entry point to add webservice resources to another router. +/// +public static class ServiceResource { /// - /// Entry point to add webservice resources to another router. + /// Provides a router that will invoke the methods of the + /// specified resource type to generate responses. /// - public static class ServiceResource - { - - /// - /// Provides a router that will invoke the methods of the - /// specified resource type to generate responses. - /// - /// The resource type to be provided - public static ServiceResourceBuilder From<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new() => new ServiceResourceBuilder().Type(); - - /// - /// Provides a router that will invoke the methods of the - /// specified resource instance to generate responses. - /// - /// The instance to be provided - public static ServiceResourceBuilder From(object instance) => new ServiceResourceBuilder().Instance(instance); + /// The resource type to be provided + public static ServiceResourceBuilder From<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new() => new ServiceResourceBuilder().Type(); - } + /// + /// Provides a router that will invoke the methods of the + /// specified resource instance to generate responses. + /// + /// The instance to be provided + public static ServiceResourceBuilder From(object instance) => new ServiceResourceBuilder().Instance(instance); } diff --git a/Playground/Program.cs b/Playground/Program.cs index fe889f60..81499980 100644 --- a/Playground/Program.cs +++ b/Playground/Program.cs @@ -1,13 +1,11 @@ using GenHTTP.Engine; -using GenHTTP.Modules.DirectoryBrowsing; using GenHTTP.Modules.IO; -using GenHTTP.Modules.Layouting; using GenHTTP.Modules.Practices; -// Content.From(Resource.FromString("Hello World")) +var app = Content.From(Resource.FromString("Hello World")); Host.Create() - .Handler(Listing.From(ResourceTree.FromDirectory("C:/"))) + .Handler(app) .Defaults() .Development() .Console() diff --git a/Testing/Acceptance/AssertX.cs b/Testing/Acceptance/AssertX.cs index e86e9573..df2186f7 100644 --- a/Testing/Acceptance/AssertX.cs +++ b/Testing/Acceptance/AssertX.cs @@ -7,54 +7,53 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance +namespace GenHTTP.Testing.Acceptance; + +/// +/// Compatibility assertions for XUnit. +/// +public static class AssertX { - /// - /// Compatibility assertions for XUnit. - /// - public static class AssertX + public static void Contains(string searchFor, string? content) { - - public static void Contains(string searchFor, string? content) - { if ((content == null) || !content.Contains(searchFor)) { throw new AssertFailedException($"String '{searchFor}' is not found in result:\r\n\r\n{content}"); } } - public static void DoesNotContain(string searchFor, string? content) - { + public static void DoesNotContain(string searchFor, string? content) + { if ((content != null) && content.Contains(searchFor)) { throw new AssertFailedException($"String '{searchFor}' is found in result:\r\n\r\n{content}"); } } - public static void StartsWith(string searchFor, string? content) => Assert.IsTrue(content?.StartsWith(searchFor) ?? false); + public static void StartsWith(string searchFor, string? content) => Assert.IsTrue(content?.StartsWith(searchFor) ?? false); - public static void EndsWith(string searchFor, string? content) => Assert.IsTrue(content?.EndsWith(searchFor) ?? false); + public static void EndsWith(string searchFor, string? content) => Assert.IsTrue(content?.EndsWith(searchFor) ?? false); - public static void Single(IEnumerable collection) => Assert.IsTrue(collection.Count() == 1); + public static void Single(IEnumerable collection) => Assert.IsTrue(collection.Count() == 1); - public static void Empty(IEnumerable collection) => Assert.IsFalse(collection.Any()); + public static void Empty(IEnumerable collection) => Assert.IsFalse(collection.Any()); - public static void Contains(T value, IEnumerable collection) => Assert.IsTrue(collection.Contains(value)); + public static void Contains(T value, IEnumerable collection) => Assert.IsTrue(collection.Contains(value)); - public static void DoesNotContain(T value, IEnumerable collection) => Assert.IsFalse(collection.Contains(value)); + public static void DoesNotContain(T value, IEnumerable collection) => Assert.IsFalse(collection.Contains(value)); - public static void IsNullOrEmpty(string? value) => Assert.IsTrue(string.IsNullOrEmpty(value)); + public static void IsNullOrEmpty(string? value) => Assert.IsTrue(string.IsNullOrEmpty(value)); - /// - /// Raises an assertion expection if the response does not have the expected status code - /// and additionally prints information about the response to be able to further debug - /// issues in workflow runs. - /// - /// The response to be evaluated - /// The expected status code to check for - public static async Task AssertStatusAsync(this HttpResponseMessage response, HttpStatusCode expectedStatus) - { + /// + /// Raises an assertion expection if the response does not have the expected status code + /// and additionally prints information about the response to be able to further debug + /// issues in workflow runs. + /// + /// The response to be evaluated + /// The expected status code to check for + public static async Task AssertStatusAsync(this HttpResponseMessage response, HttpStatusCode expectedStatus) + { if (response.StatusCode != expectedStatus) { var builder = new StringBuilder(); @@ -86,6 +85,4 @@ public static async Task AssertStatusAsync(this HttpResponseMessage response, Ht } } - } - -} +} \ No newline at end of file diff --git a/Testing/Acceptance/Engine/BasicTests.cs b/Testing/Acceptance/Engine/BasicTests.cs index 0290797a..e255e3aa 100644 --- a/Testing/Acceptance/Engine/BasicTests.cs +++ b/Testing/Acceptance/Engine/BasicTests.cs @@ -6,16 +6,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class BasicTests { - [TestClass] - public sealed class BasicTests + [TestMethod] + public async Task TestBuilder() { - - [TestMethod] - public async Task TestBuilder() - { using var runner = new TestHost(Layout.Create()); runner.Host.RequestMemoryLimit(128) @@ -30,9 +29,9 @@ public async Task TestBuilder() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestLegacyHttp() - { + [TestMethod] + public async Task TestLegacyHttp() + { using var runner = TestHost.Run(Layout.Create()); using var client = TestHost.GetClient(protocolVersion: new Version(1, 0)); @@ -42,9 +41,9 @@ public async Task TestLegacyHttp() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestConnectionClose() - { + [TestMethod] + public async Task TestConnectionClose() + { using var runner = TestHost.Run(Layout.Create()); var request = runner.GetRequest(); @@ -56,9 +55,9 @@ public async Task TestConnectionClose() Assert.IsTrue(response.Headers.Connection.Contains("Close")); } - [TestMethod] - public async Task TestEmptyQuery() - { + [TestMethod] + public async Task TestEmptyQuery() + { using var runner = TestHost.Run(Layout.Create()); using var response = await runner.GetResponseAsync("/?"); @@ -66,9 +65,9 @@ public async Task TestEmptyQuery() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestKeepalive() - { + [TestMethod] + public async Task TestKeepalive() + { using var runner = TestHost.Run(Layout.Create()); using var response = await runner.GetResponseAsync(); @@ -76,6 +75,4 @@ public async Task TestKeepalive() Assert.IsTrue(response.Headers.Connection.Contains("Keep-Alive")); } - } - -} +} \ No newline at end of file diff --git a/Testing/Acceptance/Engine/ChecksumTests.cs b/Testing/Acceptance/Engine/ChecksumTests.cs index c81c919b..68774194 100644 --- a/Testing/Acceptance/Engine/ChecksumTests.cs +++ b/Testing/Acceptance/Engine/ChecksumTests.cs @@ -3,16 +3,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public class ChecksumTests { - [TestClass] - public class ChecksumTests + [TestMethod] + public async Task TestSameErrorSameChecksum() { - - [TestMethod] - public async Task TestSameErrorSameChecksum() - { using var runner = TestHost.Run(Layout.Create()); using var resp1 = await runner.GetResponseAsync(); @@ -23,9 +22,9 @@ public async Task TestSameErrorSameChecksum() Assert.AreEqual(resp1.GetETag(), resp2.GetETag()); } - [TestMethod] - public async Task TestSameContentSameChecksum() - { + [TestMethod] + public async Task TestSameContentSameChecksum() + { using var runner = TestHost.Run(Content.From(Resource.FromString("Hello World!"))); using var resp1 = await runner.GetResponseAsync(); @@ -36,6 +35,4 @@ public async Task TestSameContentSameChecksum() Assert.AreEqual(resp1.GetETag(), resp2.GetETag()); } - } - } diff --git a/Testing/Acceptance/Engine/ChunkedContentTest.cs b/Testing/Acceptance/Engine/ChunkedContentTest.cs index 4c188cd7..ea1edf5a 100644 --- a/Testing/Acceptance/Engine/ChunkedContentTest.cs +++ b/Testing/Acceptance/Engine/ChunkedContentTest.cs @@ -6,24 +6,23 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine -{ +namespace GenHTTP.Testing.Acceptance.Engine; - [TestClass] - public sealed class ChunkedContentTest - { +[TestClass] +public sealed class ChunkedContentTest +{ - #region Supporting data structures + #region Supporting data structures - private record Model(string Value); + private record Model(string Value); - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task TestChunkedUpload() - { + [TestMethod] + public async Task TestChunkedUpload() + { var inline = Inline.Create() .Put((Model model) => model); @@ -40,8 +39,6 @@ public async Task TestChunkedUpload() AssertX.Contains("Hello World", result); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Engine/CompanionTests.cs b/Testing/Acceptance/Engine/CompanionTests.cs index b0fb43b9..6e337295 100644 --- a/Testing/Acceptance/Engine/CompanionTests.cs +++ b/Testing/Acceptance/Engine/CompanionTests.cs @@ -9,36 +9,35 @@ using GenHTTP.Api.Protocol; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class CompanionTests { - [TestClass] - public sealed class CompanionTests + private class CustomCompanion : IServerCompanion { - private class CustomCompanion : IServerCompanion - { - - public bool Called { get; private set; } + public bool Called { get; private set; } - public void OnRequestHandled(IRequest request, IResponse response) - { + public void OnRequestHandled(IRequest request, IResponse response) + { Called = true; } - public void OnServerError(ServerErrorScope scope, IPAddress? client, Exception error) - { + public void OnServerError(ServerErrorScope scope, IPAddress? client, Exception error) + { Called = true; } - } + } - /// - /// As a developer, I want to configure the server to easily log to the console. - /// - [TestMethod] - public async Task TestConsole() - { + /// + /// As a developer, I want to configure the server to easily log to the console. + /// + [TestMethod] + public async Task TestConsole() + { using var runner = new TestHost(Layout.Create()); runner.Host.Console().Start(); @@ -46,12 +45,12 @@ public async Task TestConsole() using var __ = await runner.GetResponseAsync(); } - /// - /// As a developer, I want to add custom companions to get notified by server actions. - /// - [TestMethod] - public async Task TestCustom() - { + /// + /// As a developer, I want to add custom companions to get notified by server actions. + /// + [TestMethod] + public async Task TestCustom() + { using var runner = new TestHost(Layout.Create()); var companion = new CustomCompanion(); @@ -67,6 +66,4 @@ public async Task TestCustom() Assert.IsTrue(companion.Called); } - } - } diff --git a/Testing/Acceptance/Engine/CompressionTests.cs b/Testing/Acceptance/Engine/CompressionTests.cs index a5eaa561..acc77c45 100644 --- a/Testing/Acceptance/Engine/CompressionTests.cs +++ b/Testing/Acceptance/Engine/CompressionTests.cs @@ -13,43 +13,42 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class CompressionTests { - [TestClass] - public sealed class CompressionTests + private class CustomAlgorithm : ICompressionAlgorithm { - private class CustomAlgorithm : ICompressionAlgorithm - { - - public string Name => "custom"; + public string Name => "custom"; - public Priority Priority => Priority.High; + public Priority Priority => Priority.High; - public IResponseContent Compress(IResponseContent content, CompressionLevel level) - { + public IResponseContent Compress(IResponseContent content, CompressionLevel level) + { return content; } - } + } - private class CustomAlgorithmBuilder : IBuilder - { + private class CustomAlgorithmBuilder : IBuilder + { - public ICompressionAlgorithm Build() - { + public ICompressionAlgorithm Build() + { return new CustomAlgorithm(); } - } + } - /// - /// As a developer, I expect responses to be compressed out of the box. - /// - [TestMethod] - public async Task TestCompression() - { + /// + /// As a developer, I expect responses to be compressed out of the box. + /// + [TestMethod] + public async Task TestCompression() + { using var runner = TestHost.Run(Layout.Create()); var request = runner.GetRequest(); @@ -60,13 +59,13 @@ public async Task TestCompression() Assert.AreEqual("zstd", response.Content.Headers.ContentEncoding.First()); } - /// - /// As a browser, I expect only supported compression algorithms to be used - /// to generate my response. - /// - [TestMethod] - public async Task TestSpecificAlgorithms() - { + /// + /// As a browser, I expect only supported compression algorithms to be used + /// to generate my response. + /// + [TestMethod] + public async Task TestSpecificAlgorithms() + { foreach (var algorithm in new[] { "gzip", "br", "zstd" }) { using var runner = TestHost.Run(Layout.Create()); @@ -80,12 +79,12 @@ public async Task TestSpecificAlgorithms() } } - /// - /// As a developer, I want to be able to disable compression. - /// - [TestMethod] - public async Task TestCompressionDisabled() - { + /// + /// As a developer, I want to be able to disable compression. + /// + [TestMethod] + public async Task TestCompressionDisabled() + { using var runner = TestHost.Run(Layout.Create(), false); using var response = await runner.GetResponseAsync(); @@ -93,12 +92,12 @@ public async Task TestCompressionDisabled() Assert.IsFalse(response.Content.Headers.ContentEncoding.Any()); } - /// - /// As a developer, I want to be able to add custom compression algorithms. - /// - [TestMethod] - public async Task TestCustomCompression() - { + /// + /// As a developer, I want to be able to add custom compression algorithms. + /// + [TestMethod] + public async Task TestCustomCompression() + { using var runner = new TestHost(Layout.Create()); runner.Host.Compression(CompressedContent.Default().Add(new CustomAlgorithm()).Level(CompressionLevel.Optimal)).Start(); @@ -111,12 +110,12 @@ public async Task TestCustomCompression() Assert.AreEqual("custom", response.Content.Headers.ContentEncoding.First()); } - /// - /// As a developer, I want already compressed content not to be compressed again. - /// - [TestMethod] - public async Task TestNoAdditionalCompression() - { + /// + /// As a developer, I want already compressed content not to be compressed again. + /// + [TestMethod] + public async Task TestNoAdditionalCompression() + { var image = Resource.FromString("Image!").Type(ContentType.ImageJpg); using var runner = TestHost.Run(Layout.Create().Add("uncompressed", Content.From(image))); @@ -126,9 +125,9 @@ public async Task TestNoAdditionalCompression() Assert.IsFalse(response.Content.Headers.ContentEncoding.Any()); } - [TestMethod] - public async Task TestVariyHeaderAdded() - { + [TestMethod] + public async Task TestVariyHeaderAdded() + { using var runner = TestHost.Run(Layout.Create()); var request = runner.GetRequest(); @@ -139,9 +138,9 @@ public async Task TestVariyHeaderAdded() Assert.AreEqual("Accept-Encoding", response.GetHeader("Vary")); } - [TestMethod] - public async Task TestVariyHeaderExtendedAdded() - { + [TestMethod] + public async Task TestVariyHeaderExtendedAdded() + { var handler = new FunctionalHandler(responseProvider: (r) => { return r.Respond() @@ -162,9 +161,9 @@ public async Task TestVariyHeaderExtendedAdded() Assert.IsTrue(response.Headers.Vary.Contains("Accept-Encoding")); } - [TestMethod] - public async Task TestContentType() - { + [TestMethod] + public async Task TestContentType() + { var handler = new FunctionalHandler(responseProvider: (r) => { return r.Respond() @@ -183,6 +182,4 @@ public async Task TestContentType() Assert.AreEqual("br", response.Content.Headers.ContentEncoding.First()); } - } - } diff --git a/Testing/Acceptance/Engine/ContentTypeTests.cs b/Testing/Acceptance/Engine/ContentTypeTests.cs index c815e298..09a2d1f2 100644 --- a/Testing/Acceptance/Engine/ContentTypeTests.cs +++ b/Testing/Acceptance/Engine/ContentTypeTests.cs @@ -4,16 +4,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class ContentTypeTests { - [TestClass] - public sealed class ContentTypeTests + [TestMethod] + public void MapContentTypeTests() { - - [TestMethod] - public void MapContentTypeTests() - { foreach (ContentType contentType in Enum.GetValues(typeof(ContentType))) { var mapped = new FlexibleContentType(contentType); @@ -22,15 +21,13 @@ public void MapContentTypeTests() } } - [TestMethod] - public void ConcurrentContentTypeAccessTest() - { + [TestMethod] + public void ConcurrentContentTypeAccessTest() + { Parallel.For(0, 10, (_) => { FlexibleContentType.Parse("application/json"); }); } - } - } diff --git a/Testing/Acceptance/Engine/CookieTests.cs b/Testing/Acceptance/Engine/CookieTests.cs index 5aadf82f..80dadb88 100644 --- a/Testing/Acceptance/Engine/CookieTests.cs +++ b/Testing/Acceptance/Engine/CookieTests.cs @@ -10,24 +10,23 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class CookieTests { - [TestClass] - public sealed class CookieTests + private class TestProvider : IHandler { - private class TestProvider : IHandler - { - - public ICookieCollection? Cookies { get; private set; } + public ICookieCollection? Cookies { get; private set; } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public IHandler Parent => throw new NotSupportedException(); + public IHandler Parent => throw new NotSupportedException(); - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { Cookies = request.Cookies; return request.Respond() @@ -38,14 +37,14 @@ private class TestProvider : IHandler } - } + } - /// - /// As a developer, I want to be able to set cookies to be accepted by the browser. - /// - [TestMethod] - public async Task TestCookiesCanBeReturned() - { + /// + /// As a developer, I want to be able to set cookies to be accepted by the browser. + /// + [TestMethod] + public async Task TestCookiesCanBeReturned() + { using var runner = TestHost.Run(new TestProvider().Wrap()); using var response = await runner.GetResponseAsync(); @@ -53,12 +52,12 @@ public async Task TestCookiesCanBeReturned() Assert.AreEqual("TestCookie=TestValue; Max-Age=86400; Path=/", response.GetHeader("Set-Cookie")); } - /// - /// As a developer, I want to be able to read cookies from the client. - /// - [TestMethod] - public async Task TestCookiesCanBeRead() - { + /// + /// As a developer, I want to be able to read cookies from the client. + /// + [TestMethod] + public async Task TestCookiesCanBeRead() + { var provider = new TestProvider(); using var runner = TestHost.Run(provider.Wrap()); @@ -71,6 +70,4 @@ public async Task TestCookiesCanBeRead() Assert.AreEqual("4", provider.Cookies?["3"].Value); } - } - } diff --git a/Testing/Acceptance/Engine/DeveloperModeTests.cs b/Testing/Acceptance/Engine/DeveloperModeTests.cs index 7ca6d995..bc150421 100644 --- a/Testing/Acceptance/Engine/DeveloperModeTests.cs +++ b/Testing/Acceptance/Engine/DeveloperModeTests.cs @@ -9,34 +9,33 @@ using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class DeveloperModeTests { - [TestClass] - public sealed class DeveloperModeTests + private class ThrowingProvider : IHandler { - private class ThrowingProvider : IHandler - { - - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public IHandler Parent => throw new NotImplementedException(); + public IHandler Parent => throw new NotImplementedException(); - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { throw new InvalidOperationException("Nope!"); } - } + } - /// - /// As a developer of a web project, I would like to see exceptions rendered - /// in the browser, so that I can trace an error more quickly - /// - [TestMethod] - public async Task TestExceptionsWithTrace() - { + /// + /// As a developer of a web project, I would like to see exceptions rendered + /// in the browser, so that I can trace an error more quickly + /// + [TestMethod] + public async Task TestExceptionsWithTrace() + { using var runner = new TestHost(Layout.Create()); var router = Layout.Create().Index(new ThrowingProvider().Wrap()); @@ -48,13 +47,13 @@ public async Task TestExceptionsWithTrace() Assert.IsTrue((await response.GetContentAsync()).Contains("at GenHTTP")); } - /// - /// As a devops member, I do not want an web application to leak internal - /// implementation detail with exception messages - /// - [TestMethod] - public async Task TestExceptionsWithNoTrace() - { + /// + /// As a devops member, I do not want an web application to leak internal + /// implementation detail with exception messages + /// + [TestMethod] + public async Task TestExceptionsWithNoTrace() + { var router = Layout.Create().Index(new ThrowingProvider().Wrap()); using var runner = TestHost.Run(router, development: false); @@ -64,7 +63,4 @@ public async Task TestExceptionsWithNoTrace() Assert.IsFalse((await response.GetContentAsync()).Contains("at GenHTTP")); } - } - -} - +} \ No newline at end of file diff --git a/Testing/Acceptance/Engine/EncodingTests.cs b/Testing/Acceptance/Engine/EncodingTests.cs index d8b7496a..6925d279 100644 --- a/Testing/Acceptance/Engine/EncodingTests.cs +++ b/Testing/Acceptance/Engine/EncodingTests.cs @@ -5,28 +5,25 @@ using GenHTTP.Modules.IO; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class EncodingTests { - [TestClass] - public sealed class EncodingTests + /// + /// As a developer, I want UTF-8 to be my default encoding. + /// + [TestMethod] + public async Task TestUtf8DefaultEncoding() { - - /// - /// As a developer, I want UTF-8 to be my default encoding. - /// - [TestMethod] - public async Task TestUtf8DefaultEncoding() - { var layout = Layout.Create().Add("utf8", Content.From(Resource.FromString("From GenHTTP with ❤"))); using var runner = TestHost.Run(layout); - + using var response = await runner.GetResponseAsync("/utf8"); - + Assert.AreEqual("From GenHTTP with ❤", await response.GetContentAsync()); } - } - } diff --git a/Testing/Acceptance/Engine/ErrorHandlingTest.cs b/Testing/Acceptance/Engine/ErrorHandlingTest.cs index 7c723a54..198ddd6d 100644 --- a/Testing/Acceptance/Engine/ErrorHandlingTest.cs +++ b/Testing/Acceptance/Engine/ErrorHandlingTest.cs @@ -6,16 +6,15 @@ using GenHTTP.Testing.Acceptance.Utilities; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class ErrorHandlingTest { - [TestClass] - public sealed class ErrorHandlingTest + [TestMethod] + public async Task TestGenericError() { - - [TestMethod] - public async Task TestGenericError() - { var handler = new FunctionalHandler(responseProvider: (r) => { throw new NotImplementedException(); @@ -28,9 +27,9 @@ public async Task TestGenericError() await response.AssertStatusAsync(HttpStatusCode.InternalServerError); } - [TestMethod] - public async Task TestEscaping() - { + [TestMethod] + public async Task TestEscaping() + { var handler = new FunctionalHandler(responseProvider: (r) => { throw new Exception("Nah <>"); @@ -44,6 +43,4 @@ public async Task TestEscaping() AssertX.DoesNotContain("<>", await response.GetContentAsync()); } - } - } diff --git a/Testing/Acceptance/Engine/FlexibleTypeTests.cs b/Testing/Acceptance/Engine/FlexibleTypeTests.cs index db8f1c62..828758d9 100644 --- a/Testing/Acceptance/Engine/FlexibleTypeTests.cs +++ b/Testing/Acceptance/Engine/FlexibleTypeTests.cs @@ -10,22 +10,21 @@ using GenHTTP.Modules.Basics; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class FlexibleTypeTests { - [TestClass] - public sealed class FlexibleTypeTests + private class Provider : IHandler { - private class Provider : IHandler - { - - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public IHandler Parent => throw new System.NotImplementedException(); + public IHandler Parent => throw new System.NotImplementedException(); - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { return request.Respond() .Content("Hello World!") .Type("application/x-custom") @@ -33,15 +32,15 @@ private class Provider : IHandler .BuildTask(); } - } + } - /// - /// As a developer I would like to use status codes and content types - /// not supported by the server. - /// - [TestMethod] - public async Task TestFlexibleStatus() - { + /// + /// As a developer I would like to use status codes and content types + /// not supported by the server. + /// + [TestMethod] + public async Task TestFlexibleStatus() + { var content = Layout.Create().Index(new Provider().Wrap()); using var runner = TestHost.Run(content); @@ -54,6 +53,4 @@ public async Task TestFlexibleStatus() Assert.AreEqual("application/x-custom", response.GetContentHeader("Content-Type")); } - } - } diff --git a/Testing/Acceptance/Engine/ForwardingTests.cs b/Testing/Acceptance/Engine/ForwardingTests.cs index 370774c8..96db410f 100644 --- a/Testing/Acceptance/Engine/ForwardingTests.cs +++ b/Testing/Acceptance/Engine/ForwardingTests.cs @@ -6,16 +6,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class ForwardingTests { - [TestClass] - public sealed class ForwardingTests + [TestMethod] + public async Task TestModern() { - - [TestMethod] - public async Task TestModern() - { var responder = Inline.Create().Get((IRequest request) => $"{request.Client}"); using var host = TestHost.Run(responder); @@ -29,9 +28,9 @@ public async Task TestModern() Assert.AreEqual("ClientConnection { IPAddress = 85.192.1.5, Protocol = HTTPS, Host = google.com }", await response.GetContentAsync()); } - [TestMethod] - public async Task TestLegacy() - { + [TestMethod] + public async Task TestLegacy() + { var responder = Inline.Create().Get((IRequest request) => $"{request.Client}"); using var host = TestHost.Run(responder); @@ -47,9 +46,9 @@ public async Task TestLegacy() Assert.AreEqual("ClientConnection { IPAddress = 85.192.1.5, Protocol = HTTP, Host = google.com }", await response.GetContentAsync()); } - [TestMethod] - public async Task TestBoth() - { + [TestMethod] + public async Task TestBoth() + { var responder = Inline.Create().Get((IRequest request) => $"{request.Client}"); using var host = TestHost.Run(responder); @@ -67,9 +66,9 @@ public async Task TestBoth() Assert.AreEqual("ClientConnection { IPAddress = 85.192.1.1, Protocol = HTTPS, Host = google.com }", await response.GetContentAsync()); } - [TestMethod] - public async Task TestInvalid() - { + [TestMethod] + public async Task TestInvalid() + { var responder = Inline.Create().Get((IRequest request) => $"{request.Forwardings.First().ToString()}"); using var host = TestHost.Run(responder); @@ -83,6 +82,4 @@ public async Task TestInvalid() Assert.AreEqual("Forwarding { For = , Host = google.com, Protocol = }", await response.GetContentAsync()); } - } - } diff --git a/Testing/Acceptance/Engine/HeaderTests.cs b/Testing/Acceptance/Engine/HeaderTests.cs index 87a77a5c..4323d172 100644 --- a/Testing/Acceptance/Engine/HeaderTests.cs +++ b/Testing/Acceptance/Engine/HeaderTests.cs @@ -5,16 +5,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public class HeaderTests { - [TestClass] - public class HeaderTests + [TestMethod] + public async Task TestServerHeaderCanBeSet() { - - [TestMethod] - public async Task TestServerHeaderCanBeSet() - { var handler = new FunctionalHandler(responseProvider: (r) => { return r.Respond() @@ -29,9 +28,9 @@ public async Task TestServerHeaderCanBeSet() Assert.AreEqual("TFB", response.GetHeader("Server")); } - [TestMethod] - public async Task TestReservedHeaderCannotBeSet() - { + [TestMethod] + public async Task TestReservedHeaderCannotBeSet() + { var handler = new FunctionalHandler(responseProvider: (r) => { return r.Respond() @@ -46,6 +45,4 @@ public async Task TestReservedHeaderCannotBeSet() await response.AssertStatusAsync(HttpStatusCode.InternalServerError); } - } - } diff --git a/Testing/Acceptance/Engine/HostTests.cs b/Testing/Acceptance/Engine/HostTests.cs index ff87d7eb..4b2e4392 100644 --- a/Testing/Acceptance/Engine/HostTests.cs +++ b/Testing/Acceptance/Engine/HostTests.cs @@ -5,16 +5,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class HostTests { - [TestClass] - public sealed class HostTests + [TestMethod] + public async Task TestStart() { - - [TestMethod] - public async Task TestStart() - { using var runner = new TestHost(Layout.Create()); runner.Host.Start(); @@ -24,9 +23,9 @@ public async Task TestStart() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestRestart() - { + [TestMethod] + public async Task TestRestart() + { using var runner = new TestHost(Layout.Create()); runner.Host.Restart(); @@ -36,6 +35,4 @@ public async Task TestRestart() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - } - } diff --git a/Testing/Acceptance/Engine/MethodTests.cs b/Testing/Acceptance/Engine/MethodTests.cs index f63ab510..ddfafa1a 100644 --- a/Testing/Acceptance/Engine/MethodTests.cs +++ b/Testing/Acceptance/Engine/MethodTests.cs @@ -6,16 +6,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class MethodTests { - [TestClass] - public sealed class MethodTests + [TestMethod] + public async Task TestCustomMethods() { - - [TestMethod] - public async Task TestCustomMethods() - { var result = Content.From(Resource.FromString("OK")); using var host = TestHost.Run(result); @@ -27,6 +26,4 @@ public async Task TestCustomMethods() await response.AssertStatusAsync(HttpStatusCode.OK); } - } - } diff --git a/Testing/Acceptance/Engine/ParserTests.cs b/Testing/Acceptance/Engine/ParserTests.cs index 2ececaf9..d8d6086d 100644 --- a/Testing/Acceptance/Engine/ParserTests.cs +++ b/Testing/Acceptance/Engine/ParserTests.cs @@ -10,52 +10,51 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine -{ +namespace GenHTTP.Testing.Acceptance.Engine; - [TestClass] - public sealed class ParserTests - { +[TestClass] +public sealed class ParserTests +{ - #region Supporting data structures + #region Supporting data structures - private class PathReturner : IHandler - { + private class PathReturner : IHandler + { - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public IHandler Parent => throw new NotImplementedException(); + public IHandler Parent => throw new NotImplementedException(); - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { return request.Respond() .Content(request.Target.Path.ToString()) .BuildTask(); } - } + } - private class QueryReturner : IHandler - { - - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + private class QueryReturner : IHandler + { + + public ValueTask PrepareAsync() => ValueTask.CompletedTask; + + public IHandler Parent => throw new NotImplementedException(); - public IHandler Parent => throw new NotImplementedException(); - - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { return request.Respond() .Content(string.Join('|', request.Query.Select(kv => kv.Key + "=" + kv.Value))) .BuildTask(); } - } + } - #endregion + #endregion - [TestMethod] - public async Task TestEndodedUri() - { + [TestMethod] + public async Task TestEndodedUri() + { using var runner = TestHost.Run(new PathReturner().Wrap()); using var respose = await runner.GetResponseAsync("/söme/ürl/with specialities/"); @@ -63,9 +62,9 @@ public async Task TestEndodedUri() Assert.AreEqual("/söme/ürl/with specialities/", await respose.GetContentAsync()); } - [TestMethod] - public async Task TestEncodedQuery() - { + [TestMethod] + public async Task TestEncodedQuery() + { using var runner = TestHost.Run(new QueryReturner().Wrap()); using var respose = await runner.GetResponseAsync("/?söme key=💕"); @@ -73,9 +72,9 @@ public async Task TestEncodedQuery() Assert.AreEqual("söme key=💕", await respose.GetContentAsync()); } - [TestMethod] - public async Task TestMultipleSlashes() - { + [TestMethod] + public async Task TestMultipleSlashes() + { using var runner = TestHost.Run(new PathReturner().Wrap()); using var respose = await runner.GetResponseAsync("//one//two//three//"); @@ -83,9 +82,9 @@ public async Task TestMultipleSlashes() Assert.AreEqual("//one//two//three//", await respose.GetContentAsync()); } - [TestMethod] - public async Task TestEmptyQuery() - { + [TestMethod] + public async Task TestEmptyQuery() + { using var runner = TestHost.Run(new QueryReturner().Wrap()); using var respose = await runner.GetResponseAsync("/?"); @@ -93,9 +92,9 @@ public async Task TestEmptyQuery() Assert.AreEqual(string.Empty, await respose.GetContentAsync()); } - [TestMethod] - public async Task TestUnkeyedQuery() - { + [TestMethod] + public async Task TestUnkeyedQuery() + { using var runner = TestHost.Run(new QueryReturner().Wrap()); using var respose = await runner.GetResponseAsync("/?query"); @@ -103,9 +102,9 @@ public async Task TestUnkeyedQuery() Assert.AreEqual("query=", await respose.GetContentAsync()); } - [TestMethod] - public async Task TestQueryWithSlashes() - { + [TestMethod] + public async Task TestQueryWithSlashes() + { using var runner = TestHost.Run(new QueryReturner().Wrap()); using var respose = await runner.GetResponseAsync("/?key=/one/two"); @@ -113,9 +112,9 @@ public async Task TestQueryWithSlashes() Assert.AreEqual("key=/one/two", await respose.GetContentAsync()); } - [TestMethod] - public async Task TestQueryWithSpaces() - { + [TestMethod] + public async Task TestQueryWithSpaces() + { using var runner = TestHost.Run(new QueryReturner().Wrap()); using var respose = await runner.GetResponseAsync("/?path=/Some+Folder/With%20Subfolders/"); @@ -123,6 +122,4 @@ public async Task TestQueryWithSpaces() Assert.AreEqual("path=/Some+Folder/With Subfolders/", await respose.GetContentAsync()); } - } - } diff --git a/Testing/Acceptance/Engine/PipelineTests.cs b/Testing/Acceptance/Engine/PipelineTests.cs index a792a95b..c219db2c 100644 --- a/Testing/Acceptance/Engine/PipelineTests.cs +++ b/Testing/Acceptance/Engine/PipelineTests.cs @@ -6,16 +6,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class PipelineTests { - [TestClass] - public sealed class PipelineTests + [TestMethod] + public void ServerSupportsPipelining() { - - [TestMethod] - public void ServerSupportsPipelining() - { using var runner = TestHost.Run(Content.From(Resource.FromString("Hello World!"))); using var client = new TcpClient("127.0.0.1", runner.Port) @@ -32,8 +31,8 @@ public void ServerSupportsPipelining() ReadRequests(stream, count, "Hello World!"); } - private static void WriteRequests(Stream stream, int count) - { + private static void WriteRequests(Stream stream, int count) + { using var writer = new StreamWriter(stream, leaveOpen: true); var builder = new StringBuilder(); @@ -50,8 +49,8 @@ private static void WriteRequests(Stream stream, int count) writer.Flush(); } - private static void ReadRequests(Stream stream, int count, string searchFor) - { + private static void ReadRequests(Stream stream, int count, string searchFor) + { using var reader = new StreamReader(stream, leaveOpen: true); string? line; @@ -77,6 +76,4 @@ private static void ReadRequests(Stream stream, int count, string searchFor) Assert.AreEqual(count - 1, found); // last body does not end with \r\n } - } - } diff --git a/Testing/Acceptance/Engine/PooledDictionaryTests.cs b/Testing/Acceptance/Engine/PooledDictionaryTests.cs index 2e8f5410..027a0357 100644 --- a/Testing/Acceptance/Engine/PooledDictionaryTests.cs +++ b/Testing/Acceptance/Engine/PooledDictionaryTests.cs @@ -5,16 +5,15 @@ using GenHTTP.Engine.Utilities; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class PooledDictionaryTests { - [TestClass] - public sealed class PooledDictionaryTests + [TestMethod] + public void TestReplace() { - - [TestMethod] - public void TestReplace() - { using var dict = new PooledDictionary(); dict["one"] = "One"; @@ -26,17 +25,17 @@ public void TestReplace() Assert.AreEqual("Two", dict["one"]); } - [TestMethod] - public void TestNotFound() - { + [TestMethod] + public void TestNotFound() + { using var dict = new PooledDictionary(); Assert.ThrowsException(() => dict["nope"]); } - [TestMethod] - public void TestCounts() - { + [TestMethod] + public void TestCounts() + { using var dict = new PooledDictionary(); Assert.AreEqual(0, dict.Keys.Count); @@ -52,9 +51,9 @@ public void TestCounts() AssertX.Single(dict); } - [TestMethod] - public void TestClear() - { + [TestMethod] + public void TestClear() + { using var dict = new PooledDictionary(); dict["one"] = "One"; @@ -63,9 +62,9 @@ public void TestClear() Assert.IsFalse(dict.ContainsKey("one")); } - [TestMethod] - public void TestResize() - { + [TestMethod] + public void TestResize() + { using var dict = new PooledDictionary(); for (int i = 0; i < 25; i++) @@ -81,22 +80,20 @@ public void TestResize() } } - [TestMethod] - public void TestNoRemove() - { + [TestMethod] + public void TestNoRemove() + { using var dict = new PooledDictionary(); Assert.ThrowsException(() => dict.Remove("")); } - [TestMethod] - public void TestNoRemove2() - { + [TestMethod] + public void TestNoRemove2() + { using var dict = new PooledDictionary(); Assert.ThrowsException(() => dict.Remove(new KeyValuePair("", ""))); } - } - } diff --git a/Testing/Acceptance/Engine/ProtocolTests.cs b/Testing/Acceptance/Engine/ProtocolTests.cs index 64dd2963..1828202c 100644 --- a/Testing/Acceptance/Engine/ProtocolTests.cs +++ b/Testing/Acceptance/Engine/ProtocolTests.cs @@ -11,24 +11,23 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class ProtocolTests { - [TestClass] - public sealed class ProtocolTests + private class ValueRecorder : IHandler { - private class ValueRecorder : IHandler - { - - public string? Value { get; private set; } + public string? Value { get; private set; } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public IHandler Parent => throw new NotImplementedException(); + public IHandler Parent => throw new NotImplementedException(); - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { if (request.Content is not null) { using var reader = new StreamReader(request.Content); @@ -38,17 +37,17 @@ private class ValueRecorder : IHandler return new ValueTask(request.Respond().Build()); } - } + } - private class ContentLengthResponder : IHandler - { + private class ContentLengthResponder : IHandler + { - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public IHandler Parent => throw new NotImplementedException(); + public IHandler Parent => throw new NotImplementedException(); - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { var content = request.Content?.Length.ToString() ?? "No Content"; return request.Respond() @@ -57,14 +56,14 @@ private class ContentLengthResponder : IHandler .BuildTask(); } - } + } - /// - /// As a client I can stream data to the server. - /// - [TestMethod] - public async Task TestPost() - { + /// + /// As a client I can stream data to the server. + /// + [TestMethod] + public async Task TestPost() + { var recorder = new ValueRecorder(); var str = "From client with ❤"; @@ -81,12 +80,12 @@ public async Task TestPost() Assert.AreEqual(str, recorder.Value); } - /// - /// As a client I can submit large data. - /// - [TestMethod] - public async Task TestPutLarge() - { + /// + /// As a client I can submit large data. + /// + [TestMethod] + public async Task TestPutLarge() + { using var runner = TestHost.Run(new ContentLengthResponder().Wrap()); using var content = new MemoryStream(); @@ -113,6 +112,4 @@ public async Task TestPutLarge() Assert.AreEqual("1310720", await response.GetContentAsync()); } - } - } diff --git a/Testing/Acceptance/Engine/ResponseTests.cs b/Testing/Acceptance/Engine/ResponseTests.cs index 045aff0c..4b3a2149 100644 --- a/Testing/Acceptance/Engine/ResponseTests.cs +++ b/Testing/Acceptance/Engine/ResponseTests.cs @@ -13,29 +13,28 @@ using GenHTTP.Modules.Basics; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class ResponseTests { - [TestClass] - public sealed class ResponseTests + private class ResponseProvider : IHandler { - private class ResponseProvider : IHandler - { - - public DateTime Modified { get; } + public DateTime Modified { get; } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public IHandler Parent => throw new NotImplementedException(); + public IHandler Parent => throw new NotImplementedException(); - public ResponseProvider() - { + public ResponseProvider() + { Modified = DateTime.Now.AddDays(-10); } - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { return request.Method.KnownMethod switch { RequestMethod.POST => request.Respond() @@ -52,14 +51,14 @@ public ResponseProvider() }; } - } + } - /// - /// As a developer, I'd like to use all of the response builders methods. - /// - [TestMethod] - public async Task TestProperties() - { + /// + /// As a developer, I'd like to use all of the response builders methods. + /// + [TestMethod] + public async Task TestProperties() + { var provider = new ResponseProvider(); var router = Layout.Create().Index(provider.Wrap()); @@ -79,12 +78,12 @@ public async Task TestProperties() Assert.AreEqual("Test Runner", response.GetHeader("X-Powered-By")); } - /// - /// As a client, I'd like a response containing an empty body to return a Content-Length of 0. - /// - [TestMethod] - public async Task TestEmptyBody() - { + /// + /// As a client, I'd like a response containing an empty body to return a Content-Length of 0. + /// + [TestMethod] + public async Task TestEmptyBody() + { var provider = new ResponseProvider(); var router = Layout.Create().Index(provider.Wrap()); @@ -101,6 +100,4 @@ public async Task TestEmptyBody() Assert.AreEqual("0", response.GetContentHeader("Content-Length")); } - } - } diff --git a/Testing/Acceptance/Engine/RoutingTests.cs b/Testing/Acceptance/Engine/RoutingTests.cs index 8f36e7fb..58663e07 100644 --- a/Testing/Acceptance/Engine/RoutingTests.cs +++ b/Testing/Acceptance/Engine/RoutingTests.cs @@ -5,25 +5,22 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class RoutingTests { - [TestClass] - public sealed class RoutingTests + /// + /// As a client, I expect the server to return 404 for non-existing files. + /// + [TestMethod] + public async Task NotFoundForUnknownRoute() { - - /// - /// As a client, I expect the server to return 404 for non-existing files. - /// - [TestMethod] - public async Task NotFoundForUnknownRoute() - { using var runner = TestHost.Run(Layout.Create()); using var response = await runner.GetResponseAsync(); await response.AssertStatusAsync(HttpStatusCode.NotFound); } - } - } diff --git a/Testing/Acceptance/Engine/SecurityTests.cs b/Testing/Acceptance/Engine/SecurityTests.cs index b3b2cdcd..a91a8020 100644 --- a/Testing/Acceptance/Engine/SecurityTests.cs +++ b/Testing/Acceptance/Engine/SecurityTests.cs @@ -15,19 +15,18 @@ using GenHTTP.Modules.Security; using GenHTTP.Modules.Security.Providers; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class SecurityTests { - [TestClass] - public sealed class SecurityTests + /// + /// As a developer I would like to serve my application in a secure manner. + /// + [TestMethod] + public Task TestSecure() { - - /// - /// As a developer I would like to serve my application in a secure manner. - /// - [TestMethod] - public Task TestSecure() - { return RunSecure(async (insec, sec) => { using var client = TestHost.GetClient(ignoreSecurityErrors: true); @@ -39,13 +38,13 @@ public Task TestSecure() }); } - /// - /// As a developer, I expect the server to redirect to a secure endpoint - /// by default. - /// - [TestMethod] - public Task TestDefaultRedirection() - { + /// + /// As a developer, I expect the server to redirect to a secure endpoint + /// by default. + /// + [TestMethod] + public Task TestDefaultRedirection() + { return RunSecure(async (insec, sec) => { using var client = TestHost.GetClient(followRedirects: false); @@ -57,13 +56,13 @@ public Task TestDefaultRedirection() }); } - /// - /// As a developer, I expect HTTP requests not to be redirected if - /// upgrades are allowed but not requested by the client. - /// - [TestMethod] - public Task TestNoRedirectionWithAllowed() - { + /// + /// As a developer, I expect HTTP requests not to be redirected if + /// upgrades are allowed but not requested by the client. + /// + [TestMethod] + public Task TestNoRedirectionWithAllowed() + { return RunSecure(async (insec, sec) => { using var client = TestHost.GetClient(followRedirects: false); @@ -74,13 +73,13 @@ public Task TestNoRedirectionWithAllowed() }, mode: SecureUpgrade.Allow); } - /// - /// As I developer, I expect requests to be upgraded if requested - /// by the client. - /// - [TestMethod] - public Task TestRedirectionWhenRequested() - { + /// + /// As I developer, I expect requests to be upgraded if requested + /// by the client. + /// + [TestMethod] + public Task TestRedirectionWhenRequested() + { return RunSecure(async (insec, sec) => { using var client = TestHost.GetClient(followRedirects: false); @@ -97,13 +96,13 @@ public Task TestRedirectionWhenRequested() }, mode: SecureUpgrade.Allow); } - /// - /// As the hoster of a web application, I want my application to enforce strict - /// transport security, so that man-in-the-middle attacks can be avoided to some extend. - /// - [TestMethod] - public Task TestTransportPolicy() - { + /// + /// As the hoster of a web application, I want my application to enforce strict + /// transport security, so that man-in-the-middle attacks can be avoided to some extend. + /// + [TestMethod] + public Task TestTransportPolicy() + { return RunSecure(async (insec, sec) => { using var client = TestHost.GetClient(ignoreSecurityErrors: true); @@ -121,13 +120,13 @@ public Task TestTransportPolicy() }, mode: SecureUpgrade.None); } - /// - /// As the operator of the server, I expect the server to resume - /// normal operation after a security error has happened. - /// - [TestMethod] - public Task TestSecurityError() - { + /// + /// As the operator of the server, I expect the server to resume + /// normal operation after a security error has happened. + /// + [TestMethod] + public Task TestSecurityError() + { return RunSecure(async (insec, sec) => { await Assert.ThrowsExceptionAsync(async () => @@ -144,13 +143,13 @@ await Assert.ThrowsExceptionAsync(async () => }); } - /// - /// As a web developer, I can decide not to return a certificate which will - /// abort the server SSL handshake. - /// - [TestMethod] - public Task TestNoCertificate() - { + /// + /// As a web developer, I can decide not to return a certificate which will + /// abort the server SSL handshake. + /// + [TestMethod] + public Task TestNoCertificate() + { return RunSecure(async (insec, sec) => { await Assert.ThrowsExceptionAsync(async () => @@ -162,8 +161,8 @@ await Assert.ThrowsExceptionAsync(async () => }, host: "myserver"); } - private static async Task RunSecure(Func logic, SecureUpgrade? mode = null, string host = "localhost") - { + private static async Task RunSecure(Func logic, SecureUpgrade? mode = null, string host = "localhost") + { var content = Layout.Create().Index(Content.From(Resource.FromString("Hello Alice!"))); using var runner = new TestHost(Layout.Create(), mode is null); @@ -187,41 +186,38 @@ private static async Task RunSecure(Func logic, SecureUpgr await logic((ushort)runner.Port, (ushort)port); } - private static async ValueTask GetCertificate() - { - using var stream = await Resource.FromAssembly("Certificate.pfx").Build().GetContentAsync(); + private static async ValueTask GetCertificate() + { + using var stream = await Resource.FromAssembly("Certificate.pfx").Build().GetContentAsync(); - using var mem = new MemoryStream(); + using var mem = new MemoryStream(); - await stream.CopyToAsync(mem); + await stream.CopyToAsync(mem); #if NET8_0 return new X509Certificate2(mem.ToArray()); #else - return X509CertificateLoader.LoadPkcs12(mem.ToArray(), null); + return X509CertificateLoader.LoadPkcs12(mem.ToArray(), null); #endif - } + } - private class PickyCertificateProvider : ICertificateProvider - { + private class PickyCertificateProvider : ICertificateProvider + { - private string Host { get; } + private string Host { get; } - private X509Certificate2 Certificate { get; } + private X509Certificate2 Certificate { get; } - public PickyCertificateProvider(string host, X509Certificate2 certificate) - { + public PickyCertificateProvider(string host, X509Certificate2 certificate) + { Host = host; Certificate = certificate; } - public X509Certificate2? Provide(string? host) - { + public X509Certificate2? Provide(string? host) + { return host == Host ? Certificate : null; } - } - } -} - +} \ No newline at end of file diff --git a/Testing/Acceptance/Engine/SimpleCertificateProviderTest.cs b/Testing/Acceptance/Engine/SimpleCertificateProviderTest.cs index 8f008545..7b7ad359 100644 --- a/Testing/Acceptance/Engine/SimpleCertificateProviderTest.cs +++ b/Testing/Acceptance/Engine/SimpleCertificateProviderTest.cs @@ -5,16 +5,15 @@ using NSubstitute; -namespace GenHTTP.Testing.Acceptance.Engine +namespace GenHTTP.Testing.Acceptance.Engine; + +[TestClass] +public sealed class SimpleCertificateProviderTest { - [TestClass] - public sealed class SimpleCertificateProviderTest + [TestMethod] + public void TestProvider() { - - [TestMethod] - public void TestProvider() - { using var cert = Substitute.For(); var provider = new SimpleCertificateProvider(cert); @@ -22,6 +21,4 @@ public void TestProvider() Assert.IsNotNull(provider.Provide("google.com")); } - } - } diff --git a/Testing/Acceptance/Engine/WireTests.cs b/Testing/Acceptance/Engine/WireTests.cs index dcf97c88..6e63b1a7 100644 --- a/Testing/Acceptance/Engine/WireTests.cs +++ b/Testing/Acceptance/Engine/WireTests.cs @@ -9,19 +9,18 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Engine -{ +namespace GenHTTP.Testing.Acceptance.Engine; - [TestClass] - public sealed class WireTests - { - private const string NL = "\r\n"; +[TestClass] +public sealed class WireTests +{ + private const string NL = "\r\n"; - #region Tests + #region Tests - [TestMethod] - public async Task TestLowerCaseRequests() - { + [TestMethod] + public async Task TestLowerCaseRequests() + { var app = Inline.Create().Get((IRequest r) => r.Headers["x-my-header"]); using var host = TestHost.Run(app); @@ -37,9 +36,9 @@ public async Task TestLowerCaseRequests() AssertX.Contains("abc", result); } - [TestMethod] - public async Task TestWhitespaceRequest() - { + [TestMethod] + public async Task TestWhitespaceRequest() + { var app = Inline.Create().Get("/some-path/", (IRequest r) => r.Headers["X-My-Header"]); using var host = TestHost.Run(app); @@ -55,33 +54,33 @@ public async Task TestWhitespaceRequest() AssertX.Contains("abc", result); } - [TestMethod] - public async Task TestNoHost() - { + [TestMethod] + public async Task TestNoHost() + { await TestAsync("GET / HTTP/1.0\r\n", "Host"); } - [TestMethod] - public async Task TestUnsupportedProtocolVersion() - { + [TestMethod] + public async Task TestUnsupportedProtocolVersion() + { await TestAsync("GET / HTTP/2.0\r\n", "Unexpected protocol version"); } - [TestMethod] - public async Task TestUnexpectedProtocol() - { + [TestMethod] + public async Task TestUnexpectedProtocol() + { await TestAsync("GET / GENHTTP/1.0\r\n", "HTTP protocol version expected"); } - [TestMethod] - public async Task TestContentLengthNotNumeric() - { + [TestMethod] + public async Task TestContentLengthNotNumeric() + { await TestAsync("GET / HTTP/1.0\r\nContent-Length: ABC\r\n", "Content-Length header is expected to be a numeric value"); } - [TestMethod] - public async Task TestNoKeepAliveForHttp10() - { + [TestMethod] + public async Task TestNoKeepAliveForHttp10() + { using var host = TestHost.Run(Layout.Create()); var result = await SendAsync(host, (w) => @@ -94,9 +93,9 @@ public async Task TestNoKeepAliveForHttp10() AssertX.DoesNotContain("Keep-Alive", result); } - [TestMethod] - public async Task TestNoKeepAliveForConnectionClose() - { + [TestMethod] + public async Task TestNoKeepAliveForConnectionClose() + { using var host = TestHost.Run(Layout.Create()); var result = await SendAsync(host, (w) => @@ -110,9 +109,9 @@ public async Task TestNoKeepAliveForConnectionClose() AssertX.DoesNotContain("Keep-Alive", result); } - [TestMethod] - public async Task TestNonHttp() - { + [TestMethod] + public async Task TestNonHttp() + { using var host = TestHost.Run(Layout.Create()); var result = await SendAsync(host, (w) => @@ -124,9 +123,9 @@ public async Task TestNonHttp() AssertX.Contains("Unable to read HTTP verb from request line", result); } - [TestMethod] - public async Task TestNonHttpButText() - { + [TestMethod] + public async Task TestNonHttpButText() + { using var host = TestHost.Run(Layout.Create()); var result = await SendAsync(host, (w) => @@ -138,12 +137,12 @@ public async Task TestNonHttpButText() AssertX.Contains("HTTP protocol version expected", result); } - #endregion + #endregion - #region Helpers + #region Helpers - private static async ValueTask TestAsync(string request, string assertion) - { + private static async ValueTask TestAsync(string request, string assertion) + { using var host = TestHost.Run(Layout.Create()); var result = await SendAsync(host, (w) => @@ -155,8 +154,8 @@ private static async ValueTask TestAsync(string request, string assertion) AssertX.Contains(assertion, result); } - private static async ValueTask SendAsync(TestHost host, Action sender) - { + private static async ValueTask SendAsync(TestHost host, Action sender) + { using var client = new TcpClient("127.0.0.1", host.Port) { ReceiveTimeout = 1000 @@ -175,8 +174,6 @@ private static async ValueTask SendAsync(TestHost host, Action r.UserAgent) .Keys("123"); @@ -103,9 +102,9 @@ public async Task TestCustomExtractor() await response.AssertStatusAsync(HttpStatusCode.OK); } - [TestMethod] - public async Task TestCustomAuthenticator() - { + [TestMethod] + public async Task TestCustomAuthenticator() + { static ValueTask authenticator(IRequest r, string k) => (k.Length == 5) ? new ValueTask(new ApiKeyUser(k)) : new ValueTask(); var auth = ApiKeyAuthentication.Create() @@ -121,23 +120,21 @@ public async Task TestCustomAuthenticator() await response.AssertStatusAsync(HttpStatusCode.OK); } - private static TestHost GetRunnerWithKeys(params string[] keys) - { + private static TestHost GetRunnerWithKeys(params string[] keys) + { var auth = ApiKeyAuthentication.Create() .Keys(keys); return GetRunnerWithAuth(auth); } - private static TestHost GetRunnerWithAuth(ApiKeyConcernBuilder auth) - { + private static TestHost GetRunnerWithAuth(ApiKeyConcernBuilder auth) + { var content = GetContent().Authentication(auth); return TestHost.Run(content); } - private static LayoutBuilder GetContent() => Layout.Create().Add(Content.From(Resource.FromString("Hello World!"))); - - } + private static LayoutBuilder GetContent() => Layout.Create().Add(Content.From(Resource.FromString("Hello World!"))); } diff --git a/Testing/Acceptance/Modules/Authentication/BasicAuthenticationTests.cs b/Testing/Acceptance/Modules/Authentication/BasicAuthenticationTests.cs index 34381ec6..2954f8fd 100644 --- a/Testing/Acceptance/Modules/Authentication/BasicAuthenticationTests.cs +++ b/Testing/Acceptance/Modules/Authentication/BasicAuthenticationTests.cs @@ -11,16 +11,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Authentication +namespace GenHTTP.Testing.Acceptance.Modules.Authentication; + +[TestClass] +public sealed class BasicAuthenticationTests { - [TestClass] - public sealed class BasicAuthenticationTests + [TestMethod] + public async Task TestNoUser() { - - [TestMethod] - public async Task TestNoUser() - { var content = GetContent().Authentication(BasicAuthentication.Create()); using var runner = TestHost.Run(content); @@ -30,9 +29,9 @@ public async Task TestNoUser() await response.AssertStatusAsync(HttpStatusCode.Unauthorized); } - [TestMethod] - public async Task TestValidUser() - { + [TestMethod] + public async Task TestValidUser() + { var content = GetContent().Authentication(BasicAuthentication.Create() .Add("user", "password")); @@ -43,9 +42,9 @@ public async Task TestValidUser() Assert.AreEqual("user", await response.GetContentAsync()); } - [TestMethod] - public async Task TestInvalidPassword() - { + [TestMethod] + public async Task TestInvalidPassword() + { var content = GetContent().Authentication(BasicAuthentication.Create() .Add("user", "password")); @@ -56,9 +55,9 @@ public async Task TestInvalidPassword() Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); } - [TestMethod] - public async Task TestInvalidUser() - { + [TestMethod] + public async Task TestInvalidUser() + { var content = GetContent().Authentication(BasicAuthentication.Create()); using var runner = TestHost.Run(content); @@ -68,9 +67,9 @@ public async Task TestInvalidUser() await response.AssertStatusAsync(HttpStatusCode.Unauthorized); } - [TestMethod] - public async Task TestCustomUser() - { + [TestMethod] + public async Task TestCustomUser() + { var content = GetContent().Authentication(BasicAuthentication.Create((u, p) => new ValueTask(new BasicAuthenticationUser("my")))); using var runner = TestHost.Run(content); @@ -80,9 +79,9 @@ public async Task TestCustomUser() Assert.AreEqual("my", await response.GetContentAsync()); } - [TestMethod] - public async Task TestNoCustomUser() - { + [TestMethod] + public async Task TestNoCustomUser() + { var content = GetContent().Authentication(BasicAuthentication.Create((u, p) => new ValueTask())); using var runner = TestHost.Run(content); @@ -92,13 +91,13 @@ public async Task TestNoCustomUser() await response.AssertStatusAsync(HttpStatusCode.Unauthorized); } - [TestMethod] - public async Task TestOtherAuthenticationIsNotAccepted() - { + [TestMethod] + public async Task TestOtherAuthenticationIsNotAccepted() + { var content = GetContent().Authentication(BasicAuthentication.Create()); using var runner = TestHost.Run(content); - + var request = runner.GetRequest(); request.Headers.Add("Authorization", "Bearer 123"); @@ -106,10 +105,10 @@ public async Task TestOtherAuthenticationIsNotAccepted() await response.AssertStatusAsync(HttpStatusCode.Unauthorized); } - - [TestMethod] - public async Task TestNoValidBase64() - { + + [TestMethod] + public async Task TestNoValidBase64() + { var content = GetContent().Authentication(BasicAuthentication.Create()); using var runner = TestHost.Run(content); @@ -122,19 +121,17 @@ public async Task TestNoValidBase64() await response.AssertStatusAsync(HttpStatusCode.Unauthorized); } - private static async Task GetResponse(TestHost runner, string user, string password) - { + private static async Task GetResponse(TestHost runner, string user, string password) + { using var client = TestHost.GetClient(creds: new NetworkCredential(user, password)); return await runner.GetResponseAsync(client: client); } - private static LayoutBuilder GetContent() - { + private static LayoutBuilder GetContent() + { return Layout.Create() .Index(new UserReturningHandlerBuilder()); } - } - } diff --git a/Testing/Acceptance/Modules/Authentication/BearerAuthenticationTests.cs b/Testing/Acceptance/Modules/Authentication/BearerAuthenticationTests.cs index b23c5125..47117b2a 100644 --- a/Testing/Acceptance/Modules/Authentication/BearerAuthenticationTests.cs +++ b/Testing/Acceptance/Modules/Authentication/BearerAuthenticationTests.cs @@ -12,28 +12,27 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Authentication -{ +namespace GenHTTP.Testing.Acceptance.Modules.Authentication; - [TestClass] - public sealed class BearerAuthenticationTests - { - private const string VALID_TOKEN = @"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; +[TestClass] +public sealed class BearerAuthenticationTests +{ + private const string VALID_TOKEN = @"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; - #region Supporting data structures + #region Supporting data structures - internal class MyUser : IUser - { + internal class MyUser : IUser + { - public string DisplayName { get; set; } = ""; + public string DisplayName { get; set; } = ""; - } + } - #endregion + #endregion - [TestMethod] - public async Task TestValidToken() - { + [TestMethod] + public async Task TestValidToken() + { var auth = BearerAuthentication.Create() .AllowExpired(); @@ -44,9 +43,9 @@ public async Task TestValidToken() Assert.AreEqual("Secured", await response.GetContentAsync()); } - [TestMethod] - public async Task TestCustomValidator() - { + [TestMethod] + public async Task TestCustomValidator() + { var auth = BearerAuthentication.Create() .Validation((token) => throw new ProviderException(ResponseStatus.Forbidden, "Nah")) .AllowExpired(); @@ -56,9 +55,9 @@ public async Task TestCustomValidator() await response.AssertStatusAsync(HttpStatusCode.Forbidden); } - [TestMethod] - public async Task TestNoUser() - { + [TestMethod] + public async Task TestNoUser() + { var auth = BearerAuthentication.Create() .UserMapping((r, t) => new()) .AllowExpired(); @@ -68,9 +67,9 @@ public async Task TestNoUser() await response.AssertStatusAsync(HttpStatusCode.OK); } - [TestMethod] - public async Task TestUser() - { + [TestMethod] + public async Task TestUser() + { var auth = BearerAuthentication.Create() .UserMapping((r, t) => new(new MyUser() { DisplayName = "User Name" })) .AllowExpired(); @@ -80,9 +79,9 @@ public async Task TestUser() await response.AssertStatusAsync(HttpStatusCode.OK); } - [TestMethod] - public async Task TestNoToken() - { + [TestMethod] + public async Task TestNoToken() + { var auth = BearerAuthentication.Create() .AllowExpired(); @@ -91,9 +90,9 @@ public async Task TestNoToken() await response.AssertStatusAsync(HttpStatusCode.Unauthorized); } - [TestMethod] - public async Task TestMalformedToken() - { + [TestMethod] + public async Task TestMalformedToken() + { var auth = BearerAuthentication.Create() .AllowExpired(); @@ -102,8 +101,8 @@ public async Task TestMalformedToken() await response.AssertStatusAsync(HttpStatusCode.BadRequest); } - private static async Task Execute(BearerAuthenticationConcernBuilder builder, string? token = null) - { + private static async Task Execute(BearerAuthenticationConcernBuilder builder, string? token = null) + { var handler = Inline.Create() .Get(() => "Secured") .Add(builder); @@ -120,6 +119,4 @@ private static async Task Execute(BearerAuthenticationConce return await host.GetResponseAsync(request); } - } - } diff --git a/Testing/Acceptance/Modules/Authentication/UserInjectionTests.cs b/Testing/Acceptance/Modules/Authentication/UserInjectionTests.cs index b2b872bf..c3731636 100644 --- a/Testing/Acceptance/Modules/Authentication/UserInjectionTests.cs +++ b/Testing/Acceptance/Modules/Authentication/UserInjectionTests.cs @@ -9,18 +9,17 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Authentication -{ +namespace GenHTTP.Testing.Acceptance.Modules.Authentication; - [TestClass] - public class UserInjectionTests - { +[TestClass] +public class UserInjectionTests +{ - #region Tests + #region Tests - [TestMethod] - public async Task TestUserInjected() - { + [TestMethod] + public async Task TestUserInjected() + { using var runner = GetRunner(); using var client = TestHost.GetClient(creds: new NetworkCredential("abc", "def")); @@ -31,9 +30,9 @@ public async Task TestUserInjected() Assert.AreEqual("abc", await response.GetContentAsync()); } - [TestMethod] - public async Task TestNoUser() - { + [TestMethod] + public async Task TestNoUser() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync(); @@ -41,12 +40,12 @@ public async Task TestNoUser() await response.AssertStatusAsync(HttpStatusCode.Unauthorized); } - #endregion + #endregion - #region Helpers + #region Helpers - private static TestHost GetRunner() - { + private static TestHost GetRunner() + { var auth = BasicAuthentication.Create() .Add("abc", "def"); @@ -61,8 +60,6 @@ private static TestHost GetRunner() return TestHost.Run(content); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/Authentication/UserReturningHandler.cs b/Testing/Acceptance/Modules/Authentication/UserReturningHandler.cs index 6788f047..c772738c 100644 --- a/Testing/Acceptance/Modules/Authentication/UserReturningHandler.cs +++ b/Testing/Acceptance/Modules/Authentication/UserReturningHandler.cs @@ -7,30 +7,29 @@ using GenHTTP.Modules.Authentication; using GenHTTP.Modules.IO; -namespace GenHTTP.Testing.Acceptance.Modules.Authentication -{ +namespace GenHTTP.Testing.Acceptance.Modules.Authentication; - public class UserReturningHandlerBuilder : IHandlerBuilder - { +public class UserReturningHandlerBuilder : IHandlerBuilder +{ - public IHandler Build(IHandler parent) => new UserReturningHandler(parent); + public IHandler Build(IHandler parent) => new UserReturningHandler(parent); - } +} - public class UserReturningHandler : IHandler - { +public class UserReturningHandler : IHandler +{ - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public IHandler Parent { get; } + public IHandler Parent { get; } - public UserReturningHandler(IHandler parent) - { + public UserReturningHandler(IHandler parent) + { Parent = parent; } - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { var content = request.GetUser()?.DisplayName ?? throw new ProviderException(ResponseStatus.BadRequest, "No user!"); return request.Respond() @@ -38,6 +37,4 @@ public UserReturningHandler(IHandler parent) .BuildTask(); } - } - } diff --git a/Testing/Acceptance/Modules/CacheValidationTests.cs b/Testing/Acceptance/Modules/CacheValidationTests.cs index 037db444..6198960c 100644 --- a/Testing/Acceptance/Modules/CacheValidationTests.cs +++ b/Testing/Acceptance/Modules/CacheValidationTests.cs @@ -7,16 +7,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules +namespace GenHTTP.Testing.Acceptance.Modules; + +[TestClass] +public sealed class CacheValidationTests { - [TestClass] - public sealed class CacheValidationTests + [TestMethod] + public async Task TestETagIsGenerated() { - - [TestMethod] - public async Task TestETagIsGenerated() - { using var runner = TestHost.Run(Content.From(Resource.FromString("Hello World!"))); using var response = await runner.GetResponseAsync(); @@ -29,9 +28,9 @@ public async Task TestETagIsGenerated() AssertX.EndsWith("\"", eTag); } - [TestMethod] - public async Task TestServerReturnsUnmodified() - { + [TestMethod] + public async Task TestServerReturnsUnmodified() + { using var runner = TestHost.Run(Content.From(Resource.FromString("Hello World!"))); using var response = await runner.GetResponseAsync(); @@ -49,9 +48,9 @@ public async Task TestServerReturnsUnmodified() Assert.AreEqual("0", cached.GetContentHeader("Content-Length")); } - [TestMethod] - public async Task TestServerReturnsModified() - { + [TestMethod] + public async Task TestServerReturnsModified() + { using var runner = TestHost.Run(Content.From(Resource.FromString("Hello World!"))); var request = runner.GetRequest(); @@ -63,9 +62,9 @@ public async Task TestServerReturnsModified() await reloaded.AssertStatusAsync(HttpStatusCode.OK); } - [TestMethod] - public async Task TestNoContentNoEtag() - { + [TestMethod] + public async Task TestNoContentNoEtag() + { var noContent = new FunctionalHandler(responseProvider: (r) => { return r.Respond().Status(Api.Protocol.ResponseStatus.NoContent).Build(); @@ -78,9 +77,9 @@ public async Task TestNoContentNoEtag() Assert.IsFalse(response.Headers.Contains("ETag")); } - [TestMethod] - public async Task TestOtherMethodNoETag() - { + [TestMethod] + public async Task TestOtherMethodNoETag() + { using var runner = TestHost.Run(Content.From(Resource.FromString("Hello World!"))); var request = runner.GetRequest(); @@ -92,6 +91,4 @@ public async Task TestOtherMethodNoETag() Assert.IsFalse(response.Headers.Contains("ETag")); } - } - } diff --git a/Testing/Acceptance/Modules/Caching/CacheTests.cs b/Testing/Acceptance/Modules/Caching/CacheTests.cs index 2909b4a7..a60549f0 100644 --- a/Testing/Acceptance/Modules/Caching/CacheTests.cs +++ b/Testing/Acceptance/Modules/Caching/CacheTests.cs @@ -7,18 +7,17 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Caching -{ +namespace GenHTTP.Testing.Acceptance.Modules.Caching; - public record CachedEntry(string Data, DateTime? Date = null, int? Int = null); +public record CachedEntry(string Data, DateTime? Date = null, int? Int = null); - [TestClass] - public class CacheTests - { +[TestClass] +public class CacheTests +{ - [TestMethod] - public async Task TestHit() - { + [TestMethod] + public async Task TestHit() + { foreach (var cache in GetCaches()) { var now = DateTime.Now; @@ -28,16 +27,16 @@ public async Task TestHit() Assert.AreEqual(1, (await cache.GetEntriesAsync("k")).Length); var hit = (await cache.GetEntryAsync("k", "v"))!; - + Assert.AreEqual("1", hit.Data); Assert.AreEqual(now, hit.Date); Assert.AreEqual(42, hit.Int); } } - [TestMethod] - public async Task TestMiss() - { + [TestMethod] + public async Task TestMiss() + { foreach (var cache in GetCaches()) { Assert.AreEqual(0, (await cache.GetEntriesAsync("k")).Length); @@ -46,9 +45,9 @@ public async Task TestMiss() } } - [TestMethod] - public async Task TestVariantMiss() - { + [TestMethod] + public async Task TestVariantMiss() + { foreach (var cache in GetCaches()) { await cache.StoreAsync("k", "v1", new CachedEntry("1")); @@ -57,9 +56,9 @@ public async Task TestVariantMiss() } } - [TestMethod] - public async Task TestRemoval() - { + [TestMethod] + public async Task TestRemoval() + { foreach (var cache in GetCaches()) { await cache.StoreAsync("k", "v", new CachedEntry("1")); @@ -72,9 +71,9 @@ public async Task TestRemoval() } } - [TestMethod] - public async Task TestStreaming() - { + [TestMethod] + public async Task TestStreaming() + { foreach (var cache in GetCaches()) { using var stream = new MemoryStream(new byte[] { 1 }); @@ -90,9 +89,9 @@ public async Task TestStreaming() } } - [TestMethod] - public async Task TestStreamingOverwrite() - { + [TestMethod] + public async Task TestStreamingOverwrite() + { foreach (var cache in GetCaches()) { using var stream = new MemoryStream(new byte[] { 1 }); @@ -105,9 +104,9 @@ public async Task TestStreamingOverwrite() } } - [TestMethod] - public async Task TestDirectStreaming() - { + [TestMethod] + public async Task TestDirectStreaming() + { foreach (var cache in GetCaches()) { await cache.StoreDirectAsync("k", "v", (s) => s.WriteAsync(new byte[] { 1 })); @@ -120,9 +119,9 @@ public async Task TestDirectStreaming() } } - [TestMethod] - public async Task TestDirectStreamingOverwrite() - { + [TestMethod] + public async Task TestDirectStreamingOverwrite() + { foreach (var cache in GetCaches()) { await cache.StoreDirectAsync("k", "v", (s) => s.WriteAsync(new byte[] { 1 })); @@ -133,9 +132,9 @@ public async Task TestDirectStreamingOverwrite() } } - [TestMethod] - public async Task TestStreamingMiss() - { + [TestMethod] + public async Task TestStreamingMiss() + { foreach (var cache in GetCaches()) { Assert.AreEqual(0, (await cache.GetEntriesAsync("k")).Length); @@ -144,8 +143,8 @@ public async Task TestStreamingMiss() } } - private static ICache[] GetCaches() - { + private static ICache[] GetCaches() + { return new ICache[] { Cache.Memory().Build(), @@ -153,6 +152,4 @@ private static ICache[] GetCaches() }; } - } - } diff --git a/Testing/Acceptance/Modules/ClientCaching/PolicyTests.cs b/Testing/Acceptance/Modules/ClientCaching/PolicyTests.cs index 60d1a721..1b1fda93 100644 --- a/Testing/Acceptance/Modules/ClientCaching/PolicyTests.cs +++ b/Testing/Acceptance/Modules/ClientCaching/PolicyTests.cs @@ -9,16 +9,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.ClientCaching +namespace GenHTTP.Testing.Acceptance.Modules.ClientCaching; + +[TestClass] +public sealed class PolicyTests { - [TestClass] - public sealed class PolicyTests + [TestMethod] + public async Task TestExpireHeaderSet() { - - [TestMethod] - public async Task TestExpireHeaderSet() - { var content = Content.From(Resource.FromString("Content")) .Add(ClientCache.Policy().Duration(1)); @@ -29,9 +28,9 @@ public async Task TestExpireHeaderSet() Assert.IsNotNull(response.GetContentHeader("Expires")); } - [TestMethod] - public async Task TestExpireHeaderNotSetForOtherMethods() - { + [TestMethod] + public async Task TestExpireHeaderNotSetForOtherMethods() + { var content = Content.From(Resource.FromString("Content")) .Add(ClientCache.Policy().Duration(1)); @@ -45,9 +44,9 @@ public async Task TestExpireHeaderNotSetForOtherMethods() AssertX.IsNullOrEmpty(response.GetContentHeader("Expires")); } - [TestMethod] - public async Task TestExpireHeaderNotSetForOtherStatus() - { + [TestMethod] + public async Task TestExpireHeaderNotSetForOtherStatus() + { var content = Layout.Create() .Add(ClientCache.Policy().Duration(1)); @@ -58,9 +57,9 @@ public async Task TestExpireHeaderNotSetForOtherStatus() AssertX.IsNullOrEmpty(response.GetContentHeader("Expires")); } - [TestMethod] - public async Task TestPredicate() - { + [TestMethod] + public async Task TestPredicate() + { var content = Content.From(Resource.FromString("Content")) .Add(ClientCache.Policy().Duration(1).Predicate((_, r) => r.ContentType?.RawType != "text/plain")); @@ -71,6 +70,4 @@ public async Task TestPredicate() AssertX.IsNullOrEmpty(response.GetContentHeader("Expires")); } - } - } diff --git a/Testing/Acceptance/Modules/Controllers/ActionTests.cs b/Testing/Acceptance/Modules/Controllers/ActionTests.cs index 3793903e..81c868c0 100644 --- a/Testing/Acceptance/Modules/Controllers/ActionTests.cs +++ b/Testing/Acceptance/Modules/Controllers/ActionTests.cs @@ -13,73 +13,72 @@ using GenHTTP.Modules.IO; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Modules.Controllers +namespace GenHTTP.Testing.Acceptance.Modules.Controllers; + +[TestClass] +public sealed class ActionTests { - [TestClass] - public sealed class ActionTests - { + #region Supporting data structures - #region Supporting data structures + public sealed class Model + { - public sealed class Model - { + public string? Field { get; set; } - public string? Field { get; set; } + } - } + public sealed class TestController + { - public sealed class TestController + public IHandlerBuilder Index() { - - public IHandlerBuilder Index() - { return Content.From(Resource.FromString("Hello World!")); } - public IHandlerBuilder Action(int? query) - { + public IHandlerBuilder Action(int? query) + { return Content.From(Resource.FromString(query?.ToString() ?? "Action")); } - [ControllerAction(RequestMethod.PUT)] - public IHandlerBuilder Action(int? value1, string value2) - { + [ControllerAction(RequestMethod.PUT)] + public IHandlerBuilder Action(int? value1, string value2) + { return Content.From(Resource.FromString((value1?.ToString() ?? "Action") + $" {value2}")); } - public IHandlerBuilder SimpleAction([FromPath] int id) - { + public IHandlerBuilder SimpleAction([FromPath] int id) + { return Content.From(Resource.FromString(id.ToString())); } - public IHandlerBuilder ComplexAction(int three, [FromPath] int one, [FromPath] int two) - { + public IHandlerBuilder ComplexAction(int three, [FromPath] int one, [FromPath] int two) + { return Content.From(Resource.FromString((one + two + three).ToString())); } - [ControllerAction(RequestMethod.POST)] - public IHandlerBuilder Action(Model data) - { + [ControllerAction(RequestMethod.POST)] + public IHandlerBuilder Action(Model data) + { return Content.From(Resource.FromString(data.Field ?? "no content")); } - public IHandlerBuilder HypenCAsing99() - { + public IHandlerBuilder HypenCAsing99() + { return Content.From(Resource.FromString("OK")); } - public void Void() { } + public void Void() { } - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task TestIndex() - { + [TestMethod] + public async Task TestIndex() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/"); @@ -88,9 +87,9 @@ public async Task TestIndex() Assert.AreEqual("Hello World!", await response.GetContentAsync()); } - [TestMethod] - public async Task TestAction() - { + [TestMethod] + public async Task TestAction() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/action/"); @@ -99,9 +98,9 @@ public async Task TestAction() Assert.AreEqual("Action", await response.GetContentAsync()); } - [TestMethod] - public async Task TestActionWithQuery() - { + [TestMethod] + public async Task TestActionWithQuery() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/action/?query=0815"); @@ -110,9 +109,9 @@ public async Task TestActionWithQuery() Assert.AreEqual("815", await response.GetContentAsync()); } - [TestMethod] - public async Task TestActionWithQueryFromBody() - { + [TestMethod] + public async Task TestActionWithQueryFromBody() + { using var runner = GetRunner(); var dict = new Dictionary() @@ -131,9 +130,9 @@ public async Task TestActionWithQueryFromBody() Assert.AreEqual("Action test", await response.GetContentAsync()); } - [TestMethod] - public async Task TestActionWithBody() - { + [TestMethod] + public async Task TestActionWithBody() + { using var runner = GetRunner(); var request = runner.GetRequest("/t/action/"); @@ -149,9 +148,9 @@ public async Task TestActionWithBody() Assert.AreEqual("FieldData", await response.GetContentAsync()); } - [TestMethod] - public async Task TestActionWithParameter() - { + [TestMethod] + public async Task TestActionWithParameter() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/simple-action/4711/"); @@ -160,9 +159,9 @@ public async Task TestActionWithParameter() Assert.AreEqual("4711", await response.GetContentAsync()); } - [TestMethod] - public async Task TestActionWithBadParameter() - { + [TestMethod] + public async Task TestActionWithBadParameter() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/simple-action/string/"); @@ -170,9 +169,9 @@ public async Task TestActionWithBadParameter() await response.AssertStatusAsync(HttpStatusCode.BadRequest); } - [TestMethod] - public async Task TestActionWithMixedParameters() - { + [TestMethod] + public async Task TestActionWithMixedParameters() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/complex-action/1/2/?three=3"); @@ -181,9 +180,9 @@ public async Task TestActionWithMixedParameters() Assert.AreEqual("6", await response.GetContentAsync()); } - [TestMethod] - public async Task TestActionWithNoResult() - { + [TestMethod] + public async Task TestActionWithNoResult() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/void/"); @@ -191,9 +190,9 @@ public async Task TestActionWithNoResult() await response.AssertStatusAsync(HttpStatusCode.NoContent); } - [TestMethod] - public async Task TestNonExistingAction() - { + [TestMethod] + public async Task TestNonExistingAction() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/nope/"); @@ -201,9 +200,9 @@ public async Task TestNonExistingAction() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestHypenCasing() - { + [TestMethod] + public async Task TestHypenCasing() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/hypen-casing-99/"); @@ -212,9 +211,9 @@ public async Task TestHypenCasing() Assert.AreEqual("OK", await response.GetContentAsync()); } - [TestMethod] - public async Task TestIndexController() - { + [TestMethod] + public async Task TestIndexController() + { using var runner = TestHost.Run(Layout.Create().IndexController()); using var response = await runner.GetResponseAsync("/simple-action/4711/"); @@ -223,17 +222,15 @@ public async Task TestIndexController() Assert.AreEqual("4711", await response.GetContentAsync()); } - #endregion + #endregion - #region Helpers + #region Helpers - private TestHost GetRunner() - { + private TestHost GetRunner() + { return TestHost.Run(Layout.Create().AddController("t")); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/Controllers/DataTests.cs b/Testing/Acceptance/Modules/Controllers/DataTests.cs index 586ab589..2bc25bcc 100644 --- a/Testing/Acceptance/Modules/Controllers/DataTests.cs +++ b/Testing/Acceptance/Modules/Controllers/DataTests.cs @@ -13,30 +13,29 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Controllers -{ +namespace GenHTTP.Testing.Acceptance.Modules.Controllers; - [TestClass] - public sealed class DataTests - { +[TestClass] +public sealed class DataTests +{ - #region Controller + #region Controller - public class TestController - { + public class TestController + { - [ControllerAction(RequestMethod.POST)] - public DateOnly Date(DateOnly date) => date; + [ControllerAction(RequestMethod.POST)] + public DateOnly Date(DateOnly date) => date; - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task TestDateOnly() - { + [TestMethod] + public async Task TestDateOnly() + { using var host = GetHost(); var request = host.GetRequest("/t/date/", HttpMethod.Post); @@ -55,9 +54,9 @@ public async Task TestDateOnly() Assert.AreEqual("2024-03-11", await response.GetContentAsync()); } - [TestMethod] - public async Task TestInvalidDateOnly() - { + [TestMethod] + public async Task TestInvalidDateOnly() + { using var host = GetHost(); var request = host.GetRequest("/t/date/", HttpMethod.Post); @@ -74,12 +73,12 @@ public async Task TestInvalidDateOnly() await response.AssertStatusAsync(HttpStatusCode.BadRequest); } - #endregion + #endregion - #region Helpers + #region Helpers - private static TestHost GetHost() - { + private static TestHost GetHost() + { var app = Layout.Create() .AddController("t", serializers: Serialization.Default(), injectors: Injection.Default(), @@ -88,8 +87,6 @@ private static TestHost GetHost() return TestHost.Run(app); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/Controllers/ErrorHandlingTests.cs b/Testing/Acceptance/Modules/Controllers/ErrorHandlingTests.cs index 6ec5aba8..c01866f5 100644 --- a/Testing/Acceptance/Modules/Controllers/ErrorHandlingTests.cs +++ b/Testing/Acceptance/Modules/Controllers/ErrorHandlingTests.cs @@ -4,38 +4,37 @@ using GenHTTP.Modules.Controllers; -namespace GenHTTP.Testing.Acceptance.Modules.Controllers -{ +namespace GenHTTP.Testing.Acceptance.Modules.Controllers; - [TestClass] - public sealed class ErrorHandlingTests - { +[TestClass] +public sealed class ErrorHandlingTests +{ - #region Supporting data structures + #region Supporting data structures - public sealed class ControllerWithNullablePath - { + public sealed class ControllerWithNullablePath + { - public int Test([FromPath] int? id) => 42; + public int Test([FromPath] int? id) => 42; - } + } - public sealed class ComplexPath { } + public sealed class ComplexPath { } - public sealed class ControllerWithComplexPath - { + public sealed class ControllerWithComplexPath + { - public int Test([FromPath] ComplexPath value) => 42; + public int Test([FromPath] ComplexPath value) => 42; - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public void TestNoNullablePathArguments() - { + [TestMethod] + public void TestNoNullablePathArguments() + { Assert.ThrowsException(() => { var controller = Controller.From(); @@ -43,9 +42,9 @@ public void TestNoNullablePathArguments() }); } - [TestMethod] - public void TestNoComplexPathArguments() - { + [TestMethod] + public void TestNoComplexPathArguments() + { Assert.ThrowsException(() => { var controller = Controller.From(); @@ -53,8 +52,6 @@ public void TestNoComplexPathArguments() }); } - #endregion - - } + #endregion -} +} \ No newline at end of file diff --git a/Testing/Acceptance/Modules/Controllers/ResultTypeTests.cs b/Testing/Acceptance/Modules/Controllers/ResultTypeTests.cs index 5b1fe690..56b8399b 100644 --- a/Testing/Acceptance/Modules/Controllers/ResultTypeTests.cs +++ b/Testing/Acceptance/Modules/Controllers/ResultTypeTests.cs @@ -12,47 +12,46 @@ using GenHTTP.Modules.Conversion; using GenHTTP.Modules.Reflection; -namespace GenHTTP.Testing.Acceptance.Modules.Controllers +namespace GenHTTP.Testing.Acceptance.Modules.Controllers; + +[TestClass] +public sealed class ResultTypeTests { - [TestClass] - public sealed class ResultTypeTests - { + #region Supporting data structures - #region Supporting data structures + public sealed class TestController + { - public sealed class TestController + public IHandlerBuilder HandlerBuilder() { - - public IHandlerBuilder HandlerBuilder() - { return Content.From(Resource.FromString("HandlerBuilder")); } - public IHandler Handler(IHandler parent) - { + public IHandler Handler(IHandler parent) + { return Content.From(Resource.FromString("Handler")).Build(parent); } - public IResponseBuilder ResponseBuilder(IRequest request) - { + public IResponseBuilder ResponseBuilder(IRequest request) + { return request.Respond().Content("ResponseBuilder"); } - public IResponse Response(IRequest request) - { + public IResponse Response(IRequest request) + { return request.Respond().Content("Response").Build(); } - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task ControllerMayReturnHandlerBuilder() - { + [TestMethod] + public async Task ControllerMayReturnHandlerBuilder() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/handler-builder/"); @@ -61,9 +60,9 @@ public async Task ControllerMayReturnHandlerBuilder() Assert.AreEqual("HandlerBuilder", await response.GetContentAsync()); } - [TestMethod] - public async Task ControllerMayReturnHandler() - { + [TestMethod] + public async Task ControllerMayReturnHandler() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/handler/"); @@ -72,9 +71,9 @@ public async Task ControllerMayReturnHandler() Assert.AreEqual("Handler", await response.GetContentAsync()); } - [TestMethod] - public async Task ControllerMayReturnResponseBuilder() - { + [TestMethod] + public async Task ControllerMayReturnResponseBuilder() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/response-builder/"); @@ -83,9 +82,9 @@ public async Task ControllerMayReturnResponseBuilder() Assert.AreEqual("ResponseBuilder", await response.GetContentAsync()); } - [TestMethod] - public async Task ControllerMayReturnResponse() - { + [TestMethod] + public async Task ControllerMayReturnResponse() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/response/"); @@ -94,12 +93,12 @@ public async Task ControllerMayReturnResponse() Assert.AreEqual("Response", await response.GetContentAsync()); } - #endregion + #endregion - #region Helpers + #region Helpers - private static TestHost GetRunner() - { + private static TestHost GetRunner() + { var controller = Controller.From() .Serializers(Serialization.Default()) .Injectors(Injection.Default()); @@ -107,8 +106,6 @@ private static TestHost GetRunner() return TestHost.Run(Layout.Create().Add("t", controller)); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/Controllers/SeoTests.cs b/Testing/Acceptance/Modules/Controllers/SeoTests.cs index e10800b6..3c7b3324 100644 --- a/Testing/Acceptance/Modules/Controllers/SeoTests.cs +++ b/Testing/Acceptance/Modules/Controllers/SeoTests.cs @@ -10,42 +10,41 @@ using GenHTTP.Modules.IO; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Modules.Controllers +namespace GenHTTP.Testing.Acceptance.Modules.Controllers; + +[TestClass] +public sealed class SeoTests { - [TestClass] - public sealed class SeoTests - { + #region Supporting data structures - #region Supporting data structures + public sealed class TestController + { - public sealed class TestController + public IHandlerBuilder Action() { - - public IHandlerBuilder Action() - { return Content.From(Resource.FromString("Action")); } - [ControllerAction(RequestMethod.DELETE)] - public IHandlerBuilder Action([FromPath] int id) - { + [ControllerAction(RequestMethod.DELETE)] + public IHandlerBuilder Action([FromPath] int id) + { return Content.From(Resource.FromString(id.ToString())); } - } + } - #endregion + #endregion - #region Tests + #region Tests - /// - /// As the developer of a web application, I don't want the MCV framework to generate duplicate content - /// by accepting upper case letters in action names. - /// - [TestMethod] - public async Task TestActionCasingMatters() - { + /// + /// As the developer of a web application, I don't want the MCV framework to generate duplicate content + /// by accepting upper case letters in action names. + /// + [TestMethod] + public async Task TestActionCasingMatters() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/Action/"); @@ -53,17 +52,15 @@ public async Task TestActionCasingMatters() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - #endregion + #endregion - #region Helpers + #region Helpers - private TestHost GetRunner() - { + private TestHost GetRunner() + { return TestHost.Run(Layout.Create().AddController("t")); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/Conversion/ErrorHandlingTests.cs b/Testing/Acceptance/Modules/Conversion/ErrorHandlingTests.cs index d264a7a1..a2ca4d35 100644 --- a/Testing/Acceptance/Modules/Conversion/ErrorHandlingTests.cs +++ b/Testing/Acceptance/Modules/Conversion/ErrorHandlingTests.cs @@ -6,24 +6,23 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Conversion -{ +namespace GenHTTP.Testing.Acceptance.Modules.Conversion; - [TestClass] - public class ErrorHandlingTests - { +[TestClass] +public class ErrorHandlingTests +{ - #region Supporting data structures + #region Supporting data structures - record class MyEntity(string Data); + record class MyEntity(string Data); - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task UndeserializableBodyReturnsWithBadRequest() - { + [TestMethod] + public async Task UndeserializableBodyReturnsWithBadRequest() + { var inline = Inline.Create() .Post("/t", (MyEntity entity) => entity.Data); @@ -41,8 +40,6 @@ public async Task UndeserializableBodyReturnsWithBadRequest() await response.AssertStatusAsync(HttpStatusCode.BadRequest); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/ConversionTests.cs b/Testing/Acceptance/Modules/ConversionTests.cs index 00489826..67f0bef2 100644 --- a/Testing/Acceptance/Modules/ConversionTests.cs +++ b/Testing/Acceptance/Modules/ConversionTests.cs @@ -1,138 +1,130 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; +using System.Net; using System.Net.Http; using System.Threading.Tasks; - using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -using GenHTTP.Modules.Conversion.Providers; -using GenHTTP.Modules.Conversion.Providers.Forms; - +using GenHTTP.Modules.Conversion.Serializers; +using GenHTTP.Modules.Conversion.Serializers.Forms; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Conversion -{ +namespace GenHTTP.Testing.Acceptance.Modules; - [TestClass] - public sealed class ConversionTests - { +[TestClass] +public sealed class ConversionTests +{ - #region Supporting data structures + #region Supporting data structures #pragma warning disable CS0649 - private class FieldData - { + private class FieldData + { - public int field; + public int field; - } + } - private class PropertyData - { + private class PropertyData + { - public int Field { get; set; } + public int Field { get; set; } - } + } - private class TypedData - { + private class TypedData + { - public bool Boolean { get; set; } + public bool Boolean { get; set; } - public double Double { get; set; } + public double Double { get; set; } - public string? String { get; set; } + public string? String { get; set; } - public EnumData Enum { get; set; } + public EnumData Enum { get; set; } - } + } - private enum EnumData { One, Two } + private enum EnumData { One, Two } #pragma warning restore CS0649 - private class ConversionHandlerBuilder : IHandlerBuilder - { - private readonly ISerializationFormat _Format; - - public ConversionHandlerBuilder(ISerializationFormat format) - { - _Format = format; - } - - public IHandler Build(IHandler parent) - { - return new ConversionHandler(_Format, parent); - } + private class ConversionHandlerBuilder : IHandlerBuilder + { + private readonly ISerializationFormat _Format; + public ConversionHandlerBuilder(ISerializationFormat format) + { + _Format = format; } - private class ConversionHandler : IHandler + public IHandler Build(IHandler parent) { + return new ConversionHandler(_Format, parent); + } - public IHandler Parent { get; } - - public ISerializationFormat Format { get; } + } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + private class ConversionHandler : IHandler + { - public ConversionHandler(ISerializationFormat format, IHandler parent) - { - Parent = parent; - Format = format; - } + public IHandler Parent { get; } - public async ValueTask HandleAsync(IRequest request) - { - var obj = await Format.DeserializeAsync(request.Content!, typeof(T)); + public ISerializationFormat Format { get; } - return (await Format.SerializeAsync(request, obj!)).Build(); - } + public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ConversionHandler(ISerializationFormat format, IHandler parent) + { + Parent = parent; + Format = format; } - #endregion + public async ValueTask HandleAsync(IRequest request) + { + var obj = await Format.DeserializeAsync(request.Content!, typeof(T)); - #region Tests + return (await Format.SerializeAsync(request, obj!)).Build(); + } - [TestMethod] - public async Task TestFormFieldSerialization() => await RunTest("field=20"); + } - [TestMethod] - public async Task TestFormPropertySerialization() => await RunTest("Field=20"); + #endregion - [TestMethod] - public async Task TestFormTypeSerialization() => await RunTest("Boolean=1&Double=0.2&String=Test&Enum=One"); + #region Tests - [TestMethod] - public async Task TestFormDefaultValueSerialization() => await RunTest("Boolean=0&Double=0&String=&Enum=One"); + [TestMethod] + public async Task TestFormFieldSerialization() => await RunTest("field=20"); - #endregion + [TestMethod] + public async Task TestFormPropertySerialization() => await RunTest("Field=20"); - #region Helpers + [TestMethod] + public async Task TestFormTypeSerialization() => await RunTest("Boolean=1&Double=0.2&String=Test&Enum=One"); - private static async Task RunTest(string serialized) where TFormat : ISerializationFormat, new() - { - var handler = new ConversionHandlerBuilder(new TFormat()); + [TestMethod] + public async Task TestFormDefaultValueSerialization() => await RunTest("Boolean=0&Double=0&String=&Enum=One"); - using var runner = TestHost.Run(handler); + #endregion - var request = runner.GetRequest(); + #region Helpers - request.Method = HttpMethod.Post; - request.Content = new StringContent(serialized); + private static async Task RunTest(string serialized) where TFormat : ISerializationFormat, new() + { + var handler = new ConversionHandlerBuilder(new TFormat()); - using var response = await runner.GetResponseAsync(request); + using var runner = TestHost.Run(handler); - await response.AssertStatusAsync(HttpStatusCode.OK); - Assert.AreEqual(serialized, await response.GetContentAsync()); - } + var request = runner.GetRequest(); - #endregion + request.Method = HttpMethod.Post; + request.Content = new StringContent(serialized); + using var response = await runner.GetResponseAsync(request); + + await response.AssertStatusAsync(HttpStatusCode.OK); + Assert.AreEqual(serialized, await response.GetContentAsync()); } + #endregion + } diff --git a/Testing/Acceptance/Modules/DirectoryBrowsing/FormatterTest.cs b/Testing/Acceptance/Modules/DirectoryBrowsing/FormatterTest.cs index 93038e22..66a4dec4 100644 --- a/Testing/Acceptance/Modules/DirectoryBrowsing/FormatterTest.cs +++ b/Testing/Acceptance/Modules/DirectoryBrowsing/FormatterTest.cs @@ -1,16 +1,15 @@ using GenHTTP.Modules.DirectoryBrowsing.Provider; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.DirectoryBrowsing +namespace GenHTTP.Testing.Acceptance.Modules.DirectoryBrowsing; + +[TestClass] +public sealed class FormatterTest { - [TestClass] - public sealed class FormatterTest + [TestMethod] + public void TestFormatting() { - - [TestMethod] - public void TestFormatting() - { Assert.AreEqual("512 Bytes", FileSizeFormatter.Format(512)); Assert.AreEqual("2.78 KB", FileSizeFormatter.Format(2842)); @@ -22,6 +21,4 @@ public void TestFormatting() Assert.AreEqual("2.78 TB", FileSizeFormatter.Format(2842L * 1024 * 1024 * 1024)); } - } - } diff --git a/Testing/Acceptance/Modules/ErrorHandling/CustomErrorMapperTests.cs b/Testing/Acceptance/Modules/ErrorHandling/CustomErrorMapperTests.cs index 3d3c1c2e..c9ac9517 100644 --- a/Testing/Acceptance/Modules/ErrorHandling/CustomErrorMapperTests.cs +++ b/Testing/Acceptance/Modules/ErrorHandling/CustomErrorMapperTests.cs @@ -11,19 +11,18 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.ErrorHandling -{ +namespace GenHTTP.Testing.Acceptance.Modules.ErrorHandling; - [TestClass] - public sealed class CustomErrorMapperTests - { +[TestClass] +public sealed class CustomErrorMapperTests +{ - #region Supporting data structures + #region Supporting data structures - public class ErrorLengthMapper : IErrorMapper + public class ErrorLengthMapper : IErrorMapper + { + public ValueTask GetNotFound(IRequest request, IHandler handler) { - public ValueTask GetNotFound(IRequest request, IHandler handler) - { return new ( request.Respond() @@ -33,8 +32,8 @@ public class ErrorLengthMapper : IErrorMapper ); } - public ValueTask Map(IRequest request, IHandler handler, Exception error) - { + public ValueTask Map(IRequest request, IHandler handler, Exception error) + { return new ( request.Respond() @@ -43,15 +42,15 @@ public class ErrorLengthMapper : IErrorMapper .Build() ); } - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task Test404Mapped() - { + [TestMethod] + public async Task Test404Mapped() + { var test = Layout.Create() .Add(ErrorHandler.From(new ErrorLengthMapper())); @@ -61,9 +60,9 @@ public async Task Test404Mapped() Assert.AreEqual("404", await response.GetContentAsync()); } - [TestMethod] - public async Task TestExceptionMapped() - { + [TestMethod] + public async Task TestExceptionMapped() + { Action thrower = () => throw new Exception("Nope!"); var test = Layout.Create() @@ -76,8 +75,6 @@ public async Task TestExceptionMapped() Assert.AreEqual("5", await response.GetContentAsync()); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/ErrorHandling/StructuredErrorMapperTests.cs b/Testing/Acceptance/Modules/ErrorHandling/StructuredErrorMapperTests.cs index 7227bf43..b9d4a96d 100644 --- a/Testing/Acceptance/Modules/ErrorHandling/StructuredErrorMapperTests.cs +++ b/Testing/Acceptance/Modules/ErrorHandling/StructuredErrorMapperTests.cs @@ -10,16 +10,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.ErrorHandling +namespace GenHTTP.Testing.Acceptance.Modules.ErrorHandling; + +[TestClass] +public sealed class StructuredErrorMapperTests { - [TestClass] - public sealed class StructuredErrorMapperTests + [TestMethod] + public async Task TestNotFound() { - - [TestMethod] - public async Task TestNotFound() - { using var host = TestHost.Run(Inline.Create()); using var response = await host.GetResponseAsync(); @@ -33,9 +32,9 @@ public async Task TestNotFound() Assert.IsNull(model.StackTrace); } - [TestMethod] - public async Task TestGeneralError() - { + [TestMethod] + public async Task TestGeneralError() + { var handler = Inline.Create() .Get(() => DoThrow(new Exception("Oops"))); @@ -52,9 +51,9 @@ public async Task TestGeneralError() Assert.IsNotNull(model.StackTrace); } - [TestMethod] - public async Task TestProviderError() - { + [TestMethod] + public async Task TestProviderError() + { var handler = Inline.Create() .Get(() => DoThrow(new ProviderException(ResponseStatus.Locked, "Locked up!"))); @@ -71,9 +70,9 @@ public async Task TestProviderError() Assert.IsNotNull(model.StackTrace); } - [TestMethod] - public async Task TestNoTraceInProduction() - { + [TestMethod] + public async Task TestNoTraceInProduction() + { var handler = Inline.Create() .Get(() => DoThrow(new Exception("Oops"))); @@ -88,11 +87,9 @@ public async Task TestNoTraceInProduction() Assert.IsNull(model.StackTrace); } - private static void DoThrow(Exception e) - { + private static void DoThrow(Exception e) + { throw e; } - } - } diff --git a/Testing/Acceptance/Modules/Functional/InlineTests.cs b/Testing/Acceptance/Modules/Functional/InlineTests.cs index 3706c523..a63e9934 100644 --- a/Testing/Acceptance/Modules/Functional/InlineTests.cs +++ b/Testing/Acceptance/Modules/Functional/InlineTests.cs @@ -14,24 +14,23 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Functional -{ +namespace GenHTTP.Testing.Acceptance.Modules.Functional; - [TestClass] - public sealed class InlineTests - { +[TestClass] +public sealed class InlineTests +{ - #region Supporting data structures + #region Supporting data structures - public record MyClass(string String, int Int, double Double); + public record MyClass(string String, int Int, double Double); - private enum EnumData { One, Two } + private enum EnumData { One, Two } - #endregion + #endregion - [TestMethod] - public async Task TestGetRoot() - { + [TestMethod] + public async Task TestGetRoot() + { using var host = TestHost.Run(Inline.Create().Get(() => 42)); using var response = await host.GetResponseAsync(); @@ -39,9 +38,9 @@ public async Task TestGetRoot() Assert.AreEqual("42", await response.GetContentAsync()); } - [TestMethod] - public async Task TestGetPath() - { + [TestMethod] + public async Task TestGetPath() + { using var host = TestHost.Run(Inline.Create().Get("/blubb", () => 42)); using var response = await host.GetResponseAsync("/blubb"); @@ -49,9 +48,9 @@ public async Task TestGetPath() Assert.AreEqual("42", await response.GetContentAsync()); } - [TestMethod] - public async Task TestGetQueryParam() - { + [TestMethod] + public async Task TestGetQueryParam() + { using var host = TestHost.Run(Inline.Create().Get((int param) => param + 1)); using var response = await host.GetResponseAsync("/?param=41"); @@ -59,9 +58,9 @@ public async Task TestGetQueryParam() Assert.AreEqual("42", await response.GetContentAsync()); } - [TestMethod] - public async Task TestGetEmptyBooleanQueryParam() - { + [TestMethod] + public async Task TestGetEmptyBooleanQueryParam() + { using var host = TestHost.Run(Inline.Create().Get((bool param) => param)); using var response = await host.GetResponseAsync("/?param="); @@ -69,9 +68,9 @@ public async Task TestGetEmptyBooleanQueryParam() Assert.AreEqual("0", await response.GetContentAsync()); } - [TestMethod] - public async Task TestGetEmptyDoubleQueryParam() - { + [TestMethod] + public async Task TestGetEmptyDoubleQueryParam() + { using var host = TestHost.Run(Inline.Create().Get((double param) => param)); using var response = await host.GetResponseAsync("/?param="); @@ -79,9 +78,9 @@ public async Task TestGetEmptyDoubleQueryParam() Assert.AreEqual("0", await response.GetContentAsync()); } - [TestMethod] - public async Task TestGetEmptyStringQueryParam() - { + [TestMethod] + public async Task TestGetEmptyStringQueryParam() + { using var host = TestHost.Run(Inline.Create().Get((string param) => param)); using var response = await host.GetResponseAsync("/?param="); @@ -89,9 +88,9 @@ public async Task TestGetEmptyStringQueryParam() Assert.AreEqual("", await response.GetContentAsync()); } - [TestMethod] - public async Task TestGetEmptyEnumQueryParam() - { + [TestMethod] + public async Task TestGetEmptyEnumQueryParam() + { using var host = TestHost.Run(Inline.Create().Get((EnumData param) => param)); using var response = await host.GetResponseAsync("/?param="); @@ -99,9 +98,9 @@ public async Task TestGetEmptyEnumQueryParam() Assert.AreEqual("One", await response.GetContentAsync()); } - [TestMethod] - public async Task TestGetPathParam() - { + [TestMethod] + public async Task TestGetPathParam() + { using var host = TestHost.Run(Inline.Create().Get(":param", (int param) => param + 1)); using var response = await host.GetResponseAsync("/41"); @@ -109,9 +108,9 @@ public async Task TestGetPathParam() Assert.AreEqual("42", await response.GetContentAsync()); } - [TestMethod] - public async Task TestNotFound() - { + [TestMethod] + public async Task TestNotFound() + { using var host = TestHost.Run(Inline.Create().Get(() => 42)); using var response = await host.GetResponseAsync("/nope"); @@ -119,9 +118,9 @@ public async Task TestNotFound() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestRaw() - { + [TestMethod] + public async Task TestRaw() + { using var host = TestHost.Run(Inline.Create().Get((IRequest request) => { return request.Respond() @@ -134,9 +133,9 @@ public async Task TestRaw() Assert.AreEqual("42", await response.GetContentAsync()); } - [TestMethod] - public async Task TestStream() - { + [TestMethod] + public async Task TestStream() + { using var host = TestHost.Run(Inline.Create().Get(() => new MemoryStream(Encoding.UTF8.GetBytes("42")))); using var response = await host.GetResponseAsync(); @@ -144,9 +143,9 @@ public async Task TestStream() Assert.AreEqual("42", await response.GetContentAsync()); } - [TestMethod] - public async Task TestJson() - { + [TestMethod] + public async Task TestJson() + { using var host = TestHost.Run(Inline.Create().Get(() => new MyClass("42", 42, 42.0))); using var response = await host.GetResponseAsync(); @@ -154,9 +153,9 @@ public async Task TestJson() Assert.AreEqual("{\"string\":\"42\",\"int\":42,\"double\":42}", await response.GetContentAsync()); } - [TestMethod] - public async Task TestPostJson() - { + [TestMethod] + public async Task TestPostJson() + { using var host = TestHost.Run(Inline.Create().Post((MyClass input) => input)); var request = host.GetRequest(); @@ -170,9 +169,9 @@ public async Task TestPostJson() Assert.AreEqual("{\"string\":\"42\",\"int\":42,\"double\":42}", await response.GetContentAsync()); } - [TestMethod] - public async Task TestAsync() - { + [TestMethod] + public async Task TestAsync() + { using var host = TestHost.Run(Inline.Create().Get(async () => { var stream = new MemoryStream(); @@ -189,9 +188,9 @@ public async Task TestAsync() Assert.AreEqual("42", await response.GetContentAsync()); } - [TestMethod] - public async Task TestHandlerBuilder() - { + [TestMethod] + public async Task TestHandlerBuilder() + { var target = "https://www.google.de/"; using var host = TestHost.Run(Inline.Create().Get(() => Redirect.To(target))); @@ -201,9 +200,9 @@ public async Task TestHandlerBuilder() Assert.AreEqual(target, response.GetHeader("Location")); } - [TestMethod] - public async Task TestHandler() - { + [TestMethod] + public async Task TestHandler() + { var target = "https://www.google.de/"; using var host = TestHost.Run(Inline.Create().Get((IHandler parent) => Redirect.To(target).Build(parent))); @@ -213,6 +212,4 @@ public async Task TestHandler() Assert.AreEqual(target, response.GetHeader("Location")); } - } - } diff --git a/Testing/Acceptance/Modules/IO/ChangeTrackingTests.cs b/Testing/Acceptance/Modules/IO/ChangeTrackingTests.cs index c2dee169..909cb503 100644 --- a/Testing/Acceptance/Modules/IO/ChangeTrackingTests.cs +++ b/Testing/Acceptance/Modules/IO/ChangeTrackingTests.cs @@ -7,16 +7,15 @@ using GenHTTP.Testing.Acceptance.Utilities; -namespace GenHTTP.Testing.Acceptance.Modules.IO +namespace GenHTTP.Testing.Acceptance.Modules.IO; + +[TestClass] +public sealed class ChangeTrackingTests { - [TestClass] - public sealed class ChangeTrackingTests + [TestMethod] + public async Task TestChanges() { - - [TestMethod] - public async Task TestChanges() - { var file = Path.GetTempFileName(); try @@ -42,15 +41,15 @@ public async Task TestChanges() } } - [TestMethod] - public async Task TestBuildWithTracking() - { + [TestMethod] + public async Task TestBuildWithTracking() + { Assert.IsTrue(await Resource.FromAssembly("File.txt").BuildWithTracking().HasChanged()); } - [TestMethod] - public void TestMetaInformation() - { + [TestMethod] + public void TestMetaInformation() + { var resource = Resource.FromAssembly("File.txt").Build(); var tracked = resource.Track(); @@ -61,6 +60,4 @@ public void TestMetaInformation() Assert.AreEqual(resource.ContentType, tracked.ContentType); } - } - } diff --git a/Testing/Acceptance/Modules/IO/ContentTests.cs b/Testing/Acceptance/Modules/IO/ContentTests.cs index 68739dfd..8dca7b3a 100644 --- a/Testing/Acceptance/Modules/IO/ContentTests.cs +++ b/Testing/Acceptance/Modules/IO/ContentTests.cs @@ -5,16 +5,15 @@ using GenHTTP.Modules.IO; -namespace GenHTTP.Testing.Acceptance.Modules.IO +namespace GenHTTP.Testing.Acceptance.Modules.IO; + +[TestClass] +public sealed class ContentTests { - [TestClass] - public sealed class ContentTests + [TestMethod] + public async Task TestContent() { - - [TestMethod] - public async Task TestContent() - { using var runner = TestHost.Run(Content.From(Resource.FromString("Hello World!"))); using var response = await runner.GetResponseAsync(); @@ -23,9 +22,9 @@ public async Task TestContent() Assert.AreEqual("Hello World!", await response.GetContentAsync()); } - [TestMethod] - public async Task TestContentIgnoresRouting() - { + [TestMethod] + public async Task TestContentIgnoresRouting() + { using var runner = TestHost.Run(Content.From(Resource.FromString("Hello World!"))); using var response = await runner.GetResponseAsync("/some/path"); @@ -33,6 +32,4 @@ public async Task TestContentIgnoresRouting() await response.AssertStatusAsync(HttpStatusCode.OK); } - } - } diff --git a/Testing/Acceptance/Modules/IO/DownloadTests.cs b/Testing/Acceptance/Modules/IO/DownloadTests.cs index a3221887..382a681c 100644 --- a/Testing/Acceptance/Modules/IO/DownloadTests.cs +++ b/Testing/Acceptance/Modules/IO/DownloadTests.cs @@ -8,16 +8,15 @@ using GenHTTP.Modules.IO; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Modules.IO +namespace GenHTTP.Testing.Acceptance.Modules.IO; + +[TestClass] +public sealed class DownloadTests { - [TestClass] - public sealed class DownloadTests + [TestMethod] + public async Task TestDownload() { - - [TestMethod] - public async Task TestDownload() - { using var runner = TestHost.Run(Download.From(Resource.FromAssembly("File.txt"))); using var response = await runner.GetResponseAsync(); @@ -28,9 +27,9 @@ public async Task TestDownload() Assert.AreEqual("text/plain", response.GetContentHeader("Content-Type")); } - [TestMethod] - public async Task TestDownloadDoesNotAcceptRouting() - { + [TestMethod] + public async Task TestDownloadDoesNotAcceptRouting() + { var layout = Layout.Create() .Add("file.txt", Download.From(Resource.FromAssembly("File.txt"))); @@ -41,9 +40,9 @@ public async Task TestDownloadDoesNotAcceptRouting() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task DownloadsCannotBeModified() - { + [TestMethod] + public async Task DownloadsCannotBeModified() + { var download = Download.From(Resource.FromAssembly("File.txt")); using var runner = TestHost.Run(download); @@ -58,9 +57,9 @@ public async Task DownloadsCannotBeModified() await response.AssertStatusAsync(HttpStatusCode.MethodNotAllowed); } - [TestMethod] - public async Task TestFileName() - { + [TestMethod] + public async Task TestFileName() + { var download = Download.From(Resource.FromAssembly("File.txt")) .FileName("myfile.txt"); @@ -71,9 +70,9 @@ public async Task TestFileName() Assert.AreEqual("attachment; filename=\"myfile.txt\"", response.GetContentHeader("Content-Disposition")); } - [TestMethod] - public async Task TestNoFileName() - { + [TestMethod] + public async Task TestNoFileName() + { var download = Download.From(Resource.FromAssembly("File.txt")); using var runner = TestHost.Run(download); @@ -82,10 +81,10 @@ public async Task TestNoFileName() Assert.AreEqual("attachment", response.GetContentHeader("Content-Disposition")); } - - [TestMethod] - public async Task TestFileNameFromResource() - { + + [TestMethod] + public async Task TestFileNameFromResource() + { var download = Download.From(Resource.FromAssembly("File.txt").Name("myfile.txt")); using var runner = TestHost.Run(download); @@ -95,6 +94,4 @@ public async Task TestFileNameFromResource() Assert.AreEqual("attachment; filename=\"myfile.txt\"", response.GetContentHeader("Content-Disposition")); } - } - } diff --git a/Testing/Acceptance/Modules/IO/RangeTests.cs b/Testing/Acceptance/Modules/IO/RangeTests.cs index 01a05805..a32fedf9 100644 --- a/Testing/Acceptance/Modules/IO/RangeTests.cs +++ b/Testing/Acceptance/Modules/IO/RangeTests.cs @@ -6,26 +6,25 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.IO +namespace GenHTTP.Testing.Acceptance.Modules.IO; + +[TestClass] +public class RangeTests { + private const string CONTENT = "0123456789"; - [TestClass] - public class RangeTests + [TestMethod] + public async Task TestRangesAreOptional() { - private const string CONTENT = "0123456789"; - - [TestMethod] - public async Task TestRangesAreOptional() - { using var response = await GetResponse(null); await response.AssertStatusAsync(HttpStatusCode.OK); Assert.AreEqual(CONTENT, await response.GetContentAsync()); } - [TestMethod] - public async Task TestFullRangeIsSatisfied() - { + [TestMethod] + public async Task TestFullRangeIsSatisfied() + { using var response = await GetResponse("bytes=1-8"); await response.AssertStatusAsync(HttpStatusCode.PartialContent); @@ -33,9 +32,9 @@ public async Task TestFullRangeIsSatisfied() Assert.AreEqual("bytes 1-8/10", response.GetContentHeader("Content-Range")); } - [TestMethod] - public async Task TestRangeFromStartIsSatisfied() - { + [TestMethod] + public async Task TestRangeFromStartIsSatisfied() + { using var response = await GetResponse("bytes=4-"); await response.AssertStatusAsync(HttpStatusCode.PartialContent); @@ -43,9 +42,9 @@ public async Task TestRangeFromStartIsSatisfied() Assert.AreEqual("bytes 4-9/10", response.GetContentHeader("Content-Range")); } - [TestMethod] - public async Task TestRangeFromEndIsSatisfied() - { + [TestMethod] + public async Task TestRangeFromEndIsSatisfied() + { using var response = await GetResponse("bytes=-4"); await response.AssertStatusAsync(HttpStatusCode.PartialContent); @@ -53,9 +52,9 @@ public async Task TestRangeFromEndIsSatisfied() Assert.AreEqual("bytes 6-9/10", response.GetContentHeader("Content-Range")); } - [TestMethod] - public async Task TestSingleRangeIsSatisfied() - { + [TestMethod] + public async Task TestSingleRangeIsSatisfied() + { using var response = await GetResponse("bytes=1-1"); await response.AssertStatusAsync(HttpStatusCode.PartialContent); @@ -63,54 +62,54 @@ public async Task TestSingleRangeIsSatisfied() Assert.AreEqual("bytes 1-1/10", response.GetContentHeader("Content-Range")); } - [TestMethod] - public async Task TestFullRangeNotSatisfied() - { + [TestMethod] + public async Task TestFullRangeNotSatisfied() + { using var response = await GetResponse("bytes=9-13"); await response.AssertStatusAsync(HttpStatusCode.RequestedRangeNotSatisfiable); Assert.AreEqual("bytes */10", response.GetContentHeader("Content-Range")); } - [TestMethod] - public async Task TestRangeFromStartNotSatisfied() - { + [TestMethod] + public async Task TestRangeFromStartNotSatisfied() + { using var response = await GetResponse("bytes=12-"); await response.AssertStatusAsync(HttpStatusCode.RequestedRangeNotSatisfiable); Assert.AreEqual("bytes */10", response.GetContentHeader("Content-Range")); } - [TestMethod] - public async Task TestRangeFromEndNotSatisfied() - { + [TestMethod] + public async Task TestRangeFromEndNotSatisfied() + { using var response = await GetResponse("bytes=-12"); await response.AssertStatusAsync(HttpStatusCode.RequestedRangeNotSatisfiable); Assert.AreEqual("bytes */10", response.GetContentHeader("Content-Range")); } - [TestMethod] - public async Task TestMultipleRangesNotSatisfied() - { + [TestMethod] + public async Task TestMultipleRangesNotSatisfied() + { using var response = await GetResponse("bytes=1-2,3-4"); await response.AssertStatusAsync(HttpStatusCode.RequestedRangeNotSatisfiable); Assert.AreEqual("bytes */10", response.GetContentHeader("Content-Range")); } - [TestMethod] - public async Task TestOneBasedIndexDoesNotWork() - { + [TestMethod] + public async Task TestOneBasedIndexDoesNotWork() + { using var response = await GetResponse("bytes=1-10"); await response.AssertStatusAsync(HttpStatusCode.RequestedRangeNotSatisfiable); Assert.AreEqual("bytes */10", response.GetContentHeader("Content-Range")); } - [TestMethod] - public async Task TestHeadRequest() - { + [TestMethod] + public async Task TestHeadRequest() + { using var response = await GetResponse("bytes=1-8", HttpMethod.Head); await response.AssertStatusAsync(HttpStatusCode.PartialContent); @@ -121,27 +120,27 @@ public async Task TestHeadRequest() Assert.AreEqual("bytes", response.GetHeader("Accept-Ranges")); } - [TestMethod] - public async Task TestRangesIgnoredOnPostRequests() - { + [TestMethod] + public async Task TestRangesIgnoredOnPostRequests() + { using var response = await GetResponse("bytes=1-8", HttpMethod.Post); await response.AssertStatusAsync(HttpStatusCode.OK); Assert.AreEqual(CONTENT, await response.GetContentAsync()); } - [TestMethod] - public async Task TestRangesAreTaggedDifferently() - { + [TestMethod] + public async Task TestRangesAreTaggedDifferently() + { using var withRange = await GetResponse("bytes=1-8"); using var withoutRange = await GetResponse(null); Assert.AreNotEqual(withRange.GetHeader("ETag"), withoutRange.GetHeader("ETag")); } - [TestMethod] - public async Task TestAddSupportForSingleFile() - { + [TestMethod] + public async Task TestAddSupportForSingleFile() + { var download = Download.From(Resource.FromString("Hello World!")) .AddRangeSupport(); @@ -152,8 +151,8 @@ public async Task TestAddSupportForSingleFile() Assert.AreEqual("bytes", response.GetHeader("Accept-Ranges")); } - private static async Task GetResponse(string? requestedRange, HttpMethod? method = null) - { + private static async Task GetResponse(string? requestedRange, HttpMethod? method = null) + { using var runner = GetRunner(); var request = runner.GetRequest(method: method ?? HttpMethod.Get); @@ -166,8 +165,8 @@ private static async Task GetResponse(string? requestedRang return await runner.GetResponseAsync(request); } - private static TestHost GetRunner() - { + private static TestHost GetRunner() + { var content = Content.From(Resource.FromString(CONTENT)); content.AddRangeSupport(); @@ -175,6 +174,4 @@ private static TestHost GetRunner() return TestHost.Run(content); } - } - } diff --git a/Testing/Acceptance/Modules/IO/RangedStreamTests.cs b/Testing/Acceptance/Modules/IO/RangedStreamTests.cs index 0042f50a..30163ef1 100644 --- a/Testing/Acceptance/Modules/IO/RangedStreamTests.cs +++ b/Testing/Acceptance/Modules/IO/RangedStreamTests.cs @@ -6,58 +6,57 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.IO +namespace GenHTTP.Testing.Acceptance.Modules.IO; + +[TestClass] +public class RangedStreamTests { - [TestClass] - public class RangedStreamTests + [TestMethod] + public void TestFullRange() { - - [TestMethod] - public void TestFullRange() - { Assert.AreEqual("0123456789", GetRange(0, 9, 0, 10)); } - [TestMethod] - public void TestNotAllWritten() - { + [TestMethod] + public void TestNotAllWritten() + { Assert.AreEqual("23", GetRange(0, 9, 2, 2)); } - [TestMethod] - public void TestRangeExtracted() - { + [TestMethod] + public void TestRangeExtracted() + { Assert.AreEqual("3456", GetRange(3, 6, 0, 10)); } - [TestMethod] - public void TestNothingToWrite() - { + [TestMethod] + public void TestNothingToWrite() + { Assert.AreEqual("", GetRange(12, 14, 0, 10)); } - [TestMethod] - public void TestEndOfLargeFile() - { + [TestMethod] + public void TestEndOfLargeFile() + { Assert.AreEqual("12345", GetRange(10_001, 10_005, 0, 10, position: 10_000)); } - [TestMethod] - public void TestSomewhereInLargeFile() - { + [TestMethod] + public void TestSomewhereInLargeFile() + { Assert.AreEqual("0123456789", GetRange(0, 10_000, 0, 10, position: 5000)); } - [TestMethod] - public void TestOutOfLargeFile() - { + [TestMethod] + public void TestOutOfLargeFile() + { Assert.AreEqual("", GetRange(0, 10_000, 0, 10, position: 15_000)); } - [TestMethod] - public void TestBasics() - { + [TestMethod] + public void TestBasics() + { using var stream = new RangedStream(new MemoryStream(), 0, 10); Assert.AreEqual(0, stream.Position); @@ -75,19 +74,17 @@ public void TestBasics() Assert.ThrowsException(() => stream.SetLength(0)); } - private static string GetRange(ulong start, ulong end, int offset, int count, int position = 0) - { + private static string GetRange(ulong start, ulong end, int offset, int count, int position = 0) + { using var target = new MemoryStream(); using var stream = new RangedStream(target, start, end); stream.Position = position; - + stream.Write(Encoding.ASCII.GetBytes("0123456789"), offset, count); return Encoding.ASCII.GetString(target.ToArray()); } - } - } diff --git a/Testing/Acceptance/Modules/IO/ResourceTest.cs b/Testing/Acceptance/Modules/IO/ResourceTest.cs index bfffd6c2..3e766409 100644 --- a/Testing/Acceptance/Modules/IO/ResourceTest.cs +++ b/Testing/Acceptance/Modules/IO/ResourceTest.cs @@ -12,16 +12,15 @@ using GenHTTP.Testing.Acceptance.Utilities; -namespace GenHTTP.Testing.Acceptance.Modules.IO +namespace GenHTTP.Testing.Acceptance.Modules.IO; + +[TestClass] +public sealed class ResourceTest { - [TestClass] - public sealed class ResourceTest + [TestMethod] + public async Task TestStringResource() { - - [TestMethod] - public async Task TestStringResource() - { var resource = Resource.FromString("Hello World") .Build(); @@ -37,9 +36,9 @@ public async Task TestStringResource() Assert.IsNull(resource.Name); } - [TestMethod] - public async Task TestFileResource() - { + [TestMethod] + public async Task TestFileResource() + { var file = Path.GetTempFileName(); try @@ -64,9 +63,9 @@ public async Task TestFileResource() } } - [TestMethod] - public async Task TestAssemblyResource() - { + [TestMethod] + public async Task TestAssemblyResource() + { var resource = Resource.FromAssembly("File.txt") .Build(); @@ -81,9 +80,9 @@ public async Task TestAssemblyResource() Assert.IsNotNull(resource.Modified); } - [TestMethod] - public async Task TestAssemblyResourceRouting() - { + [TestMethod] + public async Task TestAssemblyResourceRouting() + { var layout = Layout.Create() .Add("1", Content.From(Resource.FromAssembly("File.txt"))) .Add("2", Content.From(Resource.FromAssembly("OtherFile.txt"))); @@ -97,21 +96,21 @@ public async Task TestAssemblyResourceRouting() Assert.AreEqual("This is other text!", await f2.GetContentAsync()); } - [TestMethod] - public void TestStringMetaData() - { + [TestMethod] + public void TestStringMetaData() + { TestMetaData(Resource.FromString("Hello World")); } - [TestMethod] - public void TestEmbeddedMetaData() - { + [TestMethod] + public void TestEmbeddedMetaData() + { TestMetaData(Resource.FromAssembly("File.txt")); } - [TestMethod] - public void TestFileMetaData() - { + [TestMethod] + public void TestFileMetaData() + { var file = Path.GetTempFileName(); FileUtil.WriteText(file, "blubb"); @@ -126,8 +125,8 @@ public void TestFileMetaData() } } - private static void TestMetaData(IResourceBuilder builder, bool modified = true) where T : IResourceBuilder - { + private static void TestMetaData(IResourceBuilder builder, bool modified = true) where T : IResourceBuilder + { var now = DateTime.UtcNow; builder.Name("MyFile.txt") @@ -150,12 +149,10 @@ private static void TestMetaData(IResourceBuilder builder, bool modified = } } - [TestMethod] - public void TestNonExistentFile() - { + [TestMethod] + public void TestNonExistentFile() + { Assert.ThrowsException(() => Resource.FromFile("blubb.txt").Build()); } - } - } diff --git a/Testing/Acceptance/Modules/IO/ResourceTreeTests.cs b/Testing/Acceptance/Modules/IO/ResourceTreeTests.cs index 453dfebc..a5010d74 100644 --- a/Testing/Acceptance/Modules/IO/ResourceTreeTests.cs +++ b/Testing/Acceptance/Modules/IO/ResourceTreeTests.cs @@ -4,16 +4,15 @@ using GenHTTP.Modules.IO; -namespace GenHTTP.Testing.Acceptance.Modules.IO +namespace GenHTTP.Testing.Acceptance.Modules.IO; + +[TestClass] +public sealed class ResourceTreeTests { - [TestClass] - public sealed class ResourceTreeTests + [TestMethod] + public async Task TestAssembly() { - - [TestMethod] - public async Task TestAssembly() - { var tree = ResourceTree.FromAssembly("Resources").Build(); Assert.IsNotNull(await tree.TryGetNodeAsync("Subdirectory")); @@ -25,6 +24,4 @@ public async Task TestAssembly() Assert.AreEqual(5, (await tree.GetResources()).Count); } - } - } diff --git a/Testing/Acceptance/Modules/IO/ResourcesTests.cs b/Testing/Acceptance/Modules/IO/ResourcesTests.cs index e364f633..f476cf4e 100644 --- a/Testing/Acceptance/Modules/IO/ResourcesTests.cs +++ b/Testing/Acceptance/Modules/IO/ResourcesTests.cs @@ -6,16 +6,15 @@ using GenHTTP.Modules.IO; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Modules.IO +namespace GenHTTP.Testing.Acceptance.Modules.IO; + +[TestClass] +public sealed class ResourcesTests { - [TestClass] - public sealed class ResourcesTests + [TestMethod] + public async Task TestFileDownload() { - - [TestMethod] - public async Task TestFileDownload() - { using var runner = TestHost.Run(Resources.From(ResourceTree.FromAssembly())); using var response = await runner.GetResponseAsync("/Resources/File.txt"); @@ -24,9 +23,9 @@ public async Task TestFileDownload() Assert.AreEqual("This is text!", await response.GetContentAsync()); } - [TestMethod] - public async Task TestSubdirectoryFileDownload() - { + [TestMethod] + public async Task TestSubdirectoryFileDownload() + { using var runner = TestHost.Run(Resources.From(ResourceTree.FromAssembly())); using var response = await runner.GetResponseAsync("/Resources/Subdirectory/AnotherFile.txt"); @@ -35,9 +34,9 @@ public async Task TestSubdirectoryFileDownload() Assert.AreEqual("This is another text!", await response.GetContentAsync()); } - [TestMethod] - public async Task TestNoFileDownload() - { + [TestMethod] + public async Task TestNoFileDownload() + { using var runner = TestHost.Run(Resources.From(ResourceTree.FromAssembly())); using var response = await runner.GetResponseAsync("/Resources/nah.txt"); @@ -45,9 +44,9 @@ public async Task TestNoFileDownload() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestNoSubdirectoryFileDownload() - { + [TestMethod] + public async Task TestNoSubdirectoryFileDownload() + { using var runner = TestHost.Run(Resources.From(ResourceTree.FromAssembly())); using var response = await runner.GetResponseAsync("/Resources/nah/File.txt"); @@ -55,9 +54,9 @@ public async Task TestNoSubdirectoryFileDownload() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestRootDownload() - { + [TestMethod] + public async Task TestRootDownload() + { using var runner = TestHost.Run(Resources.From(ResourceTree.FromAssembly("Resources"))); using var response = await runner.GetResponseAsync("/File.txt"); @@ -66,9 +65,9 @@ public async Task TestRootDownload() Assert.AreEqual("This is text!", await response.GetContentAsync()); } - [TestMethod] - public async Task TestDirectory() - { + [TestMethod] + public async Task TestDirectory() + { using var runner = TestHost.Run(Resources.From(ResourceTree.FromAssembly())); using var response = await runner.GetResponseAsync("/Resources/nah/"); @@ -76,9 +75,9 @@ public async Task TestDirectory() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestNonExistingDirectory() - { + [TestMethod] + public async Task TestNonExistingDirectory() + { using var runner = TestHost.Run(Resources.From(ResourceTree.FromAssembly())); using var response = await runner.GetResponseAsync("/Resources/nah/"); @@ -86,6 +85,4 @@ public async Task TestNonExistingDirectory() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - } - } diff --git a/Testing/Acceptance/Modules/IO/VirtualTreeTests.cs b/Testing/Acceptance/Modules/IO/VirtualTreeTests.cs index 657f348e..c3a09470 100644 --- a/Testing/Acceptance/Modules/IO/VirtualTreeTests.cs +++ b/Testing/Acceptance/Modules/IO/VirtualTreeTests.cs @@ -9,16 +9,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.IO +namespace GenHTTP.Testing.Acceptance.Modules.IO; + +[TestClass] +public sealed class VirtualTreeTests { - [TestClass] - public sealed class VirtualTreeTests + [TestMethod] + public async Task TestNestedTree() { - - [TestMethod] - public async Task TestNestedTree() - { var tree = ResourceTree.FromAssembly("Resources"); var virt = VirtualTree.Create() @@ -35,9 +34,9 @@ public async Task TestNestedTree() Assert.IsNotNull(virt.Modified); } - [TestMethod] - public async Task TestResource() - { + [TestMethod] + public async Task TestResource() + { var virt = VirtualTree.Create() .Add("res.txt", Resource.FromString("Blubb")) .Build(); @@ -48,9 +47,9 @@ public async Task TestResource() Assert.IsNotNull(file); } - [TestMethod] - public async Task TestUsage() - { + [TestMethod] + public async Task TestUsage() + { var tree = ResourceTree.FromAssembly("Resources"); var virt = VirtualTree.Create() @@ -66,8 +65,6 @@ public async Task TestUsage() await response.AssertStatusAsync(HttpStatusCode.OK); } - private static RoutingTarget GetTarget(string path) => new(new PathBuilder(path).Build()); - - } + private static RoutingTarget GetTarget(string path) => new(new PathBuilder(path).Build()); } diff --git a/Testing/Acceptance/Modules/LayoutTests.cs b/Testing/Acceptance/Modules/LayoutTests.cs index aa9b026a..252cf86c 100644 --- a/Testing/Acceptance/Modules/LayoutTests.cs +++ b/Testing/Acceptance/Modules/LayoutTests.cs @@ -1,89 +1,84 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; - using System.Net; using System.Threading.Tasks; - using GenHTTP.Modules.IO; using GenHTTP.Modules.Layouting; using System; -namespace GenHTTP.Testing.Acceptance.Modules +namespace GenHTTP.Testing.Acceptance.Modules; + +[TestClass] +public sealed class LayoutTests { - [TestClass] - public sealed class LayoutTests + /// + /// As a developer I can define the default route to be devlivered. + /// + [TestMethod] + public async Task TestGetIndex() { + var layout = Layout.Create() + .Index(Content.From(Resource.FromString("Hello World!"))); - /// - /// As a developer I can define the default route to be devlivered. - /// - [TestMethod] - public async Task TestGetIndex() - { - var layout = Layout.Create() - .Index(Content.From(Resource.FromString("Hello World!"))); + using var runner = TestHost.Run(layout); - using var runner = TestHost.Run(layout); + using var response = await runner.GetResponseAsync(); - using var response = await runner.GetResponseAsync(); - - await response.AssertStatusAsync(HttpStatusCode.OK); - Assert.AreEqual("Hello World!", await response.GetContentAsync()); + await response.AssertStatusAsync(HttpStatusCode.OK); + Assert.AreEqual("Hello World!", await response.GetContentAsync()); - using var notFound = await runner.GetResponseAsync("/notfound"); + using var notFound = await runner.GetResponseAsync("/notfound"); - await notFound.AssertStatusAsync(HttpStatusCode.NotFound); - } + await notFound.AssertStatusAsync(HttpStatusCode.NotFound); + } - /// - /// As a developer I can set a default handler to be used for requests. - /// - [TestMethod] - public async Task TestDefaultContent() - { - var layout = Layout.Create().Add(Content.From(Resource.FromString("Hello World!"))); + /// + /// As a developer I can set a default handler to be used for requests. + /// + [TestMethod] + public async Task TestDefaultContent() + { + var layout = Layout.Create().Add(Content.From(Resource.FromString("Hello World!"))); - using var runner = TestHost.Run(layout); + using var runner = TestHost.Run(layout); - foreach (var path in new string[] { "/something", "/" }) - { - using var response = await runner.GetResponseAsync(path); + foreach (var path in new string[] { "/something", "/" }) + { + using var response = await runner.GetResponseAsync(path); - await response.AssertStatusAsync(HttpStatusCode.OK); - Assert.AreEqual("Hello World!", await response.GetContentAsync()); - } + await response.AssertStatusAsync(HttpStatusCode.OK); + Assert.AreEqual("Hello World!", await response.GetContentAsync()); } + } - /// - /// As the developer of a web application, I don't want my application - /// to produce duplicate content for missing trailing slashes. - /// - [TestMethod] - public async Task TestRedirect() - { - var layout = Layout.Create() - .Add("section", Layout.Create().Index(Content.From(Resource.FromString("Hello World!")))); - - using var runner = TestHost.Run(layout); + /// + /// As the developer of a web application, I don't want my application + /// to produce duplicate content for missing trailing slashes. + /// + [TestMethod] + public async Task TestRedirect() + { + var layout = Layout.Create() + .Add("section", Layout.Create().Index(Content.From(Resource.FromString("Hello World!")))); - using var response = await runner.GetResponseAsync("/section/"); + using var runner = TestHost.Run(layout); - await response.AssertStatusAsync(HttpStatusCode.OK); - Assert.AreEqual("Hello World!", await response.GetContentAsync()); + using var response = await runner.GetResponseAsync("/section/"); - using var redirected = await runner.GetResponseAsync("/section"); + await response.AssertStatusAsync(HttpStatusCode.OK); + Assert.AreEqual("Hello World!", await response.GetContentAsync()); - await redirected.AssertStatusAsync(HttpStatusCode.MovedPermanently); - AssertX.EndsWith("/section/", redirected.GetHeader("Location")!); - } + using var redirected = await runner.GetResponseAsync("/section"); - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void TestIllegalPathCharacters() - { - Layout.Create().Add("some/path", Content.From(Resource.FromString("Hello World"))); - } + await redirected.AssertStatusAsync(HttpStatusCode.MovedPermanently); + AssertX.EndsWith("/section/", redirected.GetHeader("Location")!); + } + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestIllegalPathCharacters() + { + Layout.Create().Add("some/path", Content.From(Resource.FromString("Hello World"))); } } diff --git a/Testing/Acceptance/Modules/ListingTests.cs b/Testing/Acceptance/Modules/ListingTests.cs index 92571d68..661e3fc8 100644 --- a/Testing/Acceptance/Modules/ListingTests.cs +++ b/Testing/Acceptance/Modules/ListingTests.cs @@ -1,28 +1,24 @@ using System.IO; using System.Net; using System.Threading.Tasks; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - using GenHTTP.Modules.DirectoryBrowsing; using GenHTTP.Modules.IO; - using GenHTTP.Testing.Acceptance.Utilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GenHTTP.Testing.Acceptance.Modules; -namespace GenHTTP.Testing.Acceptance.Providers +[TestClass] +public sealed class ListingTests { - [TestClass] - public sealed class ListingTests + /// + /// As an user of a web application, I can view the folders and files available + /// on root level of a listed directory. + /// + [TestMethod] + public async Task TestGetMainListing() { - - /// - /// As an user of a web application, I can view the folders and files available - /// on root level of a listed directory. - /// - [TestMethod] - public async Task TestGetMainListing() - { using var runner = GetEnvironment(); using var response = await runner.GetResponseAsync("/"); @@ -37,13 +33,13 @@ public async Task TestGetMainListing() AssertX.DoesNotContain("..", content); } - /// - /// As an user of a web application, I can view the folders and files available - /// within a subdirectory of a listed directory. - /// - [TestMethod] - public async Task TestGetSubdirectory() - { + /// + /// As an user of a web application, I can view the folders and files available + /// within a subdirectory of a listed directory. + /// + [TestMethod] + public async Task TestGetSubdirectory() + { using var runner = GetEnvironment(); using var response = await runner.GetResponseAsync("/Subdirectory/"); @@ -53,13 +49,13 @@ public async Task TestGetSubdirectory() AssertX.Contains("..", content); } - /// - /// As an user of a web application, I can download the files listed by the - /// directory listing feature. - /// - [TestMethod] - public async Task TestDownload() - { + /// + /// As an user of a web application, I can download the files listed by the + /// directory listing feature. + /// + [TestMethod] + public async Task TestDownload() + { using var runner = GetEnvironment(); using var response = await runner.GetResponseAsync("/my.txt"); @@ -67,9 +63,9 @@ public async Task TestDownload() Assert.AreEqual("Hello World!", await response.GetContentAsync()); } - [TestMethod] - public async Task TestNonExistingFolder() - { + [TestMethod] + public async Task TestNonExistingFolder() + { using var runner = GetEnvironment(); using var response = await runner.GetResponseAsync("/idonotexist/"); @@ -77,9 +73,9 @@ public async Task TestNonExistingFolder() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestSameListingSameChecksum() - { + [TestMethod] + public async Task TestSameListingSameChecksum() + { using var runner = GetEnvironment(); using var resp1 = await runner.GetResponseAsync(); @@ -90,8 +86,8 @@ public async Task TestSameListingSameChecksum() Assert.AreEqual(resp1.GetETag(), resp2.GetETag()); } - private static TestHost GetEnvironment() - { + private static TestHost GetEnvironment() + { var tempFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(tempFolder); @@ -107,6 +103,4 @@ private static TestHost GetEnvironment() return TestHost.Run(listing); } - } - } diff --git a/Testing/Acceptance/Modules/LoadBalancerTests.cs b/Testing/Acceptance/Modules/LoadBalancerTests.cs index fbd058a1..c197ac1c 100644 --- a/Testing/Acceptance/Modules/LoadBalancerTests.cs +++ b/Testing/Acceptance/Modules/LoadBalancerTests.cs @@ -1,23 +1,19 @@ using System.Net; using System.Threading.Tasks; - using GenHTTP.Api.Infrastructure; - using GenHTTP.Modules.IO; using GenHTTP.Modules.LoadBalancing; - using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Providers +namespace GenHTTP.Testing.Acceptance.Modules; + +[TestClass] +public sealed class LoadBalancerTests { - [TestClass] - public sealed class LoadBalancerTests + [TestMethod] + public async Task TestProxy() { - - [TestMethod] - public async Task TestProxy() - { using var upstream = TestHost.Run(Content.From(Resource.FromString("Proxy!"))); var loadbalancer = LoadBalancer.Create() @@ -28,12 +24,12 @@ public async Task TestProxy() using var response = await runner.GetResponseAsync(); await response.AssertStatusAsync(HttpStatusCode.OK); - Assert.AreEqual("Proxy!", await response.GetContentAsync()); + Assert.AreEqual("Proxy!", await response.GetContentAsync()); } - [TestMethod] - public async Task TestRedirect() - { + [TestMethod] + public async Task TestRedirect() + { var loadbalancer = LoadBalancer.Create() .Redirect($"http://node"); @@ -45,9 +41,9 @@ public async Task TestRedirect() Assert.AreEqual("http://node/page", response.GetHeader("Location")); } - [TestMethod] - public async Task TestCustomHandler() - { + [TestMethod] + public async Task TestCustomHandler() + { var loadbalancer = LoadBalancer.Create() .Add(Content.From(Resource.FromString("My Content!"))); @@ -59,9 +55,9 @@ public async Task TestCustomHandler() Assert.AreEqual("My Content!", await response.GetContentAsync()); } - [TestMethod] - public async Task TestPriorities() - { + [TestMethod] + public async Task TestPriorities() + { var loadbalancer = LoadBalancer.Create() .Add(Content.From(Resource.FromString("Prio A")), r => Priority.High) .Add(Content.From(Resource.FromString("Prio B")), r => Priority.Low); @@ -73,9 +69,9 @@ public async Task TestPriorities() Assert.AreEqual("Prio A", await response.GetContentAsync()); } - [TestMethod] - public async Task TestMultiplePriorities() - { + [TestMethod] + public async Task TestMultiplePriorities() + { var loadbalancer = LoadBalancer.Create() .Add(Content.From(Resource.FromString("Prio A1")), r => Priority.High) .Add(Content.From(Resource.FromString("Prio A2")), r => Priority.High) @@ -87,10 +83,10 @@ public async Task TestMultiplePriorities() AssertX.StartsWith("Prio A", await response.GetContentAsync()); } - - [TestMethod] - public async Task TestNoNodes() - { + + [TestMethod] + public async Task TestNoNodes() + { using var runner = TestHost.Run(LoadBalancer.Create()); using var response = await runner.GetResponseAsync(); @@ -98,6 +94,4 @@ public async Task TestNoNodes() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - } - } diff --git a/Testing/Acceptance/Modules/ProtobufTests.cs b/Testing/Acceptance/Modules/ProtobufTests.cs index 7576b090..f7850c78 100644 --- a/Testing/Acceptance/Modules/ProtobufTests.cs +++ b/Testing/Acceptance/Modules/ProtobufTests.cs @@ -14,33 +14,33 @@ using ProtoBuf; -namespace GenHTTP.Testing.Acceptance.Modules +namespace GenHTTP.Testing.Acceptance.Modules; + +[TestClass] +public sealed class ProtobufTests { - [TestClass] - public sealed class ProtobufTests - { - #region Supporting structures + #region Supporting structures - [ProtoContract] - public sealed class TestEntity - { - [ProtoMember(1)] - public int ID { get; set; } + [ProtoContract] + public sealed class TestEntity + { + [ProtoMember(1)] + public int ID { get; set; } - [ProtoMember(2)] - public string? Name { get; set; } + [ProtoMember(2)] + public string? Name { get; set; } - [ProtoMember(3)] - public double? Nullable { get; set; } + [ProtoMember(3)] + public double? Nullable { get; set; } - } - public sealed class TestResource - { + } + public sealed class TestResource + { - [ResourceMethod] - public TestEntity? GetEntity() - { + [ResourceMethod] + public TestEntity? GetEntity() + { TestEntity entity = new TestEntity() { @@ -51,21 +51,21 @@ public sealed class TestResource return entity; } - [ResourceMethod(RequestMethod.POST)] - public TestEntity PostEntity(TestEntity entity) - { + [ResourceMethod(RequestMethod.POST)] + public TestEntity PostEntity(TestEntity entity) + { return entity; } - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task TestGetEntityAsProtobuf() - { + [TestMethod] + public async Task TestGetEntityAsProtobuf() + { TestEntity? result = null; await WithResponse(string.Empty, HttpMethod.Get, null, "application/protobuf", "application/protobuf", async r => { @@ -77,9 +77,9 @@ await WithResponse(string.Empty, HttpMethod.Get, null, "application/protobuf", " Assert.AreEqual("test1", result!.Name); } - [TestMethod] - public async Task TestPostEntityAsProtobuf() - { + [TestMethod] + public async Task TestPostEntityAsProtobuf() + { TestEntity entity = new TestEntity() { ID = 2, @@ -108,12 +108,12 @@ await WithResponse(string.Empty, HttpMethod.Post, encodedEntity, "application/pr } - #endregion + #endregion - #region Helpers + #region Helpers - private async Task WithResponse(string uri, HttpMethod method, byte[]? body, string? contentType, string? accept, Func logic) - { + private async Task WithResponse(string uri, HttpMethod method, byte[]? body, string? contentType, string? accept, Func logic) + { using var service = GetService(); var request = service.GetRequest($"/t/{uri}"); @@ -144,8 +144,8 @@ private async Task WithResponse(string uri, HttpMethod method, byte[]? body, str await logic(response); } - private static TestHost GetService() - { + private static TestHost GetService() + { var service = ServiceResource.From() .Serializers(Serialization.Default().AddProtobuf()) .Injectors(Injection.Default()); @@ -153,8 +153,6 @@ private static TestHost GetService() return TestHost.Run(Layout.Create().Add("t", service)); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/RedirectTests.cs b/Testing/Acceptance/Modules/RedirectTests.cs index 6804ba0b..2d931dfc 100644 --- a/Testing/Acceptance/Modules/RedirectTests.cs +++ b/Testing/Acceptance/Modules/RedirectTests.cs @@ -2,22 +2,19 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - using GenHTTP.Modules.Basics; using GenHTTP.Modules.Layouting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GenHTTP.Testing.Acceptance.Modules; -namespace GenHTTP.Testing.Acceptance.Providers +[TestClass] +public sealed class RedirectTests { - [TestClass] - public sealed class RedirectTests + [TestMethod] + public async Task TestTemporary() { - - [TestMethod] - public async Task TestTemporary() - { var redirect = Redirect.To("https://google.de/", true); using var runner = TestHost.Run(redirect); @@ -28,9 +25,9 @@ public async Task TestTemporary() Assert.AreEqual("https://google.de/", response.GetHeader("Location")); } - [TestMethod] - public async Task TestTemporaryPost() - { + [TestMethod] + public async Task TestTemporaryPost() + { var redirect = Redirect.To("https://google.de/", true); using var runner = TestHost.Run(redirect); @@ -44,9 +41,9 @@ public async Task TestTemporaryPost() Assert.AreEqual("https://google.de/", response.GetHeader("Location")); } - [TestMethod] - public async Task TestPermanent() - { + [TestMethod] + public async Task TestPermanent() + { var redirect = Redirect.To("https://google.de/"); using var runner = TestHost.Run(redirect); @@ -57,9 +54,9 @@ public async Task TestPermanent() Assert.AreEqual("https://google.de/", response.GetHeader("Location")); } - [TestMethod] - public async Task TestPermanentPost() - { + [TestMethod] + public async Task TestPermanentPost() + { var redirect = Redirect.To("https://google.de/", false); using var runner = TestHost.Run(redirect); @@ -73,9 +70,9 @@ public async Task TestPermanentPost() Assert.AreEqual("https://google.de/", response.GetHeader("Location")); } - [TestMethod] - public async Task TestAbsoluteRoute() - { + [TestMethod] + public async Task TestAbsoluteRoute() + { var layout = Layout.Create() .Add("redirect", Redirect.To("/me/to/")); @@ -86,6 +83,4 @@ public async Task TestAbsoluteRoute() Assert.AreEqual("/me/to/", new Uri(response.GetHeader("Location")!).AbsolutePath); } - } - } diff --git a/Testing/Acceptance/Modules/Reflection/ParameterTests.cs b/Testing/Acceptance/Modules/Reflection/ParameterTests.cs index 8f20af17..a8f261fa 100644 --- a/Testing/Acceptance/Modules/Reflection/ParameterTests.cs +++ b/Testing/Acceptance/Modules/Reflection/ParameterTests.cs @@ -8,18 +8,17 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Reflection -{ +namespace GenHTTP.Testing.Acceptance.Modules.Reflection; - [TestClass] - public sealed class ParameterTests - { +[TestClass] +public sealed class ParameterTests +{ - #region Tests + #region Tests - [TestMethod] - public async Task TestCanReadSimpleTypesFromBody() - { + [TestMethod] + public async Task TestCanReadSimpleTypesFromBody() + { var inline = Inline.Create() .Post(([FromBody] string body1, [FromBody] string body2) => $"{body1}-{body2}"); @@ -32,9 +31,9 @@ public async Task TestCanReadSimpleTypesFromBody() Assert.AreEqual("1-1", await response.GetContentAsync()); } - [TestMethod] - public async Task TestCanPassEmptyString() - { + [TestMethod] + public async Task TestCanPassEmptyString() + { var inline = Inline.Create() .Post(([FromBody] int number) => number); @@ -47,9 +46,9 @@ public async Task TestCanPassEmptyString() Assert.AreEqual("0", await response.GetContentAsync()); } - [TestMethod] - public async Task TestCanAccessBothBodyAndStream() - { + [TestMethod] + public async Task TestCanAccessBothBodyAndStream() + { var inline = Inline.Create() .Post(([FromBody] int number, Stream body) => { @@ -66,9 +65,9 @@ public async Task TestCanAccessBothBodyAndStream() Assert.AreEqual("1 - 1", await response.GetContentAsync()); } - [TestMethod] - public async Task TestConversionError() - { + [TestMethod] + public async Task TestConversionError() + { var inline = Inline.Create() .Post(([FromBody] int number) => number); @@ -79,8 +78,8 @@ public async Task TestConversionError() await response.AssertStatusAsync(HttpStatusCode.BadRequest); } - private static Task PostAsync(TestHost host, string body) - { + private static Task PostAsync(TestHost host, string body) + { var request = host.GetRequest(); request.Method = HttpMethod.Post; @@ -89,8 +88,6 @@ private static Task PostAsync(TestHost host, string body) return host.GetResponseAsync(request); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/Reflection/ResultTests.cs b/Testing/Acceptance/Modules/Reflection/ResultTests.cs index 93e7fd11..68869f04 100644 --- a/Testing/Acceptance/Modules/Reflection/ResultTests.cs +++ b/Testing/Acceptance/Modules/Reflection/ResultTests.cs @@ -10,24 +10,23 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Reflection -{ +namespace GenHTTP.Testing.Acceptance.Modules.Reflection; - [TestClass] - public sealed class ResultTests - { +[TestClass] +public sealed class ResultTests +{ - #region Supporting data structures + #region Supporting data structures - public record class MyPayload(string Message); + public record class MyPayload(string Message); - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task TestResponseCanBeModified() - { + [TestMethod] + public async Task TestResponseCanBeModified() + { var result = new Result(new("Hello World!")) .Status(ResponseStatus.Accepted) .Status(202, "Accepted Custom") @@ -50,9 +49,9 @@ public async Task TestResponseCanBeModified() Assert.AreEqual("Value", response.GetHeader("X-Custom")); } - [TestMethod] - public async Task TestStreamsCanBeWrapped() - { + [TestMethod] + public async Task TestStreamsCanBeWrapped() + { var stream = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")); var inline = Inline.Create() @@ -67,8 +66,6 @@ public async Task TestStreamsCanBeWrapped() Assert.AreEqual("Hello World!", await response.GetContentAsync()); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/ReverseProxyTests.cs b/Testing/Acceptance/Modules/ReverseProxyTests.cs index 2b50ed39..5a60a40b 100644 --- a/Testing/Acceptance/Modules/ReverseProxyTests.cs +++ b/Testing/Acceptance/Modules/ReverseProxyTests.cs @@ -1,44 +1,39 @@ using System; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; -using System.Linq; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; - using GenHTTP.Modules.IO; -using GenHTTP.Modules.ReverseProxy; using GenHTTP.Modules.Layouting; - +using GenHTTP.Modules.ReverseProxy; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Cookie = GenHTTP.Api.Protocol.Cookie; -namespace GenHTTP.Testing.Acceptance.Providers +namespace GenHTTP.Testing.Acceptance.Modules; + +[TestClass] +public sealed class ReverseProxyTests { - [TestClass] - public sealed class ReverseProxyTests + #region Supporting data structures + + private class TestSetup : IDisposable { + private readonly TestHost _Target; - #region Supporting data structures + public TestHost Runner { get; } - private class TestSetup : IDisposable + private TestSetup(TestHost source, TestHost target) { - private readonly TestHost _Target; - - public TestHost Runner { get; } - - private TestSetup(TestHost source, TestHost target) - { Runner = source; _Target = target; } - public static TestSetup Create(Func response) - { + public static TestSetup Create(Func response) + { // server hosting the actual web app var testServer = new TestHost(Layout.Create(), defaults: false); @@ -59,12 +54,12 @@ public static TestSetup Create(Func response) return new TestSetup(runner, testServer); } - #region IDisposable Support + #region IDisposable Support - private bool disposedValue = false; + private bool disposedValue = false; - protected virtual void Dispose(bool disposing) - { + protected virtual void Dispose(bool disposing) + { if (!disposedValue) { if (disposing) @@ -77,50 +72,50 @@ protected virtual void Dispose(bool disposing) } } - public void Dispose() - { + public void Dispose() + { Dispose(true); } - #endregion + #endregion - } + } - private class ProxiedRouter : IHandler - { - private readonly Func _Response; + private class ProxiedRouter : IHandler + { + private readonly Func _Response; - public ProxiedRouter(Func response) - { + public ProxiedRouter(Func response) + { _Response = response; } - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public IHandler Parent => throw new NotImplementedException(); + public IHandler Parent => throw new NotImplementedException(); - public ValueTask HandleAsync(IRequest request) - { + public ValueTask HandleAsync(IRequest request) + { return new ProxiedProvider(_Response).HandleAsync(request); } - } + } - private class ProxiedProvider : IHandler - { - private readonly Func _Response; + private class ProxiedProvider : IHandler + { + private readonly Func _Response; - public ProxiedProvider(Func response) - { + public ProxiedProvider(Func response) + { _Response = response; } - public IHandler Parent => throw new NotImplementedException(); - - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public IHandler Parent => throw new NotImplementedException(); - public ValueTask HandleAsync(IRequest request) - { + public ValueTask PrepareAsync() => ValueTask.CompletedTask; + + public ValueTask HandleAsync(IRequest request) + { Assert.AreNotEqual(request.Client, request.LocalClient); Assert.IsTrue(request.Forwardings.Count > 0); @@ -136,13 +131,13 @@ public ProxiedProvider(Func response) .BuildTask(); } - } + } - #endregion + #endregion - [TestMethod] - public async Task TestBasics() - { + [TestMethod] + public async Task TestBasics() + { using var setup = TestSetup.Create((r) => { return r.Respond().Content("Hello World!").Build(); @@ -154,9 +149,9 @@ public async Task TestBasics() Assert.AreEqual("Hello World!", await response.GetContentAsync()); } - [TestMethod] - public async Task TestRedirection() - { + [TestMethod] + public async Task TestRedirection() + { using var setup = TestSetup.Create((r) => { return r.Respond().Header("Location", $"http://localhost:{r.EndPoint.Port}/target").Status(ResponseStatus.TemporaryRedirect).Build(); @@ -169,9 +164,9 @@ public async Task TestRedirection() Assert.AreEqual($"http://localhost:{runner.Port}/target", redirected.GetHeader("Location")); } - [TestMethod] - public async Task TestHead() - { + [TestMethod] + public async Task TestHead() + { using var setup = TestSetup.Create((r) => { return r.Respond().Content("Hello World!").Build(); @@ -187,9 +182,9 @@ public async Task TestHead() await headed.AssertStatusAsync(HttpStatusCode.OK); } - [TestMethod] - public async Task TestCookies() - { + [TestMethod] + public async Task TestCookies() + { using var setup = TestSetup.Create((r) => { Assert.AreEqual("World", r.Cookies["Hello"].Value); @@ -220,9 +215,9 @@ public async Task TestCookies() Assert.AreEqual("2", returned["Two"]!.Value); } - [TestMethod] - public async Task TestHeaders() - { + [TestMethod] + public async Task TestHeaders() + { var now = DateTime.UtcNow; using var setup = TestSetup.Create((r) => @@ -249,9 +244,9 @@ public async Task TestHeaders() Assert.AreEqual(now.ToString("r"), response.GetContentHeader("Last-Modified")); } - [TestMethod] - public async Task TestPost() - { + [TestMethod] + public async Task TestPost() + { using var setup = TestSetup.Create((r) => { using var reader = new StreamReader(r.Content!); @@ -271,9 +266,9 @@ public async Task TestPost() Assert.AreEqual("Input", await response.GetContentAsync()); } - [TestMethod] - public async Task TestPathing() - { + [TestMethod] + public async Task TestPathing() + { using var setup = TestSetup.Create((r) => { return r.Respond().Content(r.Target.Path.ToString()).Build(); @@ -291,9 +286,9 @@ public async Task TestPathing() Assert.AreEqual("/login", await r3.GetContentAsync()); } - [TestMethod] - public async Task TestQuery() - { + [TestMethod] + public async Task TestQuery() + { using var setup = TestSetup.Create((r) => { var result = string.Join('|', r.Query.Select(kv => $"{kv.Key}={kv.Value}")); @@ -312,9 +307,9 @@ public async Task TestQuery() Assert.AreEqual("", await r1.GetContentAsync()); } - [TestMethod] - public async Task TestQuerySpecialChars() - { + [TestMethod] + public async Task TestQuerySpecialChars() + { using var setup = TestSetup.Create((r) => { var result = string.Join('|', r.Query.Select(kv => $"{kv.Key}={kv.Value}")); @@ -327,9 +322,9 @@ public async Task TestQuerySpecialChars() Assert.AreEqual("key= <+", await r.GetContentAsync()); } - [TestMethod] - public async Task TestPathSpecialChars() - { + [TestMethod] + public async Task TestPathSpecialChars() + { using var setup = TestSetup.Create((r) => { return r.Respond().Content(r.Target.Path.ToString(true)).Build(); @@ -341,9 +336,9 @@ public async Task TestPathSpecialChars() Assert.AreEqual("/%3F%23%26%2F%20%20", await r.GetContentAsync()); } - [TestMethod] - public async Task TestPathPreservesSpecialChars() - { + [TestMethod] + public async Task TestPathPreservesSpecialChars() + { using var setup = TestSetup.Create((r) => { return r.Respond().Content(r.Target.Path.ToString(true)).Build(); @@ -355,9 +350,9 @@ public async Task TestPathPreservesSpecialChars() Assert.AreEqual("/$@:", await r.GetContentAsync()); } - [TestMethod] - public async Task TestContentLengthPreserved() - { + [TestMethod] + public async Task TestContentLengthPreserved() + { using var setup = TestSetup.Create((r) => { return r.Respond() @@ -372,12 +367,12 @@ public async Task TestContentLengthPreserved() AssertX.IsNullOrEmpty(response.GetHeader("Transfer-Encoding")); } - [TestMethod] - public async Task TestBadGateway() - { + [TestMethod] + public async Task TestBadGateway() + { var proxy = Proxy.Create() .Upstream("http://icertainlydonotexistasadomain"); - + using var runner = TestHost.Run(proxy); using var response = await runner.GetResponseAsync(); @@ -386,9 +381,9 @@ public async Task TestBadGateway() } - [TestMethod] - public async Task TestCompression() - { + [TestMethod] + public async Task TestCompression() + { using var setup = TestSetup.Create((r) => { return r.Respond().Content("Hello World!").Build(); @@ -405,6 +400,4 @@ public async Task TestCompression() Assert.AreEqual("br", response.GetContentHeader("Content-Encoding")); } - } - -} \ No newline at end of file +} diff --git a/Testing/Acceptance/Modules/Security/CorsTests.cs b/Testing/Acceptance/Modules/Security/CorsTests.cs index c4765f03..881c2139 100644 --- a/Testing/Acceptance/Modules/Security/CorsTests.cs +++ b/Testing/Acceptance/Modules/Security/CorsTests.cs @@ -12,16 +12,15 @@ using GenHTTP.Modules.Security; using GenHTTP.Modules.Security.Cors; -namespace GenHTTP.Testing.Acceptance.Modules.Security +namespace GenHTTP.Testing.Acceptance.Modules.Security; + +[TestClass] +public sealed class CorsTests { - [TestClass] - public sealed class CorsTests + [TestMethod] + public async Task TestPreflight() { - - [TestMethod] - public async Task TestPreflight() - { using var runner = GetRunner(CorsPolicy.Permissive()); var request = new HttpRequestMessage(HttpMethod.Options, runner.GetUrl("/t")); @@ -31,9 +30,9 @@ public async Task TestPreflight() await response.AssertStatusAsync(HttpStatusCode.NoContent); } - [TestMethod] - public async Task TestPermissive() - { + [TestMethod] + public async Task TestPermissive() + { using var runner = GetRunner(CorsPolicy.Permissive()); using var response = await runner.GetResponseAsync("/t"); @@ -53,9 +52,9 @@ public async Task TestPermissive() Assert.AreEqual("Hello World", await response.GetContentAsync()); } - [TestMethod] - public async Task TestPermissiveWithoutDefaultAuthorizationHeader() - { + [TestMethod] + public async Task TestPermissiveWithoutDefaultAuthorizationHeader() + { using var runner = GetRunner(CorsPolicy.Permissive(false)); using var response = await runner.GetResponseAsync("/t"); @@ -75,9 +74,9 @@ public async Task TestPermissiveWithoutDefaultAuthorizationHeader() Assert.AreEqual("Hello World", await response.GetContentAsync()); } - [TestMethod] - public async Task TestRestrictive() - { + [TestMethod] + public async Task TestRestrictive() + { using var runner = GetRunner(CorsPolicy.Restrictive()); using var response = await runner.GetResponseAsync("/t"); @@ -95,9 +94,9 @@ public async Task TestRestrictive() Assert.IsFalse(response.Headers.Contains("Access-Control-Max-Age")); } - [TestMethod] - public async Task TestCustom() - { + [TestMethod] + public async Task TestCustom() + { var policy = CorsPolicy.Restrictive() .Add("http://google.de", new List() { new FlexibleRequestMethod(RequestMethod.GET) }, null, new List() { "Accept" }, false); @@ -119,8 +118,8 @@ public async Task TestCustom() Assert.AreEqual("Origin", response.GetHeader("Vary")); } - private static TestHost GetRunner(CorsPolicyBuilder policy) - { + private static TestHost GetRunner(CorsPolicyBuilder policy) + { var handler = Layout.Create() .Add("t", Content.From(Resource.FromString("Hello World"))) .Add(policy); @@ -128,6 +127,4 @@ private static TestHost GetRunner(CorsPolicyBuilder policy) return TestHost.Run(handler); } - } - } diff --git a/Testing/Acceptance/Modules/Security/ExtensionTests.cs b/Testing/Acceptance/Modules/Security/ExtensionTests.cs index 9990631f..72d7d5d7 100644 --- a/Testing/Acceptance/Modules/Security/ExtensionTests.cs +++ b/Testing/Acceptance/Modules/Security/ExtensionTests.cs @@ -5,16 +5,15 @@ using GenHTTP.Modules.IO; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Modules.Security +namespace GenHTTP.Testing.Acceptance.Modules.Security; + +[TestClass] +public sealed class ExtensionTests { - [TestClass] - public sealed class ExtensionTests + [TestMethod] + public async Task ServerCanBeHardened() { - - [TestMethod] - public async Task ServerCanBeHardened() - { using var runner = new TestHost(Layout.Create()); runner.Host.Handler(Content.From(Resource.FromString("Hello Eve!"))) @@ -26,6 +25,4 @@ public async Task ServerCanBeHardened() Assert.AreEqual("nosniff", response.GetHeader("X-Content-Type-Options")); } - } - } diff --git a/Testing/Acceptance/Modules/ServerCaching/PrecompressedContentTest.cs b/Testing/Acceptance/Modules/ServerCaching/PrecompressedContentTest.cs index 93d094b3..f6692b3e 100644 --- a/Testing/Acceptance/Modules/ServerCaching/PrecompressedContentTest.cs +++ b/Testing/Acceptance/Modules/ServerCaching/PrecompressedContentTest.cs @@ -8,16 +8,15 @@ using GenHTTP.Modules.IO; using GenHTTP.Modules.ServerCaching; -namespace GenHTTP.Testing.Acceptance.Modules.ServerCaching +namespace GenHTTP.Testing.Acceptance.Modules.ServerCaching; + +[TestClass] +public class PrecompressedContentTest { - [TestClass] - public class PrecompressedContentTest + [TestMethod] + public async Task TestContentCanBePreCompressed() { - - [TestMethod] - public async Task TestContentCanBePreCompressed() - { var content = Resources.From(ResourceTree.FromAssembly("Resources")) .Add(CompressedContent.Default().Level(CompressionLevel.Optimal)) .Add(ServerCache.Memory()); @@ -34,6 +33,4 @@ public async Task TestContentCanBePreCompressed() Assert.AreEqual("br", response.GetContentHeader("Content-Encoding")); } - } - } diff --git a/Testing/Acceptance/Modules/ServerCaching/ServerCacheTests.cs b/Testing/Acceptance/Modules/ServerCaching/ServerCacheTests.cs index 452d7d8c..77af4c00 100644 --- a/Testing/Acceptance/Modules/ServerCaching/ServerCacheTests.cs +++ b/Testing/Acceptance/Modules/ServerCaching/ServerCacheTests.cs @@ -18,16 +18,15 @@ using Cookie = GenHTTP.Api.Protocol.Cookie; -namespace GenHTTP.Testing.Acceptance.Modules.ServerCaching +namespace GenHTTP.Testing.Acceptance.Modules.ServerCaching; + +[TestClass] +public class ServerCacheTests { - [TestClass] - public class ServerCacheTests + [TestMethod] + public async Task TestContentIsInvalidated() { - - [TestMethod] - public async Task TestContentIsInvalidated() - { var file = Path.GetTempFileName(); try @@ -55,9 +54,9 @@ public async Task TestContentIsInvalidated() } } - [TestMethod] - public async Task TestContentNotInvalidated() - { + [TestMethod] + public async Task TestContentNotInvalidated() + { var file = Path.GetTempFileName(); try @@ -85,9 +84,9 @@ public async Task TestContentNotInvalidated() } } - [TestMethod] - public async Task TestVariationRespected() - { + [TestMethod] + public async Task TestVariationRespected() + { var file = Path.GetTempFileName(); FileUtil.WriteText(file, "This is some content!"); @@ -128,9 +127,9 @@ public async Task TestVariationRespected() } } - [TestMethod] - public async Task TestHeadersPreserved() - { + [TestMethod] + public async Task TestHeadersPreserved() + { var now = DateTime.UtcNow; var handler = new FunctionalHandler(responseProvider: (r) => @@ -164,9 +163,9 @@ public async Task TestHeadersPreserved() Assert.AreEqual("0123456789", await cached.GetContentAsync()); } - [TestMethod] - public async Task TestNoContentCached() - { + [TestMethod] + public async Task TestNoContentCached() + { var i = 0; var handler = new FunctionalHandler(responseProvider: (r) => @@ -187,9 +186,9 @@ public async Task TestNoContentCached() Assert.AreEqual(1, i); } - [TestMethod] - public async Task TestNotOkNotCached() - { + [TestMethod] + public async Task TestNotOkNotCached() + { var i = 0; var handler = new FunctionalHandler(responseProvider: (r) => @@ -210,9 +209,9 @@ public async Task TestNotOkNotCached() Assert.AreEqual(2, i); } - [TestMethod] - public async Task TestPredicateNoMatchNoCache() - { + [TestMethod] + public async Task TestPredicateNoMatchNoCache() + { var i = 0; var handler = new FunctionalHandler(responseProvider: (r) => @@ -238,9 +237,9 @@ public async Task TestPredicateNoMatchNoCache() Assert.AreEqual(2, i); } - [TestMethod] - public async Task TestPredicateMatchCached() - { + [TestMethod] + public async Task TestPredicateMatchCached() + { var i = 0; var handler = new FunctionalHandler(responseProvider: (r) => @@ -266,9 +265,9 @@ public async Task TestPredicateMatchCached() Assert.AreEqual(1, i); } - [TestMethod] - public async Task TestQueryDifferenceNotCached() - { + [TestMethod] + public async Task TestQueryDifferenceNotCached() + { var i = 0; var handler = new FunctionalHandler(responseProvider: (r) => @@ -292,9 +291,9 @@ public async Task TestQueryDifferenceNotCached() Assert.AreEqual(2, i); } - [TestMethod] - public async Task TestPostNotCached() - { + [TestMethod] + public async Task TestPostNotCached() + { var i = 0; var handler = new FunctionalHandler(responseProvider: (r) => @@ -317,8 +316,8 @@ public async Task TestPostNotCached() Assert.AreEqual(2, i); } - private static HttpRequestMessage GetPostRequest(TestHost runner) - { + private static HttpRequestMessage GetPostRequest(TestHost runner) + { var request = runner.GetRequest(); request.Method = HttpMethod.Post; @@ -327,9 +326,9 @@ private static HttpRequestMessage GetPostRequest(TestHost runner) return request; } - [TestMethod] - public async Task TestVariationCached() - { + [TestMethod] + public async Task TestVariationCached() + { var i = 0; var handler = new FunctionalHandler(responseProvider: (r) => @@ -353,8 +352,8 @@ public async Task TestVariationCached() Assert.AreEqual(1, i); } - private static HttpRequestMessage GetVaryRequest(TestHost runner) - { + private static HttpRequestMessage GetVaryRequest(TestHost runner) + { var request = runner.GetRequest(); request.Headers.Add("Key", "Value"); @@ -362,9 +361,9 @@ private static HttpRequestMessage GetVaryRequest(TestHost runner) return request; } - [TestMethod] - public async Task TestDifferentStorageBackends() - { + [TestMethod] + public async Task TestDifferentStorageBackends() + { foreach (var serverCache in GetBackends()) { var i = 0; @@ -388,9 +387,9 @@ public async Task TestDifferentStorageBackends() } } - [TestMethod] - public async Task TestAccessExpiration() - { + [TestMethod] + public async Task TestAccessExpiration() + { var i = 0; var handler = new FunctionalHandler(responseProvider: (r) => @@ -416,18 +415,16 @@ public async Task TestAccessExpiration() Assert.AreEqual(2, i); } - private static ServerCacheHandlerBuilder[] GetBackends() - { + private static ServerCacheHandlerBuilder[] GetBackends() + { var tempDir = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); return new ServerCacheHandlerBuilder[] { - ServerCache.Memory(), - ServerCache.TemporaryFiles(), + ServerCache.Memory(), + ServerCache.TemporaryFiles(), ServerCache.Persistent(tempDir) }; } - } - } diff --git a/Testing/Acceptance/Modules/SinglePageTests.cs b/Testing/Acceptance/Modules/SinglePageTests.cs index 18995d22..292c9bde 100644 --- a/Testing/Acceptance/Modules/SinglePageTests.cs +++ b/Testing/Acceptance/Modules/SinglePageTests.cs @@ -1,24 +1,20 @@ using System.IO; using System.Net; using System.Threading.Tasks; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using GenHTTP.Modules.SinglePageApplications; using GenHTTP.Modules.IO; - +using GenHTTP.Modules.SinglePageApplications; using GenHTTP.Testing.Acceptance.Utilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GenHTTP.Testing.Acceptance.Modules; -namespace GenHTTP.Testing.Acceptance.Providers +[TestClass] +public sealed class SinglePageTests { - [TestClass] - public sealed class SinglePageTests + [TestMethod] + public async Task TestIndex() { - - [TestMethod] - public async Task TestIndex() - { var root = CreateRoot(); FileUtil.WriteText(Path.Combine(root, "index.html"), "This is the index!"); @@ -35,9 +31,9 @@ public async Task TestIndex() Assert.AreEqual("This is the index!", content); } - [TestMethod] - public async Task TestIndexServedWithRouting() - { + [TestMethod] + public async Task TestIndexServedWithRouting() + { var root = CreateRoot(); FileUtil.WriteText(Path.Combine(root, "index.html"), "This is the index!"); @@ -53,9 +49,9 @@ public async Task TestIndexServedWithRouting() Assert.AreEqual("text/html", index.GetContentHeader("Content-Type")); } - [TestMethod] - public async Task TestNoIndex() - { + [TestMethod] + public async Task TestNoIndex() + { using var runner = TestHost.Run(SinglePageApplication.From(ResourceTree.FromDirectory(CreateRoot()))); using var index = await runner.GetResponseAsync("/"); @@ -63,9 +59,9 @@ public async Task TestNoIndex() await index.AssertStatusAsync(HttpStatusCode.NotFound); } - [TestMethod] - public async Task TestFile() - { + [TestMethod] + public async Task TestFile() + { var root = CreateRoot(); FileUtil.WriteText(Path.Combine(root, "some.txt"), "This is some text file :)"); @@ -82,9 +78,9 @@ public async Task TestFile() Assert.AreEqual("This is some text file :)", content); } - [TestMethod] - public async Task TestNoFile() - { + [TestMethod] + public async Task TestNoFile() + { using var runner = TestHost.Run(SinglePageApplication.From(ResourceTree.FromDirectory(CreateRoot()))); using var index = await runner.GetResponseAsync("/nope.txt"); @@ -92,8 +88,8 @@ public async Task TestNoFile() await index.AssertStatusAsync(HttpStatusCode.NotFound); } - private static string CreateRoot() - { + private static string CreateRoot() + { var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(tempDirectory); @@ -101,6 +97,4 @@ private static string CreateRoot() return tempDirectory; } - } - } diff --git a/Testing/Acceptance/Modules/StaticWebsites/StaticWebsiteTests.cs b/Testing/Acceptance/Modules/StaticWebsites/StaticWebsiteTests.cs index 62ccf9bb..99cae84f 100644 --- a/Testing/Acceptance/Modules/StaticWebsites/StaticWebsiteTests.cs +++ b/Testing/Acceptance/Modules/StaticWebsites/StaticWebsiteTests.cs @@ -6,16 +6,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.StaticWebsites +namespace GenHTTP.Testing.Acceptance.Modules.StaticWebsites; + +[TestClass] +public sealed class StaticWebsiteTests { - [TestClass] - public sealed class StaticWebsiteTests + [TestMethod] + public async Task TestWithIndex() { - - [TestMethod] - public async Task TestWithIndex() - { var tree = VirtualTree.Create() .Add("index.html", Resource.FromString("Index 1")) .Add("sub", VirtualTree.Create().Add("index.htm", Resource.FromString("Index 2"))); @@ -29,9 +28,9 @@ public async Task TestWithIndex() Assert.AreEqual("Index 2", await subIndexResponse.GetContentAsync()); } - [TestMethod] - public async Task TestNoIndex() - { + [TestMethod] + public async Task TestNoIndex() + { var tree = VirtualTree.Create() .Add("sub", VirtualTree.Create()); @@ -44,6 +43,4 @@ public async Task TestNoIndex() await subIndexResponse.AssertStatusAsync(HttpStatusCode.NotFound); } - } - } diff --git a/Testing/Acceptance/Modules/VirtualHostsTests.cs b/Testing/Acceptance/Modules/VirtualHostsTests.cs index 7baa3959..3ed205d6 100644 --- a/Testing/Acceptance/Modules/VirtualHostsTests.cs +++ b/Testing/Acceptance/Modules/VirtualHostsTests.cs @@ -7,20 +7,19 @@ using GenHTTP.Modules.IO; using GenHTTP.Modules.Layouting; -namespace GenHTTP.Testing.Acceptance.Modules +namespace GenHTTP.Testing.Acceptance.Modules; + +[TestClass] +public sealed class VirtualHostsTests { - [TestClass] - public sealed class VirtualHostsTests + /// + /// As a hoster, I would like to provide several domains using the + /// same server instance. + /// + [TestMethod] + public async Task TestDomains() { - - /// - /// As a hoster, I would like to provide several domains using the - /// same server instance. - /// - [TestMethod] - public async Task TestDomains() - { var hosts = VirtualHosts.Create() .Add("domain1.com", Content.From(Resource.FromString("domain1.com"))) .Add("domain2.com", Content.From(Resource.FromString("domain2.com"))) @@ -34,13 +33,13 @@ public async Task TestDomains() await RunTest(runner, "localhost", "default"); } - /// - /// As a developer, I expect the server to return no content if - /// no given route matches. - /// - [TestMethod] - public async Task TestNoDefault() - { + /// + /// As a developer, I expect the server to return no content if + /// no given route matches. + /// + [TestMethod] + public async Task TestNoDefault() + { using var runner = TestHost.Run(VirtualHosts.Create()); using var response = await runner.GetResponseAsync(); @@ -48,8 +47,8 @@ public async Task TestNoDefault() await response.AssertStatusAsync(HttpStatusCode.NotFound); } - private static async Task RunTest(TestHost runner, string host, string? expected = null) - { + private static async Task RunTest(TestHost runner, string host, string? expected = null) + { var request = runner.GetRequest(); request.Headers.Add("Host", host); @@ -58,6 +57,4 @@ private static async Task RunTest(TestHost runner, string host, string? expected Assert.AreEqual(expected ?? host, await response.GetContentAsync()); } - } - -} +} \ No newline at end of file diff --git a/Testing/Acceptance/Modules/WebserviceTests.cs b/Testing/Acceptance/Modules/WebserviceTests.cs index 79cb6b8a..009b050d 100644 --- a/Testing/Acceptance/Modules/WebserviceTests.cs +++ b/Testing/Acceptance/Modules/WebserviceTests.cs @@ -1,229 +1,225 @@ using System; using System.IO; using System.Net; +using System.Net.Http; using System.Text; using System.Threading.Tasks; -using System.Net.Http; using System.Xml.Serialization; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; - -using GenHTTP.Modules.IO; -using GenHTTP.Modules.Webservices; using GenHTTP.Modules.Basics; -using GenHTTP.Modules.Layouting; using GenHTTP.Modules.Conversion; +using GenHTTP.Modules.IO; +using GenHTTP.Modules.Layouting; using GenHTTP.Modules.Reflection; +using GenHTTP.Modules.Webservices; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Webservices -{ +namespace GenHTTP.Testing.Acceptance.Modules; - [TestClass] - public sealed class WebserviceTests - { +[TestClass] +public sealed class WebserviceTests +{ - #region Supporting structures + #region Supporting structures - public sealed class TestEntity - { + public sealed class TestEntity + { - public int ID { get; set; } + public int ID { get; set; } - public double? Nullable { get; set; } + public double? Nullable { get; set; } - } + } - public enum TestEnum - { - One, - Two - } + public enum TestEnum + { + One, + Two + } - public sealed class TestResource - { + public sealed class TestResource + { - [ResourceMethod("nothing")] - public void DoNothing() { } + [ResourceMethod("nothing")] + public void DoNothing() { } - [ResourceMethod("primitive")] - public int Primitive(int input) => input; + [ResourceMethod("primitive")] + public int Primitive(int input) => input; - [ResourceMethod("guid")] - public Guid Guid(Guid id) => id; + [ResourceMethod("guid")] + public Guid Guid(Guid id) => id; - [ResourceMethod(RequestMethod.POST, "entity")] - public TestEntity Entity(TestEntity entity) => entity; + [ResourceMethod(RequestMethod.POST, "entity")] + public TestEntity Entity(TestEntity entity) => entity; - [ResourceMethod(RequestMethod.PUT, "stream")] - public Stream Stream(Stream input) => new MemoryStream(Encoding.UTF8.GetBytes(input.Length.ToString())); + [ResourceMethod(RequestMethod.PUT, "stream")] + public Stream Stream(Stream input) => new MemoryStream(Encoding.UTF8.GetBytes(input.Length.ToString())); - [ResourceMethod("requestResponse")] - public ValueTask RequestResponse(IRequest request) - { + [ResourceMethod("requestResponse")] + public ValueTask RequestResponse(IRequest request) + { return request.Respond() .Content("Hello World") .Type(ContentType.TextPlain) .BuildTask(); } - [ResourceMethod("exception")] - public void Exception() => throw new ProviderException(ResponseStatus.AlreadyReported, "Already reported!"); + [ResourceMethod("exception")] + public void Exception() => throw new ProviderException(ResponseStatus.AlreadyReported, "Already reported!"); - [ResourceMethod("duplicate")] - public void Duplicate1() { } + [ResourceMethod("duplicate")] + public void Duplicate1() { } - [ResourceMethod("duplicate")] - public void Duplicate2() { } + [ResourceMethod("duplicate")] + public void Duplicate2() { } - [ResourceMethod("param/:param")] - public int PathParam(int param) => param; + [ResourceMethod("param/:param")] + public int PathParam(int param) => param; - [ResourceMethod("regex/(?[0-9]+)")] - public int RegexParam(int param) => param; + [ResourceMethod("regex/(?[0-9]+)")] + public int RegexParam(int param) => param; - [ResourceMethod] - public void Empty() { } + [ResourceMethod] + public void Empty() { } - [ResourceMethod("enum")] - public TestEnum Enum(TestEnum input) => input; + [ResourceMethod("enum")] + public TestEnum Enum(TestEnum input) => input; - [ResourceMethod("nullable")] - public int? Nullable(int? input) => input; + [ResourceMethod("nullable")] + public int? Nullable(int? input) => input; - [ResourceMethod("request")] - public string? Request(IHandler handler, IRequest request) => "yes"; + [ResourceMethod("request")] + public string? Request(IHandler handler, IRequest request) => "yes"; - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task TestEmpty() - { + [TestMethod] + public async Task TestEmpty() + { await WithResponse("", async r => { await r.AssertStatusAsync(HttpStatusCode.NoContent); }); } - [TestMethod] - public async Task TestVoidReturn() - { + [TestMethod] + public async Task TestVoidReturn() + { await WithResponse("nothing", async r => { await r.AssertStatusAsync(HttpStatusCode.NoContent); }); } - [TestMethod] - public async Task TestPrimitives() - { + [TestMethod] + public async Task TestPrimitives() + { await WithResponse("primitive?input=42", async r => Assert.AreEqual("42", await r.GetContentAsync())); } - [TestMethod] - public async Task TestEnums() - { + [TestMethod] + public async Task TestEnums() + { await WithResponse("enum?input=One", async r => Assert.AreEqual("One", await r.GetContentAsync())); } - [TestMethod] - public async Task TestNullableSet() - { + [TestMethod] + public async Task TestNullableSet() + { await WithResponse("nullable?input=1", async r => Assert.AreEqual("1", await r.GetContentAsync())); } - [TestMethod] - public async Task TestNullableNotSet() - { + [TestMethod] + public async Task TestNullableNotSet() + { await WithResponse("nullable", async r => { await r.AssertStatusAsync(HttpStatusCode.NoContent); }); } - [TestMethod] - public async Task TestGuid() - { + [TestMethod] + public async Task TestGuid() + { var id = Guid.NewGuid().ToString(); await WithResponse($"guid?id={id}", async r => Assert.AreEqual(id, await r.GetContentAsync())); } - [TestMethod] - public async Task TestParam() - { + [TestMethod] + public async Task TestParam() + { await WithResponse("param/42", async r => Assert.AreEqual("42", await r.GetContentAsync())); } - [TestMethod] - public async Task TestConversionFailure() - { + [TestMethod] + public async Task TestConversionFailure() + { await WithResponse("param/abc", async r => { await r.AssertStatusAsync(HttpStatusCode.BadRequest); }); } - [TestMethod] - public async Task TestRegex() - { + [TestMethod] + public async Task TestRegex() + { await WithResponse("regex/42", async r => Assert.AreEqual("42", await r.GetContentAsync())); } - [TestMethod] - public async Task TestEntityWithNulls() - { + [TestMethod] + public async Task TestEntityWithNulls() + { var entity = "{\"id\":42}"; await WithResponse("entity", HttpMethod.Post, entity, null, null, async r => Assert.AreEqual(entity, await r.GetContentAsync())); } - [TestMethod] - public async Task TestEntityWithNoNulls() - { + [TestMethod] + public async Task TestEntityWithNoNulls() + { var entity = "{\"id\":42,\"nullable\":123.456}"; await WithResponse("entity", HttpMethod.Post, entity, null, null, async r => Assert.AreEqual(entity, await r.GetContentAsync())); } - [TestMethod] - public async Task TestNotSupportedUpload() - { + [TestMethod] + public async Task TestNotSupportedUpload() + { await WithResponse("entity", HttpMethod.Post, "123", "bla/blubb", null, async r => { await r.AssertStatusAsync(HttpStatusCode.UnsupportedMediaType); }); } - [TestMethod] - public async Task TestUnsupportedDownloadEnforcesDefault() - { + [TestMethod] + public async Task TestUnsupportedDownloadEnforcesDefault() + { var entity = "{\"id\":42,\"nullable\":123.456}"; await WithResponse("entity", HttpMethod.Post, entity, null, "bla/blubb", async r => Assert.AreEqual(entity, await r.GetContentAsync())); } - [TestMethod] - public async Task TestWrongMethod() - { + [TestMethod] + public async Task TestWrongMethod() + { await WithResponse("entity", HttpMethod.Put, "123", null, null, async r => { await r.AssertStatusAsync(HttpStatusCode.MethodNotAllowed); }); } - [TestMethod] - public async Task TestNoMethod() - { + [TestMethod] + public async Task TestNoMethod() + { await WithResponse("idonotexist", async r => { await r.AssertStatusAsync(HttpStatusCode.NotFound); }); } - [TestMethod] - public async Task TestStream() - { + [TestMethod] + public async Task TestStream() + { await WithResponse("stream", HttpMethod.Put, "123456", null, null, async r => Assert.AreEqual("6", await r.GetContentAsync())); } - [TestMethod] - public async Task TestRequestResponse() - { + [TestMethod] + public async Task TestRequestResponse() + { await WithResponse("requestResponse", async r => Assert.AreEqual("Hello World", await r.GetContentAsync())); } - [TestMethod] - public async Task TestRouting() - { + [TestMethod] + public async Task TestRouting() + { await WithResponse("request", async r => Assert.AreEqual("yes", await r.GetContentAsync())); } - [TestMethod] - public async Task TestEntityAsXML() - { + [TestMethod] + public async Task TestEntityAsXML() + { var entity = "11234.56"; await WithResponse("entity", HttpMethod.Post, entity, "text/xml", "text/xml", async r => @@ -237,21 +233,21 @@ await WithResponse("entity", HttpMethod.Post, entity, "text/xml", "text/xml", as }); } - [TestMethod] - public async Task TestException() - { + [TestMethod] + public async Task TestException() + { await WithResponse("exception", async r => { await r.AssertStatusAsync(HttpStatusCode.AlreadyReported); }); } - [TestMethod] - public async Task TestDuplicate() - { + [TestMethod] + public async Task TestDuplicate() + { await WithResponse("duplicate", async r => { await r.AssertStatusAsync(HttpStatusCode.BadRequest); }); } - [TestMethod] - public async Task TestWithInstance() - { + [TestMethod] + public async Task TestWithInstance() + { var layout = Layout.Create().AddService("t", new TestResource()); using var runner = TestHost.Run(layout); @@ -261,14 +257,14 @@ public async Task TestWithInstance() await response.AssertStatusAsync(HttpStatusCode.NoContent); } - #endregion + #endregion - #region Helpers + #region Helpers - private Task WithResponse(string uri, Func logic) => WithResponse(uri, HttpMethod.Get, null, null, null, logic); + private Task WithResponse(string uri, Func logic) => WithResponse(uri, HttpMethod.Get, null, null, null, logic); - private async Task WithResponse(string uri, HttpMethod method, string? body, string? contentType, string? accept, Func logic) - { + private async Task WithResponse(string uri, HttpMethod method, string? body, string? contentType, string? accept, Func logic) + { using var service = GetService(); var request = service.GetRequest($"/t/{uri}"); @@ -299,8 +295,8 @@ private async Task WithResponse(string uri, HttpMethod method, string? body, str await logic(response); } - private static TestHost GetService() - { + private static TestHost GetService() + { var service = ServiceResource.From() .Serializers(Serialization.Default()) .Injectors(Injection.Default()); @@ -308,9 +304,6 @@ private static TestHost GetService() return TestHost.Run(Layout.Create().Add("t", service)); } - #endregion - - } - + #endregion } diff --git a/Testing/Acceptance/Modules/Webservices/AmbiguityTests.cs b/Testing/Acceptance/Modules/Webservices/AmbiguityTests.cs index 5abc890d..e9abda87 100644 --- a/Testing/Acceptance/Modules/Webservices/AmbiguityTests.cs +++ b/Testing/Acceptance/Modules/Webservices/AmbiguityTests.cs @@ -9,36 +9,35 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Webservices +namespace GenHTTP.Testing.Acceptance.Modules.Webservices; + +[TestClass] +public sealed class AmbiguityTests { - [TestClass] - public sealed class AmbiguityTests - { + #region Supporting data structures - #region Supporting data structures + public sealed class TestService + { - public sealed class TestService + [ResourceMethod] + public IHandlerBuilder Wildcard() { - - [ResourceMethod] - public IHandlerBuilder Wildcard() - { return Content.From(Resource.FromString("Wildcard")); } - [ResourceMethod(path: "/my.txt")] - public string Specific() => "Specific"; + [ResourceMethod(path: "/my.txt")] + public string Specific() => "Specific"; - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task TestSpecificPreferred() - { + [TestMethod] + public async Task TestSpecificPreferred() + { var app = Layout.Create() .AddService("c"); @@ -51,8 +50,6 @@ public async Task TestSpecificPreferred() Assert.AreEqual("Specific", await response.GetContentAsync()); } - #endregion - - } + #endregion -} +} \ No newline at end of file diff --git a/Testing/Acceptance/Modules/Webservices/ExtensionTests.cs b/Testing/Acceptance/Modules/Webservices/ExtensionTests.cs index b65a5dd2..1a82517d 100644 --- a/Testing/Acceptance/Modules/Webservices/ExtensionTests.cs +++ b/Testing/Acceptance/Modules/Webservices/ExtensionTests.cs @@ -8,30 +8,29 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Webservices -{ +namespace GenHTTP.Testing.Acceptance.Modules.Webservices; - [TestClass] - public sealed class ExtensionTests - { +[TestClass] +public sealed class ExtensionTests +{ - #region Supporting data structures + #region Supporting data structures - public class TestService - { + public class TestService + { - [ResourceMethod] - public int DoWork() => 42; + [ResourceMethod] + public int DoWork() => 42; - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task TestConfiguration() - { + [TestMethod] + public async Task TestConfiguration() + { var injectors = Injection.Default(); var formats = Serialization.Default(); @@ -51,8 +50,6 @@ public async Task TestConfiguration() await r2.AssertStatusAsync(HttpStatusCode.OK); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/Webservices/HandlerResultTests.cs b/Testing/Acceptance/Modules/Webservices/HandlerResultTests.cs index e030ef73..f993c8b0 100644 --- a/Testing/Acceptance/Modules/Webservices/HandlerResultTests.cs +++ b/Testing/Acceptance/Modules/Webservices/HandlerResultTests.cs @@ -12,48 +12,47 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Webservices +namespace GenHTTP.Testing.Acceptance.Modules.Webservices; + +[TestClass] +public sealed class HandlerResultTests { - [TestClass] - public sealed class HandlerResultTests - { + #region Supporting data structures - #region Supporting data structures + public sealed class RootService + { + private static readonly IBuilder _Tree = CreateTree(); - public sealed class RootService + [ResourceMethod] + public IHandlerBuilder Root() { - private static readonly IBuilder _Tree = CreateTree(); - - [ResourceMethod] - public IHandlerBuilder Root() - { return StaticWebsite.From(_Tree); } - } + } - public sealed class PathService - { - private static readonly IBuilder _Tree = CreateTree(); + public sealed class PathService + { + private static readonly IBuilder _Tree = CreateTree(); - [ResourceMethod(path: "/mypath/:pathParam/")] - public IHandlerBuilder Pathed(string pathParam) - { + [ResourceMethod(path: "/mypath/:pathParam/")] + public IHandlerBuilder Pathed(string pathParam) + { Assert.AreEqual("param", pathParam); return StaticWebsite.From(_Tree); } - } + } - public sealed class PathAsyncService - { - private static readonly IBuilder _Tree = CreateTree(); + public sealed class PathAsyncService + { + private static readonly IBuilder _Tree = CreateTree(); - [ResourceMethod(path: "/mypath/:pathParam/")] - public Task Pathed(string pathParam) - { + [ResourceMethod(path: "/mypath/:pathParam/")] + public Task Pathed(string pathParam) + { Assert.AreEqual("param", pathParam); IHandlerBuilder handlerBuilder = StaticWebsite.From(_Tree); @@ -61,15 +60,15 @@ public Task Pathed(string pathParam) return Task.FromResult(handlerBuilder); } - } + } - #endregion + #endregion - #region Tests + #region Tests - [TestMethod] - public async Task TestRoot() - { + [TestMethod] + public async Task TestRoot() + { var app = Layout.Create() .AddService("c"); @@ -82,9 +81,9 @@ public async Task TestRoot() Assert.AreEqual("My Textfile", await response.GetContentAsync()); } - [TestMethod] - public async Task TestPathed() - { + [TestMethod] + public async Task TestPathed() + { var app = Layout.Create() .AddService("c"); @@ -97,9 +96,9 @@ public async Task TestPathed() Assert.AreEqual("My Textfile", await response.GetContentAsync()); } - [TestMethod] - public async Task TestPathedAsync() - { + [TestMethod] + public async Task TestPathedAsync() + { var app = Layout.Create() .AddService("c"); @@ -112,12 +111,12 @@ public async Task TestPathedAsync() Assert.AreEqual("My Textfile", await response.GetContentAsync()); } - #endregion + #endregion - #region Helpers + #region Helpers - public static IBuilder CreateTree() - { + public static IBuilder CreateTree() + { var subTree = VirtualTree.Create() .Add("index.htm", Resource.FromString("Sub Index")) .Add("my.txt", Resource.FromString("My Textfile")); @@ -127,8 +126,6 @@ public static IBuilder CreateTree() .Add("sub", subTree); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Modules/Webservices/ResultTypeTests.cs b/Testing/Acceptance/Modules/Webservices/ResultTypeTests.cs index a6edd7c8..10a45802 100644 --- a/Testing/Acceptance/Modules/Webservices/ResultTypeTests.cs +++ b/Testing/Acceptance/Modules/Webservices/ResultTypeTests.cs @@ -8,39 +8,38 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Modules.Webservices -{ +namespace GenHTTP.Testing.Acceptance.Modules.Webservices; - #region Supporting data structures +#region Supporting data structures - public class TestResource - { +public class TestResource +{ - [ResourceMethod("task")] - public Task AsyncTask() => Task.CompletedTask; + [ResourceMethod("task")] + public Task AsyncTask() => Task.CompletedTask; - [ResourceMethod("value-task")] - public ValueTask AsyncValueTask() => ValueTask.CompletedTask; + [ResourceMethod("value-task")] + public ValueTask AsyncValueTask() => ValueTask.CompletedTask; - [ResourceMethod("generic-task")] - public Task AsyncGenericTask() => Task.FromResult("Task result"); + [ResourceMethod("generic-task")] + public Task AsyncGenericTask() => Task.FromResult("Task result"); - [ResourceMethod("generic-value-task")] - public ValueTask AsyncGenericValueTask() => ValueTask.FromResult("ValueTask result"); + [ResourceMethod("generic-value-task")] + public ValueTask AsyncGenericValueTask() => ValueTask.FromResult("ValueTask result"); - } +} - #endregion +#endregion - [TestClass] - public class ResultTypeTests - { +[TestClass] +public class ResultTypeTests +{ - #region Tests + #region Tests - [TestMethod] - public async Task ControllerMayReturnTask() - { + [TestMethod] + public async Task ControllerMayReturnTask() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/task"); @@ -48,9 +47,9 @@ public async Task ControllerMayReturnTask() await response.AssertStatusAsync(HttpStatusCode.NoContent); } - [TestMethod] - public async Task ControllerMayReturnValueTask() - { + [TestMethod] + public async Task ControllerMayReturnValueTask() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/value-task"); @@ -58,9 +57,9 @@ public async Task ControllerMayReturnValueTask() await response.AssertStatusAsync(HttpStatusCode.NoContent); } - [TestMethod] - public async Task ControllerMayReturnGenericTask() - { + [TestMethod] + public async Task ControllerMayReturnGenericTask() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/generic-task"); @@ -69,9 +68,9 @@ public async Task ControllerMayReturnGenericTask() Assert.AreEqual("Task result", await response.GetContentAsync()); } - [TestMethod] - public async Task ControllerMayReturnGenericValueTask() - { + [TestMethod] + public async Task ControllerMayReturnGenericValueTask() + { using var runner = GetRunner(); using var response = await runner.GetResponseAsync("/t/generic-value-task"); @@ -80,19 +79,17 @@ public async Task ControllerMayReturnGenericValueTask() Assert.AreEqual("ValueTask result", await response.GetContentAsync()); } - #endregion + #endregion - #region Helpers + #region Helpers - private TestHost GetRunner() - { + private TestHost GetRunner() + { return TestHost.Run(Layout.Create().AddService("t", serializers: Serialization.Default(), injectors: Injection.Default(), formatters: Formatting.Default())); } - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/TestExtensions.cs b/Testing/Acceptance/TestExtensions.cs index 696cb21b..c477cf55 100644 --- a/Testing/Acceptance/TestExtensions.cs +++ b/Testing/Acceptance/TestExtensions.cs @@ -9,18 +9,17 @@ using System.Xml.Linq; using System.Xml.XPath; -namespace GenHTTP.Testing.Acceptance -{ +namespace GenHTTP.Testing.Acceptance; - public static class TestExtensions - { +public static class TestExtensions +{ - public static IHandlerBuilder Wrap(this IHandler handler) => new HandlerBuilder(handler); + public static IHandlerBuilder Wrap(this IHandler handler) => new HandlerBuilder(handler); - public static string? GetETag(this HttpResponseMessage response) => response.GetHeader("ETag"); + public static string? GetETag(this HttpResponseMessage response) => response.GetHeader("ETag"); - public static async Task> GetSitemap(this HttpResponseMessage response) - { + public static async Task> GetSitemap(this HttpResponseMessage response) + { var content = await response.GetContentAsync(); var sitemap = XDocument.Parse(content); @@ -34,11 +33,9 @@ public static async Task> GetSitemap(this HttpResponseMessage re .ToHashSet() ?? new HashSet(); } - public static DateTime WithoutMS(this DateTime date) - { + public static DateTime WithoutMS(this DateTime date) + { return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind); - } - - } + } } diff --git a/Testing/Acceptance/Testing/ContentTests.cs b/Testing/Acceptance/Testing/ContentTests.cs index 0f41b058..27ccce07 100644 --- a/Testing/Acceptance/Testing/ContentTests.cs +++ b/Testing/Acceptance/Testing/ContentTests.cs @@ -5,18 +5,17 @@ using GenHTTP.Modules.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GenHTTP.Testing.Acceptance.Testing -{ +namespace GenHTTP.Testing.Acceptance.Testing; - [TestClass] - public sealed class ContentTests - { +[TestClass] +public sealed class ContentTests +{ - public record class MyType(int ID); + public record class MyType(int ID); - [TestMethod] - public async Task TestDeserialization() - { + [TestMethod] + public async Task TestDeserialization() + { var expectation = new MyType(42); var handler = Inline.Create() @@ -29,9 +28,9 @@ public async Task TestDeserialization() Assert.AreEqual(expectation, await response.GetContentAsync()); } - [TestMethod] - public async Task TestNull() - { + [TestMethod] + public async Task TestNull() + { var handler = Inline.Create() .Get(() => (MyType?)null); @@ -42,9 +41,9 @@ public async Task TestNull() Assert.IsNull(await response.GetOptionalContentAsync()); } - [TestMethod] - public async Task TestUnsupported() - { + [TestMethod] + public async Task TestUnsupported() + { var handler = Inline.Create() .Get(() => new Result("Nah").Type(FlexibleContentType.Get("text/html"))); @@ -58,6 +57,4 @@ await Assert.ThrowsExceptionAsync(async () => }); } - } - } diff --git a/Testing/Acceptance/Utilities/FileUtil.cs b/Testing/Acceptance/Utilities/FileUtil.cs index ce65eb6f..123276b5 100644 --- a/Testing/Acceptance/Utilities/FileUtil.cs +++ b/Testing/Acceptance/Utilities/FileUtil.cs @@ -2,28 +2,25 @@ using System.Text; using System.Threading.Tasks; -namespace GenHTTP.Testing.Acceptance.Utilities +namespace GenHTTP.Testing.Acceptance.Utilities; + +public static class FileUtil { - public static class FileUtil + public static void WriteText(string path, string content) { - - public static void WriteText(string path, string content) - { using var file = File.Create(path, 4096, FileOptions.WriteThrough); file.Write(Encoding.UTF8.GetBytes(content)); file.Flush(); } - public static async ValueTask WriteTextAsync(string path, string content) - { + public static async ValueTask WriteTextAsync(string path, string content) + { using var file = File.Create(path, 4096, FileOptions.WriteThrough); await file.WriteAsync(Encoding.UTF8.GetBytes(content)); await file.FlushAsync(); } - } - } diff --git a/Testing/Acceptance/Utilities/FunctionalHandler.cs b/Testing/Acceptance/Utilities/FunctionalHandler.cs index e15f1b70..e9e4abc1 100644 --- a/Testing/Acceptance/Utilities/FunctionalHandler.cs +++ b/Testing/Acceptance/Utilities/FunctionalHandler.cs @@ -4,42 +4,39 @@ using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; -namespace GenHTTP.Testing.Acceptance.Utilities -{ +namespace GenHTTP.Testing.Acceptance.Utilities; - public sealed class FunctionalHandler : IHandlerWithParent - { - private readonly Func? _ResponseProvider; +public sealed class FunctionalHandler : IHandlerWithParent +{ + private readonly Func? _ResponseProvider; - private IHandler? _Parent; + private IHandler? _Parent; - #region Get-/Setters + #region Get-/Setters - public IHandler Parent - { - get { return _Parent ?? throw new InvalidOperationException(); } - set { _Parent = value; } - } + public IHandler Parent + { + get { return _Parent ?? throw new InvalidOperationException(); } + set { _Parent = value; } + } - #endregion + #endregion - #region Initialization + #region Initialization - public FunctionalHandler(Func? responseProvider = null) - { + public FunctionalHandler(Func? responseProvider = null) + { _ResponseProvider = responseProvider; } - #endregion + #endregion - #region Functionality + #region Functionality - public ValueTask PrepareAsync() => ValueTask.CompletedTask; + public ValueTask PrepareAsync() => ValueTask.CompletedTask; - public ValueTask HandleAsync(IRequest request) => new((_ResponseProvider is not null) ? _ResponseProvider(request) : null); + public ValueTask HandleAsync(IRequest request) => new((_ResponseProvider is not null) ? _ResponseProvider(request) : null); - #endregion - - } + #endregion } diff --git a/Testing/Acceptance/Utilities/HandlerBuilder.cs b/Testing/Acceptance/Utilities/HandlerBuilder.cs index 40eb4624..af5381af 100644 --- a/Testing/Acceptance/Utilities/HandlerBuilder.cs +++ b/Testing/Acceptance/Utilities/HandlerBuilder.cs @@ -2,25 +2,24 @@ using GenHTTP.Api.Content; -namespace GenHTTP.Testing.Acceptance.Utilities -{ +namespace GenHTTP.Testing.Acceptance.Utilities; - public class HandlerBuilder : IHandlerBuilder - { - private readonly IHandler _Handler; +public class HandlerBuilder : IHandlerBuilder +{ + private readonly IHandler _Handler; - private readonly List _Concerns = new(); + private readonly List _Concerns = new(); - public HandlerBuilder(IHandler handler) { _Handler = handler; } + public HandlerBuilder(IHandler handler) { _Handler = handler; } - public HandlerBuilder Add(IConcernBuilder concern) - { + public HandlerBuilder Add(IConcernBuilder concern) + { _Concerns.Add(concern); return this; } - public IHandler Build(IHandler parent) - { + public IHandler Build(IHandler parent) + { return Concerns.Chain(parent, _Concerns, (p) => { if (_Handler is IHandlerWithParent par) @@ -29,9 +28,7 @@ public IHandler Build(IHandler parent) } return _Handler; - }); ; + }); } - } - } diff --git a/Testing/Acceptance/Utilities/IHandlerWithParent.cs b/Testing/Acceptance/Utilities/IHandlerWithParent.cs index 2533a14e..01326a2a 100644 --- a/Testing/Acceptance/Utilities/IHandlerWithParent.cs +++ b/Testing/Acceptance/Utilities/IHandlerWithParent.cs @@ -1,13 +1,10 @@ using GenHTTP.Api.Content; -namespace GenHTTP.Testing.Acceptance.Utilities -{ - - public interface IHandlerWithParent : IHandler - { +namespace GenHTTP.Testing.Acceptance.Utilities; - public new IHandler Parent { get; set; } +public interface IHandlerWithParent : IHandler +{ - } + public new IHandler Parent { get; set; } } diff --git a/Testing/Testing/ContentExtensions.cs b/Testing/Testing/ContentExtensions.cs index 990d4688..795e7981 100644 --- a/Testing/Testing/ContentExtensions.cs +++ b/Testing/Testing/ContentExtensions.cs @@ -6,48 +6,47 @@ using GenHTTP.Modules.Conversion; using GenHTTP.Modules.Protobuf; -namespace GenHTTP.Testing -{ +namespace GenHTTP.Testing; - public static class ContentExtensions - { +public static class ContentExtensions +{ - /// - /// Reads the response body as a string. - /// - /// The response to read - /// The content of the HTTP response - public static async ValueTask GetContentAsync(this HttpResponseMessage response) => await response.Content.ReadAsStringAsync(); + /// + /// Reads the response body as a string. + /// + /// The response to read + /// The content of the HTTP response + public static async ValueTask GetContentAsync(this HttpResponseMessage response) => await response.Content.ReadAsStringAsync(); - /// - /// Deserializes the payload of the HTTP response into the given type. - /// - /// The type of the payload to be read - /// The response to read the payload from - /// The deserialized payload of the response - /// Thrown if the format returned by the server is not supported - /// - /// This method supports all formats that ship with the GenHTTP framework (JSON, XML, form encoded, Protobuf) - /// and falls back to JSON if the server does not indicate a content type. - /// - public static async ValueTask GetContentAsync(this HttpResponseMessage response) - { + /// + /// Deserializes the payload of the HTTP response into the given type. + /// + /// The type of the payload to be read + /// The response to read the payload from + /// The deserialized payload of the response + /// Thrown if the format returned by the server is not supported + /// + /// This method supports all formats that ship with the GenHTTP framework (JSON, XML, form encoded, Protobuf) + /// and falls back to JSON if the server does not indicate a content type. + /// + public static async ValueTask GetContentAsync(this HttpResponseMessage response) + { return await response.GetOptionalContentAsync() ?? throw new InvalidOperationException("Server did not return a result"); } - /// - /// Attempts to deserialize the payload of the HTTP response into the given type. - /// - /// The type of the payload to be read - /// The response to read the payload from - /// The deserialized payload of the response or null, if the server did not return data - /// Thrown if the format returned by the server is not supported - /// - /// This method supports all formats that ship with the GenHTTP framework (JSON, XML, form encoded, Protobuf) - /// and falls back to JSON if the server does not indicate a content type. - /// - public static async ValueTask GetOptionalContentAsync(this HttpResponseMessage response) - { + /// + /// Attempts to deserialize the payload of the HTTP response into the given type. + /// + /// The type of the payload to be read + /// The response to read the payload from + /// The deserialized payload of the response or null, if the server did not return data + /// Thrown if the format returned by the server is not supported + /// + /// This method supports all formats that ship with the GenHTTP framework (JSON, XML, form encoded, Protobuf) + /// and falls back to JSON if the server does not indicate a content type. + /// + public static async ValueTask GetOptionalContentAsync(this HttpResponseMessage response) + { if (response.StatusCode == HttpStatusCode.NoContent) { return default; @@ -60,12 +59,10 @@ public static async ValueTask GetContentAsync(this HttpResponseMessage res .Build(); var format = registry.GetFormat(type) ?? throw new InvalidOperationException($"Unable to find deserializer for content type '{type}'"); - + using var body = await response.Content.ReadAsStreamAsync(); return (T?)await format.DeserializeAsync(body, typeof(T)); } - } - } diff --git a/Testing/Testing/HeaderExtensions.cs b/Testing/Testing/HeaderExtensions.cs index bce03541..4986ac4d 100644 --- a/Testing/Testing/HeaderExtensions.cs +++ b/Testing/Testing/HeaderExtensions.cs @@ -1,14 +1,13 @@ using System.Linq; using System.Net.Http; -namespace GenHTTP.Testing +namespace GenHTTP.Testing; + +public static class HeaderExtensions { - - public static class HeaderExtensions - { - public static string? GetHeader(this HttpResponseMessage response, string key) - { + public static string? GetHeader(this HttpResponseMessage response, string key) + { if (response.Headers.TryGetValues(key, out var values)) { return values.FirstOrDefault(); @@ -17,8 +16,8 @@ public static class HeaderExtensions return null; } - public static string? GetContentHeader(this HttpResponseMessage response, string key) - { + public static string? GetContentHeader(this HttpResponseMessage response, string key) + { if (response.Content.Headers.TryGetValues(key, out var values)) { return values.FirstOrDefault(); @@ -27,6 +26,4 @@ public static class HeaderExtensions return null; } - } - } diff --git a/Testing/Testing/TestHost.cs b/Testing/Testing/TestHost.cs index c1ec81ab..ed3c97df 100644 --- a/Testing/Testing/TestHost.cs +++ b/Testing/Testing/TestHost.cs @@ -10,53 +10,52 @@ using GenHTTP.Modules.Practices; -namespace GenHTTP.Testing -{ +namespace GenHTTP.Testing; - /// - /// Hosts GenHTTP projects on a random port and provides convenience functionality - /// to test the responses of the server. - /// - public class TestHost : IDisposable - { +/// +/// Hosts GenHTTP projects on a random port and provides convenience functionality +/// to test the responses of the server. +/// +public class TestHost : IDisposable +{ #if NET8_0 private static volatile int _NextPort = 20000; #else - private static volatile int _NextPort = 30000; + private static volatile int _NextPort = 30000; #endif - private static readonly HttpClient _DefaultClient = GetClient(); + private static readonly HttpClient _DefaultClient = GetClient(); - #region Get-/Setters + #region Get-/Setters - /// - /// The port this host listens to. - /// - public int Port { get; private set; } + /// + /// The port this host listens to. + /// + public int Port { get; private set; } - /// - /// The host managed by this testing host. - /// - public IServerHost Host { get; private set; } + /// + /// The host managed by this testing host. + /// + public IServerHost Host { get; private set; } - #endregion + #endregion - #region Lifecycle + #region Lifecycle - static TestHost() - { + static TestHost() + { HttpWebRequest.DefaultCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore); } - /// - /// Creates a test host that will use the given handler to provide content, - /// but has yet to be started. - /// - /// The handler to be tested - /// true, if the defaults (such as compression) should be added to this handler - /// true, if the server should be started in development mode - public TestHost(IHandlerBuilder handlerBuilder, bool defaults = true, bool development = true) - { + /// + /// Creates a test host that will use the given handler to provide content, + /// but has yet to be started. + /// + /// The handler to be tested + /// true, if the defaults (such as compression) should be added to this handler + /// true, if the server should be started in development mode + public TestHost(IHandlerBuilder handlerBuilder, bool defaults = true, bool development = true) + { Port = NextPort(); Host = Engine.Host.Create() @@ -74,15 +73,15 @@ public TestHost(IHandlerBuilder handlerBuilder, bool defaults = true, bool devel } } - /// - /// Creates a test host that will use the given handler to provide content - /// and starts it immediately. - /// - /// The handler to be tested - /// true, if the defaults (such as compression) should be added to this handler - /// true, if the server should be started in development mode - public static TestHost Run(IHandlerBuilder handlerBuilder, bool defaults = true, bool development = true) - { + /// + /// Creates a test host that will use the given handler to provide content + /// and starts it immediately. + /// + /// The handler to be tested + /// true, if the defaults (such as compression) should be added to this handler + /// true, if the server should be started in development mode + public static TestHost Run(IHandlerBuilder handlerBuilder, bool defaults = true, bool development = true) + { var runner = new TestHost(handlerBuilder, defaults, development); runner.Start(); @@ -90,83 +89,83 @@ public static TestHost Run(IHandlerBuilder handlerBuilder, bool defaults = true, return runner; } - /// - /// Starts the server managed by this testing host. - /// - /// - /// Dispose this runner to shut down the server and release all resources - /// or close the host via the property. - /// - public void Start() - { + /// + /// Starts the server managed by this testing host. + /// + /// + /// Dispose this runner to shut down the server and release all resources + /// or close the host via the property. + /// + public void Start() + { Host.Start(); } - #endregion - - #region Functionality - - /// - /// Returns the next free port to be used by the testing host - /// to provide content. - /// - /// - /// You typically do not need to call this method by yourself, as the - /// test host runner methods will automatically claim the next free port. - /// - /// The next free port to be used - public static int NextPort() => Interlocked.Increment(ref _NextPort); - - /// - /// Computes the URL which can be used to fetch the given path - /// from the hosted server. - /// - /// The path to fetch from the server - /// The URL that can be used to fetch the content - public string GetUrl(string? path = null) => $"http://localhost:{Port}{path ?? ""}"; - - /// - /// Fetches a request instance for the given path and method which - /// can then be configured before being passed to the - /// method. - /// - /// The path the request should fetch - /// The method to be used for the request, if not GET - /// The newly created request message - public HttpRequestMessage GetRequest(string? path = null, HttpMethod? method = null) - { + #endregion + + #region Functionality + + /// + /// Returns the next free port to be used by the testing host + /// to provide content. + /// + /// + /// You typically do not need to call this method by yourself, as the + /// test host runner methods will automatically claim the next free port. + /// + /// The next free port to be used + public static int NextPort() => Interlocked.Increment(ref _NextPort); + + /// + /// Computes the URL which can be used to fetch the given path + /// from the hosted server. + /// + /// The path to fetch from the server + /// The URL that can be used to fetch the content + public string GetUrl(string? path = null) => $"http://localhost:{Port}{path ?? ""}"; + + /// + /// Fetches a request instance for the given path and method which + /// can then be configured before being passed to the + /// method. + /// + /// The path the request should fetch + /// The method to be used for the request, if not GET + /// The newly created request message + public HttpRequestMessage GetRequest(string? path = null, HttpMethod? method = null) + { return new HttpRequestMessage(method ?? HttpMethod.Get, GetUrl(path)); } - /// - /// Runs a GET request against the given path. - /// - /// The path to be fetched - /// The configured HTTP client to be used or null, if the default client should be used - /// The response returned by the server - public async Task GetResponseAsync(string? path = null, HttpClient? client = null) - { + /// + /// Runs a GET request against the given path. + /// + /// The path to be fetched + /// The configured HTTP client to be used or null, if the default client should be used + /// The response returned by the server + public async Task GetResponseAsync(string? path = null, HttpClient? client = null) + { var actualClient = client ?? _DefaultClient; return await actualClient.GetAsync(GetUrl(path)); } - /// - /// Executes the given request against the test server. - /// - /// The request to be executed - /// The configured HTTP client to be used or null, if the default client should be used - /// The response returned by the server - public async Task GetResponseAsync(HttpRequestMessage message, HttpClient? client = null) - { + /// + /// Executes the given request against the test server. + /// + /// The request to be executed + /// The configured HTTP client to be used or null, if the default client should be used + /// The response returned by the server + public async Task GetResponseAsync(HttpRequestMessage message, HttpClient? client = null) + { var actualClient = client ?? _DefaultClient; return await actualClient.SendAsync(message); } - public static HttpClient GetClient(bool ignoreSecurityErrors = false, bool followRedirects = false, - Version? protocolVersion = null, NetworkCredential? creds = null, CookieContainer? cookies = null) - { + public static HttpClient GetClient(bool ignoreSecurityErrors = false, bool followRedirects = false, + Version? protocolVersion = null, NetworkCredential? creds = null, CookieContainer? cookies = null) + { var handler = new HttpClientHandler { AllowAutoRedirect = followRedirects, @@ -195,14 +194,14 @@ public static HttpClient GetClient(bool ignoreSecurityErrors = false, bool follo return client; } - #endregion + #endregion - #region IDisposable Support + #region IDisposable Support - private bool disposed = false; + private bool disposed = false; - protected virtual void Dispose(bool disposing) - { + protected virtual void Dispose(bool disposing) + { if (!disposed) { if (disposing) @@ -214,18 +213,15 @@ protected virtual void Dispose(bool disposing) } } - /// - /// Stops the test host and releases all resources. - /// - public void Dispose() - { + /// + /// Stops the test host and releases all resources. + /// + public void Dispose() + { Dispose(true); GC.SuppressFinalize(this); } - #endregion - - } - + #endregion }
{name}{FileSizeFormatter.Format(size)}