-
Hi, What should we add or change to make your life better?I want to migrate ProxyKit to YARP and we implemented:
Why is this important to you?Probably you know how to exclude some endponts, but custom response dispatcher is mandatory because without it we can't cache user and client based on incoming token from master instance. I added middleware to exclude endpoints but it doesn't work: endpoints.MapReverseProxy(proxyBuilder =>
{
proxyBuilder.Use(async (context, next) =>
{
var endpoint = context.GetEndpoint();
var excludedEndpointsProvider = app.ApplicationServices.GetRequiredService<IExcludedEndpointsProvider>();
if (excludedEndpointsProvider.IsExcluded(endpoint?.DisplayName, false))
{
context.Response.StatusCode = StatusCodes.Status203NonAuthoritative;
await context.Response.WriteAsync("Endpoint excluded.");
return;
}
await next();
});
proxyBuilder.UseLoadBalancing();
}); |
Beta Was this translation helpful? Give feedback.
Replies: 16 comments 11 replies
-
Your exclusion example looks ok, why do you say it didn't work? Any request that matched that
I'm not sure what you're asking for here, can you give some examples? |
Beta Was this translation helpful? Give feedback.
-
I put breakpoint inside This is my implementation when I use ProxyKit. appBuilder.RunProxy(async proxyContext =>
{
proxyContext.Request.EnableBuffering();
var memoryCache = app.ApplicationServices.GetService<IMemoryCache>();
var masterUrl = memoryCache.Get<string>(Constants.MASTER_URL_CACHE_KEY);
var forwardContext = proxyContext.ForwardTo(masterUrl).AddXForwardedHeaders();
var response = await forwardContext.Send();
using var scope = app.ApplicationServices.CreateScope();
var handler = scope.ServiceProvider.GetService<IForwardedResponseDispatcher>();
if (handler != null)
{
var isProcessed = await handler.ProcessResponse(response);
if (!isProcessed && !response.Headers.Contains(RESPONSE_HEADER_NAME))
{
response.Headers.Add(RESPONSE_HEADER_NAME, masterUrl);
}
}
return response;
}); So, when I get response from master instance, I have to process it differently in one case. My YARP routes configuration: Routes = new List<RouteConfig>
{
new()
{
RouteId = "api",
ClusterId = CURRENT_INSTANCE,
Match = new RouteMatch
{
Path = "{**catch-all}",
}
}
}; I have 2 kinds of endpoints:
|
Beta Was this translation helpful? Give feedback.
-
But the request still gets proxied? I agree that doesn't make sense, all proxied requests would go through MapReverseProxy and your middleware. If you're not using the routing system anyways, try the direct proxying API instead, it's much closer to what you were using in proxykit. https://microsoft.github.io/reverse-proxy/articles/direct-forwarding.html |
Beta Was this translation helpful? Give feedback.
-
No, request is not proxied. This is my proxy configuration provider. public class CustomProxyConfigProvider : IProxyConfigProvider
{
private CustomMemoryConfig _config;
private readonly ILogger<CustomProxyConfigProvider> _logger;
public CustomProxyConfigProvider(ILogger<CustomProxyConfigProvider> logger)
{
_logger = logger;
_config = new CustomMemoryConfig(null);
}
public IProxyConfig GetConfig() => _config;
public void SetMasterUrl(string masterUrl)
{
_logger.LogInformation($"Set master url: '{masterUrl}'.");
string currentMasterUrl = GetMasterUrl();
if (currentMasterUrl != masterUrl)
{
_logger.LogInformation($"Replace master url: '{currentMasterUrl}' on '{masterUrl}'.");
var oldConfig = _config;
_config = new CustomMemoryConfig(masterUrl);
oldConfig.SignalChange();
}
}
public string GetMasterUrl()
{
string masterUrl = null;
var destinations = _config.Clusters[0].Destinations;
if (destinations != null && destinations.TryGetValue(Constants.MASTER_URL_CACHE_KEY, out DestinationConfig config))
{
masterUrl = config.Address;
}
return masterUrl;
}
private class CustomMemoryConfig : IProxyConfig
{
private readonly CancellationTokenSource _cts = new();
private const string CURRENT_INSTANCE = "current-instance";
public CustomMemoryConfig(string masterUrl)
{
Routes = new List<RouteConfig>
{
new()
{
RouteId = "api",
ClusterId = CURRENT_INSTANCE,
Match = new RouteMatch
{
Path = "{**catch-all}",
}
}
};
Clusters = new[]
{
new ClusterConfig
{
ClusterId = CURRENT_INSTANCE,
Destinations = !string.IsNullOrEmpty(masterUrl)
? new Dictionary<string, DestinationConfig>(StringComparer.OrdinalIgnoreCase)
{
{
Constants.MASTER_URL_CACHE_KEY,
new DestinationConfig { Address = masterUrl }
}
}
: new Dictionary<string, DestinationConfig>(),
HealthCheck = !string.IsNullOrEmpty(masterUrl)
? new HealthCheckConfig
{
Active = new ActiveHealthCheckConfig
{
Enabled = true,
Interval = TimeSpan.FromMinutes(1),
Timeout = TimeSpan.FromSeconds(10),
Policy = HealthCheckConstants.ActivePolicy.ConsecutiveFailures,
Path = Constants.HEALTH_ENDPOINT
}
}
: null
},
};
ChangeToken = new CancellationChangeToken(_cts.Token);
}
public IReadOnlyList<RouteConfig> Routes { get; }
public IReadOnlyList<ClusterConfig> Clusters { get; }
public IChangeToken ChangeToken { get; }
internal void SignalChange()
{
_cts.Cancel();
}
}
} Destination is set correctly, application logs corrrect url |
Beta Was this translation helpful? Give feedback.
-
If it's not proxied then it's being handled by a different route. If you enable ASP.NET's Debug logging you should see which route was selected instead. |
Beta Was this translation helpful? Give feedback.
-
We use .Net 5 but I think it will be the same setting |
Beta Was this translation helpful? Give feedback.
-
Hi @Tratcher, app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.UseRequestsProxy();
}); When I commented |
Beta Was this translation helpful? Give feedback.
-
And you know you have a master instance if masterUrl is set? In that case you can make this work by only adding the RouteConfig it masterUrl is set. You'll need to adjust the Order property so it overrides the controllers, int.MinValue should work. |
Beta Was this translation helpful? Give feedback.
-
Hi @Tratcher, proxyBuilder.Use(async (context, next) =>
{
string endpoint = context.Request.Path.ToString().ToLowerInvariant();
if (!UseProxy(proxyBuilder, endpoint))
{
return;
}
context.Request.EnableBuffering();
using IServiceScope scope = proxyBuilder.ApplicationServices.CreateScope();
var proxyConfigurator = scope.ServiceProvider.GetRequiredService<IProxyConfigProvider>() as CustomProxyConfigProvider;
string masterUrl = proxyConfigurator?.GetMasterUrl();
await next();
if (context.Response.StatusCode == StatusCodes.Status200OK && endpoint.Contains(TOKEN_ENDPOINT))
{
var tokenResponseProcessor = scope.ServiceProvider.GetRequiredService<IForwardedTokenResponseProcessor>();
await tokenResponseProcessor.Process(context.Response, context.Request);
context.Response.Headers.Add(RESPONSE_HEADER_NAME, masterUrl);
}
}); Do you know how I should confogure YARP to skip these endpoints? |
Beta Was this translation helpful? Give feedback.
-
Can you show the rest of startup for context? The UseProxy check can't go in the proxy middleware, routing has already happened, nothing else will handle the request if you don't here. To fully customize when a request should be proxied vs handled locally, you'd need a middleware in front of routing that did the UseProxy check. If the check returned true then you'd call the proxy APIs directly, and if falls you'd call next so routing could happen. Also, it's dangerous to try setting response headers after calling next. If the response has a body that has already started (Response.HasStarted) then setting headers will throw, they've already been sent to the client. This kind of check would need to happen in a response transform. |
Beta Was this translation helpful? Give feedback.
-
Triage: Closing as answered, please re-open if you are still facing issues. |
Beta Was this translation helpful? Give feedback.
-
Hi @Tratcher , app.UseEndpoints(endpoints =>
{
endpoints.ConfigureProxy(forwarder);
endpoints.MapControllers();
endpoints.MapHealthChecks(Constants.HEALTH_ENDPOINT, new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
}); ConfigureProxy endpoints.Map("{**catch-all}", async httpContext =>
{
var useProxy = true;
string requestUrl = httpContext.Request.Path.ToString().ToLowerInvariant();
using IServiceScope scope = endpoints.ServiceProvider.CreateScope();
var excludedEndpointsProvider = scope.ServiceProvider.GetRequiredService<IExcludedEndpointsProvider>();
var memoryCache = scope.ServiceProvider.GetService<IMemoryCache>();
int? timeoutInSeconds = scope.ServiceProvider.GetRequiredService<IOptions<HttpSettings>>().Value.TimeoutInSeconds;
if (excludedEndpointsProvider.IsExcluded(requestUrl, false))
{
useProxy = false;
}
string masterUrl = memoryCache.Get<string>(Constants.MASTER_URL_CACHE_KEY) ?? string.Empty;
if (string.IsNullOrWhiteSpace(masterUrl))
{
useProxy = false;
}
var requestConfig = new ForwarderRequestConfig
{
ActivityTimeout = timeoutInSeconds.HasValue ? TimeSpan.FromSeconds(timeoutInSeconds.Value) : null,
AllowResponseBuffering = true,
};
var httpClient = new HttpMessageInvoker(new SocketsHttpHandler
{
UseProxy = useProxy,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None,
UseCookies = false,
});
ForwarderError error = await forwarder.SendAsync(
httpContext,
masterUrl,
httpClient,
requestConfig,
new CustomTransformer());
if (error == ForwarderError.None && useProxy && requestUrl.Contains(TOKEN_ENDPOINT))
{
var tokenResponseProcessor = scope.ServiceProvider.GetRequiredService<IForwardedTokenResponseProcessor>();
await tokenResponseProcessor.Process(httpContext.Response, httpContext.Request);
}
}); I tried to register direct forwarding before and after registering controllers and health check but I got the same result. Requests weren't forwarded. |
Beta Was this translation helpful? Give feedback.
-
Routing is based on finding the most specific match. You're proxying Also, I don't think that |
Beta Was this translation helpful? Give feedback.
-
I have 3 different routings patterns:
In my scenario I have to forward all requests except endpoints below:
|
Beta Was this translation helpful? Give feedback.
-
I prepared a repository to reproduce my scenario. |
Beta Was this translation helpful? Give feedback.
-
@karelz @Tratcher ForwarderError error = await _forwarder.SendAsync(
context,
_masterUrl,
httpClient,
requestConfig,
new CustomTransformer());
using IServiceScope scope = context.RequestServices.CreateScope();
var tokenResponseProcessor = scope.ServiceProvider.GetRequiredService<IForwardedTokenResponseProcessor>();
if (error == ForwarderError.None && requestUri.Contains(TOKEN_ENDPOINT))
{
await tokenResponseProcessor.Process(context.Response, context.Request);
}
|
Beta Was this translation helpful? Give feedback.
But the request still gets proxied? I agree that doesn't make sense, all proxied requests would go through MapReverseProxy and your middleware.
If you're not using the routing system anyways, try the direct proxying API instead, it's much closer to what you were using in proxykit. https://microsoft.github.io/reverse-proxy/articles/direct-forwarding.html