Skip to content
Draft
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
32 changes: 32 additions & 0 deletions src/ReverseProxy/Transforms/Builder/StructuredTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace Yarp.ReverseProxy.Transforms.Builder;
/// </summary>
internal sealed class StructuredTransformer : HttpTransformer
{
private readonly bool _canUseRequestTransformFastPath;

/// <summary>
/// Creates a new <see cref="StructuredTransformer"/> instance.
/// </summary>
Expand All @@ -36,6 +38,7 @@ internal StructuredTransformer(bool? copyRequestHeaders, bool? copyResponseHeade
RequestTransforms = requestTransforms.ToArray();
ResponseTransforms = responseTransforms.ToArray();
ResponseTrailerTransforms = responseTrailerTransforms.ToArray();
_canUseRequestTransformFastPath = RequestTransforms.All(CanUseRequestTransformFastPath);
}

/// <summary>
Expand Down Expand Up @@ -68,6 +71,22 @@ internal StructuredTransformer(bool? copyRequestHeaders, bool? copyResponseHeade
/// </summary>
internal ResponseTrailersTransform[] ResponseTrailerTransforms { get; }

private static bool CanUseRequestTransformFastPath(RequestTransform transform)
{
// Built-in transforms are inheritable, so exact type checks preserve derived ApplyAsync overrides.
var transformType = transform.GetType();
return transformType == typeof(RequestHeaderOriginalHostTransform)
|| transformType == typeof(RequestHeaderXForwardedForTransform)
|| transformType == typeof(RequestHeaderXForwardedHostTransform)
|| transformType == typeof(RequestHeaderXForwardedProtoTransform)
|| transformType == typeof(RequestHeaderXForwardedPrefixTransform)
|| transformType == typeof(RequestHeaderForwardedTransform)
|| transformType == typeof(RequestHeaderRemoveTransform)
|| transformType == typeof(RequestHeaderValueTransform)
|| transformType == typeof(RequestHeaderRouteValueTransform)
|| transformType == typeof(RequestHeadersAllowedTransform);
}

#pragma warning disable CS0672 // We're overriding the obsolete overloads to preserve backwards compatibility.
public override ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix) =>
TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, CancellationToken.None);
Expand Down Expand Up @@ -98,6 +117,19 @@ public override async ValueTask TransformRequestAsync(HttpContext httpContext, H
return;
}

if (_canUseRequestTransformFastPath)
{
var headersCopied = ShouldCopyRequestHeaders.GetValueOrDefault(true);
foreach (var requestTransform in RequestTransforms)
{
requestTransform.ApplyFast(httpContext, proxyRequest, ref headersCopied);
}

proxyRequest.RequestUri ??= RequestUtilities.MakeDestinationAddress(
destinationPrefix, httpContext.Request.Path, httpContext.Request.QueryString);
return;
}

