Skip to content

Implement failure only transforms #1300

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions docs/docfx/articles/transforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
```
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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:
```
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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:
```
Expand All @@ -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
Expand All @@ -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:
```
Expand Down
7 changes: 4 additions & 3 deletions src/ReverseProxy/Forwarder/HttpForwarder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public async ValueTask<ForwarderError> 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();
Expand Down Expand Up @@ -481,8 +481,9 @@ private async ValueTask<ForwarderError> 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;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/ReverseProxy/Forwarder/HttpTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public virtual ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequ
/// `Transfer-Encoding: chunked`.
/// </summary>
/// <param name="httpContext">The incoming request.</param>
/// <param name="proxyResponse">The response from the destination.</param>
/// <param name="proxyResponse">The response from the destination. This can be null if the destination did not respond.</param>
/// <returns>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.</returns>
Expand Down
26 changes: 26 additions & 0 deletions src/ReverseProxy/Transforms/ResponseCondition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Yarp.ReverseProxy.Transforms
{
/// <summary>
/// Specifies the conditions under which a response transform will run.
/// </summary>
public enum ResponseCondition
{
/// <summary>
/// The transform runs for all conditions.
/// </summary>
Always,

/// <summary>
/// The transform only runs if there is a successful response with a status code less than 400.
/// </summary>
Success,

/// <summary>
/// The transform only runs if there is no response or a response with a 400+ status code.
/// </summary>
Failure
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ namespace Yarp.ReverseProxy.Transforms
/// </summary>
public class ResponseHeaderRemoveTransform : ResponseTransform
{
public ResponseHeaderRemoveTransform(string headerName, bool always)
public ResponseHeaderRemoveTransform(string headerName, ResponseCondition condition)
{
if (string.IsNullOrEmpty(headerName))
{
throw new ArgumentException($"'{nameof(headerName)}' cannot be null or empty.", nameof(headerName));
}

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.
/// <inheritdoc/>
Expand All @@ -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);
}
Expand Down
9 changes: 5 additions & 4 deletions src/ReverseProxy/Transforms/ResponseHeaderValueTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Yarp.ReverseProxy.Transforms
/// </summary>
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))
{
Expand All @@ -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; }

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ namespace Yarp.ReverseProxy.Transforms
/// </summary>
public class ResponseTrailerRemoveTransform : ResponseTrailersTransform
{
public ResponseTrailerRemoveTransform(string headerName, bool always)
public ResponseTrailerRemoveTransform(string headerName, ResponseCondition condition)
{
if (string.IsNullOrEmpty(headerName))
{
throw new ArgumentException($"'{nameof(headerName)}' cannot be null or empty.", nameof(headerName));
}

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.
/// <inheritdoc/>
Expand All @@ -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<IHttpResponseTrailersFeature>();
var responseTrailers = responseTrailersFeature?.Trailers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Yarp.ReverseProxy.Transforms
/// </summary>
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))
{
Expand All @@ -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; }

Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/ReverseProxy/Transforms/ResponseTransformContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class ResponseTransformContext
public HttpContext HttpContext { get; init; } = default!;

/// <summary>
/// The incoming proxy response.
/// The proxy response. This can be null if the destination did not respond.
/// </summary>
public HttpResponseMessage? ProxyResponse { get; init; }

Expand Down
Loading