diff --git a/docs/docfx/articles/direct-forwarding.md b/docs/docfx/articles/direct-forwarding.md index 8e0a59c9..656e1068 100644 --- a/docs/docfx/articles/direct-forwarding.md +++ b/docs/docfx/articles/direct-forwarding.md @@ -98,6 +98,23 @@ 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); + }); +} +``` + ### The HTTP Client The http client may be customized, but the above example is recommended for common proxy scenarios. diff --git a/samples/ReverseProxy.Direct.Sample/Startup.cs b/samples/ReverseProxy.Direct.Sample/Startup.cs index 2d1ad28d..c1993c73 100644 --- a/samples/ReverseProxy.Direct.Sample/Startup.cs +++ b/samples/ReverseProxy.Direct.Sample/Startup.cs @@ -48,6 +48,9 @@ namespace Yarp.Sample 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 => @@ -78,18 +81,8 @@ namespace Yarp.Sample }); - // 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. - endpoints.Map("/{**catch-all}", async httpContext => - { - var error = await forwarder.SendAsync(httpContext, "https://example.com", httpClient, requestOptions, transformer); - // Check if the proxy operation was successful - if (error != ForwarderError.None) - { - var errorFeature = httpContext.Features.Get(); - var exception = errorFeature.Exception; - } - }); + // 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); }); } diff --git a/src/ReverseProxy/Forwarder/DirectForwardingHttpClientProvider.cs b/src/ReverseProxy/Forwarder/DirectForwardingHttpClientProvider.cs new file mode 100644 index 00000000..7e35e1fb --- /dev/null +++ b/src/ReverseProxy/Forwarder/DirectForwardingHttpClientProvider.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Net.Http; +using Yarp.ReverseProxy.Configuration; + +namespace Yarp.ReverseProxy.Forwarder; + +internal sealed class DirectForwardingHttpClientProvider +{ + public HttpMessageInvoker HttpClient { get; } + + public DirectForwardingHttpClientProvider() : this(new ForwarderHttpClientFactory()) { } + + public DirectForwardingHttpClientProvider(IForwarderHttpClientFactory factory) + { + HttpClient = factory.CreateClient(new ForwarderHttpClientContext + { + NewConfig = HttpClientConfig.Empty + }); + } +} diff --git a/src/ReverseProxy/Management/ReverseProxyServiceCollectionExtensions.cs b/src/ReverseProxy/Management/ReverseProxyServiceCollectionExtensions.cs index f4f23354..3674c4d0 100644 --- a/src/ReverseProxy/Management/ReverseProxyServiceCollectionExtensions.cs +++ b/src/ReverseProxy/Management/ReverseProxyServiceCollectionExtensions.cs @@ -31,6 +31,9 @@ public static class ReverseProxyServiceCollectionExtensions services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + + services.AddSingleton(); + return services; } diff --git a/src/ReverseProxy/Routing/DirectForwardingIEndpointRouteBuilderExtensions.cs b/src/ReverseProxy/Routing/DirectForwardingIEndpointRouteBuilderExtensions.cs new file mode 100644 index 00000000..33ffb411 --- /dev/null +++ b/src/ReverseProxy/Routing/DirectForwardingIEndpointRouteBuilderExtensions.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Net.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Forwarder; + +namespace Microsoft.AspNetCore.Builder; + +/// +/// Extension methods for used to add direct forwarding to the ASP.NET Core request pipeline. +/// +public static class DirectForwardingIEndpointRouteBuilderExtensions +{ + /// + /// Adds direct forwarding of HTTP requests that match the specified pattern to a specific destination using default configuration for the outgoing request, default transforms, and default HTTP client. + /// + public static IEndpointConventionBuilder MapForwarder(this IEndpointRouteBuilder endpoints, string pattern, string destinationPrefix) + { + return endpoints.MapForwarder(pattern, destinationPrefix, ForwarderRequestConfig.Empty); + } + + /// + /// Adds direct forwarding of HTTP requests that match the specified pattern to a specific destination using customized configuration for the outgoing request, default transforms, and default HTTP client. + /// + public static IEndpointConventionBuilder MapForwarder(this IEndpointRouteBuilder endpoints, string pattern, string destinationPrefix, ForwarderRequestConfig requestConfig) + { + return endpoints.MapForwarder(pattern, destinationPrefix, requestConfig, HttpTransformer.Default); + } + + /// + /// Adds direct forwarding of HTTP requests that match the specified pattern to a specific destination using customized configuration for the outgoing request, customized transforms, and default HTTP client. + /// + public static IEndpointConventionBuilder MapForwarder(this IEndpointRouteBuilder endpoints, string pattern, string destinationPrefix, ForwarderRequestConfig requestConfig, HttpTransformer transformer) + { + var httpClientProvider = endpoints.ServiceProvider.GetRequiredService(); + + return endpoints.MapForwarder(pattern, destinationPrefix, requestConfig, transformer, httpClientProvider.HttpClient); + } + + /// + /// Adds direct forwarding of HTTP requests that match the specified pattern to a specific destination using customized configuration for the outgoing request, customized transforms, and customized HTTP client. + /// + public static IEndpointConventionBuilder MapForwarder(this IEndpointRouteBuilder endpoints, string pattern, string destinationPrefix, ForwarderRequestConfig requestConfig, HttpTransformer transformer, HttpMessageInvoker httpClient) + { + ArgumentNullException.ThrowIfNull(endpoints); + ArgumentNullException.ThrowIfNull(destinationPrefix); + ArgumentNullException.ThrowIfNull(httpClient); + ArgumentNullException.ThrowIfNull(requestConfig); + ArgumentNullException.ThrowIfNull(transformer); + + var forwarder = endpoints.ServiceProvider.GetRequiredService(); + + return endpoints.Map(pattern, async httpContext => + { + await forwarder.SendAsync(httpContext, destinationPrefix, httpClient, requestConfig, transformer); + }); + } +} diff --git a/testassets/ReverseProxy.Direct/Startup.cs b/testassets/ReverseProxy.Direct/Startup.cs index 66ea46d6..b5052d5f 100644 --- a/testassets/ReverseProxy.Direct/Startup.cs +++ b/testassets/ReverseProxy.Direct/Startup.cs @@ -30,7 +30,7 @@ public class Startup /// /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// - public void Configure(IApplicationBuilder app, IHttpForwarder httpProxy) + public void Configure(IApplicationBuilder app) { var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() { @@ -56,16 +56,7 @@ public class Startup app.UseRouting(); app.UseEndpoints(endpoints => { - endpoints.Map("/{**catch-all}", async httpContext => - { - await httpProxy.SendAsync(httpContext, "https://example.com", httpClient, requestConfig, transformer); - var errorFeature = httpContext.GetForwarderErrorFeature(); - if (errorFeature is not null) - { - var error = errorFeature.Error; - var exception = errorFeature.Exception; - } - }); + endpoints.MapForwarder("/{**catch-all}", "https://example.com", requestConfig, transformer, httpClient); }); }