Skip to content

Commit

Permalink
Stop Middleware shortcutting when filter is used
Browse files Browse the repository at this point in the history
  • Loading branch information
rtkelly13 committed Aug 10, 2021
1 parent 274bd4c commit d84ba99
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 20 deletions.
39 changes: 26 additions & 13 deletions src/Core/Extensions/Basic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ public static IApplicationBuilder UseProxies(this IApplicationBuilder app, Actio
foreach(var proxy in proxiesBuilder.Build())
{
builder.MapMiddlewareRoute(proxy.Route, proxyApp => proxyApp.Run(context => context.ExecuteProxyOperationAsync(proxy)));
builder.MapMiddlewareRoute(proxy.Route, proxyApp => proxyApp.Use(async (context, next) =>
{
if (!await context.ExecuteProxyOperationAsync(proxy).ConfigureAwait(false))
{
await next();
}
}));
}
});

Expand All @@ -82,8 +88,14 @@ public static void RunProxy(this IApplicationBuilder app, Action<IProxyBuilder>
proxy.HttpProxy.EndpointComputer = GetRunProxyComputer(oldHttpEndpointComputer);
if (proxy.WsProxy?.EndpointComputer.Clone() is EndpointComputerToValueTask oldWsEndpointComputer)
proxy.WsProxy.EndpointComputer = GetRunProxyComputer(oldWsEndpointComputer);

app.Run(context => context.ExecuteProxyOperationAsync(proxy));

app.Use(async (context, next) =>
{
if (!await context.ExecuteProxyOperationAsync(proxy).ConfigureAwait(false))
{
await next();
}
});
}

