diff --git a/src/ReverseProxy/Transforms/RequestTransformContext.cs b/src/ReverseProxy/Transforms/RequestTransformContext.cs index 74d87f21e..52255e428 100644 --- a/src/ReverseProxy/Transforms/RequestTransformContext.cs +++ b/src/ReverseProxy/Transforms/RequestTransformContext.cs @@ -53,8 +53,9 @@ public QueryTransformContext Query /// /// The URI prefix for the proxy request. This includes the scheme and host and can optionally include a /// port and path base. The 'Path' and 'Query' properties will be appended to this after the transforms have run. + /// Changing this value can have side effects on load balancing and health checks. /// - public string DestinationPrefix { get; init; } = default!; + public string DestinationPrefix { get; set; } = default!; /// /// A indicating that the request is being aborted. diff --git a/test/ReverseProxy.Tests/Forwarder/HttpForwarderTests.cs b/test/ReverseProxy.Tests/Forwarder/HttpForwarderTests.cs index 39f087566..87039551d 100644 --- a/test/ReverseProxy.Tests/Forwarder/HttpForwarderTests.cs +++ b/test/ReverseProxy.Tests/Forwarder/HttpForwarderTests.cs @@ -153,10 +153,12 @@ public async Task NormalRequestWithTransforms_Works() httpContext.Response.Body = proxyResponseStream; var destinationPrefix = "https://localhost:123/a/b/"; + Uri originalRequestUri = null; var transforms = new DelegateHttpTransforms() { OnRequest = (context, request, destination) => { + originalRequestUri = request.RequestUri; request.RequestUri = new Uri(destination + "prefix" + context.Request.Path + context.Request.QueryString); request.Headers.Remove("transformHeader"); @@ -214,6 +216,7 @@ public async Task NormalRequestWithTransforms_Works() var proxyError = await sut.SendAsync(httpContext, destinationPrefix, client, ForwarderRequestConfig.Empty, transforms); + Assert.Null(originalRequestUri); // Should only be set by the transformer Assert.Equal(ForwarderError.None, proxyError); Assert.Equal(234, httpContext.Response.StatusCode); var reasonPhrase = httpContext.Features.Get().ReasonPhrase; diff --git a/test/ReverseProxy.Tests/Forwarder/HttpTransformerTests.cs b/test/ReverseProxy.Tests/Forwarder/HttpTransformerTests.cs index abdb72e0a..ff451ed75 100644 --- a/test/ReverseProxy.Tests/Forwarder/HttpTransformerTests.cs +++ b/test/ReverseProxy.Tests/Forwarder/HttpTransformerTests.cs @@ -13,6 +13,7 @@ using Microsoft.Net.Http.Headers; using Xunit; using Yarp.ReverseProxy.Transforms; +using Yarp.ReverseProxy.Transforms.Builder; using Yarp.ReverseProxy.Transforms.Builder.Tests; using Yarp.Tests.Common; @@ -116,6 +117,28 @@ public async Task TransformRequestAsync_ContentLengthAndTransferEncoding_Content Assert.False(proxyRequest.Headers.TryGetValues(HeaderNames.TransferEncoding, out var _)); } + [Fact] + public async Task TransformRequestAsync_SetDestinationPrefix() + { + const string updatedDestinationPrefix = "https://contoso.com"; + var transformer = TransformBuilderTests.CreateTransformBuilder().CreateInternal(context => + { + context.AddRequestTransform(transformContext => + { + transformContext.DestinationPrefix = updatedDestinationPrefix; + return ValueTask.CompletedTask; + }); + }); + var httpContext = new DefaultHttpContext(); + var proxyRequest = new HttpRequestMessage(HttpMethod.Get, requestUri: (string)null) + { + Content = new ByteArrayContent(Array.Empty()), + }; + + await transformer.TransformRequestAsync(httpContext, proxyRequest, "https://localhost", CancellationToken.None); + Assert.Equal(new Uri(updatedDestinationPrefix), proxyRequest.RequestUri); + } + [Theory] [InlineData(HttpStatusCode.Continue)] [InlineData(HttpStatusCode.SwitchingProtocols)] diff --git a/test/ReverseProxy.Tests/Transforms/DestinationPrefixTransformTests.cs b/test/ReverseProxy.Tests/Transforms/DestinationPrefixTransformTests.cs new file mode 100644 index 000000000..c6b6ba8be --- /dev/null +++ b/test/ReverseProxy.Tests/Transforms/DestinationPrefixTransformTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Xunit; + +namespace Yarp.ReverseProxy.Transforms.Tests; + +public class DestinationPrefixTransformTests +{ + [Fact] + public async Task UpdateDestinationPrefix() + { + const string newDestinationPrefix = "http://localhost:8080"; + var context = new RequestTransformContext() + { + DestinationPrefix = "http://contoso.com:5000" + }; + var transform = new DestinationPrefixTransform(newDestinationPrefix); + await transform.ApplyAsync(context); + } + + private class DestinationPrefixTransform(string newDestinationPrefix) : RequestTransform + { + public override ValueTask ApplyAsync(RequestTransformContext context) + { + context.DestinationPrefix = newDestinationPrefix; + return ValueTask.CompletedTask; + } + } +}