Edit

Share via


YARP Middleware

Introduction

ASP.NET Core uses a middleware pipeline to divide request processing into discrete steps. The app developer can add and order middleware as needed. ASP.NET Core middleware is also used to implement and customize reverse proxy functionality.

Defaults

The getting started sample shows the following Configure method. This sets up a middleware pipeline with development tools, routing, and proxy configured endpoints (MapReverseProxy).

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();

The parameterless MapReverseProxy() in ReverseProxyIEndpointRouteBuilderExtensions overload includes all standard proxy middleware for session affinity, load balancing, passive health checks, and the final proxying of the request. Each of these check the configuration of the matched route, cluster, and destination and perform their task accordingly.

Adding Middleware

Middleware added to your application pipeline will see the request in different states of processing depending on where the middleware is added. Middleware added before UseRouting will see all requests and can manipulate them before any routing takes place. Middleware added between UseRouting and UseEndpoints can call HttpContext.GetEndpoint() to check which endpoint routing matched the request to (if any), and use any metadata that was associated with that endpoint. This is how Authentication, Authorization and CORS are handled.

ReverseProxyIEndpointRouteBuilderExtensions provides an overload of MapReverseProxy that lets you build a middleware pipeline that will run only for requests matched to proxy configured routes.

app.MapReverseProxy(proxyPipeline =>
{
    proxyPipeline.Use((context, next) =>
    {
        // Custom inline middleware

        return next();
    });
    proxyPipeline.UseSessionAffinity();
    proxyPipeline.UseLoadBalancing();
    proxyPipeline.UsePassiveHealthChecks();
});

By default this overload of MapReverseProxy only includes the minimal setup, proxying logic, and limit enforcement at the start and end of its pipeline. Middleware for session affinity, load balancing, and passive health checks are not included by default so that you can exclude, replace, or control their ordering with any additional middleware.

Custom Proxy Middleware

Middleware inside the MapReverseProxy pipeline have access to all of the proxy data and state associated with a request (the route, cluster, destinations, etc.) through the IReverseProxyFeature. This is available from HttpContext.Features or the extension method HttpContext.GetReverseProxyFeature().

The data in IReverseProxyFeature are snapshotted from the proxy configuration at the start of the proxy pipeline and will not be affected by proxy configuration changes that occur while the request is being processed.

proxyPipeline.Use((context, next) =>
{
    var proxyFeature = context.GetReverseProxyFeature();
    var cluster = proxyFeature.Cluster;
    var destinations = proxyFeature.AvailableDestinations;

    return next();
});

What to do with middleware

Middleware can generate logs, control if a request gets proxied or not, influence where it's proxied to, and add additional features like error handling, retries, etc..

Logs and Metrics

Middleware can inspect request and response fields to generate logs and aggregate metrics. See the note about bodies under "What not to do with middleware" below.

proxyPipeline.Use(async (context, next) =>
{
    LogRequest(context);
    await next();
    LogResponse(context);
});

Send an immediate response

If a middleware inspects a request and determines that it should not be proxied, it may generate its own response and return control to the server without calling next().

proxyPipeline.Use((context, next) =>
{
    if (!CheckAllowedRequest(context, out var reason))
    {
        context.Response.StatusCode = StatusCodes.Status400BadRequest;
        return context.Response.WriteAsync(reason);
    }

    return next();
});

Filter destinations

Middleware like session affinity and load balancing examine the IReverseProxyFeature and the cluster configuration to decide which destination a request should be sent to.

AllDestinations lists all destinations in the selected cluster.

AvailableDestinations lists the destinations currently considered eligible to handle the request. It is initialized to AllDestinations, excluding unhealthy ones if health checks are enabled. AvailableDestinations should be reduced to a single destination by the end of the pipeline or else one will be selected randomly from the remainder.

ProxiedDestination is set by the proxy logic at the end of the pipeline to indicate which destination was ultimately used. If there are no available destinations remaining then a 503 error response is sent.

proxyPipeline.Use(async (context, next) =>
{
    var proxyFeature = context.GetReverseProxyFeature();
    proxyFeature.AvailableDestinations = Filter(proxyFeature.AvailableDestinations);

    await next();

    Report(proxyFeature.ProxiedDestination);
});

DestinationState implements IReadOnlyList<DestinationState> so a single destination can be assigned to AvailableDestinations without creating a new list.

Error handling

Middleware can wrap the call to await next() in a try/catch block to handle exceptions from later components.

The proxy logic at the end of the pipeline (IHttpForwarder) does not throw exceptions for common request proxy errors. These are captured and reported in the IForwarderErrorFeature available from HttpContext.Features or the HttpContext.GetForwarderErrorFeature() extension method.

proxyPipeline.Use(async (context, next) =>
{
    await next();

    var errorFeature = context.GetForwarderErrorFeature();
    if (errorFeature is not null)
    {
        Report(errorFeature.Error, errorFeature.Exception);
    }
});

If the response has not started (HttpResponse.HasStarted) it can be cleared (HttpResponse.Clear()) and an alternate response sent, or the proxy feature fields may be reset and the request retried.

What not to do with middleware

Middleware should be cautious about modifying request fields such as headers in order to affect the outgoing proxied request. Such modifications may interfere with features like retries and may be better handled by transforms.

Middleware MUST check HttpResponse.HasStarted before modifying response fields after calling next(). If the response has already started being sent to the client then the middleware can no longer modify it (except maybe Trailers). Transforms can be used to inspect and suppress unwanted responses. Otherwise see the next note.

Middleware should avoid interacting with the request or response bodies. Bodies are not buffered by default, so interacting with them can prevent them from reaching their destinations. While enabling buffering is possible, it's discouraged as it can add significant memory and latency overhead. Using a wrapped, streaming approach is recommended if the body must be examined or modified. See the ResponseCompression middleware for an example.

Middleware MUST NOT do any multi-threaded work on an individual request, HttpContext and its associated members are not thread safe.