/// <summary>
Expand Down Expand Up @@ -272,12 +284,12 @@ public static void RunWsProxy(this IApplicationBuilder app, string wsEndpoint, A
/// <param name="httpProxyOptions">The HTTP options.</param>
/// <param name="wsProxyOptions">The WS options.</param>
/// <returns>A <see cref="Task"/> which completes when the request has been successfully proxied and written to the response.</returns>
public static Task ProxyAsync(this ControllerBase controller, string httpEndpoint, string wsEndpoint, HttpProxyOptions httpProxyOptions = null, WsProxyOptions wsProxyOptions = null)
public static async Task ProxyAsync(this ControllerBase controller, string httpEndpoint, string wsEndpoint, HttpProxyOptions httpProxyOptions = null, WsProxyOptions wsProxyOptions = null)
{
var httpProxy = new HttpProxy((c, a) => new ValueTask<string>(httpEndpoint), httpProxyOptions);
var wsProxy = new WsProxy((c, a) => new ValueTask<string>(wsEndpoint), wsProxyOptions);
var proxy = new Builders.Proxy(null, httpProxy, wsProxy);
return controller.HttpContext.ExecuteProxyOperationAsync(proxy);
await controller.HttpContext.ExecuteProxyOperationAsync(proxy).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -287,10 +299,10 @@ public static Task ProxyAsync(this ControllerBase controller, string httpEndpoin
/// <param name="httpEndpoint">The HTTP endpoint to use.</param>
/// <param name="httpProxyOptions">The HTTP options.</param>
/// <returns>A <see cref="Task"/> which completes when the request has been successfully proxied and written to the response.</returns>
public static Task HttpProxyAsync(this ControllerBase controller, string httpEndpoint, HttpProxyOptions httpProxyOptions = null)
public static async Task HttpProxyAsync(this ControllerBase controller, string httpEndpoint, HttpProxyOptions httpProxyOptions = null)
{
var httpProxy = new HttpProxy((c, a) => new ValueTask<string>(httpEndpoint), httpProxyOptions);
return controller.HttpContext.ExecuteHttpProxyOperationAsync(httpProxy);
await controller.HttpContext.ExecuteHttpProxyOperationAsync(httpProxy).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -300,36 +312,37 @@ public static Task HttpProxyAsync(this ControllerBase controller, string httpEnd
/// <param name="wsEndpoint">The WS endpoint to use.</param>
/// <param name="wsProxyOptions">The WS options.</param>
/// <returns>A <see cref="Task"/> which completes when the request has been successfully proxied and written to the response.</returns>
public static Task WsProxyAsync(this ControllerBase controller, string wsEndpoint, WsProxyOptions wsProxyOptions = null)
public static async Task WsProxyAsync(this ControllerBase controller, string wsEndpoint, WsProxyOptions wsProxyOptions = null)
{
var wsProxy = new WsProxy((c, a) => new ValueTask<string>(wsEndpoint), wsProxyOptions);
return controller.HttpContext.ExecuteWsProxyOperationAsync(wsProxy);
await controller.HttpContext.ExecuteWsProxyOperationAsync(wsProxy);
}

#endregion

#region Extension Helpers

internal static async Task ExecuteProxyOperationAsync(this HttpContext context, Builders.Proxy proxy)
internal static async Task<bool> ExecuteProxyOperationAsync(this HttpContext context, Builders.Proxy proxy)
{
var isWebSocket = context.WebSockets.IsWebSocketRequest;
if(isWebSocket && proxy.WsProxy != null)
{
await context.ExecuteWsProxyOperationAsync(proxy.WsProxy).ConfigureAwait(false);
return;
return true;
}

if(!isWebSocket && proxy.HttpProxy != null)
{
await context.ExecuteHttpProxyOperationAsync(proxy.HttpProxy).ConfigureAwait(false);
return;
return await context.ExecuteHttpProxyOperationAsync(proxy.HttpProxy).ConfigureAwait(false);
}

var requestType = isWebSocket ? "WebSocket" : "HTTP(S)";

// If the failures are not caught, then write a generic response.
context.Response.StatusCode = 502 /* BAD GATEWAY */;
await context.Response.WriteAsync($"Request could not be proxied.\n\nThe {requestType} request cannot be proxied because the underlying proxy definition does not have a definition of that type.").ConfigureAwait(false);

return true;
}

internal static ValueTask<string> GetEndpointFromComputerAsync(this HttpContext context, EndpointComputerToValueTask computer) => computer(context, context.GetRouteData().Values);
Expand Down
19 changes: 14 additions & 5 deletions src/Core/Extensions/Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace AspNetCore.Proxy
{
internal static class HttpExtensions
{
internal static async Task ExecuteHttpProxyOperationAsync(this HttpContext context, HttpProxy httpProxy)
internal static async Task<bool> ExecuteHttpProxyOperationAsync(this HttpContext context, HttpProxy httpProxy)
{
var uri = await context.GetEndpointFromComputerAsync(httpProxy.EndpointComputer).ConfigureAwait(false);
var options = httpProxy.Options;
Expand All @@ -23,9 +23,14 @@ internal static async Task ExecuteHttpProxyOperationAsync(this HttpContext conte
.GetService<IHttpClientFactory>()
.CreateClient(options?.HttpClientName ?? Helpers.HttpProxyClientName);

if (options?.Filter != null && !options.Filter(context))
{
return false;
}

// If `true`, this proxy call has been intercepted.
if(options?.Intercept != null && await options.Intercept(context).ConfigureAwait(false))
return;
return true;

if(context.WebSockets.IsWebSocketRequest)
throw new InvalidOperationException("A WebSocket request cannot be routed as an HTTP proxy operation.");
Expand All @@ -49,6 +54,7 @@ internal static async Task ExecuteHttpProxyOperationAsync(this HttpContext conte
if(options?.AfterReceive != null)
await options.AfterReceive(context, proxiedResponse).ConfigureAwait(false);
await context.WriteProxiedHttpResponseAsync(proxiedResponse).ConfigureAwait(false);

}
catch (Exception e)
{
Expand All @@ -59,12 +65,15 @@ internal static async Task ExecuteHttpProxyOperationAsync(this HttpContext conte
// If the failures are not caught, then write a generic response.
context.Response.StatusCode = 502 /* BAD GATEWAY */;
await context.Response.WriteAsync($"Request could not be proxied.\n\n{e.Message}\n\n{e.StackTrace}").ConfigureAwait(false);
return;

}
else
{
await options.HandleFailure(context, e).ConfigureAwait(false);
}

await options.HandleFailure(context, e).ConfigureAwait(false);
}
}
return true;
}

private static HttpRequestMessage CreateProxiedHttpRequest(this HttpContext context, string uriString, bool shouldAddForwardedHeaders)
Expand Down
37 changes: 35 additions & 2 deletions src/Core/Options/HttpProxyOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ public interface IHttpProxyOptionsBuilder : IBuilder<IHttpProxyOptionsBuilder, H
/// <returns>This instance.</returns>
IHttpProxyOptionsBuilder WithIntercept(Func<HttpContext, ValueTask<bool>> intercept);

/// <summary>
/// A <see cref="Func{HttpContext, Boolean}"/> that is invoked upon a call.
/// The result should be `true` if the call should go ahead and not be filtered
/// </summary>
/// <param name="filter"></param>
/// <returns>This instance.</returns>
IHttpProxyOptionsBuilder WithFilter(Func<HttpContext, bool> filter);

/// <summary>
/// An <see cref="Func{HttpContext, HttpRequestMessage, Task}"/> that is invoked before the call to the remote endpoint.
/// The <see cref="HttpRequestMessage"/> can be edited before the call.
Expand Down Expand Up @@ -66,6 +74,7 @@ public sealed class HttpProxyOptionsBuilder : IHttpProxyOptionsBuilder
private bool _shouldAddForwardedHeaders = true;
private string _httpClientName;
private Func<HttpContext, ValueTask<bool>> _intercept;
private Func<HttpContext, bool> _filter;
private Func<HttpContext, HttpRequestMessage, Task> _beforeSend;
private Func<HttpContext, HttpResponseMessage, Task> _afterReceive;
private Func<HttpContext, Exception, Task> _handleFailure;
Expand All @@ -90,6 +99,7 @@ public IHttpProxyOptionsBuilder New()
.WithShouldAddForwardedHeaders(_shouldAddForwardedHeaders)
.WithHttpClientName(_httpClientName)
.WithIntercept(_intercept)
.WithFilter(_filter)
.WithBeforeSend(_beforeSend)
.WithAfterReceive(_afterReceive)
.WithHandleFailure(_handleFailure);
Expand All @@ -103,6 +113,7 @@ public HttpProxyOptions Build()
_httpClientName,
_handleFailure,
_intercept,
_filter,
_beforeSend,
_afterReceive);
}
Expand Down Expand Up @@ -143,6 +154,18 @@ public IHttpProxyOptionsBuilder WithIntercept(Func<HttpContext, ValueTask<bool>>
return this;
}

/// <summary>
/// A <see cref="Func{HttpContext, Boolean}"/> that is invoked upon a call.
/// The result should be `true` if the call should go ahead and not be filtered
/// </summary>
/// <param name="filter"></param>
/// <returns>This instance.</returns>
public IHttpProxyOptionsBuilder WithFilter(Func<HttpContext, bool> filter)
{
_filter = filter;
return this;
}

/// <summary>
/// Sets the <see cref="Func{HttpContext, HttpRequestMessage, Task}"/> that is invoked before the call to the remote endpoint.
/// The <see cref="HttpRequestMessage"/> can be edited before the call.
Expand Down Expand Up @@ -210,6 +233,15 @@ public sealed class HttpProxyOptions
/// The result should be `true` if the call is intercepted and **not** meant to be forwarded.
/// </value>
public Func<HttpContext, ValueTask<bool>> Intercept { get; }

/// <summary>
/// Intercept property.
/// </summary>
/// <value>
/// A <see cref="Func{HttpContext, Boolean}"/> that is invoked upon a call.
/// The result should be `true` if the call should go ahead and not be filtered
/// </value>
public Func<HttpContext, bool> Filter { get; }

/// <summary>
/// BeforeSend property.
Expand All @@ -235,18 +267,19 @@ public sealed class HttpProxyOptions
/// <value>A <see cref="Func{HttpContext, Exception, Task}"/> that is invoked once if the proxy operation fails.</value>
public Func<HttpContext, Exception, Task> HandleFailure { get; }

internal HttpProxyOptions(
bool shouldAddForwardedHeaders,
internal HttpProxyOptions(bool shouldAddForwardedHeaders,
string httpClientName,
Func<HttpContext, Exception, Task> handleFailure,
Func<HttpContext, ValueTask<bool>> intercept,
Func<HttpContext, bool> filter,
Func<HttpContext, HttpRequestMessage, Task> beforeSend,
Func<HttpContext, HttpResponseMessage, Task> afterReceive)
{
ShouldAddForwardedHeaders = shouldAddForwardedHeaders;
HttpClientName = httpClientName;
HandleFailure = handleFailure;
Intercept = intercept;
Filter = filter;
BeforeSend = beforeSend;
AfterReceive = afterReceive;
}
Expand Down

0 comments on commit d84ba99

Please sign in to comment.