diff --git a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs index bdd5e36..7988561 100644 --- a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs +++ b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs @@ -93,7 +93,7 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable }, HttpRequest = new ForwarderRequestConfig { - ActivityTimeout = directRoute.IsWebsocket ? TimeSpan.FromHours(24) : TimeSpan.FromMinutes(2) + ActivityTimeout = directRoute.IsWebSocket ? TimeSpan.FromHours(24) : TimeSpan.FromMinutes(2) } }; clusters.Add(cluster); @@ -104,7 +104,7 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable RouteId = $"direct-{directRoute.Service}-{directRoute.Path.Replace("/", "-")}", ClusterId = directRoute.Service, Match = new RouteMatch { Path = directRoute.Path }, - Timeout = directRoute.IsWebsocket ? null : TimeSpan.FromSeconds(5), + Timeout = directRoute.IsWebSocket ? null : TimeSpan.FromSeconds(5), }; routes.Add(route); _logger.LogInformation(" Added Direct Route: {Path} -> {Service}", directRoute.Path, @@ -232,7 +232,7 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable { public required string Path { get; set; } public required string Service { get; set; } - public bool IsWebsocket { get; set; } = false; + public bool IsWebSocket { get; set; } = false; } public virtual void Dispose() diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index b1f6d63..4dbafa7 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -47,6 +47,8 @@ using (var scope = app.Services.CreateScope()) // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath); +app.MapGatewayProxy(); + app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html")); // Configure gRPC diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index c813c91..89f6c1c 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -29,6 +29,7 @@ + diff --git a/DysonNetwork.Shared/Registry/GatewayReverseProxy.cs b/DysonNetwork.Shared/Registry/GatewayReverseProxy.cs new file mode 100644 index 0000000..5e7a1c0 --- /dev/null +++ b/DysonNetwork.Shared/Registry/GatewayReverseProxy.cs @@ -0,0 +1,83 @@ +using System.Diagnostics; +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Forwarder; + +namespace DysonNetwork.Shared.Registry; + +public static class GatewayReverseProxy +{ + /// + /// Provides reverse proxy for DysonNetwork.Gateway. + /// Give the ability to the contained frontend to access other services via the gateway. + /// + /// The asp.net core application + /// The modified application + public static WebApplication MapGatewayProxy(this WebApplication app) + { + var httpClient = new HttpMessageInvoker(new SocketsHttpHandler + { + UseProxy = false, + AllowAutoRedirect = true, + AutomaticDecompression = DecompressionMethods.All, + UseCookies = true, + EnableMultipleHttp2Connections = true, + ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), + ConnectTimeout = TimeSpan.FromSeconds(15), + }); + + var transformer = new GatewayReverseProxyTransformer(); + var requestConfig = new ForwarderRequestConfig(); + + app.Map("/cgi/{**catch-all}", async (HttpContext context, IHttpForwarder forwarder) => + { + var registry = context.RequestServices.GetRequiredService(); + var gatewayUrl = await registry.GetServiceUrl("DysonNetwork.Gateway"); + if (gatewayUrl is null) + { + context.Response.StatusCode = 404; + await context.Response.WriteAsync("Gateway not found"); + return; + } + + var error = await forwarder.SendAsync( + context, + gatewayUrl, + httpClient, + requestConfig, + transformer + ); + if (error != ForwarderError.None) + { + var errorFeature = context.GetForwarderErrorFeature(); + var exception = errorFeature?.Exception; + context.Response.StatusCode = 502; + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync($"Gateway remote error: {exception?.Message}"); + } + }); + + return app; + } +} + +public class GatewayReverseProxyTransformer : HttpTransformer +{ + private const string Value = "/cgi"; + + public override ValueTask TransformRequestAsync( + HttpContext httpContext, + HttpRequestMessage proxyRequest, + string destinationPrefix, + CancellationToken cancellationToken + ) + { + httpContext.Request.Path = httpContext.Request.Path.StartsWithSegments(Value, out var remaining) + ? remaining + : httpContext.Request.Path; + + return Default.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/ServiceHelper.cs b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs similarity index 98% rename from DysonNetwork.Shared/Registry/ServiceHelper.cs rename to DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs index 6ae2adc..4ba8d39 100644 --- a/DysonNetwork.Shared/Registry/ServiceHelper.cs +++ b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection; namespace DysonNetwork.Shared.Registry; -public static class ServiceHelper +public static class ServiceInjectionHelper { public static IServiceCollection AddPusherService(this IServiceCollection services) { diff --git a/DysonNetwork.Shared/Registry/ServiceRegistry.cs b/DysonNetwork.Shared/Registry/ServiceRegistry.cs index 4562a89..8c2e0e4 100644 --- a/DysonNetwork.Shared/Registry/ServiceRegistry.cs +++ b/DysonNetwork.Shared/Registry/ServiceRegistry.cs @@ -45,4 +45,11 @@ public class ServiceRegistry(IEtcdClient etcd, ILogger logger) var key = $"/services/{serviceName}"; await etcd.DeleteAsync(key); } + + public async Task GetServiceUrl(string serviceName) + { + var key = $"/services/{serviceName}"; + var response = await etcd.GetAsync(key); + return response.Kvs.Count == 0 ? null : response.Kvs[0].Value.ToStringUtf8(); + } } \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/Startup.cs b/DysonNetwork.Shared/Registry/Startup.cs index a5e050c..e5cdeac 100644 --- a/DysonNetwork.Shared/Registry/Startup.cs +++ b/DysonNetwork.Shared/Registry/Startup.cs @@ -8,7 +8,8 @@ public static class RegistryStartup { public static IServiceCollection AddRegistryService( this IServiceCollection services, - IConfiguration configuration + IConfiguration configuration, + bool addForwarder = true ) { services.AddEtcdClient(options => @@ -19,6 +20,9 @@ public static class RegistryStartup services.AddSingleton(); services.AddHostedService(); + if (addForwarder) + services.AddHttpForwarder(); + return services; } } \ No newline at end of file diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 270aa92..b9e9d6e 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -51,10 +51,13 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -90,6 +93,8 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -98,6 +103,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -112,6 +118,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded