diff --git a/docs/docfx/articles/transforms.md b/docs/docfx/articles/transforms.md index 15ac5073f..29ecf2a08 100644 --- a/docs/docfx/articles/transforms.md +++ b/docs/docfx/articles/transforms.md @@ -724,7 +724,7 @@ This sets if all proxy response headers are copied to the client response. This |-----|-------|---------|----------| | ResponseHeader | The header name | (none) | yes | | Set/Append | The header value | (none) | yes | -| When | Success/Always | Success | no | +| When | Success/Always/Failure | Success | no | Config: ```JSON @@ -736,10 +736,10 @@ Config: ``` Code: ```csharp -routeConfig = routeConfig.WithTransformResponseHeader(headerName: "HeaderName", value: "value", append: true, always: false); +routeConfig = routeConfig.WithTransformResponseHeader(headerName: "HeaderName", value: "value", append: true, ResponseCondition.Success); ``` ```C# -transformBuilderContext.AddResponseHeader(headerName: "HeaderName", value: "value", append: true, always: false); +transformBuilderContext.AddResponseHeader(headerName: "HeaderName", value: "value", append: true, always: ResponseCondition.Success); ``` Example: ``` @@ -749,7 +749,7 @@ HeaderName: value This sets or appends the value for the named response header. Set replaces any existing header. Append adds an additional header with the given value. Note: setting "" as a header value is not recommended and can cause an undefined behavior. -`When` specifies if the response header should be included for successful responses or for all responses. Any response with a status code less than 400 is considered a success. +`When` specifies if the response header should be included for all, successful, or failure responses. Any response with a status code less than 400 is considered a success. ### ResponseHeaderRemove @@ -758,7 +758,7 @@ Note: setting "" as a header value is not recommended and can cause an undefined | Key | Value | Default | Required | |-----|-------|---------|----------| | ResponseHeaderRemove | The header name | (none) | yes | -| When | Success/Always | Success | no | +| When | Success/Always/Failure | Success | no | Config: ```JSON @@ -769,10 +769,10 @@ Config: ``` Code: ```csharp -routeConfig = routeConfig.WithTransformResponseHeaderRemove(headerName: "HeaderName", always: false); +routeConfig = routeConfig.WithTransformResponseHeaderRemove(headerName: "HeaderName", ResponseCondition.Success); ``` ```C# -transformBuilderContext.AddResponseHeaderRemove(headerName: "HeaderName", always: false); +transformBuilderContext.AddResponseHeaderRemove(headerName: "HeaderName", ResponseCondition.Success); ``` Example: ``` @@ -782,7 +782,7 @@ AnotherHeader: another-value This removes the named response header. -`When` specifies if the response header should be included for successful responses or for all responses. Any response with a status code less than 400 is considered a success. +`When` specifies if the response header should be removed for all, successful, or failure responses. Any response with a status code less than 400 is considered a success. ### ResponseHeadersAllowed @@ -847,7 +847,7 @@ This sets if all proxy response trailers are copied to the client response. This |-----|-------|---------|----------| | ResponseTrailer | The header name | (none) | yes | | Set/Append | The header value | (none) | yes | -| When | Success/Always | Success | no | +| When | Success/Always/Failure | Success | no | Config: ```JSON @@ -859,10 +859,10 @@ Config: ``` Code: ```csharp -routeConfig = routeConfig.WithTransformResponseTrailer(headerName: "HeaderName", value: "value", append: true, always: false); +routeConfig = routeConfig.WithTransformResponseTrailer(headerName: "HeaderName", value: "value", append: true, ResponseCondition.Success); ``` ```C# -transformBuilderContext.AddResponseTrailer(headerName: "HeaderName", value: "value", append: true, always: false); +transformBuilderContext.AddResponseTrailer(headerName: "HeaderName", value: "value", append: true, ResponseCondition.Success); ``` Example: ``` @@ -880,7 +880,7 @@ ResponseTrailer follows the same structure and guidance as [ResponseHeader](tran | Key | Value | Default | Required | |-----|-------|---------|----------| | ResponseTrailerRemove | The header name | (none) | yes | -| When | Success/Always | Success | no | +| When | Success/Always/Failure | Success | no | Config: ```JSON @@ -891,10 +891,10 @@ Config: ``` Code: ```csharp -routeConfig = routeConfig.WithTransformResponseTrailerRemove(headerName: "HeaderName", always: false); +routeConfig = routeConfig.WithTransformResponseTrailerRemove(headerName: "HeaderName", ResponseCondition.Success); ``` ```C# -transformBuilderContext.AddResponseTrailerRemove(headerName: "HeaderName", always: false); +transformBuilderContext.AddResponseTrailerRemove(headerName: "HeaderName", ResponseCondition.Success); ``` Example: ``` diff --git a/src/ReverseProxy/Forwarder/HttpForwarder.cs b/src/ReverseProxy/Forwarder/HttpForwarder.cs index 21b2e1dbd..346b5e610 100644 --- a/src/ReverseProxy/Forwarder/HttpForwarder.cs +++ b/src/ReverseProxy/Forwarder/HttpForwarder.cs @@ -261,7 +261,7 @@ public async ValueTask SendAsync( // "http://a".Length = 8 if (destinationPrefix == null || destinationPrefix.Length < 8) { - throw new ArgumentException(nameof(destinationPrefix)); + throw new ArgumentException("Invalid destination prefix.", nameof(destinationPrefix)); } var destinationRequest = new HttpRequestMessage(); @@ -481,8 +481,9 @@ private async ValueTask HandleRequestFailureAsync(HttpContext co if (requestBodyCopyResult != StreamCopyResult.Success) { - await transformer.TransformResponseAsync(context, null); - return HandleRequestBodyFailure(context, requestBodyCopyResult, requestBodyException!, requestException); + var error = HandleRequestBodyFailure(context, requestBodyCopyResult, requestBodyException!, requestException); + await transformer.TransformResponseAsync(context, proxyResponse: null); + return error; } } diff --git a/src/ReverseProxy/Forwarder/HttpTransformer.cs b/src/ReverseProxy/Forwarder/HttpTransformer.cs index cc098bf8a..1d9cc6508 100644 --- a/src/ReverseProxy/Forwarder/HttpTransformer.cs +++ b/src/ReverseProxy/Forwarder/HttpTransformer.cs @@ -102,7 +102,7 @@ public virtual ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequ /// `Transfer-Encoding: chunked`. /// /// The incoming request. - /// The response from the destination. + /// The response from the destination. This can be null if the destination did not respond. /// A bool indicating if the response should be proxied to the client or not. A derived implementation /// that returns false may send an alternate response inline or return control to the caller for it to retry, respond, /// etc. diff --git a/src/ReverseProxy/Transforms/ResponseCondition.cs b/src/ReverseProxy/Transforms/ResponseCondition.cs new file mode 100644 index 000000000..d52b269c3 --- /dev/null +++ b/src/ReverseProxy/Transforms/ResponseCondition.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Yarp.ReverseProxy.Transforms +{ + /// + /// Specifies the conditions under which a response transform will run. + /// + public enum ResponseCondition + { + /// + /// The transform runs for all conditions. + /// + Always, + + /// + /// The transform only runs if there is a successful response with a status code less than 400. + /// + Success, + + /// + /// The transform only runs if there is no response or a response with a 400+ status code. + /// + Failure + } +} diff --git a/src/ReverseProxy/Transforms/ResponseHeaderRemoveTransform.cs b/src/ReverseProxy/Transforms/ResponseHeaderRemoveTransform.cs index fe4285153..f2144d0ae 100644 --- a/src/ReverseProxy/Transforms/ResponseHeaderRemoveTransform.cs +++ b/src/ReverseProxy/Transforms/ResponseHeaderRemoveTransform.cs @@ -11,7 +11,7 @@ namespace Yarp.ReverseProxy.Transforms /// public class ResponseHeaderRemoveTransform : ResponseTransform { - public ResponseHeaderRemoveTransform(string headerName, bool always) + public ResponseHeaderRemoveTransform(string headerName, ResponseCondition condition) { if (string.IsNullOrEmpty(headerName)) { @@ -19,12 +19,12 @@ public ResponseHeaderRemoveTransform(string headerName, bool always) } HeaderName = headerName; - Always = always; + Condition = condition; } internal string HeaderName { get; } - internal bool Always { get; } + internal ResponseCondition Condition { get; } // Assumes the response status code has been set on the HttpContext already. /// @@ -35,7 +35,8 @@ public override ValueTask ApplyAsync(ResponseTransformContext context) throw new ArgumentNullException(nameof(context)); } - if (Always || Success(context)) + if (Condition == ResponseCondition.Always + || Success(context) == (Condition == ResponseCondition.Success)) { context.HttpContext.Response.Headers.Remove(HeaderName); } diff --git a/src/ReverseProxy/Transforms/ResponseHeaderValueTransform.cs b/src/ReverseProxy/Transforms/ResponseHeaderValueTransform.cs index cf9a2259b..89323075e 100644 --- a/src/ReverseProxy/Transforms/ResponseHeaderValueTransform.cs +++ b/src/ReverseProxy/Transforms/ResponseHeaderValueTransform.cs @@ -12,7 +12,7 @@ namespace Yarp.ReverseProxy.Transforms /// public class ResponseHeaderValueTransform : ResponseTransform { - public ResponseHeaderValueTransform(string headerName, string value, bool append, bool always) + public ResponseHeaderValueTransform(string headerName, string value, bool append, ResponseCondition condition) { if (string.IsNullOrEmpty(headerName)) { @@ -22,10 +22,10 @@ public ResponseHeaderValueTransform(string headerName, string value, bool append HeaderName = headerName; Value = value ?? throw new ArgumentNullException(nameof(value)); Append = append; - Always = always; + Condition = condition; } - internal bool Always { get; } + internal ResponseCondition Condition { get; } internal bool Append { get; } @@ -42,7 +42,8 @@ public override ValueTask ApplyAsync(ResponseTransformContext context) throw new ArgumentNullException(nameof(context)); } - if (Always || Success(context)) + if (Condition == ResponseCondition.Always + || Success(context) == (Condition == ResponseCondition.Success)) { var existingHeader = TakeHeader(context, HeaderName); if (Append) diff --git a/src/ReverseProxy/Transforms/ResponseTrailerRemoveTransform.cs b/src/ReverseProxy/Transforms/ResponseTrailerRemoveTransform.cs index ae3d64a6c..88b8c998b 100644 --- a/src/ReverseProxy/Transforms/ResponseTrailerRemoveTransform.cs +++ b/src/ReverseProxy/Transforms/ResponseTrailerRemoveTransform.cs @@ -13,7 +13,7 @@ namespace Yarp.ReverseProxy.Transforms /// public class ResponseTrailerRemoveTransform : ResponseTrailersTransform { - public ResponseTrailerRemoveTransform(string headerName, bool always) + public ResponseTrailerRemoveTransform(string headerName, ResponseCondition condition) { if (string.IsNullOrEmpty(headerName)) { @@ -21,12 +21,12 @@ public ResponseTrailerRemoveTransform(string headerName, bool always) } HeaderName = headerName; - Always = always; + Condition = condition; } internal string HeaderName { get; } - internal bool Always { get; } + internal ResponseCondition Condition { get; } // Assumes the response status code has been set on the HttpContext already. /// @@ -39,7 +39,8 @@ public override ValueTask ApplyAsync(ResponseTrailersTransformContext context) Debug.Assert(context.ProxyResponse != null); - if (Always || Success(context)) + if (Condition == ResponseCondition.Always + || Success(context) == (Condition == ResponseCondition.Success)) { var responseTrailersFeature = context.HttpContext.Features.Get(); var responseTrailers = responseTrailersFeature?.Trailers; diff --git a/src/ReverseProxy/Transforms/ResponseTrailerValueTransform.cs b/src/ReverseProxy/Transforms/ResponseTrailerValueTransform.cs index 16dca47e3..dacb8d6de 100644 --- a/src/ReverseProxy/Transforms/ResponseTrailerValueTransform.cs +++ b/src/ReverseProxy/Transforms/ResponseTrailerValueTransform.cs @@ -12,7 +12,7 @@ namespace Yarp.ReverseProxy.Transforms /// public class ResponseTrailerValueTransform : ResponseTrailersTransform { - public ResponseTrailerValueTransform(string headerName, string value, bool append, bool always) + public ResponseTrailerValueTransform(string headerName, string value, bool append, ResponseCondition condition) { if (string.IsNullOrEmpty(headerName)) { @@ -22,10 +22,10 @@ public ResponseTrailerValueTransform(string headerName, string value, bool appen HeaderName = headerName; Value = value ?? throw new ArgumentNullException(nameof(value)); Append = append; - Always = always; + Condition = condition; } - internal bool Always { get; } + internal ResponseCondition Condition { get; } internal bool Append { get; } @@ -42,7 +42,8 @@ public override ValueTask ApplyAsync(ResponseTrailersTransformContext context) throw new ArgumentNullException(nameof(context)); } - if (Always || Success(context)) + if (Condition == ResponseCondition.Always + || Success(context) == (Condition == ResponseCondition.Success)) { var existingHeader = TakeHeader(context, HeaderName); if (Append) diff --git a/src/ReverseProxy/Transforms/ResponseTransformContext.cs b/src/ReverseProxy/Transforms/ResponseTransformContext.cs index ecb5fa93c..d76e166e7 100644 --- a/src/ReverseProxy/Transforms/ResponseTransformContext.cs +++ b/src/ReverseProxy/Transforms/ResponseTransformContext.cs @@ -17,7 +17,7 @@ public class ResponseTransformContext public HttpContext HttpContext { get; init; } = default!; /// - /// The incoming proxy response. + /// The proxy response. This can be null if the destination did not respond. /// public HttpResponseMessage? ProxyResponse { get; init; } diff --git a/src/ReverseProxy/Transforms/ResponseTransformExtensions.cs b/src/ReverseProxy/Transforms/ResponseTransformExtensions.cs index e3ede95c6..5b90370af 100644 --- a/src/ReverseProxy/Transforms/ResponseTransformExtensions.cs +++ b/src/ReverseProxy/Transforms/ResponseTransformExtensions.cs @@ -36,28 +36,26 @@ public static RouteConfig WithTransformCopyResponseTrailers(this RouteConfig rou /// /// Clones the route and adds the transform which will append or set the response header. /// - public static RouteConfig WithTransformResponseHeader(this RouteConfig route, string headerName, string value, bool append = true, bool always = false) + public static RouteConfig WithTransformResponseHeader(this RouteConfig route, string headerName, string value, bool append = true, ResponseCondition condition = ResponseCondition.Success) { var type = append ? ResponseTransformFactory.AppendKey : ResponseTransformFactory.SetKey; - var when = always ? ResponseTransformFactory.AlwaysValue : ResponseTransformFactory.SuccessValue; return route.WithTransform(transform => { transform[ResponseTransformFactory.ResponseHeaderKey] = headerName; transform[type] = value; - transform[ResponseTransformFactory.WhenKey] = when; + transform[ResponseTransformFactory.WhenKey] = condition.ToString(); }); } /// /// Clones the route and adds the transform which will remove the response header. /// - public static RouteConfig WithTransformResponseHeaderRemove(this RouteConfig route, string headerName, bool always = false) + public static RouteConfig WithTransformResponseHeaderRemove(this RouteConfig route, string headerName, ResponseCondition condition = ResponseCondition.Success) { - var when = always ? ResponseTransformFactory.AlwaysValue : ResponseTransformFactory.SuccessValue; return route.WithTransform(transform => { transform[ResponseTransformFactory.ResponseHeaderRemoveKey] = headerName; - transform[ResponseTransformFactory.WhenKey] = when; + transform[ResponseTransformFactory.WhenKey] = condition.ToString(); }); } @@ -76,18 +74,18 @@ public static RouteConfig WithTransformResponseHeadersAllowed(this RouteConfig r /// /// Adds the transform which will append or set the response header. /// - public static TransformBuilderContext AddResponseHeader(this TransformBuilderContext context, string headerName, string value, bool append = true, bool always = false) + public static TransformBuilderContext AddResponseHeader(this TransformBuilderContext context, string headerName, string value, bool append = true, ResponseCondition condition = ResponseCondition.Success) { - context.ResponseTransforms.Add(new ResponseHeaderValueTransform(headerName, value, append, always)); + context.ResponseTransforms.Add(new ResponseHeaderValueTransform(headerName, value, append, condition)); return context; } /// /// Adds the transform which will remove the response header. /// - public static TransformBuilderContext AddResponseHeaderRemove(this TransformBuilderContext context, string headerName, bool always = false) + public static TransformBuilderContext AddResponseHeaderRemove(this TransformBuilderContext context, string headerName, ResponseCondition condition = ResponseCondition.Success) { - context.ResponseTransforms.Add(new ResponseHeaderRemoveTransform(headerName, always)); + context.ResponseTransforms.Add(new ResponseHeaderRemoveTransform(headerName, condition)); return context; } @@ -105,46 +103,44 @@ public static TransformBuilderContext AddResponseHeadersAllowed(this TransformBu /// /// Clones the route and adds the transform which will append or set the response trailer. /// - public static RouteConfig WithTransformResponseTrailer(this RouteConfig route, string headerName, string value, bool append = true, bool always = false) + public static RouteConfig WithTransformResponseTrailer(this RouteConfig route, string headerName, string value, bool append = true, ResponseCondition condition = ResponseCondition.Success) { var type = append ? ResponseTransformFactory.AppendKey : ResponseTransformFactory.SetKey; - var when = always ? ResponseTransformFactory.AlwaysValue : ResponseTransformFactory.SuccessValue; return route.WithTransform(transform => { transform[ResponseTransformFactory.ResponseTrailerKey] = headerName; transform[type] = value; - transform[ResponseTransformFactory.WhenKey] = when; + transform[ResponseTransformFactory.WhenKey] = condition.ToString(); }); } /// /// Adds the transform which will append or set the response trailer. /// - public static TransformBuilderContext AddResponseTrailer(this TransformBuilderContext context, string headerName, string value, bool append = true, bool always = false) + public static TransformBuilderContext AddResponseTrailer(this TransformBuilderContext context, string headerName, string value, bool append = true, ResponseCondition condition = ResponseCondition.Success) { - context.ResponseTrailersTransforms.Add(new ResponseTrailerValueTransform(headerName, value, append, always)); + context.ResponseTrailersTransforms.Add(new ResponseTrailerValueTransform(headerName, value, append, condition)); return context; } /// /// Adds the transform which will remove the response trailer. /// - public static TransformBuilderContext AddResponseTrailerRemove(this TransformBuilderContext context, string headerName, bool always = false) + public static TransformBuilderContext AddResponseTrailerRemove(this TransformBuilderContext context, string headerName, ResponseCondition condition = ResponseCondition.Success) { - context.ResponseTrailersTransforms.Add(new ResponseTrailerRemoveTransform(headerName, always)); + context.ResponseTrailersTransforms.Add(new ResponseTrailerRemoveTransform(headerName, condition)); return context; } /// /// Clones the route and adds the transform which will remove the response trailer. /// - public static RouteConfig WithTransformResponseTrailerRemove(this RouteConfig route, string headerName, bool always = false) + public static RouteConfig WithTransformResponseTrailerRemove(this RouteConfig route, string headerName, ResponseCondition condition = ResponseCondition.Success) { - var when = always ? ResponseTransformFactory.AlwaysValue : ResponseTransformFactory.SuccessValue; return route.WithTransform(transform => { transform[ResponseTransformFactory.ResponseTrailerRemoveKey] = headerName; - transform[ResponseTransformFactory.WhenKey] = when; + transform[ResponseTransformFactory.WhenKey] = condition.ToString(); }); } diff --git a/src/ReverseProxy/Transforms/ResponseTransformFactory.cs b/src/ReverseProxy/Transforms/ResponseTransformFactory.cs index 673b13f8c..f07898540 100644 --- a/src/ReverseProxy/Transforms/ResponseTransformFactory.cs +++ b/src/ReverseProxy/Transforms/ResponseTransformFactory.cs @@ -18,8 +18,6 @@ internal sealed class ResponseTransformFactory : ITransformFactory internal static readonly string ResponseHeadersAllowedKey = "ResponseHeadersAllowed"; internal static readonly string ResponseTrailersAllowedKey = "ResponseTrailersAllowed"; internal static readonly string WhenKey = "When"; - internal static readonly string AlwaysValue = "Always"; - internal static readonly string SuccessValue = "Success"; internal static readonly string AppendKey = "Append"; internal static readonly string SetKey = "Set"; @@ -46,9 +44,9 @@ public bool Validate(TransformRouteValidationContext context, IReadOnlyDictionar if (transformValues.TryGetValue(WhenKey, out var whenValue)) { TransformHelpers.TryCheckTooManyParameters(context, transformValues, expected: 3); - if (!string.Equals(AlwaysValue, whenValue, StringComparison.OrdinalIgnoreCase) && !string.Equals(SuccessValue, whenValue, StringComparison.OrdinalIgnoreCase)) + if (!Enum.TryParse(whenValue, ignoreCase: true, out var _)) { - context.Errors.Add(new ArgumentException($"Unexpected value for ResponseHeader:When: {whenValue}. Expected 'Always' or 'Success'")); + context.Errors.Add(new ArgumentException($"Unexpected value for ResponseHeader:When: {whenValue}. Expected 'Always', 'Success', or 'Failure'")); } } else @@ -66,9 +64,9 @@ public bool Validate(TransformRouteValidationContext context, IReadOnlyDictionar if (transformValues.TryGetValue(WhenKey, out var whenValue)) { TransformHelpers.TryCheckTooManyParameters(context, transformValues, expected: 3); - if (!string.Equals(AlwaysValue, whenValue, StringComparison.OrdinalIgnoreCase) && !string.Equals(SuccessValue, whenValue, StringComparison.OrdinalIgnoreCase)) + if (!Enum.TryParse(whenValue, ignoreCase: true, out var _)) { - context.Errors.Add(new ArgumentException($"Unexpected value for ResponseTrailer:When: {whenValue}. Expected 'Always' or 'Success'")); + context.Errors.Add(new ArgumentException($"Unexpected value for ResponseTrailer:When: {whenValue}. Expected 'Always', 'Success', or 'Failure'")); } } else @@ -86,9 +84,9 @@ public bool Validate(TransformRouteValidationContext context, IReadOnlyDictionar if (transformValues.TryGetValue(WhenKey, out var whenValue)) { TransformHelpers.TryCheckTooManyParameters(context, transformValues, expected: 2); - if (!string.Equals(AlwaysValue, whenValue, StringComparison.OrdinalIgnoreCase) && !string.Equals(SuccessValue, whenValue, StringComparison.OrdinalIgnoreCase)) + if (!Enum.TryParse(whenValue, ignoreCase: true, out var _)) { - context.Errors.Add(new ArgumentException($"Unexpected value for ResponseHeaderRemove:When: {whenValue}. Expected 'Always' or 'Success'")); + context.Errors.Add(new ArgumentException($"Unexpected value for ResponseHeaderRemove:When: {whenValue}. Expected 'Always', 'Success', or 'Failure'")); } } else @@ -101,9 +99,9 @@ public bool Validate(TransformRouteValidationContext context, IReadOnlyDictionar if (transformValues.TryGetValue(WhenKey, out var whenValue)) { TransformHelpers.TryCheckTooManyParameters(context, transformValues, expected: 2); - if (!string.Equals(AlwaysValue, whenValue, StringComparison.OrdinalIgnoreCase) && !string.Equals(SuccessValue, whenValue, StringComparison.OrdinalIgnoreCase)) + if (!Enum.TryParse(whenValue, ignoreCase: true, out var _)) { - context.Errors.Add(new ArgumentException($"Unexpected value for ResponseTrailerRemove:When: {whenValue}. Expected 'Always' or 'Success'")); + context.Errors.Add(new ArgumentException($"Unexpected value for ResponseTrailerRemove:When: {whenValue}. Expected 'Always', 'Success', or 'Failure'")); } } else @@ -141,11 +139,11 @@ public bool Build(TransformBuilderContext context, IReadOnlyDictionary(whenValue, ignoreCase: true); } else { @@ -154,11 +152,11 @@ public bool Build(TransformBuilderContext context, IReadOnlyDictionary(whenValue, ignoreCase: true); } else { @@ -180,11 +178,11 @@ public bool Build(TransformBuilderContext context, IReadOnlyDictionary(whenValue, ignoreCase: true); } else { TransformHelpers.CheckTooManyParameters(transformValues, expected: 1); } - context.AddResponseHeaderRemove(removeResponseHeaderName, always); + context.AddResponseHeaderRemove(removeResponseHeaderName, condition); } else if (transformValues.TryGetValue(ResponseTrailerRemoveKey, out var removeResponseTrailerName)) { - var always = false; + var condition = ResponseCondition.Success; if (transformValues.TryGetValue(WhenKey, out var whenValue)) { TransformHelpers.CheckTooManyParameters(transformValues, expected: 2); - always = string.Equals(AlwaysValue, whenValue, StringComparison.OrdinalIgnoreCase); + condition = Enum.Parse(whenValue, ignoreCase: true); } else { TransformHelpers.CheckTooManyParameters(transformValues, expected: 1); } - context.AddResponseTrailerRemove(removeResponseTrailerName, always); + context.AddResponseTrailerRemove(removeResponseTrailerName, condition); } else if (transformValues.TryGetValue(ResponseHeadersAllowedKey, out var allowedHeaders)) { diff --git a/test/ReverseProxy.Tests/Transforms/ResponseHeaderRemoveTransformTests.cs b/test/ReverseProxy.Tests/Transforms/ResponseHeaderRemoveTransformTests.cs index f0541ca3a..8bd6ea140 100644 --- a/test/ReverseProxy.Tests/Transforms/ResponseHeaderRemoveTransformTests.cs +++ b/test/ReverseProxy.Tests/Transforms/ResponseHeaderRemoveTransformTests.cs @@ -8,34 +8,35 @@ using Microsoft.AspNetCore.Http; using Xunit; using Yarp.Tests.Common; -using Yarp.ReverseProxy.Utilities.Tests; namespace Yarp.ReverseProxy.Transforms.Tests { public class ResponseHeaderRemoveTransformTests { [Theory] - [InlineData("header1", "value1", 200, false, "header1", "")] - [InlineData("header1", "value1", 404, false, "header1", "header1")] - [InlineData("header1", "value1", 200, true, "header1", "")] - [InlineData("header1", "value1", 404, true, "header1", "")] - [InlineData("header1", "value1", 200, false, "headerX", "header1")] - [InlineData("header1", "value1", 404, false, "headerX", "header1")] - [InlineData("header1", "value1", 200, true, "headerX", "header1")] - [InlineData("header1", "value1", 404, true, "headerX", "header1")] - [InlineData("header1; header2; header3", "value1, value2, value3", 200, false, "header2", "header1; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 404, false, "header2", "header1; header2; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 200, true, "header2", "header1; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 404, true, "header2", "header1; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 200, false, "headerX", "header1; header2; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 404, false, "headerX", "header1; header2; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 200, true, "headerX", "header1; header2; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 404, true, "headerX", "header1; header2; header3")] - [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 200, false, "header2", "header1; header3")] - [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 404, false, "header2", "header1; header2; header3")] - [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 200, true, "header2", "header1; header3")] - [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 404, true, "header2", "header1; header3")] - public async Task RemoveHeader_Success(string names, string values, int status, bool always, string removedHeader, string expected) + [InlineData("header1", "value1", 200, ResponseCondition.Success, "header1", "")] + [InlineData("header1", "value1", 404, ResponseCondition.Success, "header1", "header1")] + [InlineData("header1", "value1", 200, ResponseCondition.Failure, "header1", "header1")] + [InlineData("header1", "value1", 404, ResponseCondition.Failure, "header1", "")] + [InlineData("header1", "value1", 200, ResponseCondition.Always, "header1", "")] + [InlineData("header1", "value1", 404, ResponseCondition.Always, "header1", "")] + [InlineData("header1", "value1", 200, ResponseCondition.Success, "headerX", "header1")] + [InlineData("header1", "value1", 404, ResponseCondition.Success, "headerX", "header1")] + [InlineData("header1", "value1", 200, ResponseCondition.Always, "headerX", "header1")] + [InlineData("header1", "value1", 404, ResponseCondition.Always, "headerX", "header1")] + [InlineData("header1; header2; header3", "value1, value2, value3", 200, ResponseCondition.Success, "header2", "header1; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 404, ResponseCondition.Success, "header2", "header1; header2; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 200, ResponseCondition.Always, "header2", "header1; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 404, ResponseCondition.Always, "header2", "header1; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 200, ResponseCondition.Success, "headerX", "header1; header2; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 404, ResponseCondition.Success, "headerX", "header1; header2; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 200, ResponseCondition.Always, "headerX", "header1; header2; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 404, ResponseCondition.Always, "headerX", "header1; header2; header3")] + [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 200, ResponseCondition.Success, "header2", "header1; header3")] + [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 404, ResponseCondition.Success, "header2", "header1; header2; header3")] + [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 200, ResponseCondition.Always, "header2", "header1; header3")] + [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 404, ResponseCondition.Always, "header2", "header1; header3")] + public async Task RemoveHeader_Success(string names, string values, int status, ResponseCondition condition, string removedHeader, string expected) { var httpContext = new DefaultHttpContext(); httpContext.Response.StatusCode = status; @@ -45,7 +46,7 @@ public async Task RemoveHeader_Success(string names, string values, int status, httpContext.Response.Headers.Add(pair.Name, pair.Values); } - var transform = new ResponseHeaderRemoveTransform(removedHeader, always); + var transform = new ResponseHeaderRemoveTransform(removedHeader, condition); await transform.ApplyAsync(new ResponseTransformContext() { HttpContext = httpContext, @@ -58,14 +59,15 @@ await transform.ApplyAsync(new ResponseTransformContext() } [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task RemoveHeader_ResponseNull_DoNothing(bool always) + [InlineData(ResponseCondition.Always)] + [InlineData(ResponseCondition.Success)] + [InlineData(ResponseCondition.Failure)] + public async Task RemoveHeader_ResponseNull_DoNothing(ResponseCondition condition) { var httpContext = new DefaultHttpContext(); httpContext.Response.StatusCode = 502; - var transform = new ResponseHeaderRemoveTransform("header1", always); + var transform = new ResponseHeaderRemoveTransform("header1", condition); await transform.ApplyAsync(new ResponseTransformContext() { HttpContext = httpContext, diff --git a/test/ReverseProxy.Tests/Transforms/ResponseHeaderValueTransformTests.cs b/test/ReverseProxy.Tests/Transforms/ResponseHeaderValueTransformTests.cs index e135a0ffc..a5d951e50 100644 --- a/test/ReverseProxy.Tests/Transforms/ResponseHeaderValueTransformTests.cs +++ b/test/ReverseProxy.Tests/Transforms/ResponseHeaderValueTransformTests.cs @@ -12,30 +12,33 @@ public class ResponseHeaderValueTransformTests { [Theory] // Using ";" to represent multi-line headers - [InlineData("", 400, "new", false, false, "", false)] - [InlineData("", 502, "new", false, false, "", true)] - [InlineData("", 200, "new", false, false, "new", false)] - [InlineData("", 400, "new", false, true, "new", false)] - [InlineData("", 200, "new", false, true, "new", false)] - [InlineData("", 502, "new", false, true, "new", true)] - [InlineData("start", 400, "new", false, false, "start", false)] - [InlineData("start", 200, "new", false, false, "new", false)] - [InlineData("start", 502, "new", false, false, "start", true)] - [InlineData("start", 400, "new", false, true, "new", false)] - [InlineData("start", 200, "new", false, true, "new", false)] - [InlineData("start", 400, "new", true, false, "start", false)] - [InlineData("start", 200, "new", true, false, "start;new", false)] - [InlineData("start", 400, "new", true, true, "start;new", false)] - [InlineData("start", 200, "new", true, true, "start;new", false)] - [InlineData("start,value", 400, "new", true, false, "start,value", false)] - [InlineData("start,value", 200, "new", true, false, "start,value;new", false)] - [InlineData("start,value", 400, "new", true, true, "start,value;new", false)] - [InlineData("start,value", 200, "new", true, true, "start,value;new", false)] - [InlineData("start;value", 400, "new", true, false, "start;value", false)] - [InlineData("start;value", 200, "new", true, false, "start;value;new", false)] - [InlineData("start;value", 400, "new", true, true, "start;value;new", false)] - [InlineData("start;value", 200, "new", true, true, "start;value;new", false)] - public async Task AddResponseHeader_Success(string startValue, int status, string value, bool append, bool always, string expected, bool responseNull) + [InlineData("", 400, "new", false, ResponseCondition.Success, "", false)] + [InlineData("", 502, "new", false, ResponseCondition.Success, "", true)] + [InlineData("", 200, "new", false, ResponseCondition.Success, "new", false)] + [InlineData("", 400, "new", false, ResponseCondition.Always, "new", false)] + [InlineData("", 200, "new", false, ResponseCondition.Always, "new", false)] + [InlineData("", 502, "new", false, ResponseCondition.Always, "new", true)] + [InlineData("", 502, "new", false, ResponseCondition.Failure, "new", false)] + [InlineData("", 502, "new", false, ResponseCondition.Failure, "new", true)] + [InlineData("", 200, "new", false, ResponseCondition.Failure, "", false)] + [InlineData("start", 400, "new", false, ResponseCondition.Success, "start", false)] + [InlineData("start", 200, "new", false, ResponseCondition.Success, "new", false)] + [InlineData("start", 502, "new", false, ResponseCondition.Success, "start", true)] + [InlineData("start", 400, "new", false, ResponseCondition.Always, "new", false)] + [InlineData("start", 200, "new", false, ResponseCondition.Always, "new", false)] + [InlineData("start", 400, "new", true, ResponseCondition.Success, "start", false)] + [InlineData("start", 200, "new", true, ResponseCondition.Success, "start;new", false)] + [InlineData("start", 400, "new", true, ResponseCondition.Always, "start;new", false)] + [InlineData("start", 200, "new", true, ResponseCondition.Always, "start;new", false)] + [InlineData("start,value", 400, "new", true, ResponseCondition.Success, "start,value", false)] + [InlineData("start,value", 200, "new", true, ResponseCondition.Success, "start,value;new", false)] + [InlineData("start,value", 400, "new", true, ResponseCondition.Always, "start,value;new", false)] + [InlineData("start,value", 200, "new", true, ResponseCondition.Always, "start,value;new", false)] + [InlineData("start;value", 400, "new", true, ResponseCondition.Success, "start;value", false)] + [InlineData("start;value", 200, "new", true, ResponseCondition.Success, "start;value;new", false)] + [InlineData("start;value", 400, "new", true, ResponseCondition.Always, "start;value;new", false)] + [InlineData("start;value", 200, "new", true, ResponseCondition.Always, "start;value;new", false)] + public async Task AddResponseHeader_Success(string startValue, int status, string value, bool append, ResponseCondition condition, string expected, bool responseNull) { var httpContext = new DefaultHttpContext(); httpContext.Response.Headers["name"] = startValue.Split(";", System.StringSplitOptions.RemoveEmptyEntries); @@ -46,7 +49,7 @@ public async Task AddResponseHeader_Success(string startValue, int status, strin ProxyResponse = responseNull ? null : new HttpResponseMessage(), HeadersCopied = true, }; - var transform = new ResponseHeaderValueTransform("name", value, append, always); + var transform = new ResponseHeaderValueTransform("name", value, append, condition); await transform.ApplyAsync(transformContext); Assert.Equal(expected.Split(";", System.StringSplitOptions.RemoveEmptyEntries), httpContext.Response.Headers["name"]); } diff --git a/test/ReverseProxy.Tests/Transforms/ResponseTrailerRemoveTransformTests.cs b/test/ReverseProxy.Tests/Transforms/ResponseTrailerRemoveTransformTests.cs index aff5fbfcb..ca3ce78b9 100644 --- a/test/ReverseProxy.Tests/Transforms/ResponseTrailerRemoveTransformTests.cs +++ b/test/ReverseProxy.Tests/Transforms/ResponseTrailerRemoveTransformTests.cs @@ -9,34 +9,35 @@ using Microsoft.AspNetCore.Http.Features; using Xunit; using Yarp.Tests.Common; -using Yarp.ReverseProxy.Utilities.Tests; namespace Yarp.ReverseProxy.Transforms.Tests { public class ResponseTrailerRemoveTransformTests { [Theory] - [InlineData("header1", "value1", 200, false, "header1", "")] - [InlineData("header1", "value1", 404, false, "header1", "header1")] - [InlineData("header1", "value1", 200, true, "header1", "")] - [InlineData("header1", "value1", 404, true, "header1", "")] - [InlineData("header1", "value1", 200, false, "headerX", "header1")] - [InlineData("header1", "value1", 404, false, "headerX", "header1")] - [InlineData("header1", "value1", 200, true, "headerX", "header1")] - [InlineData("header1", "value1", 404, true, "headerX", "header1")] - [InlineData("header1; header2; header3", "value1, value2, value3", 200, false, "header2", "header1; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 404, false, "header2", "header1; header2; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 200, true, "header2", "header1; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 404, true, "header2", "header1; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 200, false, "headerX", "header1; header2; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 404, false, "headerX", "header1; header2; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 200, true, "headerX", "header1; header2; header3")] - [InlineData("header1; header2; header3", "value1, value2, value3", 404, true, "headerX", "header1; header2; header3")] - [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 200, false, "header2", "header1; header3")] - [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 404, false, "header2", "header1; header2; header3")] - [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 200, true, "header2", "header1; header3")] - [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 404, true, "header2", "header1; header3")] - public async Task RemoveTrailerFromFeature_Success(string names, string values, int status, bool always, string removedHeader, string expected) + [InlineData("header1", "value1", 200, ResponseCondition.Success, "header1", "")] + [InlineData("header1", "value1", 404, ResponseCondition.Success, "header1", "header1")] + [InlineData("header1", "value1", 200, ResponseCondition.Failure, "header1", "header1")] + [InlineData("header1", "value1", 404, ResponseCondition.Failure, "header1", "")] + [InlineData("header1", "value1", 200, ResponseCondition.Always, "header1", "")] + [InlineData("header1", "value1", 404, ResponseCondition.Always, "header1", "")] + [InlineData("header1", "value1", 200, ResponseCondition.Success, "headerX", "header1")] + [InlineData("header1", "value1", 404, ResponseCondition.Success, "headerX", "header1")] + [InlineData("header1", "value1", 200, ResponseCondition.Always, "headerX", "header1")] + [InlineData("header1", "value1", 404, ResponseCondition.Always, "headerX", "header1")] + [InlineData("header1; header2; header3", "value1, value2, value3", 200, ResponseCondition.Success, "header2", "header1; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 404, ResponseCondition.Success, "header2", "header1; header2; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 200, ResponseCondition.Always, "header2", "header1; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 404, ResponseCondition.Always, "header2", "header1; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 200, ResponseCondition.Success, "headerX", "header1; header2; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 404, ResponseCondition.Success, "headerX", "header1; header2; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 200, ResponseCondition.Always, "headerX", "header1; header2; header3")] + [InlineData("header1; header2; header3", "value1, value2, value3", 404, ResponseCondition.Always, "headerX", "header1; header2; header3")] + [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 200, ResponseCondition.Success, "header2", "header1; header3")] + [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 404, ResponseCondition.Success, "header2", "header1; header2; header3")] + [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 200, ResponseCondition.Always, "header2", "header1; header3")] + [InlineData("header1; header2; header2; header3", "value1, value2-1, value2-2, value3", 404, ResponseCondition.Always, "header2", "header1; header3")] + public async Task RemoveTrailerFromFeature_Success(string names, string values, int status, ResponseCondition condition, string removedHeader, string expected) { var httpContext = new DefaultHttpContext(); httpContext.Response.StatusCode = status; @@ -48,7 +49,7 @@ public async Task RemoveTrailerFromFeature_Success(string names, string values, trailerFeature.Trailers.Add(pair.Name, pair.Values); } - var transform = new ResponseTrailerRemoveTransform(removedHeader, always); + var transform = new ResponseTrailerRemoveTransform(removedHeader, condition); await transform.ApplyAsync(new ResponseTrailersTransformContext() { HttpContext = httpContext, diff --git a/test/ReverseProxy.Tests/Transforms/ResponseTrailerValueTransformTests.cs b/test/ReverseProxy.Tests/Transforms/ResponseTrailerValueTransformTests.cs index 27ea1c2a3..393b9f439 100644 --- a/test/ReverseProxy.Tests/Transforms/ResponseTrailerValueTransformTests.cs +++ b/test/ReverseProxy.Tests/Transforms/ResponseTrailerValueTransformTests.cs @@ -13,27 +13,29 @@ public class ResponseTrailerValueTransformTests { [Theory] // Using ";" to represent multi-line headers - [InlineData("", 400, "new", false, false, "")] - [InlineData("", 200, "new", false, false, "new")] - [InlineData("", 400, "new", false, true, "new")] - [InlineData("", 200, "new", false, true, "new")] - [InlineData("start", 400, "new", false, false, "start")] - [InlineData("start", 200, "new", false, false, "new")] - [InlineData("start", 400, "new", false, true, "new")] - [InlineData("start", 200, "new", false, true, "new")] - [InlineData("start", 400, "new", true, false, "start")] - [InlineData("start", 200, "new", true, false, "start;new")] - [InlineData("start", 400, "new", true, true, "start;new")] - [InlineData("start", 200, "new", true, true, "start;new")] - [InlineData("start,value", 400, "new", true, false, "start,value")] - [InlineData("start,value", 200, "new", true, false, "start,value;new")] - [InlineData("start,value", 400, "new", true, true, "start,value;new")] - [InlineData("start,value", 200, "new", true, true, "start,value;new")] - [InlineData("start;value", 400, "new", true, false, "start;value")] - [InlineData("start;value", 200, "new", true, false, "start;value;new")] - [InlineData("start;value", 400, "new", true, true, "start;value;new")] - [InlineData("start;value", 200, "new", true, true, "start;value;new")] - public async Task AddResponseTrailer_Success(string startValue, int status, string value, bool append, bool always, string expected) + [InlineData("", 400, "new", false, ResponseCondition.Success, "")] + [InlineData("", 200, "new", false, ResponseCondition.Success, "new")] + [InlineData("", 400, "new", false, ResponseCondition.Failure, "new")] + [InlineData("", 200, "new", false, ResponseCondition.Failure, "")] + [InlineData("", 400, "new", false, ResponseCondition.Always, "new")] + [InlineData("", 200, "new", false, ResponseCondition.Always, "new")] + [InlineData("start", 400, "new", false, ResponseCondition.Success, "start")] + [InlineData("start", 200, "new", false, ResponseCondition.Success, "new")] + [InlineData("start", 400, "new", false, ResponseCondition.Always, "new")] + [InlineData("start", 200, "new", false, ResponseCondition.Always, "new")] + [InlineData("start", 400, "new", true, ResponseCondition.Success, "start")] + [InlineData("start", 200, "new", true, ResponseCondition.Success, "start;new")] + [InlineData("start", 400, "new", true, ResponseCondition.Always, "start;new")] + [InlineData("start", 200, "new", true, ResponseCondition.Always, "start;new")] + [InlineData("start,value", 400, "new", true, ResponseCondition.Success, "start,value")] + [InlineData("start,value", 200, "new", true, ResponseCondition.Success, "start,value;new")] + [InlineData("start,value", 400, "new", true, ResponseCondition.Always, "start,value;new")] + [InlineData("start,value", 200, "new", true, ResponseCondition.Always, "start,value;new")] + [InlineData("start;value", 400, "new", true, ResponseCondition.Success, "start;value")] + [InlineData("start;value", 200, "new", true, ResponseCondition.Success, "start;value;new")] + [InlineData("start;value", 400, "new", true, ResponseCondition.Always, "start;value;new")] + [InlineData("start;value", 200, "new", true, ResponseCondition.Always, "start;value;new")] + public async Task AddResponseTrailer_Success(string startValue, int status, string value, bool append, ResponseCondition condition, string expected) { var httpContext = new DefaultHttpContext(); var trailerFeature = new TestTrailersFeature(); @@ -46,7 +48,7 @@ public async Task AddResponseTrailer_Success(string startValue, int status, stri ProxyResponse = new HttpResponseMessage(), HeadersCopied = true, }; - var transform = new ResponseTrailerValueTransform("name", value, append, always); + var transform = new ResponseTrailerValueTransform("name", value, append, condition); await transform.ApplyAsync(transformContext); Assert.Equal(expected.Split(";", System.StringSplitOptions.RemoveEmptyEntries), trailerFeature.Trailers["name"]); } diff --git a/test/ReverseProxy.Tests/Transforms/ResponseTransformExtensionsTests.cs b/test/ReverseProxy.Tests/Transforms/ResponseTransformExtensionsTests.cs index a9e716acd..f3e2d54fa 100644 --- a/test/ReverseProxy.Tests/Transforms/ResponseTransformExtensionsTests.cs +++ b/test/ReverseProxy.Tests/Transforms/ResponseTransformExtensionsTests.cs @@ -38,41 +38,45 @@ public void WithTransformCopyResponseTrailers(bool copy) } [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void WithTransformResponseHeader(bool append, bool always) + [InlineData(false, ResponseCondition.Success)] + [InlineData(false, ResponseCondition.Always)] + [InlineData(false, ResponseCondition.Failure)] + [InlineData(true, ResponseCondition.Success)] + [InlineData(true, ResponseCondition.Always)] + [InlineData(true, ResponseCondition.Failure)] + public void WithTransformResponseHeader(bool append, ResponseCondition condition) { var routeConfig = new RouteConfig(); - routeConfig = routeConfig.WithTransformResponseHeader("name", "value", append, always); + routeConfig = routeConfig.WithTransformResponseHeader("name", "value", append, condition); var builderContext = ValidateAndBuild(routeConfig, _factory); - ValidateResponseHeader(builderContext, append, always); + ValidateResponseHeader(builderContext, append, condition); } [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void AddResponseHeader(bool append, bool always) + [InlineData(false, ResponseCondition.Success)] + [InlineData(false, ResponseCondition.Always)] + [InlineData(false, ResponseCondition.Failure)] + [InlineData(true, ResponseCondition.Success)] + [InlineData(true, ResponseCondition.Always)] + [InlineData(true, ResponseCondition.Failure)] + public void AddResponseHeader(bool append, ResponseCondition condition) { var builderContext = CreateBuilderContext(); - builderContext.AddResponseHeader("name", "value", append, always); + builderContext.AddResponseHeader("name", "value", append, condition); - ValidateResponseHeader(builderContext, append, always); + ValidateResponseHeader(builderContext, append, condition); } - private static void ValidateResponseHeader(TransformBuilderContext builderContext, bool append, bool always) + private static void ValidateResponseHeader(TransformBuilderContext builderContext, bool append, ResponseCondition condition) { var responseTransform = Assert.Single(builderContext.ResponseTransforms); var responseHeaderValueTransform = Assert.IsType(responseTransform); Assert.Equal("name", responseHeaderValueTransform.HeaderName); Assert.Equal("value", responseHeaderValueTransform.Value); Assert.Equal(append, responseHeaderValueTransform.Append); - Assert.Equal(always, responseHeaderValueTransform.Always); + Assert.Equal(condition, responseHeaderValueTransform.Condition); } [Fact] @@ -120,41 +124,45 @@ public void AddResponseHeadersAllowed() } [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void WithTransformResponseTrailer(bool append, bool always) + [InlineData(false, ResponseCondition.Success)] + [InlineData(false, ResponseCondition.Always)] + [InlineData(false, ResponseCondition.Failure)] + [InlineData(true, ResponseCondition.Success)] + [InlineData(true, ResponseCondition.Always)] + [InlineData(true, ResponseCondition.Failure)] + public void WithTransformResponseTrailer(bool append, ResponseCondition condition) { var routeConfig = new RouteConfig(); - routeConfig = routeConfig.WithTransformResponseTrailer("name", "value", append, always); + routeConfig = routeConfig.WithTransformResponseTrailer("name", "value", append, condition); var builderContext = ValidateAndBuild(routeConfig, _factory); - ValidateResponseTrailer(builderContext, append, always); + ValidateResponseTrailer(builderContext, append, condition); } [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void AddResponseTrailer(bool append, bool always) + [InlineData(false, ResponseCondition.Success)] + [InlineData(false, ResponseCondition.Always)] + [InlineData(false, ResponseCondition.Failure)] + [InlineData(true, ResponseCondition.Success)] + [InlineData(true, ResponseCondition.Always)] + [InlineData(true, ResponseCondition.Failure)] + public void AddResponseTrailer(bool append, ResponseCondition condition) { var builderContext = CreateBuilderContext(); - builderContext.AddResponseTrailer("name", "value", append, always); + builderContext.AddResponseTrailer("name", "value", append, condition); - ValidateResponseTrailer(builderContext, append, always); + ValidateResponseTrailer(builderContext, append, condition); } - private static void ValidateResponseTrailer(TransformBuilderContext builderContext, bool append, bool always) + private static void ValidateResponseTrailer(TransformBuilderContext builderContext, bool append, ResponseCondition condition) { var responseTransform = Assert.Single(builderContext.ResponseTrailersTransforms); var responseHeaderValueTransform = Assert.IsType(responseTransform); Assert.Equal("name", responseHeaderValueTransform.HeaderName); Assert.Equal("value", responseHeaderValueTransform.Value); Assert.Equal(append, responseHeaderValueTransform.Append); - Assert.Equal(always, responseHeaderValueTransform.Always); + Assert.Equal(condition, responseHeaderValueTransform.Condition); } [Fact] diff --git a/testassets/ReverseProxy.Code/Startup.cs b/testassets/ReverseProxy.Code/Startup.cs index 5482db170..55fae52a8 100644 --- a/testassets/ReverseProxy.Code/Startup.cs +++ b/testassets/ReverseProxy.Code/Startup.cs @@ -82,7 +82,7 @@ public void ConfigureServices(IServiceCollection services) { // Suppress the response body from errors. // The status code was already copied. - if (!context.ProxyResponse.IsSuccessStatusCode) + if (context.ProxyResponse?.IsSuccessStatusCode == false) { context.SuppressResponseBody = true; }