Gateway proxy for contained frontend to access other services

This commit is contained in:
2025-07-25 22:24:02 +08:00
parent a4b84f0717
commit d13fb8b0e4
8 changed files with 109 additions and 5 deletions

View File

@@ -29,6 +29,7 @@
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
<PackageReference Include="StackExchange.Redis" Version="2.8.41" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -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
{
/// <summary>
/// Provides reverse proxy for DysonNetwork.Gateway.
/// Give the ability to the contained frontend to access other services via the gateway.
/// </summary>
/// <param name="app">The asp.net core application</param>
/// <returns>The modified application</returns>
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<ServiceRegistry>();
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);
}
}

View File

@@ -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)
{

View File

@@ -45,4 +45,11 @@ public class ServiceRegistry(IEtcdClient etcd, ILogger<ServiceRegistry> logger)
var key = $"/services/{serviceName}";
await etcd.DeleteAsync(key);
}
public async Task<string?> GetServiceUrl(string serviceName)
{
var key = $"/services/{serviceName}";
var response = await etcd.GetAsync(key);
return response.Kvs.Count == 0 ? null : response.Kvs[0].Value.ToStringUtf8();
}
}

View File

@@ -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<ServiceRegistry>();
services.AddHostedService<RegistryHostedService>();
if (addForwarder)
services.AddHttpForwarder();
return services;
}
}