From 6822677e6ac1b7fae11763ae24f13adca748ac10 Mon Sep 17 00:00:00 2001 From: Asaf Madari <100016552+Asafima@users.noreply.github.com> Date: Sun, 11 May 2025 08:47:38 +0300 Subject: [PATCH 1/5] Update extensibility-transforms.md Clarify request body transforms require existing body or IHttpRequestBodyDetectionFeature --- .../servers/yarp/extensibility-transforms.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md index 465942ec7520..1554308459f5 100644 --- a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md +++ b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md @@ -52,6 +52,7 @@ The below example uses simple, inefficient buffering to transform requests. A mo This sample requires YARP 1.1, see https://github.com/microsoft/reverse-proxy/pull/1569. +#### Example: Modifying an existing request body ```csharp .AddTransforms(context => { @@ -76,6 +77,54 @@ This sample requires YARP 1.1, see https://github.com/microsoft/reverse-proxy/pu }); ``` +### Important limitations +> **Custom transforms can only modify a request body if one is already present** in the incoming request. +> They **cannot add a new body** to a request that originally did not have one (e.g., a POST request with no body or a GET request). +> If you need to add a body where none exists, you must do so in **middleware that runs before YARP**, not in a transform. + +#### Example: Adding a body to a request that did not originally have one +```csharp +public class AddRequestBodyMiddleware +{ + private readonly RequestDelegate _next; + + public AddRequestBodyMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + // Only modify specific route and method + if (context.Request.Method == HttpMethods.Post && context.Request.Path == "/my-special-route") + { + var bodyContent = "key=value"; + var bodyBytes = Encoding.UTF8.GetBytes(bodyContent); + + // Create a new request body + context.Request.Body = new MemoryStream(bodyBytes); + context.Request.ContentLength = bodyBytes.Length; + + // Replace IHttpRequestBodyDetectionFeature so YARP knows a body is present + context.Features.Set(new CustomBodyDetectionFeature()); + } + + await _next(context); + } + + // Helper class to indicate the request can have a body + private class CustomBodyDetectionFeature : IHttpRequestBodyDetectionFeature + { + public bool CanHaveBody => true; + } +} + +``` + +#### Note +> You can use `context.GetRouteModel().Config.RouteId` in middleware to conditionally apply this logic for specific YARP routes. + + ## Response body transforms YARP does not provide any built in transforms for modifying the response body. However, the body can be modified by custom transforms. From 8dd8192d4ed7a9eb6da0a80e46b4afa6249f809e Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Mon, 19 May 2025 16:05:43 -0400 Subject: [PATCH 2/5] Updates --- .../servers/yarp/extensibility-transforms.md | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md index 1554308459f5..3f56c79191e5 100644 --- a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md +++ b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md @@ -52,7 +52,6 @@ The below example uses simple, inefficient buffering to transform requests. A mo This sample requires YARP 1.1, see https://github.com/microsoft/reverse-proxy/pull/1569. -#### Example: Modifying an existing request body ```csharp .AddTransforms(context => { @@ -77,12 +76,10 @@ This sample requires YARP 1.1, see https://github.com/microsoft/reverse-proxy/pu }); ``` -### Important limitations -> **Custom transforms can only modify a request body if one is already present** in the incoming request. -> They **cannot add a new body** to a request that originally did not have one (e.g., a POST request with no body or a GET request). -> If you need to add a body where none exists, you must do so in **middleware that runs before YARP**, not in a transform. +Custom transforms can only modify a request body if one is already present. They can't add a new body to a request that doesn't have one (for example, a POST request without a body or a GET request). If you need to add a body for a specific HTTP method and route, you must do so in middleware that runs before YARP, not in a transform. + +The following middleware demonstrates how to add a body to a request that doesn't have one: -#### Example: Adding a body to a request that did not originally have one ```csharp public class AddRequestBodyMiddleware { @@ -96,7 +93,8 @@ public class AddRequestBodyMiddleware public async Task InvokeAsync(HttpContext context) { // Only modify specific route and method - if (context.Request.Method == HttpMethods.Post && context.Request.Path == "/my-special-route") + if (context.Request.Method == HttpMethods.Post && + context.Request.Path == "/special-route") { var bodyContent = "key=value"; var bodyBytes = Encoding.UTF8.GetBytes(bodyContent); @@ -105,8 +103,10 @@ public class AddRequestBodyMiddleware context.Request.Body = new MemoryStream(bodyBytes); context.Request.ContentLength = bodyBytes.Length; - // Replace IHttpRequestBodyDetectionFeature so YARP knows a body is present - context.Features.Set(new CustomBodyDetectionFeature()); + // Replace IHttpRequestBodyDetectionFeature so YARP knows + // a body is present + context.Features.Set( + new CustomBodyDetectionFeature()); } await _next(context); @@ -118,13 +118,11 @@ public class AddRequestBodyMiddleware public bool CanHaveBody => true; } } - ``` -#### Note +> [!NOTE] > You can use `context.GetRouteModel().Config.RouteId` in middleware to conditionally apply this logic for specific YARP routes. - ## Response body transforms YARP does not provide any built in transforms for modifying the response body. However, the body can be modified by custom transforms. From 4350e93c0d704420830e190ec2d4d54ceed58738 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Mon, 19 May 2025 16:10:15 -0400 Subject: [PATCH 3/5] Additional fixups --- .../servers/yarp/extensibility-transforms.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md index 3f56c79191e5..5f46aa8c6bd1 100644 --- a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md +++ b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md @@ -12,9 +12,10 @@ ai-usage: ai-assisted # Request and Response Transform Extensibility ## Introduction + When proxying a request it's common to modify parts of the request or response to adapt to the destination server's requirements or to flow additional data such as the client's original IP address. This process is implemented via Transforms. Types of transforms are defined globally for the application and then individual routes supply the parameters to enable and configure those transforms. The original request objects are not modified by these transforms, only the proxy requests. -YARP includes a set of built-in request and response transforms that can be used. See [Transforms](./transforms.md) for more details. If those transforms are not sufficient, then custom transforms can be added. +YARP includes a set of built-in request and response transforms that can be used. For more information, see . If those transforms are not sufficient, then custom transforms can be added. ## `RequestTransform` @@ -22,25 +23,19 @@ All request transforms must derive from the abstract base class [`RequestTransfo A request transform may conditionally produce an immediate response such as for error conditions. This prevents any remaining transforms from running and the request from being proxied. This is indicated by setting the `HttpResponse.StatusCode` to a value other than 200, or calling `HttpResponse.StartAsync()`, or writing to the `HttpResponse.Body` or `BodyWriter`. -### `AddRequestTransform` - -[AddRequestTransform](xref:Yarp.ReverseProxy.Transforms.TransformBuilderContextFuncExtensions.AddRequestTransform*) is a `TransformBuilderContext` extension method that defines a request transform as a `Func`. This allows creating a custom request transform without implementing a `RequestTransform` derived class. + is a `TransformBuilderContext` extension method that defines a request transform as a `Func`. This allows creating a custom request transform without implementing a `RequestTransform` derived class. ## `ResponseTransform` -All response transforms must derive from the abstract base class [`ResponseTransform`](xref:Yarp.ReverseProxy.Transforms.ResponseTransform). These can freely modify the client `HttpResponse`. Avoid reading or modifying the response body as this may disrupt the proxying flow. Consider also adding a parametrized extension method on `TransformBuilderContext` for discoverability and easy of use. +All response transforms must derive from the abstract base class . These can freely modify the client `HttpResponse`. Avoid reading or modifying the response body as this may disrupt the proxying flow. Consider also adding a parametrized extension method on `TransformBuilderContext` for discoverability and easy of use. -### `AddResponseTransform` - -[AddResponseTransform](xref:Yarp.ReverseProxy.Transforms.TransformBuilderContextFuncExtensions.AddResponseTransform*) is a `TransformBuilderContext` extension method that defines a response transform as a `Func`. This allows creating a custom response transform without implementing a `ResponseTransform` derived class. + is a `TransformBuilderContext` extension method that defines a response transform as a `Func`. This allows creating a custom response transform without implementing a `ResponseTransform` derived class. ## `ResponseTrailersTransform` -All response trailers transforms must derive from the abstract base class [ResponseTrailersTransform](xref:Yarp.ReverseProxy.Transforms.ResponseTrailersTransform). These can freely modify the client HttpResponse trailers. These run after the response body and should not attempt to modify the response headers or body. Consider also adding a parametrized extension method on `TransformBuilderContext` for discoverability and easy of use. - -### `AddResponseTrailersTransform` +All response trailers transforms must derive from the abstract base class . These can freely modify the client HttpResponse trailers. These run after the response body and should not attempt to modify the response headers or body. Consider also adding a parametrized extension method on `TransformBuilderContext` for discoverability and easy of use. -[AddResponseTrailersTransform](xref:Yarp.ReverseProxy.Transforms.TransformBuilderContextFuncExtensions.AddResponseTrailersTransform*) is a `TransformBuilderContext` extension method that defines a response trailers transform as a `Func`. This allows creating a custom response trailers transform without implementing a `ResponseTrailersTransform` derived class. + is a `TransformBuilderContext` extension method that defines a response trailers transform as a `Func`. This allows creating a custom response trailers transform without implementing a `ResponseTrailersTransform` derived class. ## Request body transforms From 49ccd2b95d89f8e8e8a7d940ff482a03db608c22 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Mon, 19 May 2025 16:12:15 -0400 Subject: [PATCH 4/5] Update --- .../fundamentals/servers/yarp/extensibility-transforms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md index 5f46aa8c6bd1..cb7fbba29fb9 100644 --- a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md +++ b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md @@ -71,7 +71,7 @@ This sample requires YARP 1.1, see https://github.com/microsoft/reverse-proxy/pu }); ``` -Custom transforms can only modify a request body if one is already present. They can't add a new body to a request that doesn't have one (for example, a POST request without a body or a GET request). If you need to add a body for a specific HTTP method and route, you must do so in middleware that runs before YARP, not in a transform. +Custom transforms can only modify a request body if one is already present. They can't add a new body to a request that doesn't have one (for example, a POST request without a body or a GET request). If you need to add a body for a specific HTTP method and route, you must do so in [middleware](xref:fundamentals/middleware/index) that runs before YARP, not in a transform. The following middleware demonstrates how to add a body to a request that doesn't have one: From 7357cc7f884944c9911a76902e3a16de06e78311 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Mon, 19 May 2025 17:03:11 -0400 Subject: [PATCH 5/5] Update aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md Co-authored-by: Miha Zupan --- .../fundamentals/servers/yarp/extensibility-transforms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md index cb7fbba29fb9..dfbb255561a3 100644 --- a/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md +++ b/aspnetcore/fundamentals/servers/yarp/extensibility-transforms.md @@ -88,7 +88,7 @@ public class AddRequestBodyMiddleware public async Task InvokeAsync(HttpContext context) { // Only modify specific route and method - if (context.Request.Method == HttpMethods.Post && + if (context.Request.Method == HttpMethods.Get && context.Request.Path == "/special-route") { var bodyContent = "key=value";