From bacb82321500a49bcd853d2f9061c2e8306adedb Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 14 Nov 2023 16:45:30 -0800 Subject: [PATCH 1/3] Minimal APIs --- docs/docfx/articles/ab-testing.md | 46 ++--- docs/docfx/articles/authn-authz.md | 29 +-- docs/docfx/articles/config-files.md | 37 ++-- docs/docfx/articles/config-providers.md | 1 - docs/docfx/articles/cors.md | 27 +-- docs/docfx/articles/diagnosing-yarp-issues.md | 184 ++++++++---------- docs/docfx/articles/direct-forwarding.md | 130 ++++++++----- docs/docfx/articles/http-client-config.md | 75 ++++--- docs/docfx/articles/http3.md | 38 +--- docs/docfx/articles/lets-encrypt.md | 26 +-- docs/docfx/articles/middleware.md | 24 +-- docs/docfx/articles/rate-limiting.md | 35 ++-- docs/docfx/articles/runtimes.md | 3 +- docs/docfx/articles/session-affinity.md | 2 +- reverse-proxy.sln | 11 -- samples/BasicYarpSample/Program.cs | 38 ++-- samples/BasicYarpSample/README.md | 3 +- samples/BasicYarpSample/Startup.cs | 47 ----- .../Combined/Program.cs | 55 +++--- .../Combined/Startup.cs | 46 ----- .../Ingress/Program.cs | 51 ++--- .../Ingress/Startup.cs | 49 ----- .../Monitor/Program.cs | 58 +++--- .../Monitor/Startup.cs | 51 ----- .../backend/Program.cs | 48 +++-- .../backend/Startup.cs | 61 ------ samples/Prometheus/HttpLoadApp/Program.cs | 48 ++--- .../Program.cs | 49 +++-- .../Startup.cs | 58 ------ samples/ReverseProxy.Auth.Sample/Program.cs | 98 ++++++++-- samples/ReverseProxy.Auth.Sample/README.md | 2 +- samples/ReverseProxy.Auth.Sample/Startup.cs | 112 ----------- samples/ReverseProxy.Code.Sample/Program.cs | 115 +++++++++-- samples/ReverseProxy.Code.Sample/README.md | 13 +- samples/ReverseProxy.Code.Sample/Startup.cs | 124 ------------ samples/ReverseProxy.Config.Sample/Program.cs | 37 ++-- samples/ReverseProxy.Config.Sample/Startup.cs | 48 ----- .../Program.cs | 38 ++-- .../Startup.cs | 50 ----- samples/ReverseProxy.Direct.Sample/Program.cs | 118 +++++++++-- samples/ReverseProxy.Direct.Sample/README.md | 2 +- samples/ReverseProxy.Direct.Sample/Startup.cs | 128 ------------ .../Program.cs | 41 ++-- .../ReverseProxy.LetsEncrypt.Sample/README.md | 2 +- .../Startup.cs | 49 ----- .../ReverseProxy.Metrics.Sample/Program.cs | 64 +++--- samples/ReverseProxy.Metrics.Sample/README.md | 2 +- .../ReverseProxy.Metrics.Sample/Startup.cs | 69 ------- .../ReverseProxy.Minimal.Sample/Program.cs | 17 -- .../Properties/launchSettings.json | 12 -- samples/ReverseProxy.Minimal.Sample/README.md | 24 --- .../ReverseProxy.Minimal.Sample.csproj | 14 -- .../appsettings.Development.json | 9 - .../appsettings.json | 30 --- .../ReverseProxy.Transforms.Sample/Program.cs | 62 +++--- .../ReverseProxy.Transforms.Sample/Startup.cs | 74 ------- samples/SampleServer/Program.cs | 36 ++-- samples/SampleServer/Startup.cs | 38 ---- testassets/BenchmarkApp/Program.cs | 65 +++++-- testassets/BenchmarkApp/Startup.cs | 63 ------ testassets/ReverseProxy.Code/Program.cs | 159 +++++++++++++-- testassets/ReverseProxy.Code/Startup.cs | 162 --------------- testassets/ReverseProxy.Config/Program.cs | 53 +++-- .../ReverseProxy.Config.csproj | 2 +- testassets/ReverseProxy.Config/Startup.cs | 71 ------- testassets/ReverseProxy.Direct/Program.cs | 117 ++++++++--- .../ReverseProxy.Direct.csproj | 2 +- testassets/ReverseProxy.Direct/Startup.cs | 98 ---------- testassets/TestClient/Program.cs | 150 +++++++------- testassets/TestClient/TestClient.csproj | 2 +- testassets/TestServer/Program.cs | 33 ++-- testassets/TestServer/Startup.cs | 41 ---- testassets/TestServer/TestServer.csproj | 2 +- 73 files changed, 1270 insertions(+), 2508 deletions(-) delete mode 100644 samples/BasicYarpSample/Startup.cs delete mode 100644 samples/KubernetesIngress.Sample/Combined/Startup.cs delete mode 100644 samples/KubernetesIngress.Sample/Ingress/Startup.cs delete mode 100644 samples/KubernetesIngress.Sample/Monitor/Startup.cs delete mode 100644 samples/KubernetesIngress.Sample/backend/Startup.cs delete mode 100644 samples/Prometheus/ReverseProxy.Metrics-Prometheus.Sample/Startup.cs delete mode 100644 samples/ReverseProxy.Auth.Sample/Startup.cs delete mode 100644 samples/ReverseProxy.Code.Sample/Startup.cs delete mode 100644 samples/ReverseProxy.Config.Sample/Startup.cs delete mode 100644 samples/ReverseProxy.ConfigFilter.Sample/Startup.cs delete mode 100644 samples/ReverseProxy.Direct.Sample/Startup.cs delete mode 100644 samples/ReverseProxy.LetsEncrypt.Sample/Startup.cs delete mode 100644 samples/ReverseProxy.Metrics.Sample/Startup.cs delete mode 100644 samples/ReverseProxy.Minimal.Sample/Program.cs delete mode 100644 samples/ReverseProxy.Minimal.Sample/Properties/launchSettings.json delete mode 100644 samples/ReverseProxy.Minimal.Sample/README.md delete mode 100644 samples/ReverseProxy.Minimal.Sample/ReverseProxy.Minimal.Sample.csproj delete mode 100644 samples/ReverseProxy.Minimal.Sample/appsettings.Development.json delete mode 100644 samples/ReverseProxy.Minimal.Sample/appsettings.json delete mode 100644 samples/ReverseProxy.Transforms.Sample/Startup.cs delete mode 100644 samples/SampleServer/Startup.cs delete mode 100644 testassets/BenchmarkApp/Startup.cs delete mode 100644 testassets/ReverseProxy.Code/Startup.cs delete mode 100644 testassets/ReverseProxy.Config/Startup.cs delete mode 100644 testassets/ReverseProxy.Direct/Startup.cs delete mode 100644 testassets/TestServer/Startup.cs diff --git a/docs/docfx/articles/ab-testing.md b/docs/docfx/articles/ab-testing.md index 5a4f52fd6..dbf4620a8 100644 --- a/docs/docfx/articles/ab-testing.md +++ b/docs/docfx/articles/ab-testing.md @@ -7,34 +7,28 @@ A/B testing and rolling upgrades require procedures for dynamically assigning in ## Example ``` - public void Configure(IApplicationBuilder app, IProxyStateLookup lookup) +app.MapReverseProxy(proxyPipeline => +{ + // Custom cluster selection + proxyPipeline.Use((context, next) => { - app.UseRouting(); - app.UseEndpoints(endpoints => + context.RequestServices.GetRequiredService(); + if (lookup.TryGetCluster(ChooseCluster(context), out var cluster)) { - endpoints.MapReverseProxy(proxyPipeline => - { - // Custom cluster selection - proxyPipeline.Use((context, next) => - { - if (lookup.TryGetCluster(ChooseCluster(context), out var cluster)) - { - context.ReassignProxyRequest(cluster); - } - - return next(); - }); - proxyPipeline.UseSessionAffinity(); - proxyPipeline.UseLoadBalancing(); - }); - }); - } - - private string ChooseCluster(HttpContext context) - { - // Decide which cluster to use. This could be random, weighted, based on headers, etc. - return Random.Shared.Next(2) == 1 ? "cluster1" : "cluster2"; - } + context.ReassignProxyRequest(cluster); + } + + return next(); + }); + proxyPipeline.UseSessionAffinity(); + proxyPipeline.UseLoadBalancing(); +}); + +private string ChooseCluster(HttpContext context) +{ + // Decide which cluster to use. This could be random, weighted, based on headers, etc. + return Random.Shared.Next(2) == 1 ? "cluster1" : "cluster2"; +} ``` ## Usage diff --git a/docs/docfx/articles/authn-authz.md b/docs/docfx/articles/authn-authz.md index 76b3d57a4..bec6c4fc3 100644 --- a/docs/docfx/articles/authn-authz.md +++ b/docs/docfx/articles/authn-authz.md @@ -38,33 +38,22 @@ Example: [Authorization policies](https://docs.microsoft.com/aspnet/core/security/authorization/policies) are an ASP.NET Core concept that the proxy utilizes. The proxy provides the above configuration to specify a policy per route and the rest is handled by existing ASP.NET Core authentication and authorization components. -Authorization policies can be configured in Startup.ConfigureServices as follows: +Authorization policies can be configured in the application as follows: ``` -public void ConfigureServices(IServiceCollection services) +services.AddAuthorization(options => { - services.AddAuthorization(options => - { - options.AddPolicy("customPolicy", policy => - policy.RequireAuthenticatedUser()); - }); -} + options.AddPolicy("customPolicy", policy => + policy.RequireAuthenticatedUser()); +}); ``` -In Startup.Configure add the Authorization and Authentication middleware between Routing and Endpoints. +In Program.cs add the Authorization and Authentication middleware. ``` -public void Configure(IApplicationBuilder app) -{ - app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); -} +app.MapReverseProxy(); ``` See the [Authentication](https://docs.microsoft.com/aspnet/core/security/authentication/) docs for setting up your preferred kind of authentication. diff --git a/docs/docfx/articles/config-files.md b/docs/docfx/articles/config-files.md index ad31914c2..9674727dc 100644 --- a/docs/docfx/articles/config-files.md +++ b/docs/docfx/articles/config-files.md @@ -4,34 +4,25 @@ The reverse proxy can load configuration for routes and clusters from files using the IConfiguration abstraction from Microsoft.Extensions. The examples given here use JSON, but any IConfiguration source should work. The configuration will also be updated without restarting the proxy if the source file changes. ## Loading Configuration -To load the proxy configuration from IConfiguration add the following code in Startup: +To load the proxy configuration from IConfiguration add the following code in Program.cs: ```c# -public IConfiguration Configuration { get; } +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; -public Startup(IConfiguration configuration) -{ - Configuration = configuration; -} +var builder = WebApplication.CreateBuilder(args); -public void ConfigureServices(IServiceCollection services) -{ - services.AddReverseProxy() - .LoadFromConfig(Configuration.GetSection("ReverseProxy")); -} +builder.Services.AddControllers(); +// Add the reverse proxy capability to the server +builder.Services.AddReverseProxy() + // Initialize the reverse proxy from the "ReverseProxy" section of configuration + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); -public void Configure(IApplicationBuilder app, IWebHostEnvironment env) -{ - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } +var app = builder.Build(); - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); -} +// Register the reverse proxy routes +app.MapReverseProxy(); + +app.Run(); ``` **Note**: For details about middleware ordering see [here](https://docs.microsoft.com/aspnet/core/fundamentals/middleware/#middleware-order). diff --git a/docs/docfx/articles/config-providers.md b/docs/docfx/articles/config-providers.md index 34ef6fe99..4516c6fbe 100644 --- a/docs/docfx/articles/config-providers.md +++ b/docs/docfx/articles/config-providers.md @@ -78,7 +78,6 @@ As of 1.1, YARP supports loading the proxy configuration from multiple sources. ``` or ``` - services.AddReverseProxy() .LoadFromMemory(routes, clusters) .LoadFromConfig(Configuration.GetSection("ReverseProxy")); diff --git a/docs/docfx/articles/cors.md b/docs/docfx/articles/cors.md index 8d8a73b29..c7a0c23fd 100644 --- a/docs/docfx/articles/cors.md +++ b/docs/docfx/articles/cors.md @@ -38,34 +38,23 @@ Example: [CORS policies](https://docs.microsoft.com/aspnet/core/security/cors#cors-with-named-policy-and-middleware) are an ASP.NET Core concept that the proxy utilizes. The proxy provides the above configuration to specify a policy per route and the rest is handled by existing ASP.NET Core CORS Middleware. -CORS policies can be configured in Startup.ConfigureServices as follows: +CORS policies can be configured in the application as follows: ``` -public void ConfigureServices(IServiceCollection services) +services.AddCors(options => { - services.AddCors(options => + options.AddPolicy("customPolicy", builder => { - options.AddPolicy("customPolicy", builder => - { - builder.AllowAnyOrigin(); - }); + builder.AllowAnyOrigin(); }); -} +}); ``` -In Startup.Configure add the CORS middleware between Routing and Endpoints. +In then add the CORS middleware. ``` -public void Configure(IApplicationBuilder app) -{ - app.UseRouting(); - - app.UseCors(); +app.UseCors(); - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); -} +app.MapReverseProxy(); ``` diff --git a/docs/docfx/articles/diagnosing-yarp-issues.md b/docs/docfx/articles/diagnosing-yarp-issues.md index c495e7082..4e263bba1 100644 --- a/docs/docfx/articles/diagnosing-yarp-issues.md +++ b/docs/docfx/articles/diagnosing-yarp-issues.md @@ -54,19 +54,11 @@ The above gives general information about the request and how it was processed. ASP.NET includes a middleware component that can be used to provide more details about the request and response. The `UseHttpLogging` component can be added to the request pipeline. It will add additional entries to the log detailing the incoming and outgoing request headers. ``` C# -// This method gets called by the runtime. Use this method to configure the HTTP request -// pipeline that handles requests -public void Configure(IApplicationBuilder app, IWebHostEnvironment env) -{ - app.UseHttpLogging(); - // Enable endpoint routing, required for the reverse proxy - app.UseRouting(); - // Register the reverse proxy routes - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); -} +app.UseHttpLogging(); +// Enable endpoint routing, required for the reverse proxy +app.UseRouting(); +// Register the reverse proxy routes +app.MapReverseProxy(); ``` For example: @@ -112,62 +104,59 @@ To use either of these you create a class implementing a [telemetry interface](h ```C# public class ForwarderTelemetry : IForwarderTelemetryConsumer { - /// Called before forwarding a request. - public void OnForwarderStart(DateTime timestamp, string destinationPrefix) - { - Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderStart :: Destination prefix: {destinationPrefix}"); - } - - /// Called after forwarding a request. - public void OnForwarderStop(DateTime timestamp, int statusCode) - { - Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderStop :: Status: {statusCode}"); - } - - /// Called before if forwarding the request failed. - public void OnForwarderFailed(DateTime timestamp, ForwarderError error) - { - Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderFailed :: Error: {error.ToString()}"); - } - - /// Called when reaching a given stage of forwarding a request. - public void OnForwarderStage(DateTime timestamp, ForwarderStage stage) - { - Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderStage :: Stage: {stage.ToString()}"); - } - - /// Called periodically while a content transfer is active. - public void OnContentTransferring(DateTime timestamp, bool isRequest, long contentLength, long iops, TimeSpan readTime, TimeSpan writeTime) - { - Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnContentTransferring :: Is request: {isRequest}, Content length: {contentLength}, IOps: {iops}, Read time: {readTime:s\\.fff}, Write time: {writeTime:s\\.fff}"); - } - - /// Called after transferring the request or response content. - public void OnContentTransferred(DateTime timestamp, bool isRequest, long contentLength, long iops, TimeSpan readTime, TimeSpan writeTime, TimeSpan firstReadTime) - { - Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnContentTransferred :: Is request: {isRequest}, Content length: {contentLength}, IOps: {iops}, Read time: {readTime:s\\.fff}, Write time: {writeTime:s\\.fff}"); - } - - /// Called before forwarding a request from `ForwarderMiddleware`, therefore is not called for direct forwarding scenarios. - public void OnForwarderInvoke(DateTime timestamp, string clusterId, string routeId, string destinationId) - { - Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderInvoke:: Cluster id: {clusterId}, Route Id: { routeId}, Destination: {destinationId}"); - } + /// Called before forwarding a request. + public void OnForwarderStart(DateTime timestamp, string destinationPrefix) + { + Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderStart :: Destination prefix: {destinationPrefix}"); + } + + /// Called after forwarding a request. + public void OnForwarderStop(DateTime timestamp, int statusCode) + { + Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderStop :: Status: {statusCode}"); + } + + /// Called before if forwarding the request failed. + public void OnForwarderFailed(DateTime timestamp, ForwarderError error) + { + Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderFailed :: Error: {error.ToString()}"); + } + + /// Called when reaching a given stage of forwarding a request. + public void OnForwarderStage(DateTime timestamp, ForwarderStage stage) + { + Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderStage :: Stage: {stage.ToString()}"); + } + + /// Called periodically while a content transfer is active. + public void OnContentTransferring(DateTime timestamp, bool isRequest, long contentLength, long iops, TimeSpan readTime, TimeSpan writeTime) + { + Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnContentTransferring :: Is request: {isRequest}, Content length: {contentLength}, IOps: {iops}, Read time: {readTime:s\\.fff}, Write time: {writeTime:s\\.fff}"); + } + + /// Called after transferring the request or response content. + public void OnContentTransferred(DateTime timestamp, bool isRequest, long contentLength, long iops, TimeSpan readTime, TimeSpan writeTime, TimeSpan firstReadTime) + { + Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnContentTransferred :: Is request: {isRequest}, Content length: {contentLength}, IOps: {iops}, Read time: {readTime:s\\.fff}, Write time: {writeTime:s\\.fff}"); + } + + /// Called before forwarding a request from `ForwarderMiddleware`, therefore is not called for direct forwarding scenarios. + public void OnForwarderInvoke(DateTime timestamp, string clusterId, string routeId, string destinationId) + { + Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => OnForwarderInvoke:: Cluster id: {clusterId}, Route Id: { routeId}, Destination: {destinationId}"); + } } ``` -And then register the class as part of `ConfigureServices`, for example: +And then register the class as part of services, for example: ```C# -public void ConfigureServices(IServiceCollection services) -{ - services.AddTelemetryConsumer(); +services.AddTelemetryConsumer(); - // Add the reverse proxy to capability to the server - var proxyBuilder = services.AddReverseProxy(); - // Initialize the reverse proxy from the "ReverseProxy" section of configuration - proxyBuilder.LoadFromConfig(Configuration.GetSection("ReverseProxy")); -} +// Add the reverse proxy to capability to the server +var proxyBuilder = services.AddReverseProxy(); +// Initialize the reverse proxy from the "ReverseProxy" section of configuration +proxyBuilder.LoadFromConfig(Configuration.GetSection("ReverseProxy")); ``` Then you will log details on each part of the request, for example: @@ -184,22 +173,18 @@ Forwarder Telemetry [06:41:03.655] => OnForwarderStop :: Status: 200 The events for Telemetry are fired as they occur, so you can [fish out the HttpContext](https://docs.microsoft.com/aspnet/core/fundamentals/http-context#use-httpcontext-from-custom-components) and the YARP feature from it: ``` C# -public void ConfigureServices(IServiceCollection services) -{ - services.AddTelemetryConsumer(); - services.AddHttpContextAccessor(); - ... -} - +services.AddTelemetryConsumer(); +services.AddHttpContextAccessor(); +... public void OnForwarderInvoke(DateTime timestamp, string clusterId, string routeId, string destinationId) { - var context = new HttpContextAccessor().HttpContext; - var YarpFeature = context.GetReverseProxyFeature(); + var context = new HttpContextAccessor().HttpContext; + var YarpFeature = context.GetReverseProxyFeature(); - var dests = from d in YarpFeature.AvailableDestinations - select d.Model.Config.Address; + var dests = from d in YarpFeature.AvailableDestinations + select d.Model.Config.Address; - Console.WriteLine($"Destinations: {string.Join(", ", dests)}"); + Console.WriteLine($"Destinations: {string.Join(", ", dests)}"); } ``` @@ -208,37 +193,30 @@ public void OnForwarderInvoke(DateTime timestamp, string clusterId, string route Another way to inspect the state for requests is to insert additional middleware into the request pipeline. You can insert between the other stages to see the state of the request. ```C# -public void Configure(IApplicationBuilder app) +// We can customize the proxy pipeline and add/remove/replace steps +app.MapReverseProxy(proxyPipeline => { - app.UseRouting(); - app.UseEndpoints(endpoints => - { - // We can customize the proxy pipeline and add/remove/replace steps - endpoints.MapReverseProxy(proxyPipeline => - { - // Use a custom proxy middleware, defined below - proxyPipeline.Use(MyCustomProxyStep); - // Don't forget to include these two middleware when you make a custom proxy pipeline (if you need them). - proxyPipeline.UseSessionAffinity(); - proxyPipeline.UseLoadBalancing(); - }); - }); -} - + // Use a custom proxy middleware, defined below + proxyPipeline.Use(MyCustomProxyStep); + // Don't forget to include these two middleware when you make a custom proxy pipeline (if you need them). + proxyPipeline.UseSessionAffinity(); + proxyPipeline.UseLoadBalancing(); +}); +... public Task MyCustomProxyStep(HttpContext context, Func next) { - // Can read data from the request via the context - foreach (var header in context.Request.Headers) - { - Console.WriteLine($"{header.Key}: {header.Value}"); - } - - // The context also stores a ReverseProxyFeature which holds proxy specific data such as the cluster, route and destinations - var proxyFeature = context.GetReverseProxyFeature(); - Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(proxyFeature.Route.Config)); - - // Important - required to move to the next step in the proxy pipeline - return next(); + // Can read data from the request via the context + foreach (var header in context.Request.Headers) + { + Console.WriteLine($"{header.Key}: {header.Value}"); + } + + // The context also stores a ReverseProxyFeature which holds proxy specific data such as the cluster, route and destinations + var proxyFeature = context.GetReverseProxyFeature(); + Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(proxyFeature.Route.Config)); + + // Important - required to move to the next step in the proxy pipeline + return next(); } ``` diff --git a/docs/docfx/articles/direct-forwarding.md b/docs/docfx/articles/direct-forwarding.md index 1f38f7097..bdb8f60ed 100644 --- a/docs/docfx/articles/direct-forwarding.md +++ b/docs/docfx/articles/direct-forwarding.md @@ -32,47 +32,100 @@ See [ReverseProxy.Direct.Sample](https://github.com/microsoft/reverse-proxy/tree Follow the [Getting Started](xref:getting-started) guide to create a project and add the Yarp.ReverseProxy nuget dependency. -### Update Startup +### Update Program.cs -In this example the IHttpForwarder is registered in DI, injected into the `Startup.Configure` method, and used to forward requests from a specific route to `https://localhost:10000/prefix/`. +In this example the IHttpForwarder is registered in DI, injected into the endpoint method, and used to forward requests from a specific route to `https://localhost:10000/prefix/`. The optional transforms show how to copy all request headers except for the `Host`, it's common that the destination requires its own `Host` from the url. ```C# -public void ConfigureServices(IServiceCollection services) +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Forwarder; +using Yarp.ReverseProxy.Transforms; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddHttpForwarder(); + +var app = builder.Build(); + +// Configure our own HttpMessageInvoker for outbound calls for proxy operations +var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() { - services.AddHttpForwarder(); -} - -public void Configure(IApplicationBuilder app, IHttpForwarder forwarder) + UseProxy = false, + AllowAutoRedirect = false, + AutomaticDecompression = DecompressionMethods.None, + UseCookies = false, + ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), + ConnectTimeout = TimeSpan.FromSeconds(15), +}); + +// Setup our own request transform class +var transformer = new CustomTransformer(); // or HttpTransformer.Default; +var requestOptions = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; + +app.UseRouting(); + +var transformer = new CustomTransformer(); // or HttpTransformer.Default; +var requestConfig = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; + +// When using IHttpForwarder for direct forwarding you are responsible for routing, destination discovery, load balancing, affinity, etc.. +// For an alternate example that includes those features see BasicYarpSample. +app.Map("/test/{**catch-all}", async (HttpContext httpContext, IHttpForwarder forwarder) => { - var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() + var error = await forwarder.SendAsync(httpContext, "https://localhost:10000/", + httpClient, requestConfig, transformer); + // Check if the operation was successful + if (error != ForwarderError.None) { - UseProxy = false, - AllowAutoRedirect = false, - AutomaticDecompression = DecompressionMethods.None, - UseCookies = false, - ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), - ConnectTimeout = TimeSpan.FromSeconds(15), - }); - var transformer = new CustomTransformer(); // or HttpTransformer.Default; - var requestConfig = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; - - app.UseRouting(); - app.UseEndpoints(endpoints => + var errorFeature = httpContext.GetForwarderErrorFeature(); + var exception = errorFeature.Exception; + } +}); + +app.Run(); + +/// +/// Custom request transformation +/// +internal class CustomTransformer : HttpTransformer +{ + /// + /// A callback that is invoked prior to sending the proxied request. All HttpRequestMessage + /// fields are initialized except RequestUri, which will be initialized after the + /// callback if no value is provided. The string parameter represents the destination + /// URI prefix that should be used when constructing the RequestUri. The headers + /// are copied by the base implementation, excluding some protocol headers like HTTP/2 + /// pseudo headers (":authority"). + /// + /// The incoming request. + /// The outgoing proxy request. + /// The uri prefix for the selected destination server which can be used to create + /// the RequestUri. + public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) { - endpoints.Map("/{**catch-all}", async httpContext => - { - var error = await forwarder.SendAsync(httpContext, "https://localhost:10000/", - httpClient, requestConfig, transformer); - // Check if the operation was successful - if (error != ForwarderError.None) - { - var errorFeature = httpContext.GetForwarderErrorFeature(); - var exception = errorFeature.Exception; - } - }); - }); + // Copy all request headers + await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); + + // Customize the query string: + var queryContext = new QueryTransformContext(httpContext.Request); + queryContext.Collection.Remove("param1"); + queryContext.Collection["area"] = "xx2"; + + // Assign the custom uri. Be careful about extra slashes when concatenating here. RequestUtilities.MakeDestinationAddress is a safe default. + proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress("https://example.com", httpContext.Request.Path, queryContext.QueryString); + + // Suppress the original request header, use the one from the destination Uri. + proxyRequest.Headers.Host = null; + } } ``` @@ -102,18 +155,7 @@ private class CustomTransformer : HttpTransformer There are also [extension methods](xref:Microsoft.AspNetCore.Builder.DirectForwardingIEndpointRouteBuilderExtensions) available that simplify the mapping of IHttpForwarder to endpoints. ```C# -... - -public void Configure(IApplicationBuilder app, IHttpForwarder forwarder) -{ - ... - - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapForwarder("/{**catch-all}", "https://localhost:10000/", requestConfig, transformer, httpClient); - }); -} +app.MapForwarder("/{**catch-all}", "https://localhost:10000/", requestConfig, transformer, httpClient); ``` ### The HTTP Client diff --git a/docs/docfx/articles/http-client-config.md b/docs/docfx/articles/http-client-config.md index 78ca25775..0be64893e 100644 --- a/docs/docfx/articles/http-client-config.md +++ b/docs/docfx/articles/http-client-config.md @@ -55,18 +55,14 @@ Configuration settings: ``` Note that if you're using an encoding other than ASCII, you also need to set your server to accept requests and/or send responses with such headers. For example, when using Kestrel as the server, use [`KestrelServerOptions.RequestHeaderEncodingSelector`](https://docs.microsoft.com/dotnet/api/Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.RequestHeaderEncodingSelector) / [`.ResponseHeaderEncodingSelector`](https://docs.microsoft.com/dotnet/api/Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.ResponseHeaderEncodingSelector) to configure Kestrel to allow `Latin1` ("`iso-8859-1`") headers: ```C# -private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup() - .ConfigureKestrel(kestrel => - { - kestrel.RequestHeaderEncodingSelector = _ => Encoding.Latin1; - // and/or - kestrel.ResponseHeaderEncodingSelector = _ => Encoding.Latin1; - }); - }); +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.ConfigureKestrel(kestrel => +{ + kestrel.RequestHeaderEncodingSelector = _ => Encoding.Latin1; + // and/or + kestrel.ResponseHeaderEncodingSelector = _ => Encoding.Latin1; +}); + ``` - EnableMultipleHttp2Connections - enables opening additional HTTP/2 connections to the same server when the maximum number of concurrent streams is reached on all existing connections. The default is `true`. See [SocketsHttpHandler.EnableMultipleHttp2Connections](https://docs.microsoft.com/dotnet/api/system.net.http.socketshttphandler.enablemultiplehttp2connections) ```JSON @@ -156,36 +152,33 @@ HTTP client configuration uses the type [HttpClientConfig](xref:Yarp.ReverseProx The following is an example of `HttpClientConfig` using [code based](config-providers.md) configuration. An instance of `HttpClientConfig` is assigned to the [ClusterConfig.HttpClient](xref:Yarp.ReverseProxy.Configuration.ClusterConfig) property before passing the cluster array to `LoadFromMemory` method. ```C# -public void ConfigureServices(IServiceCollection services) +var routes = new[] { - var routes = new[] + new RouteConfig() { - new RouteConfig() + RouteId = "route1", + ClusterId = "cluster1", + Match = { - RouteId = "route1", - ClusterId = "cluster1", - Match = - { - Path = "{**catch-all}" - } + Path = "{**catch-all}" } - }; - var clusters = new[] + } +}; +var clusters = new[] +{ + new ClusterConfig() { - new ClusterConfig() + ClusterId = "cluster1", + Destinations = { - ClusterId = "cluster1", - Destinations = - { - { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } } - }, - HttpClient = new HttpClientConfig { MaxConnectionsPerServer = 10, SslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12 } - } - }; + { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } } + }, + HttpClient = new HttpClientConfig { MaxConnectionsPerServer = 10, SslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12 } + } +}; - services.AddReverseProxy() - .LoadFromMemory(routes, clusters); -} +services.AddReverseProxy() + .LoadFromMemory(routes, clusters); ``` ## Configuring the http client @@ -193,12 +186,12 @@ public void ConfigureServices(IServiceCollection services) `ConfigureHttpClient` provides a callback to customize the `SocketsHttpHandler` settings used for proxying requests. This will be called each time a cluster is added or changed. Cluster settings are applied to the handler before the callback. Custom data can be provided in the cluster metadata. This example shows adding a client certificate that will authenticate the proxy to the destination servers. ```C# - var clientCert = new X509Certificate2("path"); - services.AddReverseProxy() - .ConfigureHttpClient((context, handler) => - { - handler.SslOptions.ClientCertificates.Add(clientCert); - }) +var clientCert = new X509Certificate2("path"); +services.AddReverseProxy() + .ConfigureHttpClient((context, handler) => + { + handler.SslOptions.ClientCertificates.Add(clientCert); + }) ``` ## Custom IForwarderHttpClientFactory diff --git a/docs/docfx/articles/http3.md b/docs/docfx/articles/http3.md index 21e882777..3472a5bb1 100644 --- a/docs/docfx/articles/http3.md +++ b/docs/docfx/articles/http3.md @@ -1,47 +1,27 @@ # HTTP/3 ## Introduction -YARP 1.1 supports HTTP/3 for inbound and outbound connections using the HTTP/3 preview support in .NET 6. To enable the HTTP/3 protocol in YARP you need to: +YARP 1.1 supports HTTP/3 for inbound and outbound connections using the HTTP/3 support in .NET 7. To enable the HTTP/3 protocol in YARP you need to: - Configure inbound connections in Kestrel - Configure outbound connections in HttpClient -- Enable preview features ## Set up HTTP/3 on Kestrel Protocols are required in the listener options: ```C# -var myHostBuilder = Host.CreateDefaultBuilder(args); -myHostBuilder.ConfigureWebHostDefaults(webHostBuilder => +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.ConfigureKestrel(kestrel => +{ + kestrel.ListenAnyIP(443, portOptions => { - webHostBuilder.ConfigureKestrel(kestrel => - { - kestrel.ListenAnyIP(443, portOptions => - { - portOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; - portOptions.UseHttps(); - }); - }); - webHostBuilder.UseStartup(); + portOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + portOptions.UseHttps(); }); -``` -To use HTTP/3 with .NET 6, preview features need to be enabled via a setting in the project file: -```csproj - - True - +}); ``` ## HttpClient -There is the switch which enables HTTP/3 either programmatically: -```C# -AppContext.SetSwitch("System.Net.SocketsHttpHandler.Http3Support", true); -``` -or in project file: -```csproj - -``` - -In addition to this, the default version of HttpRequest should be replaced by "3", find more details about [HttpRequest configuration](http-client-config.md#httprequest). +The default version of HttpRequest should be replaced by "3", find more details about [HttpRequest configuration](http-client-config.md#httprequest). diff --git a/docs/docfx/articles/lets-encrypt.md b/docs/docfx/articles/lets-encrypt.md index d287ad8b0..4a382a3c9 100644 --- a/docs/docfx/articles/lets-encrypt.md +++ b/docs/docfx/articles/lets-encrypt.md @@ -38,18 +38,10 @@ There are required options for LettuceEncrypt that should be set, see the exampl } ``` -## Update Startup +## Update Services ```C# -using Microsoft.Extensions.DependencyInjection; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddLettuceEncrypt(); - } -} +services.AddLettuceEncrypt(); ``` For more options (i.e. saving certificates) see examples in [LettuceEncrypt doc](https://github.com/natemcmaster/LettuceEncrypt). @@ -61,20 +53,16 @@ If your project is explicitly using kestrel options to configure IP addresses, p Example: ```C# -var myHostBuilder = Host.CreateDefaultBuilder(args); -myHostBuilder.ConfigureWebHostDefaults(webHostBuilder => +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.ConfigureKestrel(kestrel => { - webHostBuilder.ConfigureKestrel(kestrel => + kestrel.ListenAnyIP(443, portOptions => { - kestrel.ListenAnyIP(443, portOptions => + portOptions.UseHttps(h => { - portOptions.UseHttps(h => - { - h.UseLettuceEncrypt(kestrel.ApplicationServices); - }); + h.UseLettuceEncrypt(kestrel.ApplicationServices); }); }); - webHostBuilder.UseStartup(); }); ``` diff --git a/docs/docfx/articles/middleware.md b/docs/docfx/articles/middleware.md index 879309b4f..acc1e2cb8 100644 --- a/docs/docfx/articles/middleware.md +++ b/docs/docfx/articles/middleware.md @@ -8,21 +8,12 @@ ASP.NET Core uses a [middleware pipeline](https://docs.microsoft.com/aspnet/core The [getting started](getting-started.md) sample shows the following Configure method. This sets up a middleware pipeline with development tools, routing, and proxy configured endpoints (`MapReverseProxy`). ```C# -public void Configure(IApplicationBuilder app, IWebHostEnvironment env) -{ - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - // Enable endpoint routing, required for the reverse proxy - app.UseRouting(); - // Register the reverse proxy routes - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); -} +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); +var app = builder.Build(); +app.MapReverseProxy(); +app.Run(); ``` The parmeterless `MapReverseProxy()` in [ReverseProxyIEndpointRouteBuilderExtensions](xref:Microsoft.AspNetCore.Builder.ReverseProxyIEndpointRouteBuilderExtensions) overload includes all standard proxy middleware for [session affinity](session-affinity.md), [load balancing](load-balancing.md), [passive health checks](dests-health-checks.md#passive-health-checks), and the final proxying of the request. Each of these check the configuration of the matched route, cluster, and destination and perform their task accordingly. @@ -34,7 +25,7 @@ Middleware added to your application pipeline will see the request in different [ReverseProxyIEndpointRouteBuilderExtensions](xref:Microsoft.AspNetCore.Builder.ReverseProxyIEndpointRouteBuilderExtensions) provides an overload of `MapReverseProxy` that lets you build a middleware pipeline that will run only for requests matched to proxy configured routes. ``` -endpoints.MapReverseProxy(proxyPipeline => +app.MapReverseProxy(proxyPipeline => { proxyPipeline.Use((context, next) => { @@ -155,4 +146,3 @@ Middleware MUST check `HttpResponse.HasStarted` before modifying response fields Middleware should avoid interacting with the request or response bodies. Bodies are not buffered by default, so interacting with them can prevent them from reaching their destinations. While enabling buffering is possible, it's discouraged as it can add significant memory and latency overhead. Using a wrapped, streaming approach is recommended if the body must be examined or modified. See the [ResponseCompression](https://github.com/dotnet/aspnetcore/blob/24588220006bc164b63293129cc94ac6292250e4/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs#L55-L73) middleware for an example. Middleware MUST NOT do any multi-threaded work on an individual request, `HttpContext` and its associated members are not thread safe. - diff --git a/docs/docfx/articles/rate-limiting.md b/docs/docfx/articles/rate-limiting.md index 39a488c48..34c392d8d 100644 --- a/docs/docfx/articles/rate-limiting.md +++ b/docs/docfx/articles/rate-limiting.md @@ -11,7 +11,7 @@ No rate limiting is performed on requests unless enabled in the route or applica Example: ```c# -builder.Services.AddRateLimiter(options => options.GlobalLimiter = globalLimiter); +services.AddRateLimiter(options => options.GlobalLimiter = globalLimiter); ``` ## Configuration @@ -45,37 +45,26 @@ Example: [RateLimiter policies](https://learn.microsoft.com/aspnet/core/performance/rate-limit) are an ASP.NET Core concept that the proxy utilizes. The proxy provides the above configuration to specify a policy per route and the rest is handled by existing ASP.NET Core rate limiting middleware. -RateLimiter policies can be configured in Startup.ConfigureServices as follows: +RateLimiter policies can be configured in services as follows: ```c# -public void ConfigureServices(IServiceCollection services) +services.AddRateLimiter(options => { - services.AddRateLimiter(options => + options.AddFixedWindowLimiter("customPolicy", opt => { - options.AddFixedWindowLimiter("customPolicy", opt => - { - opt.PermitLimit = 4; - opt.Window = TimeSpan.FromSeconds(12); - opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; - opt.QueueLimit = 2; - }); + opt.PermitLimit = 4; + opt.Window = TimeSpan.FromSeconds(12); + opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + opt.QueueLimit = 2; }); -} +}); ``` -In Startup.Configure add the RateLimiter middleware between Routing and Endpoints. +Then add the RateLimiter middleware. ```c# -public void Configure(IApplicationBuilder app) -{ - app.UseRouting(); - - app.UseRateLimiter(); +app.UseRateLimiter(); - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); -} +app.MapReverseProxy(); ``` See the [Rate Limiting](https://learn.microsoft.com/aspnet/core/performance/rate-limit) docs for setting up your preferred kind of rate limiting. diff --git a/docs/docfx/articles/runtimes.md b/docs/docfx/articles/runtimes.md index eda161d69..97eebf40a 100644 --- a/docs/docfx/articles/runtimes.md +++ b/docs/docfx/articles/runtimes.md @@ -5,7 +5,7 @@ title: Supported Runtimes # YARP Supported Runtimes -YARP 2.0 supports ASP.NET Core 6.0 and newer. You can download the .NET SDK from https://dotnet.microsoft.com/download/dotnet/. See [Releases](https://github.com/microsoft/reverse-proxy/releases) for specific version support. +YARP 2.0+ supports ASP.NET Core 6.0 and newer. You can download the .NET SDK from https://dotnet.microsoft.com/download/dotnet/. See [Releases](https://github.com/microsoft/reverse-proxy/releases) for specific version support. YARP is taking advantage of new .NET features and optimizations as they become available. This does mean that some features may not be available if you're running on the previous versions of ASP.NET. @@ -25,4 +25,3 @@ YARP is taking advantage of new .NET features and optimizations as they become a - [Zero-byte reads on HttpClient's response streams](https://github.com/dotnet/runtime/pull/61913) - reduces memory usage. - [Header allocation reductions](https://github.com/dotnet/runtime/pull/62981) - reduces memory usage. - [Kestrel Http/2 perf improvements](https://github.com/dotnet/aspnetcore/pull/40925) - Improve contention and throughput for multiple requests on one connection. - diff --git a/docs/docfx/articles/session-affinity.md b/docs/docfx/articles/session-affinity.md index bee6b9b19..2acfa2f3f 100644 --- a/docs/docfx/articles/session-affinity.md +++ b/docs/docfx/articles/session-affinity.md @@ -9,7 +9,7 @@ Session affinity services are registered in the DI container automatically by `A Example: ```C# -endpoints.MapReverseProxy(proxyPipeline => +app.MapReverseProxy(proxyPipeline => { proxyPipeline.UseSessionAffinity(); proxyPipeline.UseLoadBalancing(); diff --git a/reverse-proxy.sln b/reverse-proxy.sln index fa445642a..0e2a6b252 100644 --- a/reverse-proxy.sln +++ b/reverse-proxy.sln @@ -81,8 +81,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yarp.Kubernetes.Ingress", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "backend", "samples\KubernetesIngress.Sample\backend\backend.csproj", "{EB5663B9-31BA-4930-A302-4B87B8DEB74A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReverseProxy.Minimal.Sample", "samples\ReverseProxy.Minimal.Sample\ReverseProxy.Minimal.Sample.csproj", "{AA34BE13-7193-4036-A886-A7EE6CD36940}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpSys.Sample", "HttpSys.Sample", "{34CB3C61-2F42-49FB-ACDC-2B0EC9EE8746}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleHttpSysServer", "samples\ReverseProxy.HttpSysDelegation.Sample\SampleHttpSysServer\SampleHttpSysServer.csproj", "{4A298B67-E7B5-4010-A6F2-5CFBB82BFBB1}" @@ -317,14 +315,6 @@ Global {EB5663B9-31BA-4930-A302-4B87B8DEB74A}.Release|Any CPU.Build.0 = Release|Any CPU {EB5663B9-31BA-4930-A302-4B87B8DEB74A}.Release|x64.ActiveCfg = Release|Any CPU {EB5663B9-31BA-4930-A302-4B87B8DEB74A}.Release|x64.Build.0 = Release|Any CPU - {AA34BE13-7193-4036-A886-A7EE6CD36940}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA34BE13-7193-4036-A886-A7EE6CD36940}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA34BE13-7193-4036-A886-A7EE6CD36940}.Debug|x64.ActiveCfg = Debug|Any CPU - {AA34BE13-7193-4036-A886-A7EE6CD36940}.Debug|x64.Build.0 = Debug|Any CPU - {AA34BE13-7193-4036-A886-A7EE6CD36940}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA34BE13-7193-4036-A886-A7EE6CD36940}.Release|Any CPU.Build.0 = Release|Any CPU - {AA34BE13-7193-4036-A886-A7EE6CD36940}.Release|x64.ActiveCfg = Release|Any CPU - {AA34BE13-7193-4036-A886-A7EE6CD36940}.Release|x64.Build.0 = Release|Any CPU {4A298B67-E7B5-4010-A6F2-5CFBB82BFBB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4A298B67-E7B5-4010-A6F2-5CFBB82BFBB1}.Debug|Any CPU.Build.0 = Debug|Any CPU {4A298B67-E7B5-4010-A6F2-5CFBB82BFBB1}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -407,7 +397,6 @@ Global {04E87669-7E7B-4217-83A4-EF80B534D14B} = {0B4635FC-8B63-4408-8077-F1CFDA5AB6D7} {84B920E2-A501-457D-8F1A-9CC7AF8B5F2D} = {04E87669-7E7B-4217-83A4-EF80B534D14B} {EB5663B9-31BA-4930-A302-4B87B8DEB74A} = {32B6B967-6EAF-42A1-9FD6-A59F752FF76B} - {AA34BE13-7193-4036-A886-A7EE6CD36940} = {149C61A2-D9F8-49B9-9F9B-3C953FEF53AA} {34CB3C61-2F42-49FB-ACDC-2B0EC9EE8746} = {149C61A2-D9F8-49B9-9F9B-3C953FEF53AA} {4A298B67-E7B5-4010-A6F2-5CFBB82BFBB1} = {34CB3C61-2F42-49FB-ACDC-2B0EC9EE8746} {7AA90031-4644-4F38-9790-60E7A7609F58} = {34CB3C61-2F42-49FB-ACDC-2B0EC9EE8746} diff --git a/samples/BasicYarpSample/Program.cs b/samples/BasicYarpSample/Program.cs index 3ce741561..ed93603e0 100644 --- a/samples/BasicYarpSample/Program.cs +++ b/samples/BasicYarpSample/Program.cs @@ -1,22 +1,20 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; -namespace BasicYARPSample -{ - public class Program - { - public static void Main(string[] args) - { - // Create a Kestrel web server, and tell it to use the Startup class - // for the service configuration - var myHostBuilder = Host.CreateDefaultBuilder(args); - myHostBuilder.ConfigureWebHostDefaults(webHostBuilder => - { - webHostBuilder.UseStartup(); - }); - var myHost = myHostBuilder.Build(); - myHost.Run(); - } - } -} +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +// Add the reverse proxy capability to the server +builder.Services.AddReverseProxy() + // Initialize the reverse proxy from the "ReverseProxy" section of configuration + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); + +var app = builder.Build(); + +// Register the reverse proxy routes +app.MapReverseProxy(); + +app.Run(); diff --git a/samples/BasicYarpSample/README.md b/samples/BasicYarpSample/README.md index 202721768..f75ba03e0 100644 --- a/samples/BasicYarpSample/README.md +++ b/samples/BasicYarpSample/README.md @@ -24,8 +24,7 @@ The proxy will listen to HTTP requests on port 5000, and HTTPS on port 5001. The ## Files - [BasicYarpSample.csproj](BasicYarpSample.csproj) - A C# project file (conceptually similar to a make file) that tells it to target the .NET 6 runtime, and to reference the proxy library from [nuget](https://www.nuget.org/packages/Yarp.ReverseProxy/) (.NET's package manager). -- [Program.cs](Program.cs) - Provides the main entrypoint for .NET which uses an WebHostBuilder to initialize the server which listens for http requests. Typically, this file does not need to be modified for any proxy scenarios. -- [Startup.cs](Startup.cs) - Provides a class that is used to configure and control how http requests are handled by the server. In this sample, it does the bare minimum of: +- [Program.cs](Program.cs) - Provides the main entrypoint for .NET which uses a WebApplication to initialize the server which listens for http requests. This is also used to configure and control how http requests are handled by the server. In this sample, it does the bare minimum of: - Adding proxy functionality to the services collection. - Specifying that the proxy configuration will come from the config file (alternatively it could be specified via code). - Telling ASP.NET to use its routing service, to register the routes from YARP into its routing table, and use YARP to handle those requests. diff --git a/samples/BasicYarpSample/Startup.cs b/samples/BasicYarpSample/Startup.cs deleted file mode 100644 index 63f84d795..000000000 --- a/samples/BasicYarpSample/Startup.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace BasicYARPSample -{ - // Sets up the ASP.NET application with the reverse proxy enabled. - public class Startup - { - public Startup(IConfiguration configuration) - { - // Default configuration comes from AppSettings.json file in project/output - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add capabilities to - // the web application via services in the DI container. - public void ConfigureServices(IServiceCollection services) - { - // Add the reverse proxy capability to the server - var proxyBuilder = services.AddReverseProxy(); - // Initialize the reverse proxy from the "ReverseProxy" section of configuration - proxyBuilder.LoadFromConfig(Configuration.GetSection("ReverseProxy")); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request - // pipeline that handles requests - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - // Enable endpoint routing, required for the reverse proxy - app.UseRouting(); - // Register the reverse proxy routes - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); - } - } -} diff --git a/samples/KubernetesIngress.Sample/Combined/Program.cs b/samples/KubernetesIngress.Sample/Combined/Program.cs index 33f265fde..724d119bd 100644 --- a/samples/KubernetesIngress.Sample/Combined/Program.cs +++ b/samples/KubernetesIngress.Sample/Combined/Program.cs @@ -1,43 +1,32 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; +using System.Diagnostics; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Sinks.SystemConsole.Themes; -namespace Yarp.Kubernetes.Ingress -{ - public static class Program - { - public static void Main(string[] args) - { - using var serilog = new LoggerConfiguration() - .MinimumLevel.Debug() - .Enrich.FromLogContext() - .WriteTo.Console(theme: AnsiConsoleTheme.Code) - .CreateLogger(); +var builder = WebApplication.CreateBuilder(args); - Host.CreateDefaultBuilder(args) - .ConfigureWebHost(webBuilder => - { - webBuilder.UseKubernetesReverseProxyCertificateSelector(); - }) - .ConfigureAppConfiguration(config => - { - config.AddJsonFile("/app/config/yarp.json", optional: true); - }) - .ConfigureLogging(logging => - { - logging.ClearProviders(); - logging.AddSerilog(serilog, dispose: false); - }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }).Build().Run(); - } - } -} +using var serilog = new LoggerConfiguration() + .MinimumLevel.Debug() + .Enrich.FromLogContext() + .WriteTo.Console(theme: AnsiConsoleTheme.Code) + .CreateLogger(); +builder.Logging.ClearProviders(); +builder.Logging.AddSerilog(serilog, dispose: false); + +builder.Configuration.AddJsonFile("/app/config/yarp.json", optional: true); +builder.WebHost.UseKubernetesReverseProxyCertificateSelector(); +builder.Services.AddKubernetesReverseProxy(builder.Configuration); + +var app = builder.Build(); + +app.MapReverseProxy(); + +app.Run(); diff --git a/samples/KubernetesIngress.Sample/Combined/Startup.cs b/samples/KubernetesIngress.Sample/Combined/Startup.cs deleted file mode 100644 index 5c0a44b3e..000000000 --- a/samples/KubernetesIngress.Sample/Combined/Startup.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Yarp.Kubernetes.Ingress -{ - public class Startup - { - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddKubernetesReverseProxy(_configuration); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. -#pragma warning disable CA1822 // Mark members as static - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) -#pragma warning restore CA1822 // Mark members as static - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); - } - } -} diff --git a/samples/KubernetesIngress.Sample/Ingress/Program.cs b/samples/KubernetesIngress.Sample/Ingress/Program.cs index 417d400c6..fed56463e 100644 --- a/samples/KubernetesIngress.Sample/Ingress/Program.cs +++ b/samples/KubernetesIngress.Sample/Ingress/Program.cs @@ -1,34 +1,35 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; +using System.Diagnostics; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Sinks.SystemConsole.Themes; +using Yarp.Kubernetes.Protocol; -namespace Yarp.Kubernetes.Ingress -{ - public static class Program - { - public static void Main(string[] args) - { - using var serilog = new LoggerConfiguration() - .MinimumLevel.Debug() - .Enrich.FromLogContext() - .WriteTo.Console(theme: AnsiConsoleTheme.Code) - .CreateLogger(); +var builder = WebApplication.CreateBuilder(args); - Host.CreateDefaultBuilder(args) - .ConfigureLogging(logging => - { - logging.ClearProviders(); - logging.AddSerilog(serilog, dispose: false); - }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }).Build().Run(); - } - } -} +using var serilog = new LoggerConfiguration() + .MinimumLevel.Debug() + .Enrich.FromLogContext() + .WriteTo.Console(theme: AnsiConsoleTheme.Code) + .CreateLogger(); +builder.Logging.ClearProviders(); +builder.Logging.AddSerilog(serilog, dispose: false); + +var services = builder.Services; +services.Configure(builder.Configuration.Bind); +services.AddHostedService(); +services.AddReverseProxy() + .LoadFromMessages(); + +var app = builder.Build(); + +app.MapReverseProxy(); + +app.Run(); diff --git a/samples/KubernetesIngress.Sample/Ingress/Startup.cs b/samples/KubernetesIngress.Sample/Ingress/Startup.cs deleted file mode 100644 index 815b1afa9..000000000 --- a/samples/KubernetesIngress.Sample/Ingress/Startup.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Yarp.Kubernetes.Protocol; - -namespace Yarp.Kubernetes.Ingress -{ - public class Startup - { - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.Configure(_configuration.Bind); - services.AddHostedService(); - services.AddReverseProxy() - .LoadFromMessages(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. -#pragma warning disable CA1822 // Mark members as static - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) -#pragma warning restore CA1822 // Mark members as static - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); - } - } -} diff --git a/samples/KubernetesIngress.Sample/Monitor/Program.cs b/samples/KubernetesIngress.Sample/Monitor/Program.cs index 5fc16ac68..f7fc3b9cf 100644 --- a/samples/KubernetesIngress.Sample/Monitor/Program.cs +++ b/samples/KubernetesIngress.Sample/Monitor/Program.cs @@ -1,39 +1,35 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Sinks.SystemConsole.Themes; -namespace Yarp.Kubernetes.Controller.App -{ - public static class Program - { - public static void Main(string[] args) - { - using var serilog = new LoggerConfiguration() - .MinimumLevel.Debug() - .Enrich.FromLogContext() - .WriteTo.Console(theme: AnsiConsoleTheme.Code) - .CreateLogger(); - - Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration(config => - { - config.AddJsonFile("/app/config/yarp.json", optional: true); - }) - .ConfigureLogging(logging => - { - logging.ClearProviders(); - logging.AddSerilog(serilog, dispose: false); - }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }).Build().Run(); - } - } -} +var builder = WebApplication.CreateBuilder(args); + +using var serilog = new LoggerConfiguration() + .MinimumLevel.Debug() + .Enrich.FromLogContext() + .WriteTo.Console(theme: AnsiConsoleTheme.Code) + .CreateLogger(); +builder.Logging.ClearProviders(); +builder.Logging.AddSerilog(serilog, dispose: false); + +builder.Configuration.AddJsonFile("/app/config/yarp.json", optional: true); + +builder.Services.AddKubernetesIngressMonitor(builder.Configuration); + +// Add ASP.NET Core controller support +builder.Services.AddControllers() + .AddKubernetesDispatchController(); + +var app = builder.Build(); + +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + +app.Run(); diff --git a/samples/KubernetesIngress.Sample/Monitor/Startup.cs b/samples/KubernetesIngress.Sample/Monitor/Startup.cs deleted file mode 100644 index 0a57b0cbc..000000000 --- a/samples/KubernetesIngress.Sample/Monitor/Startup.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Yarp.Kubernetes.Controller.App -{ - public class Startup - { - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddKubernetesIngressMonitor(_configuration); - - // Add ASP.NET Core controller support - services.AddControllers() - .AddKubernetesDispatchController(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. -#pragma warning disable CA1822 // Mark members as static - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) -#pragma warning restore CA1822 // Mark members as static - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - }); - } - } -} diff --git a/samples/KubernetesIngress.Sample/backend/Program.cs b/samples/KubernetesIngress.Sample/backend/Program.cs index c23b53621..4bcc7cbaf 100644 --- a/samples/KubernetesIngress.Sample/backend/Program.cs +++ b/samples/KubernetesIngress.Sample/backend/Program.cs @@ -1,26 +1,34 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using System.Net; +using System.Text.Json; +using Microsoft.AspNetCore.Builder; -namespace Backend +Activity.DefaultIdFormat = ActivityIdFormat.W3C; + +var builder = WebApplication.CreateBuilder(args); + +var app = builder.Build(); + +app.MapGet("/", async context => { - public class Program + var backendInfo = new BackendInfo() { - public static void Main(string[] args) - { - Activity.DefaultIdFormat = ActivityIdFormat.W3C; - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } + IP = context.Connection.LocalIpAddress.ToString(), + Hostname = Dns.GetHostName(), + }; + + context.Response.ContentType = "application/json; charset=utf-8"; + await JsonSerializer.SerializeAsync(context.Response.Body, backendInfo); +}); + +app.Run(); + +internal class BackendInfo +{ + public string IP { get; set; } = default!; + + public string Hostname { get; set; } = default!; } diff --git a/samples/KubernetesIngress.Sample/backend/Startup.cs b/samples/KubernetesIngress.Sample/backend/Startup.cs deleted file mode 100644 index c859467a8..000000000 --- a/samples/KubernetesIngress.Sample/backend/Startup.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Net; -using System.Text.Json; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; - -namespace Backend -{ - public class Startup - { - private readonly JsonSerializerOptions options = new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapGet("/", async context => - { - var backendInfo = new BackendInfo() - { - IP = context.Connection.LocalIpAddress.ToString(), - Hostname = Dns.GetHostName(), - }; - - context.Response.ContentType = "application/json; charset=utf-8"; - await JsonSerializer.SerializeAsync(context.Response.Body, backendInfo); - }); - }); - } - - private class BackendInfo - { - public string IP { get; set; } = default!; - - public string Hostname { get; set; } = default!; - } - } -} diff --git a/samples/Prometheus/HttpLoadApp/Program.cs b/samples/Prometheus/HttpLoadApp/Program.cs index 58f3c69e9..275a2afdb 100644 --- a/samples/Prometheus/HttpLoadApp/Program.cs +++ b/samples/Prometheus/HttpLoadApp/Program.cs @@ -2,36 +2,28 @@ using System.Net.Http; using System.Threading.Tasks; -namespace HttpLoadApp +var client = new HttpClient(); + +string[] planets = { "Mercury", "Venus", "Earth", "Mars", "Jupiter" }; + +foreach (var planet in planets) { - public class Program + _ = Task.Run( () => { - private static readonly HttpClient client = new HttpClient(); - public static void Main(string[] args) - { - string[] planets = { "Mercury", "Venus", "Earth", "Mars", "Jupiter" }; - - foreach (var planet in planets) - { - Task.Run( () => - { - CreateLoad("http://localhost:5000/" + planet); - }); - } - var c = Console.ReadKey(); - } + CreateLoad("http://localhost:5000/" + planet); + }); +} +var c = Console.ReadKey(); - private static async void CreateLoad(string UrlPrefix) - { - var i = 0; - while(true) - { - var url = UrlPrefix + "/" + i; - var resp = await client.GetAsync(url); - var result = await resp.Content.ReadAsStringAsync(); - Console.WriteLine($"Requested: {url}, Result: {resp.StatusCode}, Length: {result.Length}"); - i++; - } - } +async void CreateLoad(string UrlPrefix) +{ + var i = 0; + while(true) + { + var url = UrlPrefix + "/" + i; + var resp = await client.GetAsync(url); + var result = await resp.Content.ReadAsStringAsync(); + Console.WriteLine($"Requested: {url}, Result: {resp.StatusCode}, Length: {result.Length}"); + i++; } } diff --git a/samples/Prometheus/ReverseProxy.Metrics-Prometheus.Sample/Program.cs b/samples/Prometheus/ReverseProxy.Metrics-Prometheus.Sample/Program.cs index 502857306..fccfd70f3 100644 --- a/samples/Prometheus/ReverseProxy.Metrics-Prometheus.Sample/Program.cs +++ b/samples/Prometheus/ReverseProxy.Metrics-Prometheus.Sample/Program.cs @@ -1,29 +1,26 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; - -namespace Yarp.Sample -{ - /// - /// Class that contains the entrypoint for the Reverse Proxy sample app. - /// - public class Program - { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Prometheus; +using Yarp.Sample; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); + +//Enable metric collection for all the underlying event counters used by YARP +builder.Services.AddAllPrometheusMetrics(); + +var app = builder.Build(); + +// Add the reverse proxy endpoints based on routes +app.MapReverseProxy(); + +// Add the /Metrics endpoint for prometheus to query on +app.MapMetrics(); + +app.Run(); diff --git a/samples/Prometheus/ReverseProxy.Metrics-Prometheus.Sample/Startup.cs b/samples/Prometheus/ReverseProxy.Metrics-Prometheus.Sample/Startup.cs deleted file mode 100644 index 23abd8edb..000000000 --- a/samples/Prometheus/ReverseProxy.Metrics-Prometheus.Sample/Startup.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Prometheus; - -namespace Yarp.Sample -{ - /// - /// ASP .NET Core pipeline initialization. - /// - public class Startup - { - private readonly IConfiguration _configuration; - - /// - /// Initializes a new instance of the class. - /// - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - services.AddReverseProxy() - .LoadFromConfig(_configuration.GetSection("ReverseProxy")); - - //Enable metric collection for all the underlying event counters used by YARP - services.AddAllPrometheusMetrics(); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - - //Configure the endpoint routing including the proxy - app.UseEndpoints(endpoints => - { - // Add the reverse proxy endpoints based on routes - endpoints.MapReverseProxy(); - - // Add the /Metrics endpoint for prometheus to query on - endpoints.MapMetrics(); - }); - } - - } -} diff --git a/samples/ReverseProxy.Auth.Sample/Program.cs b/samples/ReverseProxy.Auth.Sample/Program.cs index 502857306..59497cd66 100644 --- a/samples/ReverseProxy.Auth.Sample/Program.cs +++ b/samples/ReverseProxy.Auth.Sample/Program.cs @@ -1,29 +1,85 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Transforms; +using Yarp.Sample; -namespace Yarp.Sample -{ - /// - /// Class that contains the entrypoint for the Reverse Proxy sample app. - /// - public class Program +var builder = WebApplication.CreateBuilder(args); + +var services = builder.Services; + +// Required to supply the authentication UI in Views/* +services.AddRazorPages(); + +services.AddSingleton(); + +services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) + .AddTransforms(transformBuilderContext => // Add transforms inline { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) + // For each route+cluster pair decide if we want to add transforms, and if so, which? + // This logic is re-run each time a route is rebuilt. + + // Only do this for routes that require auth. + if (string.Equals("myPolicy", transformBuilderContext.Route.AuthorizationPolicy)) { - CreateHostBuilder(args).Build().Run(); - } + transformBuilderContext.AddRequestTransform(async transformContext => + { + // AuthN and AuthZ will have already been completed after request routing. + var ticket = await transformContext.HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + var tokenService = transformContext.HttpContext.RequestServices.GetRequiredService(); + var token = await tokenService.GetAuthTokenAsync(ticket.Principal); - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => + // Reject invalid requests + if (string.IsNullOrEmpty(token)) { - webBuilder.UseStartup(); - }); - } -} + var response = transformContext.HttpContext.Response; + response.StatusCode = 401; + return; + } + + transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + }); + } + }); ; + +services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(); + +services.AddAuthorization(options => +{ + // Creates a policy called "myPolicy" that depends on having a claim "myCustomClaim" with the value "green". + // See AccountController.Login method for where this claim is applied to the user identity + // This policy can then be used by routes in the proxy, see "ClaimsAuthRoute" in appsettings.json + options.AddPolicy("myPolicy", builder => builder + .RequireClaim("myCustomClaim", "green") + .RequireAuthenticatedUser()); + + // The default policy is to require authentication, but no additional claims + // Uncommenting the following would have no effect + // options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + + // FallbackPolicy is used for routes that do not specify a policy in config + // Make all routes that do not specify a policy to be anonymous (this is the default). + options.FallbackPolicy = null; + // Or make all routes that do not specify a policy require some auth: + // options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); +}); + +var app = builder.Build(); + +app.UseStaticFiles(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); +app.MapReverseProxy(); + +app.Run(); diff --git a/samples/ReverseProxy.Auth.Sample/README.md b/samples/ReverseProxy.Auth.Sample/README.md index 26e5f9f22..f0711d835 100644 --- a/samples/ReverseProxy.Auth.Sample/README.md +++ b/samples/ReverseProxy.Auth.Sample/README.md @@ -4,7 +4,7 @@ This sample shows how the YARP proxy can be integrated with the ASP.NET [authent The sample includes the following parts: -- ### [Startup.cs](Startup.cs) +- ### [Program.cs](Program.cs) Sets up the ASP.NET server to have the proxy together with the other middleware for authentication, authorization and Razor pages. It sets up a custom authorization policy "myPolicy" with a custom claim. diff --git a/samples/ReverseProxy.Auth.Sample/Startup.cs b/samples/ReverseProxy.Auth.Sample/Startup.cs deleted file mode 100644 index c848930a8..000000000 --- a/samples/ReverseProxy.Auth.Sample/Startup.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net.Http.Headers; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Yarp.ReverseProxy.Transforms; - -namespace Yarp.Sample -{ - /// - /// ASP .NET Core pipeline initialization. - /// - public class Startup - { - private readonly IConfiguration _configuration; - - /// - /// Initializes a new instance of the class. - /// - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - // Required to supply the authentication UI in Views/* - services.AddRazorPages(); - - services.AddSingleton(); - - services.AddReverseProxy() - .LoadFromConfig(_configuration.GetSection("ReverseProxy")) - .AddTransforms(transformBuilderContext => // Add transforms inline - { - // For each route+cluster pair decide if we want to add transforms, and if so, which? - // This logic is re-run each time a route is rebuilt. - - // Only do this for routes that require auth. - if (string.Equals("myPolicy", transformBuilderContext.Route.AuthorizationPolicy)) - { - transformBuilderContext.AddRequestTransform(async transformContext => - { - // AuthN and AuthZ will have already been completed after request routing. - var ticket = await transformContext.HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); - var tokenService = transformContext.HttpContext.RequestServices.GetRequiredService(); - var token = await tokenService.GetAuthTokenAsync(ticket.Principal); - - // Reject invalid requests - if (string.IsNullOrEmpty(token)) - { - var response = transformContext.HttpContext.Response; - response.StatusCode = 401; - return; - } - - transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - }); - } - }); ; - - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) - .AddCookie(); - - services.AddAuthorization(options => - { - // Creates a policy called "myPolicy" that depends on having a claim "myCustomClaim" with the value "green". - // See AccountController.Login method for where this claim is applied to the user identity - // This policy can then be used by routes in the proxy, see "ClaimsAuthRoute" in appsettings.json - options.AddPolicy("myPolicy", builder => builder - .RequireClaim("myCustomClaim", "green") - .RequireAuthenticatedUser()); - - // The default policy is to require authentication, but no additional claims - // Uncommenting the following would have no effect - // options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); - - // FallbackPolicy is used for routes that do not specify a policy in config - // Make all routes that do not specify a policy to be anonymous (this is the default). - options.FallbackPolicy = null; - // Or make all routes that do not specify a policy require some auth: - // options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); - }); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - // The order of these is important as it defines the steps that will be used to handle each request - app.UseDeveloperExceptionPage(); - app.UseStaticFiles(); - - app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - endpoints.MapReverseProxy(); - }); - } - } -} diff --git a/samples/ReverseProxy.Code.Sample/Program.cs b/samples/ReverseProxy.Code.Sample/Program.cs index 502857306..d9507289a 100644 --- a/samples/ReverseProxy.Code.Sample/Program.cs +++ b/samples/ReverseProxy.Code.Sample/Program.cs @@ -1,29 +1,108 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using System.Collections.Generic; +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Configuration; +using Microsoft.AspNetCore.Http; +using Yarp.ReverseProxy.Model; -namespace Yarp.Sample +const string DEBUG_HEADER = "Debug"; +const string DEBUG_METADATA_KEY = "debug"; +const string DEBUG_VALUE = "true"; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddReverseProxy() + .LoadFromMemory(GetRoutes(), GetClusters()); + +var app = builder.Build(); + + +app.Map("/update", context => +{ + context.RequestServices.GetRequiredService().Update(GetRoutes(), GetClusters()); + return Task.CompletedTask; +}); +// We can customize the proxy pipeline and add/remove/replace steps +app.MapReverseProxy(proxyPipeline => { - /// - /// Class that contains the entrypoint for the Reverse Proxy sample app. - /// - public class Program + // Use a custom proxy middleware, defined below + proxyPipeline.Use(MyCustomProxyStep); + // Don't forget to include these two middleware when you make a custom proxy pipeline (if you need them). + proxyPipeline.UseSessionAffinity(); + proxyPipeline.UseLoadBalancing(); +}); + +app.Run(); + +RouteConfig[] GetRoutes() +{ + return + [ + new RouteConfig() + { + RouteId = "route" + Random.Shared.Next(), // Forces a new route id each time GetRoutes is called. + ClusterId = "cluster1", + Match = new RouteMatch + { + // Path or Hosts are required for each route. This catch-all pattern matches all request paths. + Path = "{**catch-all}" + } + } + ]; +} + +ClusterConfig[] GetClusters() +{ + var debugMetadata = new Dictionary { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) + { DEBUG_METADATA_KEY, DEBUG_VALUE } + }; + + return + [ + new ClusterConfig() { - CreateHostBuilder(args).Build().Run(); + ClusterId = "cluster1", + SessionAffinity = new SessionAffinityConfig { Enabled = true, Policy = "Cookie", AffinityKeyName = ".Yarp.ReverseProxy.Affinity" }, + Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "destination1", new DestinationConfig() { Address = "https://example.com" } }, + { "debugdestination1", new DestinationConfig() { + Address = "https://bing.com", + Metadata = debugMetadata } + }, + } } + ]; +} - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); +/// +/// Custom proxy step that filters destinations based on a header in the inbound request +/// Looks at each destination metadata, and filters in/out based on their debug flag and the inbound header +/// +Task MyCustomProxyStep(HttpContext context, Func next) +{ + // Can read data from the request via the context + var useDebugDestinations = context.Request.Headers.TryGetValue(DEBUG_HEADER, out var headerValues) && headerValues.Count == 1 && headerValues[0] == DEBUG_VALUE; + + // The context also stores a ReverseProxyFeature which holds proxy specific data such as the cluster, route and destinations + var availableDestinationsFeature = context.Features.Get(); + var filteredDestinations = new List(); + + // Filter destinations based on criteria + foreach (var d in availableDestinationsFeature.AvailableDestinations) + { + //Todo: Replace with a lookup of metadata - but not currently exposed correctly here + if (d.DestinationId.Contains("debug") == useDebugDestinations) { filteredDestinations.Add(d); } } + availableDestinationsFeature.AvailableDestinations = filteredDestinations; + + // Important - required to move to the next step in the proxy pipeline + return next(); } diff --git a/samples/ReverseProxy.Code.Sample/README.md b/samples/ReverseProxy.Code.Sample/README.md index ac20fd88c..e33a0ce02 100644 --- a/samples/ReverseProxy.Code.Sample/README.md +++ b/samples/ReverseProxy.Code.Sample/README.md @@ -30,11 +30,8 @@ This sample shows two common customizations via code of the YARP reverse proxy: The following files are key to implementing the features described above: -- ### [Startup.cs](Startup.cs) - Provides the initialization routines for ASP.NET and the reverse proxy. - - ```ConfigureServices``` is called once and sets up the proxy passing in the InMemoryConfigProvider instance. The sample routes and clusters definitions are created as part of this initialization. The config provider instance is used for the lifetime of the proxy. - - ```Configure``` is called once at startup to setup the request pipeline. As an additional step is added, the proxy pipeline is configured here. - - ```MyCustomProxyStep``` is the implementation of the additional step. It finds the proxy functionality via features added to the HttpContext, and then filters the destinations based on the presence of a "Debug" header in the request. +- ### [Program.cs](Program.cs) + Provides the initialization routines for ASP.NET and the reverse proxy. It: + - sets up the proxy passing in the InMemoryConfigProvider instance. The sample routes and clusters definitions are created as part of this initialization. The config provider instance is used for the lifetime of the proxy. + - sets up the request pipeline. As an additional step is added, the proxy pipeline is configured here. + - ```MyCustomProxyStep``` is the implementation of the additional step. It finds the proxy functionality via features added to the HttpContext, and then filters the destinations based on the presence of a "Debug" header in the request. diff --git a/samples/ReverseProxy.Code.Sample/Startup.cs b/samples/ReverseProxy.Code.Sample/Startup.cs deleted file mode 100644 index de41c6956..000000000 --- a/samples/ReverseProxy.Code.Sample/Startup.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Yarp.ReverseProxy.Configuration; -using Yarp.ReverseProxy.Model; - -namespace Yarp.Sample -{ - /// - /// Initialiaztion for ASP.NET using YARP reverse proxy - /// - public class Startup - { - private const string DEBUG_HEADER = "Debug"; - private const string DEBUG_METADATA_KEY = "debug"; - private const string DEBUG_VALUE = "true"; - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - // Programatically creating route and cluster configs. This allows loading the data from an arbitrary source. - services.AddReverseProxy() - .LoadFromMemory(GetRoutes(), GetClusters()); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.Map("/update", context => - { - context.RequestServices.GetRequiredService().Update(GetRoutes(), GetClusters()); - return Task.CompletedTask; - }); - // We can customize the proxy pipeline and add/remove/replace steps - endpoints.MapReverseProxy(proxyPipeline => - { - // Use a custom proxy middleware, defined below - proxyPipeline.Use(MyCustomProxyStep); - // Don't forget to include these two middleware when you make a custom proxy pipeline (if you need them). - proxyPipeline.UseSessionAffinity(); - proxyPipeline.UseLoadBalancing(); - }); - }); - } - - private RouteConfig[] GetRoutes() - { - return new[] - { - new RouteConfig() - { - RouteId = "route" + Random.Shared.Next(), // Forces a new route id each time GetRoutes is called. - ClusterId = "cluster1", - Match = new RouteMatch - { - // Path or Hosts are required for each route. This catch-all pattern matches all request paths. - Path = "{**catch-all}" - } - } - }; - } - private ClusterConfig[] GetClusters() - { - var debugMetadata = new Dictionary(); - debugMetadata.Add(DEBUG_METADATA_KEY, DEBUG_VALUE); - - return new[] - { - new ClusterConfig() - { - ClusterId = "cluster1", - SessionAffinity = new SessionAffinityConfig { Enabled = true, Policy = "Cookie", AffinityKeyName = ".Yarp.ReverseProxy.Affinity" }, - Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "destination1", new DestinationConfig() { Address = "https://example.com" } }, - { "debugdestination1", new DestinationConfig() { - Address = "https://bing.com", - Metadata = debugMetadata } - }, - } - } - }; - } - - - /// - /// Custom proxy step that filters destinations based on a header in the inbound request - /// Looks at each destination metadata, and filters in/out based on their debug flag and the inbound header - /// - public Task MyCustomProxyStep(HttpContext context, Func next) - { - // Can read data from the request via the context - var useDebugDestinations = context.Request.Headers.TryGetValue(DEBUG_HEADER, out var headerValues) && headerValues.Count == 1 && headerValues[0] == DEBUG_VALUE; - - // The context also stores a ReverseProxyFeature which holds proxy specific data such as the cluster, route and destinations - var availableDestinationsFeature = context.Features.Get(); - var filteredDestinations = new List(); - - // Filter destinations based on criteria - foreach (var d in availableDestinationsFeature.AvailableDestinations) - { - //Todo: Replace with a lookup of metadata - but not currently exposed correctly here - if (d.DestinationId.Contains("debug") == useDebugDestinations) { filteredDestinations.Add(d); } - } - availableDestinationsFeature.AvailableDestinations = filteredDestinations; - - // Important - required to move to the next step in the proxy pipeline - return next(); - } - } -} diff --git a/samples/ReverseProxy.Config.Sample/Program.cs b/samples/ReverseProxy.Config.Sample/Program.cs index 502857306..78bd9f72e 100644 --- a/samples/ReverseProxy.Config.Sample/Program.cs +++ b/samples/ReverseProxy.Config.Sample/Program.cs @@ -1,29 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; -namespace Yarp.Sample -{ - /// - /// Class that contains the entrypoint for the Reverse Proxy sample app. - /// - public class Program - { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } +var builder = WebApplication.CreateBuilder(args); - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} +builder.Services.AddControllers(); +builder.Services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); + +var app = builder.Build(); + +app.UseCors(); +app.MapReverseProxy(); + +app.Run(); diff --git a/samples/ReverseProxy.Config.Sample/Startup.cs b/samples/ReverseProxy.Config.Sample/Startup.cs deleted file mode 100644 index 0b3de57d8..000000000 --- a/samples/ReverseProxy.Config.Sample/Startup.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Yarp.Sample -{ - /// - /// ASP .NET Core pipeline initialization. - /// - public class Startup - { - private readonly IConfiguration _configuration; - - /// - /// Initializes a new instance of the class. - /// - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - services.AddReverseProxy() - .LoadFromConfig(_configuration.GetSection("ReverseProxy")); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - app.UseCors(); - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); - } - } -} diff --git a/samples/ReverseProxy.ConfigFilter.Sample/Program.cs b/samples/ReverseProxy.ConfigFilter.Sample/Program.cs index 502857306..6cf4fe635 100644 --- a/samples/ReverseProxy.ConfigFilter.Sample/Program.cs +++ b/samples/ReverseProxy.ConfigFilter.Sample/Program.cs @@ -1,29 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Yarp.Sample; -namespace Yarp.Sample -{ - /// - /// Class that contains the entrypoint for the Reverse Proxy sample app. - /// - public class Program - { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } +var builder = WebApplication.CreateBuilder(args); - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} +builder.Services.AddControllers(); +builder.Services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) + .AddConfigFilter(); + +var app = builder.Build(); + +app.MapReverseProxy(); + +app.Run(); diff --git a/samples/ReverseProxy.ConfigFilter.Sample/Startup.cs b/samples/ReverseProxy.ConfigFilter.Sample/Startup.cs deleted file mode 100644 index c859daed5..000000000 --- a/samples/ReverseProxy.ConfigFilter.Sample/Startup.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Yarp.Sample -{ - /// - /// ASP .NET Core pipeline initialization. - /// - public class Startup - { - private readonly IConfiguration _configuration; - - /// - /// Initializes a new instance of the class. - /// - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - // Load the configuration and initialize the config filter - services.AddReverseProxy() - .LoadFromConfig(_configuration.GetSection("ReverseProxy")) - .AddConfigFilter(); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); - } - } -} diff --git a/samples/ReverseProxy.Direct.Sample/Program.cs b/samples/ReverseProxy.Direct.Sample/Program.cs index 502857306..6655a1b74 100644 --- a/samples/ReverseProxy.Direct.Sample/Program.cs +++ b/samples/ReverseProxy.Direct.Sample/Program.cs @@ -1,29 +1,109 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Forwarder; +using Yarp.ReverseProxy.Transforms; -namespace Yarp.Sample +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddHttpForwarder(); + +var app = builder.Build(); + +// Configure our own HttpMessageInvoker for outbound calls for proxy operations +var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() +{ + UseProxy = false, + AllowAutoRedirect = false, + AutomaticDecompression = DecompressionMethods.None, + UseCookies = false, + ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), + ConnectTimeout = TimeSpan.FromSeconds(15), +}); + +// Setup our own request transform class +var transformer = new CustomTransformer(); // or HttpTransformer.Default; +var requestOptions = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; + +app.UseRouting(); + +// When using IHttpForwarder for direct forwarding you are responsible for routing, destination discovery, load balancing, affinity, etc.. +// For an alternate example that includes those features see BasicYarpSample. +app.Map("/test/{**catch-all}", async (HttpContext httpContext, IHttpForwarder forwarder) => +{ + var error = await forwarder.SendAsync(httpContext, "https://example.com", httpClient, requestOptions, + static (context, proxyRequest) => + { + // Customize the query string: + var queryContext = new QueryTransformContext(context.Request); + queryContext.Collection.Remove("param1"); + queryContext.Collection["area"] = "xx2"; + + // Assign the custom uri. Be careful about extra slashes when concatenating here. RequestUtilities.MakeDestinationAddress is a safe default. + proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress("https://example.com", context.Request.Path, queryContext.QueryString); + + // Suppress the original request header, use the one from the destination Uri. + proxyRequest.Headers.Host = null; + + return default; + }); + + // Check if the proxy operation was successful + if (error != ForwarderError.None) + { + var errorFeature = httpContext.Features.Get(); + var exception = errorFeature.Exception; + } +}); + +app.MapForwarder("/sample/{id}", "https://httpbin.org", "/anything/{id}"); +app.MapForwarder("/sample/anything/{id}", "https://httpbin.org", b => b.AddPathRemovePrefix("/sample")); + +// When using extension methods for registering IHttpForwarder providing configuration, transforms, and HttpMessageInvoker is optional (defaults will be used). +app.MapForwarder("/{**catch-all}", "https://example.com", requestOptions, transformer, httpClient); + +app.Run(); + +/// +/// Custom request transformation +/// +internal class CustomTransformer : HttpTransformer { /// - /// Class that contains the entrypoint for the Reverse Proxy sample app. + /// A callback that is invoked prior to sending the proxied request. All HttpRequestMessage + /// fields are initialized except RequestUri, which will be initialized after the + /// callback if no value is provided. The string parameter represents the destination + /// URI prefix that should be used when constructing the RequestUri. The headers + /// are copied by the base implementation, excluding some protocol headers like HTTP/2 + /// pseudo headers (":authority"). /// - public class Program + /// The incoming request. + /// The outgoing proxy request. + /// The uri prefix for the selected destination server which can be used to create + /// the RequestUri. + public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + // Copy all request headers + await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); + + // Customize the query string: + var queryContext = new QueryTransformContext(httpContext.Request); + queryContext.Collection.Remove("param1"); + queryContext.Collection["area"] = "xx2"; + + // Assign the custom uri. Be careful about extra slashes when concatenating here. RequestUtilities.MakeDestinationAddress is a safe default. + proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress("https://example.com", httpContext.Request.Path, queryContext.QueryString); + + // Suppress the original request header, use the one from the destination Uri. + proxyRequest.Headers.Host = null; } } diff --git a/samples/ReverseProxy.Direct.Sample/README.md b/samples/ReverseProxy.Direct.Sample/README.md index 992466662..a11d5962a 100644 --- a/samples/ReverseProxy.Direct.Sample/README.md +++ b/samples/ReverseProxy.Direct.Sample/README.md @@ -41,4 +41,4 @@ To enable control over mapping request and response fields and headers between t ## Files -The key functionality for this sample is all included in [Startup.cs](Startup.cs). +The key functionality for this sample is all included in [Program.cs](Program.cs). diff --git a/samples/ReverseProxy.Direct.Sample/Startup.cs b/samples/ReverseProxy.Direct.Sample/Startup.cs deleted file mode 100644 index 87ccfedae..000000000 --- a/samples/ReverseProxy.Direct.Sample/Startup.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Diagnostics; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Yarp.ReverseProxy.Forwarder; -using Yarp.ReverseProxy.Transforms; - -namespace Yarp.Sample -{ - /// - /// ASP.NET Core pipeline initialization showing how to use IHttpForwarder to directly handle forwarding requests. - /// With this approach you are responsible for destination discovery, load balancing, and related concerns. - /// - public class Startup - { - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddHttpForwarder(); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app, IHttpForwarder forwarder) - { - // Configure our own HttpMessageInvoker for outbound calls for proxy operations - var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() - { - UseProxy = false, - AllowAutoRedirect = false, - AutomaticDecompression = DecompressionMethods.None, - UseCookies = false, - ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), - ConnectTimeout = TimeSpan.FromSeconds(15), - }); - - // Setup our own request transform class - var transformer = new CustomTransformer(); // or HttpTransformer.Default; - var requestOptions = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; - - app.UseRouting(); - - // When using IHttpForwarder for direct forwarding you are responsible for routing, destination discovery, load balancing, affinity, etc.. - // For an alternate example that includes those features see BasicYarpSample. - app.UseEndpoints(endpoints => - { - endpoints.Map("/test/{**catch-all}", async httpContext => - { - var error = await forwarder.SendAsync(httpContext, "https://example.com", httpClient, requestOptions, - static (context, proxyRequest) => - { - // Customize the query string: - var queryContext = new QueryTransformContext(context.Request); - queryContext.Collection.Remove("param1"); - queryContext.Collection["area"] = "xx2"; - - // Assign the custom uri. Be careful about extra slashes when concatenating here. RequestUtilities.MakeDestinationAddress is a safe default. - proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress("https://example.com", context.Request.Path, queryContext.QueryString); - - // Suppress the original request header, use the one from the destination Uri. - proxyRequest.Headers.Host = null; - - return default; - }); - - // Check if the proxy operation was successful - if (error != ForwarderError.None) - { - var errorFeature = httpContext.Features.Get(); - var exception = errorFeature.Exception; - } - }); - - endpoints.MapForwarder("/sample/{id}", "https://httpbin.org", "/anything/{id}"); - endpoints.MapForwarder("/sample/anything/{id}", "https://httpbin.org", b => b.AddPathRemovePrefix("/sample")); - - // When using extension methods for registering IHttpForwarder providing configuration, transforms, and HttpMessageInvoker is optional (defaults will be used). - endpoints.MapForwarder("/{**catch-all}", "https://example.com", requestOptions, transformer, httpClient); - }); - } - - /// - /// Custom request transformation - /// - private class CustomTransformer : HttpTransformer - { - /// - /// A callback that is invoked prior to sending the proxied request. All HttpRequestMessage - /// fields are initialized except RequestUri, which will be initialized after the - /// callback if no value is provided. The string parameter represents the destination - /// URI prefix that should be used when constructing the RequestUri. The headers - /// are copied by the base implementation, excluding some protocol headers like HTTP/2 - /// pseudo headers (":authority"). - /// - /// The incoming request. - /// The outgoing proxy request. - /// The uri prefix for the selected destination server which can be used to create - /// the RequestUri. - public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) - { - // Copy all request headers - await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); - - // Customize the query string: - var queryContext = new QueryTransformContext(httpContext.Request); - queryContext.Collection.Remove("param1"); - queryContext.Collection["area"] = "xx2"; - - // Assign the custom uri. Be careful about extra slashes when concatenating here. RequestUtilities.MakeDestinationAddress is a safe default. - proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress("https://example.com", httpContext.Request.Path, queryContext.QueryString); - - // Suppress the original request header, use the one from the destination Uri. - proxyRequest.Headers.Host = null; - } - } - } -} diff --git a/samples/ReverseProxy.LetsEncrypt.Sample/Program.cs b/samples/ReverseProxy.LetsEncrypt.Sample/Program.cs index 9f892aa50..da43d6d1f 100644 --- a/samples/ReverseProxy.LetsEncrypt.Sample/Program.cs +++ b/samples/ReverseProxy.LetsEncrypt.Sample/Program.cs @@ -1,21 +1,22 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -namespace Yarp.Sample -{ - public class Program - { - public static void Main(string[] args) - { - // Create a Kestrel web server, and tell it to use the Startup class - // for the service configuration - var myHostBuilder = Host.CreateDefaultBuilder(args); - myHostBuilder.ConfigureWebHostDefaults(webHostBuilder => - { - webHostBuilder.UseStartup(); - }); - var myHost = myHostBuilder.Build(); - myHost.Run(); - } - } -} +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddLettuceEncrypt(); + +builder.Services.AddControllers(); +// Add the reverse proxy capability to the server +builder.Services.AddReverseProxy() + // Initialize the reverse proxy from the "ReverseProxy" section of configuration + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); + +var app = builder.Build(); + +// Register the reverse proxy routes +app.MapReverseProxy(); + +app.Run(); diff --git a/samples/ReverseProxy.LetsEncrypt.Sample/README.md b/samples/ReverseProxy.LetsEncrypt.Sample/README.md index 312c0ed92..b4f450bd9 100644 --- a/samples/ReverseProxy.LetsEncrypt.Sample/README.md +++ b/samples/ReverseProxy.LetsEncrypt.Sample/README.md @@ -4,7 +4,7 @@ The sample includes the following parts: -- ### [Startup.cs](Startup.cs) +- ### [Program.cs](Program.cs) It calls `IServiceCollection.AddLettuceEncrypt` in the `ConfigureServices` method. - ### [appsettings.json](appsettings.json) diff --git a/samples/ReverseProxy.LetsEncrypt.Sample/Startup.cs b/samples/ReverseProxy.LetsEncrypt.Sample/Startup.cs deleted file mode 100644 index 777c6f732..000000000 --- a/samples/ReverseProxy.LetsEncrypt.Sample/Startup.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Yarp.Sample -{ - // Sets up the ASP.NET application with the reverse proxy enabled. - public class Startup - { - public Startup(IConfiguration configuration) - { - // Default configuration comes from AppSettings.json file in project/output - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add capabilities to - // the web application via services in the DI container. - public void ConfigureServices(IServiceCollection services) - { - services.AddLettuceEncrypt(); - - // Add the reverse proxy capability to the server - var proxyBuilder = services.AddReverseProxy(); - // Initialize the reverse proxy from the "ReverseProxy" section of configuration - proxyBuilder.LoadFromConfig(Configuration.GetSection("ReverseProxy")); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request - // pipeline that handles requests - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - // Enable endpoint routing, required for the reverse proxy - app.UseRouting(); - // Register the reverse proxy routes - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); - } - } -} diff --git a/samples/ReverseProxy.Metrics.Sample/Program.cs b/samples/ReverseProxy.Metrics.Sample/Program.cs index 502857306..d45801a7c 100644 --- a/samples/ReverseProxy.Metrics.Sample/Program.cs +++ b/samples/ReverseProxy.Metrics.Sample/Program.cs @@ -1,29 +1,41 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; - -namespace Yarp.Sample -{ - /// - /// Class that contains the entrypoint for the Reverse Proxy sample app. - /// - public class Program - { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Yarp.Sample; + +var builder = WebApplication.CreateBuilder(args); + +var services = builder.Services; + +services.AddControllers(); + +services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); + +services.AddHttpContextAccessor(); + +// Interface that collects general metrics about the proxy forwarder +services.AddMetricsConsumer(); + +// Registration of a consumer to events for proxy forwarder telemetry +services.AddTelemetryConsumer(); + +// Registration of a consumer to events for HttpClient telemetry +services.AddTelemetryConsumer(); + +services.AddTelemetryConsumer(); + +var app = builder.Build(); + +// Custom middleware that collects and reports the proxy metrics +// Placed at the beginning so it is the first and last thing run for each request +app.UsePerRequestMetricCollection(); + +// Middleware used to intercept the WebSocket connection and collect telemetry exposed to WebSocketsTelemetryConsumer +app.UseWebSocketsTelemetry(); + +app.MapReverseProxy(); + +app.Run(); diff --git a/samples/ReverseProxy.Metrics.Sample/README.md b/samples/ReverseProxy.Metrics.Sample/README.md index bd9853867..56a0570de 100644 --- a/samples/ReverseProxy.Metrics.Sample/README.md +++ b/samples/ReverseProxy.Metrics.Sample/README.md @@ -54,7 +54,7 @@ The subsystems are: The following files are key to implementing the features described above: -### Startup.cs +### Program.cs Performs registrtion of the proxy, the listener classes and a custom ASP.NET middleware step that starts per-request telemetry and reports the results when complete diff --git a/samples/ReverseProxy.Metrics.Sample/Startup.cs b/samples/ReverseProxy.Metrics.Sample/Startup.cs deleted file mode 100644 index 65c878fa8..000000000 --- a/samples/ReverseProxy.Metrics.Sample/Startup.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Yarp.Telemetry.Consumption; - -namespace Yarp.Sample -{ - /// - /// ASP .NET Core pipeline initialization. - /// - public class Startup - { - private readonly IConfiguration _configuration; - - /// - /// Initializes a new instance of the class. - /// - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.AddReverseProxy() - .LoadFromConfig(_configuration.GetSection("ReverseProxy")); - - services.AddHttpContextAccessor(); - - // Interface that collects general metrics about the proxy forwarder - services.AddMetricsConsumer(); - - // Registration of a consumer to events for proxy forwarder telemetry - services.AddTelemetryConsumer(); - - // Registration of a consumer to events for HttpClient telemetry - services.AddTelemetryConsumer(); - - services.AddTelemetryConsumer(); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - // Custom middleware that collects and reports the proxy metrics - // Placed at the beginning so it is the first and last thing run for each request - app.UsePerRequestMetricCollection(); - - // Middleware used to intercept the WebSocket connection and collect telemetry exposed to WebSocketsTelemetryConsumer - app.UseWebSocketsTelemetry(); - - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); - } - } -} diff --git a/samples/ReverseProxy.Minimal.Sample/Program.cs b/samples/ReverseProxy.Minimal.Sample/Program.cs deleted file mode 100644 index 62a48e722..000000000 --- a/samples/ReverseProxy.Minimal.Sample/Program.cs +++ /dev/null @@ -1,17 +0,0 @@ -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddReverseProxy() - .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); -#if NET8_0_OR_GREATER -builder.Services.AddRequestTimeouts(options => -{ - options.AddPolicy("customPolicy", TimeSpan.FromSeconds(20)); -}); -#endif -var app = builder.Build(); -#if NET8_0_OR_GREATER -app.UseRequestTimeouts(); -#endif -app.MapReverseProxy(); - -app.Run(); diff --git a/samples/ReverseProxy.Minimal.Sample/Properties/launchSettings.json b/samples/ReverseProxy.Minimal.Sample/Properties/launchSettings.json deleted file mode 100644 index d0a3ee43a..000000000 --- a/samples/ReverseProxy.Minimal.Sample/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "ReverseProxy.Minimal.Sample": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/samples/ReverseProxy.Minimal.Sample/README.md b/samples/ReverseProxy.Minimal.Sample/README.md deleted file mode 100644 index 5793a13e8..000000000 --- a/samples/ReverseProxy.Minimal.Sample/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Minimal sample - -This sample brings together the [Config-based YARP Sample] and [Minimal Hosting for ASP.NET Core] introduced in .NET 6. - -Turning an empty ASP.NET Core project (`dotnet new web`) into a functioning YARP application takes only 3 lines of code: -- Adding YARP services - - ```c# - builder.Services.AddReverseProxy() - .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); - ``` -- Adding YARP to the request pipeline - - ```c# - app.MapReverseProxy(); - ``` -- Adding a `ReverseProxy` section to `appsettings.json` - - The [configuration file](appsettings.json) in this sample represents a minimal configuration for a basic YARP application. - See the [Config-based YARP Sample configuration] for a more exhaustive list of settings exposed via configuration. - -[Config-based YARP Sample]: ../ReverseProxy.Config.Sample -[Config-based YARP Sample configuration]: ../ReverseProxy.Config.Sample/appsettings.json -[Minimal Hosting for ASP.NET Core]: https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#introducing-minimal-apis diff --git a/samples/ReverseProxy.Minimal.Sample/ReverseProxy.Minimal.Sample.csproj b/samples/ReverseProxy.Minimal.Sample/ReverseProxy.Minimal.Sample.csproj deleted file mode 100644 index 382697d2a..000000000 --- a/samples/ReverseProxy.Minimal.Sample/ReverseProxy.Minimal.Sample.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0;net7.0;net8.0 - enable - enable - latest - - - - - - - diff --git a/samples/ReverseProxy.Minimal.Sample/appsettings.Development.json b/samples/ReverseProxy.Minimal.Sample/appsettings.Development.json deleted file mode 100644 index 8983e0fc1..000000000 --- a/samples/ReverseProxy.Minimal.Sample/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/samples/ReverseProxy.Minimal.Sample/appsettings.json b/samples/ReverseProxy.Minimal.Sample/appsettings.json deleted file mode 100644 index e682bae36..000000000 --- a/samples/ReverseProxy.Minimal.Sample/appsettings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "Urls": "http://localhost:5000;https://localhost:5001", - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*", - "ReverseProxy": { - "Routes": { - "minimumroute": { - "ClusterId": "minimumcluster", - "Match": { - "Path": "{**catch-all}" - } - } - }, - "Clusters": { - "minimumcluster": { - "Destinations": { - "httpbin.org": { - "Address": "https://httpbin.org/" - } - } - } - } - } -} diff --git a/samples/ReverseProxy.Transforms.Sample/Program.cs b/samples/ReverseProxy.Transforms.Sample/Program.cs index 502857306..b79d8a72b 100644 --- a/samples/ReverseProxy.Transforms.Sample/Program.cs +++ b/samples/ReverseProxy.Transforms.Sample/Program.cs @@ -1,29 +1,47 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; - -namespace Yarp.Sample -{ - /// - /// Class that contains the entrypoint for the Reverse Proxy sample app. - /// - public class Program +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Transforms; +using Yarp.Sample; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) + .AddTransforms() // Adds custom transforms via code. + .AddTransformFactory() // Adds custom transforms via config. + .AddTransforms(transformBuilderContext => // Add transforms inline { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) + // For each route+cluster pair decide if we want to add transforms, and if so, which? + // This logic is re-run each time a route is rebuilt. + + transformBuilderContext.AddPathPrefix("/prefix"); + + // Only do this for routes that require auth. + if (string.Equals("token", transformBuilderContext.Route.AuthorizationPolicy)) { - CreateHostBuilder(args).Build().Run(); + transformBuilderContext.AddRequestTransform(async transformContext => + { + // AuthN and AuthZ will have already been completed after request routing. + var ticket = await transformContext.HttpContext.AuthenticateAsync("token"); + var tokenService = transformContext.HttpContext.RequestServices.GetRequiredService(); + var token = await tokenService.GetAuthTokenAsync(ticket.Principal); + transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + }); } + }); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); + +// Register the reverse proxy routes +app.MapReverseProxy(); - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} +app.Run(); diff --git a/samples/ReverseProxy.Transforms.Sample/Startup.cs b/samples/ReverseProxy.Transforms.Sample/Startup.cs deleted file mode 100644 index ae5aff919..000000000 --- a/samples/ReverseProxy.Transforms.Sample/Startup.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net.Http.Headers; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Yarp.ReverseProxy.Transforms; - -namespace Yarp.Sample -{ - /// - /// ASP .NET Core pipeline initialization. - /// - public class Startup - { - private readonly IConfiguration _configuration; - - /// - /// Initializes a new instance of the class. - /// - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - services.AddReverseProxy() - .LoadFromConfig(_configuration.GetSection("ReverseProxy")) - .AddTransforms() // Adds custom transforms via code. - .AddTransformFactory() // Adds custom transforms via config. - .AddTransforms(transformBuilderContext => // Add transforms inline - { - // For each route+cluster pair decide if we want to add transforms, and if so, which? - // This logic is re-run each time a route is rebuilt. - - transformBuilderContext.AddPathPrefix("/prefix"); - - // Only do this for routes that require auth. - if (string.Equals("token", transformBuilderContext.Route.AuthorizationPolicy)) - { - transformBuilderContext.AddRequestTransform(async transformContext => - { - // AuthN and AuthZ will have already been completed after request routing. - var ticket = await transformContext.HttpContext.AuthenticateAsync("token"); - var tokenService = transformContext.HttpContext.RequestServices.GetRequiredService(); - var token = await tokenService.GetAuthTokenAsync(ticket.Principal); - transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - }); - } - }); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); - } - } -} diff --git a/samples/SampleServer/Program.cs b/samples/SampleServer/Program.cs index 855fb4084..b83f9d5ad 100644 --- a/samples/SampleServer/Program.cs +++ b/samples/SampleServer/Program.cs @@ -1,29 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; -namespace SampleServer -{ - /// - /// Class that contains the entrypoint for the Reverse Proxy sample app. - /// - public class Program - { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } +var builder = WebApplication.CreateBuilder(args); - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} +builder.Services.AddControllers() + .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); + +var app = builder.Build(); + +app.UseWebSockets(); +app.MapControllers(); + +app.Run(); diff --git a/samples/SampleServer/Startup.cs b/samples/SampleServer/Startup.cs deleted file mode 100644 index c1a2ad03d..000000000 --- a/samples/SampleServer/Startup.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace SampleServer -{ - /// - /// ASP .NET Core pipeline initialization. - /// - public class Startup - { - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services - .AddControllers() - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - app.UseWebSockets(); - - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } - } -} diff --git a/testassets/BenchmarkApp/Program.cs b/testassets/BenchmarkApp/Program.cs index 8c75050eb..d641d8f4b 100644 --- a/testassets/BenchmarkApp/Program.cs +++ b/testassets/BenchmarkApp/Program.cs @@ -1,27 +1,58 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; +using System; using System.IO; using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Crank.EventSources; -namespace BenchmarkApp; +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.ConfigureKestrel((context, kestrelOptions) => +{ + kestrelOptions.ConfigureHttpsDefaults(httpsOptions => + { + httpsOptions.ServerCertificate = new X509Certificate2(Path.Combine(context.HostingEnvironment.ContentRootPath, "testCert.pfx"), "testPassword"); + }); +}); + +var clusterUrls = builder.Configuration["clusterUrls"]; + +if (string.IsNullOrWhiteSpace(clusterUrls)) +{ + throw new ArgumentException("--clusterUrls is required"); +} + +var configDictionary = new Dictionary +{ + { "Routes:route:ClusterId", "cluster" }, + { "Routes:route:Match:Path", "/{**catchall}" }, + { "Clusters:cluster:HttpClient:DangerousAcceptAnyServerCertificate", "true" }, +}; -public class Program +var clusterCount = 0; +foreach (var clusterUrl in clusterUrls.Split(';')) { - public static void Main(string[] args) => - CreateHostBuilder(args).Build().Run(); - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - webBuilder.ConfigureKestrel((context, kestrelOptions) => - { - kestrelOptions.ConfigureHttpsDefaults(httpsOptions => - { - httpsOptions.ServerCertificate = new X509Certificate2(Path.Combine(context.HostingEnvironment.ContentRootPath, "testCert.pfx"), "testPassword"); - }); - }) - .UseStartup()); + configDictionary.Add($"Clusters:cluster:Destinations:destination{clusterCount++}:Address", clusterUrl); } + +var proxyConfig = new ConfigurationBuilder().AddInMemoryCollection(configDictionary).Build(); + +builder.Services.AddReverseProxy().LoadFromConfig(proxyConfig); + +var app = builder.Build(); + +BenchmarksEventSource.MeasureAspNetVersion(); +BenchmarksEventSource.MeasureNetCoreAppVersion(); + +// Register the reverse proxy routes +app.MapReverseProxy(builder => +{ + // Skip SessionAffinity, LoadBalancing and PassiveHealthChecks +}); + +app.Run(); diff --git a/testassets/BenchmarkApp/Startup.cs b/testassets/BenchmarkApp/Startup.cs deleted file mode 100644 index 241ce7360..000000000 --- a/testassets/BenchmarkApp/Startup.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Builder; -using Microsoft.Crank.EventSources; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace BenchmarkApp; - -public class Startup -{ - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - public void ConfigureServices(IServiceCollection services) - { - var clusterUrls = _configuration["clusterUrls"]; - - if (string.IsNullOrWhiteSpace(clusterUrls)) - { - throw new ArgumentException("--clusterUrls is required"); - } - - var configDictionary = new Dictionary - { - { "Routes:route:ClusterId", "cluster" }, - { "Routes:route:Match:Path", "/{**catchall}" }, - { "Clusters:cluster:HttpClient:DangerousAcceptAnyServerCertificate", "true" }, - }; - - var clusterCount = 0; - foreach (var clusterUrl in clusterUrls.Split(';')) - { - configDictionary.Add($"Clusters:cluster:Destinations:destination{clusterCount++}:Address", clusterUrl); - } - - var proxyConfig = new ConfigurationBuilder().AddInMemoryCollection(configDictionary).Build(); - - services.AddReverseProxy().LoadFromConfig(proxyConfig); - } - - public void Configure(IApplicationBuilder app) - { - BenchmarksEventSource.MeasureAspNetVersion(); - BenchmarksEventSource.MeasureNetCoreAppVersion(); - - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(builder => - { - // Skip SessionAffinity, LoadBalancing and PassiveHealthChecks - }); - }); - } -} diff --git a/testassets/ReverseProxy.Code/Program.cs b/testassets/ReverseProxy.Code/Program.cs index c1e37829e..f7728eddf 100644 --- a/testassets/ReverseProxy.Code/Program.cs +++ b/testassets/ReverseProxy.Code/Program.cs @@ -1,28 +1,153 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; -namespace Yarp.ReverseProxy.Sample; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Configuration; +using Yarp.ReverseProxy.Model; +using Yarp.ReverseProxy.Transforms; +using Yarp.Telemetry.Consumption; +using Yarp.ReverseProxy.Sample; +using System.Linq; +using Yarp.ReverseProxy; -/// -/// Class that contains the entrypoint for the Reverse Proxy sample app. -/// -public class Program +var builder = WebApplication.CreateBuilder(args); + +var services = builder.Services; +services.AddControllers(); +var routes = new[] +{ + new RouteConfig() + { + RouteId = "route1", + ClusterId = "cluster1", + Match = new RouteMatch + { + Path = "{**catch-all}" + }, +#if NET8_0_OR_GREATER + Timeout = TimeSpan.FromSeconds(5), +#endif + } +}; +var clusters = new[] { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) + new ClusterConfig() + { + ClusterId = "cluster1", + SessionAffinity = new SessionAffinityConfig { Enabled = true, Policy = "Cookie", AffinityKeyName = ".Yarp.ReverseProxy.Affinity" }, + Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } } + } + }, + new ClusterConfig() { - CreateHostBuilder(args).Build().Run(); + ClusterId = "cluster2", + Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "destination2", new DestinationConfig() { Address = "https://localhost:10001" } } + } } +}; + +services.AddReverseProxy() + .LoadFromMemory(routes, clusters) + .ConfigureHttpClient((context, handler) => + { + handler.Expect100ContinueTimeout = TimeSpan.FromMilliseconds(300); + }) + .AddTransformFactory() + .AddTransforms() + .AddTransforms(transformBuilderContext => + { + // For each route+cluster pair decide if we want to add transforms, and if so, which? + // This logic is re-run each time a route is rebuilt. + + // transformBuilderContext.AddPathPrefix("/prefix"); - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => + // Only do this for routes that require auth. + if (string.Equals("token", transformBuilderContext.Route.AuthorizationPolicy)) + { + transformBuilderContext.AddRequestTransform(async transformContext => { - webBuilder.UseStartup(); + // AuthN and AuthZ will have already been completed after request routing. + var ticket = await transformContext.HttpContext.AuthenticateAsync("token"); + var tokenService = transformContext.HttpContext.RequestServices.GetRequiredService(); + var token = await tokenService.GetAuthTokenAsync(ticket.Principal); + transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); }); -} + } + + transformBuilderContext.AddResponseTransform(context => + { + // Suppress the response body from errors. + // The status code was already copied. + if (context.ProxyResponse?.IsSuccessStatusCode == false) + { + context.SuppressResponseBody = true; + } + + return default; + }); + }); + +services.AddHttpContextAccessor(); +services.AddSingleton, ForwarderMetricsConsumer>(); +services.AddTelemetryConsumer(); +services.AddTelemetryListeners(); +#if NET8_0_OR_GREATER +services.AddRequestTimeouts(o => +{ + o.DefaultPolicy = new Microsoft.AspNetCore.Http.Timeouts.RequestTimeoutPolicy() + { + Timeout = TimeSpan.FromSeconds(1), + TimeoutStatusCode = StatusCodes.Status418ImATeapot, + }; +}); +#endif + +var app = builder.Build(); + +app.UseAuthorization(); +#if NET8_0_OR_GREATER +app.UseRequestTimeouts(); +#endif + +app.MapControllers(); +app.MapReverseProxy(proxyPipeline => +{ + // Custom endpoint selection + proxyPipeline.Use((context, next) => + { + var lookup = context.RequestServices.GetRequiredService(); + if (lookup.TryGetCluster("cluster2", out var cluster)) + { + context.ReassignProxyRequest(cluster); + } + + var someCriteria = false; // MeetsCriteria(context); + if (someCriteria) + { + var availableDestinationsFeature = context.Features.Get(); + var destination = availableDestinationsFeature.AvailableDestinations[0]; // PickDestination(availableDestinationsFeature.Destinations); + // Load balancing will no-op if we've already reduced the list of available destinations to 1. + availableDestinationsFeature.AvailableDestinations = destination; + } + + return next(); + }); + proxyPipeline.UseSessionAffinity(); + proxyPipeline.UseLoadBalancing(); +}); + +app.Run(); diff --git a/testassets/ReverseProxy.Code/Startup.cs b/testassets/ReverseProxy.Code/Startup.cs deleted file mode 100644 index e5b9eb1df..000000000 --- a/testassets/ReverseProxy.Code/Startup.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http.Headers; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Yarp.ReverseProxy.Configuration; -using Yarp.ReverseProxy.Model; -using Yarp.ReverseProxy.Transforms; -using Yarp.Telemetry.Consumption; - -namespace Yarp.ReverseProxy.Sample; - -/// -/// ASP .NET Core pipeline initialization. -/// -public class Startup -{ - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - var routes = new[] - { - new RouteConfig() - { - RouteId = "route1", - ClusterId = "cluster1", - Match = new RouteMatch - { - Path = "{**catch-all}" - }, -#if NET8_0_OR_GREATER - Timeout = TimeSpan.FromSeconds(5), -#endif - } - }; - var clusters = new[] - { - new ClusterConfig() - { - ClusterId = "cluster1", - SessionAffinity = new SessionAffinityConfig { Enabled = true, Policy = "Cookie", AffinityKeyName = ".Yarp.ReverseProxy.Affinity" }, - Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } } - } - }, - new ClusterConfig() - { - ClusterId = "cluster2", - Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "destination2", new DestinationConfig() { Address = "https://localhost:10001" } } - } - } - }; - - services.AddReverseProxy() - .LoadFromMemory(routes, clusters) - .ConfigureHttpClient((context, handler) => - { - handler.Expect100ContinueTimeout = TimeSpan.FromMilliseconds(300); - }) - .AddTransformFactory() - .AddTransforms() - .AddTransforms(transformBuilderContext => - { - // For each route+cluster pair decide if we want to add transforms, and if so, which? - // This logic is re-run each time a route is rebuilt. - - // transformBuilderContext.AddPathPrefix("/prefix"); - - // Only do this for routes that require auth. - if (string.Equals("token", transformBuilderContext.Route.AuthorizationPolicy)) - { - transformBuilderContext.AddRequestTransform(async transformContext => - { - // AuthN and AuthZ will have already been completed after request routing. - var ticket = await transformContext.HttpContext.AuthenticateAsync("token"); - var tokenService = transformContext.HttpContext.RequestServices.GetRequiredService(); - var token = await tokenService.GetAuthTokenAsync(ticket.Principal); - transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - }); - } - - transformBuilderContext.AddResponseTransform(context => - { - // Suppress the response body from errors. - // The status code was already copied. - if (context.ProxyResponse?.IsSuccessStatusCode == false) - { - context.SuppressResponseBody = true; - } - - return default; - }); - }); - - services.AddHttpContextAccessor(); - services.AddSingleton, ForwarderMetricsConsumer>(); - services.AddTelemetryConsumer(); - services.AddTelemetryListeners(); -#if NET8_0_OR_GREATER - services.AddRequestTimeouts(o => - { - o.DefaultPolicy = new Microsoft.AspNetCore.Http.Timeouts.RequestTimeoutPolicy() - { - Timeout = TimeSpan.FromSeconds(1), - TimeoutStatusCode = StatusCodes.Status418ImATeapot, - }; - }); -#endif - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app, IProxyStateLookup lookup) - { - app.UseRouting(); - app.UseAuthorization(); -#if NET8_0_OR_GREATER - app.UseRequestTimeouts(); -#endif - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - endpoints.MapReverseProxy(proxyPipeline => - { - // Custom endpoint selection - proxyPipeline.Use((context, next) => - { - if (lookup.TryGetCluster("cluster2", out var cluster)) - { - context.ReassignProxyRequest(cluster); - } - - var someCriteria = false; // MeetsCriteria(context); - if (someCriteria) - { - var availableDestinationsFeature = context.Features.Get(); - var destination = availableDestinationsFeature.AvailableDestinations[0]; // PickDestination(availableDestinationsFeature.Destinations); - // Load balancing will no-op if we've already reduced the list of available destinations to 1. - availableDestinationsFeature.AvailableDestinations = destination; - } - - return next(); - }); - proxyPipeline.UseSessionAffinity(); - proxyPipeline.UseLoadBalancing(); - }); - }); - } -} diff --git a/testassets/ReverseProxy.Config/Program.cs b/testassets/ReverseProxy.Config/Program.cs index c1e37829e..86e5ec6e0 100644 --- a/testassets/ReverseProxy.Config/Program.cs +++ b/testassets/ReverseProxy.Config/Program.cs @@ -1,28 +1,41 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Model; +using Yarp.ReverseProxy.Sample; -namespace Yarp.ReverseProxy.Sample; +var builder = WebApplication.CreateBuilder(args); -/// -/// Class that contains the entrypoint for the Reverse Proxy sample app. -/// -public class Program +builder.Services.AddControllers(); +builder.Services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy2")) + .AddConfigFilter(); + +var app = builder.Build(); + +app.MapControllers(); +app.MapReverseProxy(proxyPipeline => { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) + // Custom endpoint selection + proxyPipeline.Use((context, next) => { - CreateHostBuilder(args).Build().Run(); - } + var someCriteria = false; // MeetsCriteria(context); + if (someCriteria) + { + var availableDestinationsFeature = context.Features.Get(); + var destination = availableDestinationsFeature.AvailableDestinations[0]; // PickDestination(availableDestinationsFeature.Destinations); + // Load balancing will no-op if we've already reduced the list of available destinations to 1. + availableDestinationsFeature.AvailableDestinations = destination; + } + + return next(); + }); + proxyPipeline.UseSessionAffinity(); + proxyPipeline.UseLoadBalancing(); + proxyPipeline.UsePassiveHealthChecks(); +}).ConfigureEndpoints((builder, route) => builder.WithDisplayName($"ReverseProxy {route.RouteId}-{route.ClusterId}")); - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); -} +app.Run(); diff --git a/testassets/ReverseProxy.Config/ReverseProxy.Config.csproj b/testassets/ReverseProxy.Config/ReverseProxy.Config.csproj index d7983dfa0..d35a46b79 100644 --- a/testassets/ReverseProxy.Config/ReverseProxy.Config.csproj +++ b/testassets/ReverseProxy.Config/ReverseProxy.Config.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0 + net6.0;net7.0;net8.0 Exe Yarp.ReverseProxy.Sample diff --git a/testassets/ReverseProxy.Config/Startup.cs b/testassets/ReverseProxy.Config/Startup.cs deleted file mode 100644 index b9f515778..000000000 --- a/testassets/ReverseProxy.Config/Startup.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Yarp.ReverseProxy.Model; - -namespace Yarp.ReverseProxy.Sample; - -/// -/// ASP .NET Core pipeline initialization. -/// -public class Startup -{ - private readonly IConfiguration _configuration; - - /// - /// Initializes a new instance of the class. - /// - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - services.AddReverseProxy() - .LoadFromConfig(_configuration.GetSection("ReverseProxy")) - .LoadFromConfig(_configuration.GetSection("ReverseProxy2")) - .AddConfigFilter(); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - endpoints.MapReverseProxy(proxyPipeline => - { - // Custom endpoint selection - proxyPipeline.Use((context, next) => - { - var someCriteria = false; // MeetsCriteria(context); - if (someCriteria) - { - var availableDestinationsFeature = context.Features.Get(); - var destination = availableDestinationsFeature.AvailableDestinations[0]; // PickDestination(availableDestinationsFeature.Destinations); - // Load balancing will no-op if we've already reduced the list of available destinations to 1. - availableDestinationsFeature.AvailableDestinations = destination; - } - - return next(); - }); - proxyPipeline.UseSessionAffinity(); - proxyPipeline.UseLoadBalancing(); - proxyPipeline.UsePassiveHealthChecks(); - }) - .ConfigureEndpoints((builder, route) => builder.WithDisplayName($"ReverseProxy {route.RouteId}-{route.ClusterId}")); - }); - } -} diff --git a/testassets/ReverseProxy.Direct/Program.cs b/testassets/ReverseProxy.Direct/Program.cs index d3ce2f4ed..16d01518f 100644 --- a/testassets/ReverseProxy.Direct/Program.cs +++ b/testassets/ReverseProxy.Direct/Program.cs @@ -1,43 +1,100 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Connections; +using Yarp.ReverseProxy.Sample; +using Yarp.ReverseProxy.Forwarder; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Yarp.ReverseProxy.Transforms; +using System.Net.Http; +using System.Threading; +using System; +using System.Net; +using System.Diagnostics; +using Yarp.ReverseProxy.Transforms.Builder; + +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.ConfigureKestrel(kestrel => +{ + var logger = kestrel.ApplicationServices.GetRequiredService>(); + kestrel.ListenAnyIP(5001, portOptions => + { + portOptions.Use(async (connectionContext, next) => + { + await TlsFilter.ProcessAsync(connectionContext, next, logger); + }); + portOptions.UseHttps(); + }); +}); + +builder.Services.AddHttpForwarder(); -namespace Yarp.ReverseProxy.Sample; +var app = builder.Build(); -/// -/// Class that contains the entrypoint for the Reverse Proxy sample app. -/// -public class Program +var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() { - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) + UseProxy = false, + AllowAutoRedirect = false, + AutomaticDecompression = DecompressionMethods.None, + UseCookies = false, + ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), + ConnectTimeout = TimeSpan.FromSeconds(15), +}); + +var transformBuilder = app.Services.GetRequiredService(); +var transformer = transformBuilder.Create(context => +{ + context.AddQueryRemoveKey("param1"); + context.AddQueryValue("area", "xx2", false); + context.AddOriginalHost(false); +}); + +// or var transformer = new CustomTransformer(); +// or var transformer = HttpTransformer.Default; + +var requestConfig = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; + +app.UseRouting(); +app.UseEndpoints(endpoints => +{ + endpoints.MapForwarder("/{**catch-all}", "https://example.com", requestConfig, transformer, httpClient); +}); + +app.Run(); + +internal class CustomTransformer : HttpTransformer +{ + public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) { - CreateHostBuilder(args).Build().Run(); + // Copy all request headers + await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); + + // Customize the query string: + var queryContext = new QueryTransformContext(httpContext.Request); + queryContext.Collection.Remove("param1"); + queryContext.Collection["area"] = "xx2"; + + // Assign the custom uri. Be careful about extra slashes when concatenating here. + proxyRequest.RequestUri = new Uri(destinationPrefix + httpContext.Request.Path + queryContext.QueryString); + + // Suppress the original request header, use the one from the destination Uri. + proxyRequest.Headers.Host = null; } - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.ConfigureKestrel(kestrel => - { - var logger = kestrel.ApplicationServices.GetRequiredService>(); - kestrel.ListenAnyIP(5001, portOptions => - { - portOptions.Use(async (connectionContext, next) => - { - await TlsFilter.ProcessAsync(connectionContext, next, logger); - }); - portOptions.UseHttps(); - }); - }); - webBuilder.UseStartup(); - }); + public override ValueTask TransformResponseAsync(HttpContext httpContext, HttpResponseMessage proxyResponse, CancellationToken cancellationToken) + { + // Suppress the response body from errors. + // The status code was already copied. + if (!proxyResponse.IsSuccessStatusCode) + { + return new ValueTask(false); + } + + return base.TransformResponseAsync(httpContext, proxyResponse, cancellationToken); + } } diff --git a/testassets/ReverseProxy.Direct/ReverseProxy.Direct.csproj b/testassets/ReverseProxy.Direct/ReverseProxy.Direct.csproj index d7983dfa0..d35a46b79 100644 --- a/testassets/ReverseProxy.Direct/ReverseProxy.Direct.csproj +++ b/testassets/ReverseProxy.Direct/ReverseProxy.Direct.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0 + net6.0;net7.0;net8.0 Exe Yarp.ReverseProxy.Sample diff --git a/testassets/ReverseProxy.Direct/Startup.cs b/testassets/ReverseProxy.Direct/Startup.cs deleted file mode 100644 index e7b13102f..000000000 --- a/testassets/ReverseProxy.Direct/Startup.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Diagnostics; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Yarp.ReverseProxy.Forwarder; -using Yarp.ReverseProxy.Transforms; -using Yarp.ReverseProxy.Transforms.Builder; - -namespace Yarp.ReverseProxy.Sample; - -/// -/// ASP .NET Core pipeline initialization. -/// -public class Startup -{ - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddHttpForwarder(); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() - { - UseProxy = false, - AllowAutoRedirect = false, - AutomaticDecompression = DecompressionMethods.None, - UseCookies = false, - ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), - ConnectTimeout = TimeSpan.FromSeconds(15), - }); - - var transformBuilder = app.ApplicationServices.GetRequiredService(); - var transformer = transformBuilder.Create(context => - { - context.AddQueryRemoveKey("param1"); - context.AddQueryValue("area", "xx2", false); - context.AddOriginalHost(false); - }); - - // or var transformer = new CustomTransformer(); - // or var transformer = HttpTransformer.Default; - - var requestConfig = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; - - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapForwarder("/{**catch-all}", "https://example.com", requestConfig, transformer, httpClient); - }); - } - - private class CustomTransformer : HttpTransformer - { - public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) - { - // Copy all request headers - await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); - - // Customize the query string: - var queryContext = new QueryTransformContext(httpContext.Request); - queryContext.Collection.Remove("param1"); - queryContext.Collection["area"] = "xx2"; - - // Assign the custom uri. Be careful about extra slashes when concatenating here. - proxyRequest.RequestUri = new Uri(destinationPrefix + httpContext.Request.Path + queryContext.QueryString); - - // Suppress the original request header, use the one from the destination Uri. - proxyRequest.Headers.Host = null; - } - - public override ValueTask TransformResponseAsync(HttpContext httpContext, HttpResponseMessage proxyResponse, CancellationToken cancellationToken) - { - // Suppress the response body from errors. - // The status code was already copied. - if (!proxyResponse.IsSuccessStatusCode) - { - return new ValueTask(false); - } - - return base.TransformResponseAsync(httpContext, proxyResponse, cancellationToken); - } - } -} diff --git a/testassets/TestClient/Program.cs b/testassets/TestClient/Program.cs index 6ae246950..ab6e5fa2b 100644 --- a/testassets/TestClient/Program.cs +++ b/testassets/TestClient/Program.cs @@ -5,99 +5,91 @@ using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Threading.Tasks; +using SampleClient; using SampleClient.Scenarios; -namespace SampleClient; - -internal class Program +CommandLineArgs parsedArgs = null; +try { - public static async Task Main(string[] args) - { - CommandLineArgs parsedArgs = null; - try - { - parsedArgs = CommandLineArgs.Parse(args); - } - catch (ArgumentException) - { - // Do nothing, we will show help right after. - } - - if (parsedArgs is null || parsedArgs.Help) - { - CommandLineArgs.ShowHelp(); - return 1; - } - - var scenarioFactories = new Dictionary>(StringComparer.OrdinalIgnoreCase) { - {"Http1", () => new Http1Scenario()}, - {"Http2", () => new Http2Scenario()}, - {"Http2PostExpectContinue", () => new Http2PostExpectContinueScenario()}, - // Disabled due to a conflict with a workaround to the issue https://github.com/microsoft/reverse-proxy/issues/255. - //{"RawUpgrade", () => new RawUpgradeScenario()}, - {"WebSockets", () => new WebSocketsScenario()}, - {"SessionAffinity", () => new SessionAffinityScenario()} - }; - - if (string.IsNullOrEmpty(parsedArgs.Scenario)) - { - // Execute all scenarios - var success = true; - foreach (var kvp in scenarioFactories.OrderBy(kvp => kvp.Key)) - { - Console.WriteLine(); - Console.WriteLine($"Executing scenario '{kvp.Key}'..."); - try - { - var scenario = kvp.Value(); - await scenario.ExecuteAsync(parsedArgs, CancellationToken.None); - } - catch (Exception ex) - { - Console.WriteLine($"Unexpected exception: {ex}"); - success = false; - } - } - - Console.WriteLine(); - Console.ForegroundColor = success ? ConsoleColor.Green : ConsoleColor.Red; - Console.WriteLine($"All scenarios completed {(success ? "successfully" : "with errors")}."); - Console.ResetColor(); - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - return success ? 0 : 1; - } + parsedArgs = CommandLineArgs.Parse(args); +} +catch (ArgumentException) +{ + // Do nothing, we will show help right after. +} - if (!scenarioFactories.TryGetValue(parsedArgs.Scenario, out var scenarioFactory)) - { - Console.WriteLine($"Unknown scenario '{parsedArgs.Scenario}'. Supported values: "); - foreach (var scenarioName in scenarioFactories.Keys.OrderBy(k => k)) - { - Console.WriteLine($" {scenarioName}"); - } +if (parsedArgs is null || parsedArgs.Help) +{ + CommandLineArgs.ShowHelp(); + return 1; +} - Console.WriteLine(); - return 1; - } +var scenarioFactories = new Dictionary>(StringComparer.OrdinalIgnoreCase) { + {"Http1", () => new Http1Scenario()}, + {"Http2", () => new Http2Scenario()}, + {"Http2PostExpectContinue", () => new Http2PostExpectContinueScenario()}, + // Disabled due to a conflict with a workaround to the issue https://github.com/microsoft/reverse-proxy/issues/255. + //{"RawUpgrade", () => new RawUpgradeScenario()}, + {"WebSockets", () => new WebSocketsScenario()}, + {"SessionAffinity", () => new SessionAffinityScenario()} +}; - Console.WriteLine($"Executing scenario '{parsedArgs.Scenario}'."); +if (string.IsNullOrEmpty(parsedArgs.Scenario)) +{ + // Execute all scenarios + var success = true; + foreach (var kvp in scenarioFactories.OrderBy(kvp => kvp.Key)) + { + Console.WriteLine(); + Console.WriteLine($"Executing scenario '{kvp.Key}'..."); try { - var scenario = scenarioFactory(); + var scenario = kvp.Value(); await scenario.ExecuteAsync(parsedArgs, CancellationToken.None); } catch (Exception ex) { Console.WriteLine($"Unexpected exception: {ex}"); - return 1; + success = false; } + } + + Console.WriteLine(); + Console.ForegroundColor = success ? ConsoleColor.Green : ConsoleColor.Red; + Console.WriteLine($"All scenarios completed {(success ? "successfully" : "with errors")}."); + Console.ResetColor(); + Console.WriteLine("Press any key to exit."); + Console.ReadKey(); + return success ? 0 : 1; +} - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("All scenarios completed successfully!"); - Console.ResetColor(); - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - return 0; +if (!scenarioFactories.TryGetValue(parsedArgs.Scenario, out var scenarioFactory)) +{ + Console.WriteLine($"Unknown scenario '{parsedArgs.Scenario}'. Supported values: "); + foreach (var scenarioName in scenarioFactories.Keys.OrderBy(k => k)) + { + Console.WriteLine($" {scenarioName}"); } + + Console.WriteLine(); + return 1; } + +Console.WriteLine($"Executing scenario '{parsedArgs.Scenario}'."); +try +{ + var scenario = scenarioFactory(); + await scenario.ExecuteAsync(parsedArgs, CancellationToken.None); +} +catch (Exception ex) +{ + Console.WriteLine($"Unexpected exception: {ex}"); + return 1; +} + +Console.ForegroundColor = ConsoleColor.Green; +Console.WriteLine("All scenarios completed successfully!"); +Console.ResetColor(); +Console.WriteLine("Press any key to exit."); +Console.ReadKey(); +return 0; diff --git a/testassets/TestClient/TestClient.csproj b/testassets/TestClient/TestClient.csproj index ccf95c72d..cf5252b80 100644 --- a/testassets/TestClient/TestClient.csproj +++ b/testassets/TestClient/TestClient.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0 + net6.0;net7.0;net8.0 Exe diff --git a/testassets/TestServer/Program.cs b/testassets/TestServer/Program.cs index c70011a39..b83f9d5ad 100644 --- a/testassets/TestServer/Program.cs +++ b/testassets/TestServer/Program.cs @@ -1,28 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; -namespace SampleServer; +var builder = WebApplication.CreateBuilder(args); -/// -/// Class that contains the entrypoint for the Reverse Proxy sample app. -/// -public class Program -{ - /// - /// Entrypoint of the application. - /// - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } +builder.Services.AddControllers() + .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); -} +var app = builder.Build(); + +app.UseWebSockets(); +app.MapControllers(); + +app.Run(); diff --git a/testassets/TestServer/Startup.cs b/testassets/TestServer/Startup.cs deleted file mode 100644 index ae1e3f75c..000000000 --- a/testassets/TestServer/Startup.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace SampleServer; - -/// -/// ASP .NET Core pipeline initialization. -/// -public class Startup -{ - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - public void ConfigureServices(IServiceCollection services) - { - services - .AddControllers() - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app) - { - // Disabling https redirection behind the proxy. Forwarders are not currently set up so we can't tell if the external connection used https. - // Nor do we know the correct port to redirect to. - // app.UseHttpsRedirection(); - - app.UseWebSockets(); - - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } -} diff --git a/testassets/TestServer/TestServer.csproj b/testassets/TestServer/TestServer.csproj index 0e5c6fc8d..26e52ad7e 100644 --- a/testassets/TestServer/TestServer.csproj +++ b/testassets/TestServer/TestServer.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0 + net6.0;net7.0;net8.0 Exe SampleServer From 1601eb5e349a1e68e5ea2a516d5a3a4a5a13fe7b Mon Sep 17 00:00:00 2001 From: Chris R Date: Wed, 15 Nov 2023 13:41:50 -0800 Subject: [PATCH 2/3] Cleanup --- docs/docfx/articles/cors.md | 2 +- docs/docfx/articles/http-client-config.md | 1 - docs/docfx/articles/transforms.md | 2 +- testassets/ReverseProxy.Code/Program.cs | 9 ++------- testassets/ReverseProxy.Direct/Program.cs | 6 +----- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/docs/docfx/articles/cors.md b/docs/docfx/articles/cors.md index c7a0c23fd..740678119 100644 --- a/docs/docfx/articles/cors.md +++ b/docs/docfx/articles/cors.md @@ -49,7 +49,7 @@ services.AddCors(options => }); ``` -In then add the CORS middleware. +Then add the CORS middleware. ``` app.UseCors(); diff --git a/docs/docfx/articles/http-client-config.md b/docs/docfx/articles/http-client-config.md index 0be64893e..c002deb91 100644 --- a/docs/docfx/articles/http-client-config.md +++ b/docs/docfx/articles/http-client-config.md @@ -62,7 +62,6 @@ builder.WebHost.ConfigureKestrel(kestrel => // and/or kestrel.ResponseHeaderEncodingSelector = _ => Encoding.Latin1; }); - ``` - EnableMultipleHttp2Connections - enables opening additional HTTP/2 connections to the same server when the maximum number of concurrent streams is reached on all existing connections. The default is `true`. See [SocketsHttpHandler.EnableMultipleHttp2Connections](https://docs.microsoft.com/dotnet/api/system.net.http.socketshttphandler.enablemultiplehttp2connections) ```JSON diff --git a/docs/docfx/articles/transforms.md b/docs/docfx/articles/transforms.md index 78c556e7e..f688bda05 100644 --- a/docs/docfx/articles/transforms.md +++ b/docs/docfx/articles/transforms.md @@ -111,7 +111,7 @@ Developers that want to integrate their custom transforms with the `Transforms` Transforms can be added to routes programmatically by calling the [AddTransforms](xref:Microsoft.Extensions.DependencyInjection.ReverseProxyServiceCollectionExtensions.AddTransforms*) method. -`AddTransforms` can be called from `Startup.ConfigureServices` to provide a callback for configuring transforms. This callback is invoked each time a route is built or rebuilt and allows the developer to inspect the [RouteConfig](xref:Yarp.ReverseProxy.Configuration.RouteConfig) information and conditionally add transforms for it. +`AddTransforms` can be called after `AddReverseProxy` to provide a callback for configuring transforms. This callback is invoked each time a route is built or rebuilt and allows the developer to inspect the [RouteConfig](xref:Yarp.ReverseProxy.Configuration.RouteConfig) information and conditionally add transforms for it. The `AddTransforms` callback provides a [TransformBuilderContext](xref:Yarp.ReverseProxy.Transforms.Builder.TransformBuilderContext) where transforms can be added or configured. Most transforms provide `TransformBuilderContext` extension methods to make them easier to add. These are extensions documented below with the individual transform descriptions. diff --git a/testassets/ReverseProxy.Code/Program.cs b/testassets/ReverseProxy.Code/Program.cs index f7728eddf..09a55c0d0 100644 --- a/testassets/ReverseProxy.Code/Program.cs +++ b/testassets/ReverseProxy.Code/Program.cs @@ -1,24 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - using System; using System.Collections.Generic; -using System.Net; using System.Net.Http.Headers; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy; using Yarp.ReverseProxy.Configuration; using Yarp.ReverseProxy.Model; +using Yarp.ReverseProxy.Sample; using Yarp.ReverseProxy.Transforms; using Yarp.Telemetry.Consumption; -using Yarp.ReverseProxy.Sample; -using System.Linq; -using Yarp.ReverseProxy; var builder = WebApplication.CreateBuilder(args); diff --git a/testassets/ReverseProxy.Direct/Program.cs b/testassets/ReverseProxy.Direct/Program.cs index 16d01518f..718d578bd 100644 --- a/testassets/ReverseProxy.Direct/Program.cs +++ b/testassets/ReverseProxy.Direct/Program.cs @@ -59,11 +59,7 @@ var requestConfig = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; -app.UseRouting(); -app.UseEndpoints(endpoints => -{ - endpoints.MapForwarder("/{**catch-all}", "https://example.com", requestConfig, transformer, httpClient); -}); +app.MapForwarder("/{**catch-all}", "https://example.com", requestConfig, transformer, httpClient); app.Run(); From 9b475af9ceaef2bb40418d6ee64a274bb2ca09bc Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Thu, 16 Nov 2023 08:36:39 -0800 Subject: [PATCH 3/3] Update docs/docfx/articles/config-files.md --- docs/docfx/articles/config-files.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/docfx/articles/config-files.md b/docs/docfx/articles/config-files.md index 9674727dc..a871a5022 100644 --- a/docs/docfx/articles/config-files.md +++ b/docs/docfx/articles/config-files.md @@ -11,7 +11,6 @@ using Microsoft.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); -builder.Services.AddControllers(); // Add the reverse proxy capability to the server builder.Services.AddReverseProxy() // Initialize the reverse proxy from the "ReverseProxy" section of configuration