-
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4594d5c
commit 1502dd9
Showing
16 changed files
with
506 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
using GenHTTP.Adapters.AspNetCore.Mapping; | ||
|
||
using GenHTTP.Api.Content; | ||
using GenHTTP.Api.Infrastructure; | ||
|
||
using GenHTTP.Modules.ClientCaching; | ||
using GenHTTP.Modules.Compression; | ||
using GenHTTP.Modules.ErrorHandling; | ||
using GenHTTP.Modules.IO; | ||
|
||
using Microsoft.AspNetCore.Builder; | ||
|
||
namespace GenHTTP.Adapters.AspNetCore; | ||
|
||
public static class Adapter | ||
{ | ||
|
||
/// <summary> | ||
/// Registers the given handler to respond to requests to the specified path. | ||
/// </summary> | ||
/// <param name="app">The application to add the mapping to</param> | ||
/// <param name="path">The path to register the handler for</param> | ||
/// <param name="handler">The handler to be registered</param> | ||
/// <param name="companion">An object that will be informed about handled requests and any error</param> | ||
public static void Map(this WebApplication app, string path, IHandlerBuilder handler, IServerCompanion? companion = null) | ||
=> Map(app, path, handler.Build(), companion); | ||
|
||
/// <summary> | ||
/// Registers the given handler to respond to requests to the specified path. | ||
/// </summary> | ||
/// <param name="app">The application to add the mapping to</param> | ||
/// <param name="path">The path to register the handler for</param> | ||
/// <param name="handler">The handler to be registered</param> | ||
/// <param name="companion">An object that will be informed about handled requests and any error</param> | ||
public static void Map(this WebApplication app, string path, IHandler handler, IServerCompanion? companion = null) | ||
=> app.Map(path + "/{*any}", async (context) => await Bridge.MapAsync(context, handler, companion: companion, registeredPath: path)); | ||
|
||
/// <summary> | ||
/// Registers the given handler to respond to any request. | ||
/// </summary> | ||
/// <param name="app">The application to be configured</param> | ||
/// <param name="handler">The handler to be registered</param> | ||
/// <param name="server">The server instance that would like to execute requests</param> | ||
public static void Run(this IApplicationBuilder app, IHandler handler, IServer server) | ||
=> app.Run(async (context) => await Bridge.MapAsync(context, handler, server)); | ||
|
||
/// <summary> | ||
/// Enables default features on the given handler. This should be used on the | ||
/// outer-most handler only. | ||
/// </summary> | ||
/// <param name="builder">The handler to be configured</param> | ||
/// <param name="errorHandling">If enabled, any exception will be catched and converted into an error response</param> | ||
/// <param name="compression">If enabled, responses will automatically be compressed if possible</param> | ||
/// <param name="clientCaching">If enabled, ETags are attached to any generated response and the tag is evaluated on the next request of the same resource</param> | ||
/// <param name="rangeSupport">If enabled, clients can request ranges instead of the complete response body</param> | ||
/// <typeparam name="T">The type of the handler builder which will be returned to allow the factory pattern</typeparam> | ||
/// <returns>The handler builder instance to be chained</returns> | ||
public static T Defaults<T>(this T builder, bool errorHandling = true, bool compression = true, bool clientCaching = true, bool rangeSupport = false) where T : IHandlerBuilder<T> | ||
{ | ||
if (compression) | ||
{ | ||
builder.Add(CompressedContent.Default()); | ||
} | ||
|
||
if (rangeSupport) | ||
{ | ||
builder.Add(RangeSupport.Create()); | ||
} | ||
|
||
if (clientCaching) | ||
{ | ||
builder.Add(ClientCache.Validation()); | ||
} | ||
|
||
if (errorHandling) | ||
{ | ||
builder.Add(ErrorHandler.Default()); | ||
} | ||
|
||
return builder; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
|
||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks> | ||
|
||
<LangVersion>13.0</LangVersion> | ||
<Nullable>enable</Nullable> | ||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
|
||
<AssemblyVersion>9.2.0.0</AssemblyVersion> | ||
<FileVersion>9.2.0.0</FileVersion> | ||
<Version>9.2.0</Version> | ||
|
||
<Authors>Andreas Nägeli</Authors> | ||
<Company/> | ||
|
||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | ||
<PackageProjectUrl>https://genhttp.org/</PackageProjectUrl> | ||
|
||
<Description>Adapter to run GenHTTP handlers within an ASP.NET Core app.</Description> | ||
<PackageTags>ASP.NET Core Adapter GenHTTP</PackageTags> | ||
|
||
<PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
<IncludeSymbols>true</IncludeSymbols> | ||
<SymbolPackageFormat>snupkg</SymbolPackageFormat> | ||
|
||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | ||
<NoWarn>CS1591,CS1587,CS1572,CS1573</NoWarn> | ||
|
||
<PackageIcon>icon.png</PackageIcon> | ||
|
||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
|
||
<None Include="..\..\LICENSE" Pack="true" PackagePath="\"/> | ||
<None Include="..\..\Resources\icon.png" Pack="true" PackagePath="\"/> | ||
|
||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
|
||
<FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
|
||
<ProjectReference Include="..\..\API\GenHTTP.Api.csproj"/> | ||
|
||
<ProjectReference Include="..\..\Engine\Shared\GenHTTP.Engine.Shared.csproj" /> | ||
|
||
<ProjectReference Include="..\..\Modules\ClientCaching\GenHTTP.Modules.ClientCaching.csproj" /> | ||
|
||
<ProjectReference Include="..\..\Modules\Compression\GenHTTP.Modules.Compression.csproj" /> | ||
|
||
<ProjectReference Include="..\..\Modules\IO\GenHTTP.Modules.IO.csproj"/> | ||
<ProjectReference Include="..\..\Modules\ErrorHandling\GenHTTP.Modules.ErrorHandling.csproj"/> | ||
|
||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/> | ||
|
||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
using GenHTTP.Adapters.AspNetCore.Server; | ||
using GenHTTP.Adapters.AspNetCore.Types; | ||
using GenHTTP.Api.Content; | ||
using GenHTTP.Api.Infrastructure; | ||
using GenHTTP.Api.Protocol; | ||
using Microsoft.AspNetCore.Http; | ||
|
||
namespace GenHTTP.Adapters.AspNetCore.Mapping; | ||
|
||
public static class Bridge | ||
{ | ||
|
||
public static async ValueTask MapAsync(HttpContext context, IHandler handler, IServer? server = null, IServerCompanion? companion = null, string? registeredPath = null) | ||
{ | ||
var actualServer = server ?? new ImplicitServer(handler, companion); | ||
|
||
try | ||
{ | ||
using var request = new Request(actualServer, context); | ||
|
||
if (registeredPath != null) | ||
{ | ||
AdvanceTo(request, registeredPath); | ||
} | ||
|
||
using var response = await handler.HandleAsync(request); | ||
|
||
if (response == null) | ||
{ | ||
context.Response.StatusCode = 404; | ||
} | ||
else | ||
{ | ||
await WriteAsync(response, context); | ||
|
||
actualServer.Companion?.OnRequestHandled(request, response); | ||
} | ||
} | ||
catch (Exception e) | ||
{ | ||
actualServer.Companion?.OnServerError(ServerErrorScope.ServerConnection, context.Connection.RemoteIpAddress, e); | ||
throw; | ||
} | ||
} | ||
|
||
private static async ValueTask WriteAsync(IResponse response, HttpContext context) | ||
{ | ||
var target = context.Response; | ||
|
||
target.StatusCode = response.Status.RawStatus; | ||
|
||
foreach (var header in response.Headers) | ||
{ | ||
target.Headers.Append(header.Key, header.Value); | ||
} | ||
|
||
if (response.Modified != null) | ||
{ | ||
target.Headers.LastModified = response.Modified.Value.ToUniversalTime().ToString("r"); | ||
} | ||
|
||
if (response.Expires != null) | ||
{ | ||
target.Headers.Expires = response.Expires.Value.ToUniversalTime().ToString("r"); | ||
} | ||
|
||
if (response.HasCookies) | ||
{ | ||
foreach (var cookie in response.Cookies) | ||
{ | ||
if (cookie.Value.MaxAge != null) | ||
{ | ||
target.Cookies.Append(cookie.Key, cookie.Value.Value, new() | ||
{ | ||
MaxAge = TimeSpan.FromSeconds(cookie.Value.MaxAge.Value) | ||
}); | ||
} | ||
else | ||
{ | ||
target.Cookies.Append(cookie.Key, cookie.Value.Value); | ||
} | ||
} | ||
} | ||
|
||
if (response.Content != null) | ||
{ | ||
target.ContentLength = (long?)response.ContentLength ?? (long?)response.Content.Length; | ||
|
||
target.ContentType = response.ContentType?.Charset != null ? $"{response.ContentType?.RawType}; charset={response.ContentType?.Charset}" : response.ContentType?.RawType; | ||
|
||
if (response.ContentEncoding != null) | ||
{ | ||
target.Headers.ContentEncoding = response.ContentEncoding; | ||
} | ||
|
||
await response.Content.WriteAsync(target.Body, 65 * 1024); | ||
} | ||
} | ||
|
||
private static void AdvanceTo(Request request, string registeredPath) | ||
{ | ||
var parts = registeredPath.Split('/', StringSplitOptions.RemoveEmptyEntries); | ||
|
||
foreach (var _ in parts) | ||
{ | ||
request.Target.Advance(); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using GenHTTP.Api.Infrastructure; | ||
|
||
namespace GenHTTP.Adapters.AspNetCore.Server; | ||
|
||
public class EmptyEndpoints : List<IEndPoint>, IEndPointCollection | ||
{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
using System.Runtime.InteropServices; | ||
|
||
using GenHTTP.Api.Content; | ||
using GenHTTP.Api.Infrastructure; | ||
|
||
namespace GenHTTP.Adapters.AspNetCore.Server; | ||
|
||
public sealed class ImplicitServer : IServer | ||
{ | ||
|
||
#region Get-/Setters | ||
|
||
public string Version => RuntimeInformation.FrameworkDescription; | ||
|
||
public bool Running { get; } | ||
|
||
public bool Development | ||
{ | ||
get | ||
{ | ||
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); | ||
return string.Compare(env, "Development", StringComparison.OrdinalIgnoreCase) == 0; | ||
} | ||
} | ||
|
||
public IEndPointCollection EndPoints { get; } | ||
|
||
public IServerCompanion? Companion { get; } | ||
|
||
public IHandler Handler { get; } | ||
|
||
#endregion | ||
|
||
#region Initialization | ||
|
||
public ImplicitServer(IHandler handler, IServerCompanion? companion) | ||
{ | ||
Handler = handler; | ||
Companion = companion; | ||
|
||
EndPoints = new EmptyEndpoints(); | ||
|
||
Running = true; | ||
} | ||
|
||
#endregion | ||
|
||
#region Functionality | ||
|
||
public ValueTask DisposeAsync() => new(); | ||
|
||
public ValueTask StartAsync() => throw new InvalidOperationException("Server is managed by ASP.NET Core and cannot be started"); | ||
|
||
#endregion | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 1 addition & 2 deletions
3
Engine/Kestrel/Types/Cookies.cs → Adapters/AspNetCore/Types/Cookies.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.