var transformContext = new RequestTransformContext()
{
DestinationPrefix = destinationPrefix,
Expand Down
22 changes: 15 additions & 7 deletions src/ReverseProxy/Transforms/RequestHeaderForwardedTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
Expand Down Expand Up @@ -51,28 +52,35 @@ public RequestHeaderForwardedTransform(IRandomFactory randomFactory, NodeFormat
public override ValueTask ApplyAsync(RequestTransformContext context)
{
ArgumentNullException.ThrowIfNull(context);
Apply(context.HttpContext, context.ProxyRequest, context.HeadersCopied);
return default;
}

var httpContext = context.HttpContext;
internal override void ApplyFast(HttpContext httpContext, HttpRequestMessage proxyRequest, ref bool headersCopied)
{
Apply(httpContext, proxyRequest, headersCopied);
}

private void Apply(HttpContext httpContext, HttpRequestMessage proxyRequest, bool headersCopied)
{
switch (TransformAction)
{
case ForwardedTransformActions.Set:
RemoveHeader(context, ForwardedHeaderName);
AddHeader(context, ForwardedHeaderName, GetHeaderValue(httpContext));
RemoveHeader(proxyRequest, ForwardedHeaderName);
AddHeader(proxyRequest, ForwardedHeaderName, GetHeaderValue(httpContext));
break;
case ForwardedTransformActions.Append:
var existingValues = TakeHeader(context, ForwardedHeaderName);
var existingValues = TakeHeader(httpContext, proxyRequest, headersCopied, ForwardedHeaderName);
var values = StringValues.Concat(existingValues, GetHeaderValue(httpContext));
AddHeader(context, ForwardedHeaderName, values);
AddHeader(proxyRequest, ForwardedHeaderName, values);
break;
case ForwardedTransformActions.Remove:
RemoveHeader(context, ForwardedHeaderName);
RemoveHeader(proxyRequest, ForwardedHeaderName);
break;
default:
throw new NotImplementedException(TransformAction.ToString());
}

return default;
}

private string GetHeaderValue(HttpContext httpContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
using Yarp.ReverseProxy.Forwarder;
using Yarp.ReverseProxy.Model;
Expand Down Expand Up @@ -32,27 +34,36 @@ private RequestHeaderOriginalHostTransform(bool useOriginalHost)

public override ValueTask ApplyAsync(RequestTransformContext context)
{
var destinationConfigHost = context.HttpContext.Features.Get<IReverseProxyFeature>()?.ProxiedDestination?.Model.Config?.Host;
var originalHost = context.HttpContext.Request.Host.Value is { Length: > 0 } host ? host : null;
var existingHost = RequestUtilities.TryGetValues(context.ProxyRequest.Headers, HeaderNames.Host, out var currentHost) ? currentHost.ToString() : null;
Apply(context.HttpContext, context.ProxyRequest, context.HeadersCopied);
return default;
}

internal override void ApplyFast(HttpContext httpContext, HttpRequestMessage proxyRequest, ref bool headersCopied)
{
Apply(httpContext, proxyRequest, headersCopied);
}

private void Apply(HttpContext httpContext, HttpRequestMessage proxyRequest, bool headersCopied)
{
var destinationConfigHost = httpContext.Features.Get<IReverseProxyFeature>()?.ProxiedDestination?.Model.Config?.Host;
var originalHost = httpContext.Request.Host.Value is { Length: > 0 } host ? host : null;
var existingHost = RequestUtilities.TryGetValues(proxyRequest.Headers, HeaderNames.Host, out var currentHost) ? currentHost.ToString() : null;

if (UseOriginalHost)
{
if (!context.HeadersCopied && existingHost is null)
if (!headersCopied && existingHost is null)
{
// Propagate the host if the transform pipeline didn't already override it.
// If there was no original host specified, allow the destination config host to flow through.
context.ProxyRequest.Headers.TryAddWithoutValidation(HeaderNames.Host, originalHost ?? destinationConfigHost);
proxyRequest.Headers.TryAddWithoutValidation(HeaderNames.Host, originalHost ?? destinationConfigHost);
}
}
else if (existingHost is null || string.Equals(originalHost, existingHost, StringComparison.Ordinal))
{
// Use the host from destination configuration (which may be null) if either:
// * there is no host header set, or
// * the original host header is being suppressed and has not been modified by the transform pipeline
context.ProxyRequest.Headers.Host = destinationConfigHost;
proxyRequest.Headers.Host = destinationConfigHost;
}

return default;
}
}
11 changes: 8 additions & 3 deletions src/ReverseProxy/Transforms/RequestHeaderRemoveTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace Yarp.ReverseProxy.Transforms;

Expand All @@ -27,9 +29,12 @@ public RequestHeaderRemoveTransform(string headerName)
public override ValueTask ApplyAsync(RequestTransformContext context)
{
ArgumentNullException.ThrowIfNull(context);

RemoveHeader(context, HeaderName);

RemoveHeader(context.ProxyRequest, HeaderName);
return default;
}

internal override void ApplyFast(HttpContext httpContext, HttpRequestMessage proxyRequest, ref bool headersCopied)
{
RemoveHeader(proxyRequest, HeaderName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;

namespace Yarp.ReverseProxy.Transforms;

Expand Down Expand Up @@ -35,5 +39,24 @@ public RequestHeaderRouteValueTransform(string headerName, string routeValueKey,

return value?.ToString();
}
}

internal override void ApplyFast(HttpContext httpContext, HttpRequestMessage proxyRequest, ref bool headersCopied)
{
if (!httpContext.Request.RouteValues.TryGetValue(RouteValueKey, out var routeValue)
|| routeValue?.ToString() is not { } value)
{
return;
}

if (Append)
{
var existingValues = TakeHeader(httpContext, proxyRequest, headersCopied, HeaderName);
AddHeader(proxyRequest, HeaderName, StringValues.Concat(existingValues, value));
}
else
{
RemoveHeader(proxyRequest, HeaderName);
AddHeader(proxyRequest, HeaderName, value);
}
}
}
17 changes: 17 additions & 0 deletions src/ReverseProxy/Transforms/RequestHeaderValueTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;

namespace Yarp.ReverseProxy.Transforms;

Expand Down Expand Up @@ -31,4 +34,18 @@ protected override string GetValue(RequestTransformContext context)
{
return Value;
}

internal override void ApplyFast(HttpContext httpContext, HttpRequestMessage proxyRequest, ref bool headersCopied)
{
if (Append)
{
var existingValues = TakeHeader(httpContext, proxyRequest, headersCopied, HeaderName);
AddHeader(proxyRequest, HeaderName, StringValues.Concat(existingValues, Value));
}
else
{
RemoveHeader(proxyRequest, HeaderName);
AddHeader(proxyRequest, HeaderName, Value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;

namespace Yarp.ReverseProxy.Transforms;
Expand Down Expand Up @@ -38,9 +40,19 @@ public RequestHeaderXForwardedForTransform(string headerName, ForwardedTransform
public override ValueTask ApplyAsync(RequestTransformContext context)
{
ArgumentNullException.ThrowIfNull(context);
Apply(context.HttpContext, context.ProxyRequest, context.HeadersCopied);
return default;
}

internal override void ApplyFast(HttpContext httpContext, HttpRequestMessage proxyRequest, ref bool headersCopied)
{
Apply(httpContext, proxyRequest, headersCopied);
}

private void Apply(HttpContext httpContext, HttpRequestMessage proxyRequest, bool headersCopied)
{
string? remoteIp = null;
var remoteIpAddress = context.HttpContext.Connection.RemoteIpAddress;
var remoteIpAddress = httpContext.Connection.RemoteIpAddress;
if (remoteIpAddress is not null)
{
remoteIp = remoteIpAddress.IsIPv4MappedToIPv6 ?
Expand All @@ -51,39 +63,38 @@ public override ValueTask ApplyAsync(RequestTransformContext context)
switch (TransformAction)
{
case ForwardedTransformActions.Set:
RemoveHeader(context, HeaderName);
RemoveHeader(proxyRequest, HeaderName);
if (remoteIp is not null)
{
AddHeader(context, HeaderName, remoteIp);
AddHeader(proxyRequest, HeaderName, remoteIp);
}
break;
case ForwardedTransformActions.Append:
Append(context, remoteIp);
Append(httpContext, proxyRequest, headersCopied, remoteIp);
break;
case ForwardedTransformActions.Remove:
RemoveHeader(context, HeaderName);
RemoveHeader(proxyRequest, HeaderName);
break;
default:
throw new NotImplementedException(TransformAction.ToString());
}

return default;
}

private void Append(RequestTransformContext context, string? remoteIp)
private void Append(HttpContext httpContext, HttpRequestMessage proxyRequest, bool headersCopied, string? remoteIp)
{
var existingValues = TakeHeader(context, HeaderName);
var existingValues = TakeHeader(httpContext, proxyRequest, headersCopied, HeaderName);
if (remoteIp is null)
{
if (!string.IsNullOrEmpty(existingValues))
{
AddHeader(context, HeaderName, existingValues);
AddHeader(proxyRequest, HeaderName, existingValues);
}
}
else
{
var values = StringValues.Concat(existingValues, remoteIp);
AddHeader(context, HeaderName, values);
AddHeader(proxyRequest, HeaderName, values);
}
}
}
Loading
Loading