Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
YARP
https://github.com/dotnet/yarp
Admin
YARP is a reverse proxy toolkit for building fast, customizable proxy servers in .NET using ASP.NET
...
Tokens:
31,141
Snippets:
261
Trust Score:
8.3
Update:
2 months ago
Context
Skills
Chat
Benchmark
74.7
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# YARP - Yet Another Reverse Proxy YARP (Yet Another Reverse Proxy) is a toolkit for building high-performance, customizable reverse proxy servers in .NET. Built on ASP.NET Core infrastructure, YARP provides a robust framework for routing incoming HTTP requests to backend destination servers. The project was developed by Microsoft to address the common need for reverse proxy functionality across various internal teams, resulting in a highly configurable and extensible solution. The key differentiator for YARP is its design philosophy centered on customization. Unlike traditional reverse proxies that require breaking out to scripts or rebuilding from source, YARP allows extensive customization through code, configuration files, or programmatic configuration management. It supports features including load balancing, session affinity, health checking, request transforms, authentication, rate limiting, and direct HTTP forwarding for advanced scenarios. ## Basic Reverse Proxy Setup with Configuration File Configure YARP using appsettings.json to define routes and clusters. Routes match incoming requests based on paths, hosts, headers, and query parameters, then forward them to clusters containing destination endpoints. ```csharp // Program.cs using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); // Add reverse proxy services and load configuration from appsettings.json builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); var app = builder.Build(); // Register the reverse proxy middleware and routes app.MapReverseProxy(); app.Run(); ``` ```json // appsettings.json { "Urls": "http://localhost:5000;https://localhost:5001", "ReverseProxy": { "Routes": { "api-route": { "ClusterId": "api-cluster", "Match": { "Path": "/api/{**catch-all}" } }, "web-route": { "ClusterId": "web-cluster", "Match": { "Path": "{**catch-all}" } } }, "Clusters": { "api-cluster": { "Destinations": { "api1": { "Address": "https://api.example.com" } } }, "web-cluster": { "Destinations": { "web1": { "Address": "https://contoso.com" }, "web2": { "Address": "https://bing.com" } }, "LoadBalancingPolicy": "PowerOfTwoChoices" } } } } ``` ## Programmatic Configuration with InMemoryConfigProvider Load proxy configuration programmatically using `LoadFromMemory()` for dynamic scenarios where routes and clusters are managed through code or external configuration systems. ```csharp using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Yarp.ReverseProxy.Configuration; var builder = WebApplication.CreateBuilder(args); builder.Services.AddReverseProxy() .LoadFromMemory(GetRoutes(), GetClusters()); var app = builder.Build(); // Endpoint to dynamically update configuration at runtime app.Map("/update-config", context => { var provider = context.RequestServices.GetRequiredService<InMemoryConfigProvider>(); provider.Update(GetRoutes(), GetClusters()); return Task.CompletedTask; }); app.MapReverseProxy(); app.Run(); RouteConfig[] GetRoutes() => [ new RouteConfig { RouteId = "route1", ClusterId = "cluster1", Match = new RouteMatch { Path = "{**catch-all}", Methods = new[] { "GET", "POST" } } } ]; ClusterConfig[] GetClusters() => [ new ClusterConfig { ClusterId = "cluster1", Destinations = new Dictionary<string, DestinationConfig> { { "destination1", new DestinationConfig { Address = "https://example.com" } }, { "destination2", new DestinationConfig { Address = "https://backup.example.com" } } }, LoadBalancingPolicy = "RoundRobin" } ]; ``` ## RouteConfig - Route Definition Define routes that match incoming requests based on path patterns, HTTP methods, hosts, headers, and query parameters. Routes specify which cluster should handle matched requests. ```csharp using Yarp.ReverseProxy.Configuration; var route = new RouteConfig { RouteId = "secure-api-route", ClusterId = "api-cluster", Order = 1, // Lower numbers have higher priority Match = new RouteMatch { Path = "/api/v1/{**remainder}", Methods = new[] { "GET", "POST", "PUT", "DELETE" }, Hosts = new[] { "api.myapp.com", "*.api.myapp.com" }, Headers = new[] { new RouteHeader { Name = "X-Api-Version", Values = new[] { "1.0", "1.1" }, Mode = HeaderMatchMode.ExactHeader } }, QueryParameters = new[] { new RouteQueryParameter { Name = "format", Values = new[] { "json", "xml" }, Mode = QueryParameterMatchMode.Exact } } }, AuthorizationPolicy = "Default", RateLimiterPolicy = "api-rate-limit", CorsPolicy = "AllowSpecificOrigins", Timeout = TimeSpan.FromSeconds(30), MaxRequestBodySize = 10 * 1024 * 1024, // 10 MB Metadata = new Dictionary<string, string> { { "description", "Main API route" } } }; ``` ## ClusterConfig - Backend Server Configuration Configure clusters representing groups of destination servers with load balancing, health checking, session affinity, and HTTP client settings. ```csharp using Yarp.ReverseProxy.Configuration; using Yarp.ReverseProxy.Forwarder; var cluster = new ClusterConfig { ClusterId = "backend-cluster", LoadBalancingPolicy = "PowerOfTwoChoices", // Options: RoundRobin, Random, PowerOfTwoChoices, LeastRequests, First Destinations = new Dictionary<string, DestinationConfig> { { "server1", new DestinationConfig { Address = "https://server1.internal:8080", Health = "https://server1.internal:8080/health", Host = "server1.internal", Metadata = new Dictionary<string, string> { { "datacenter", "east" } } } }, { "server2", new DestinationConfig { Address = "https://server2.internal:8080", Health = "https://server2.internal:8080/health" } } }, SessionAffinity = new SessionAffinityConfig { Enabled = true, Policy = "Cookie", AffinityKeyName = ".Yarp.Affinity", FailurePolicy = "Redistribute", Cookie = new SessionAffinityCookieConfig { Path = "/", HttpOnly = true, SecurePolicy = CookieSecurePolicy.Always, SameSite = SameSiteMode.Strict } }, HealthCheck = new HealthCheckConfig { Active = new ActiveHealthCheckConfig { Enabled = true, Interval = TimeSpan.FromSeconds(10), Timeout = TimeSpan.FromSeconds(5), Policy = "ConsecutiveFailures", Path = "/health" }, Passive = new PassiveHealthCheckConfig { Enabled = true, Policy = "TransportFailureRate", ReactivationPeriod = TimeSpan.FromMinutes(5) } }, HttpClient = new HttpClientConfig { MaxConnectionsPerServer = 100, EnableMultipleHttp2Connections = true, RequestHeaderEncoding = "utf-8" }, HttpRequest = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100), Version = new Version(2, 0), VersionPolicy = HttpVersionPolicy.RequestVersionOrLower } }; ``` ## Custom Proxy Pipeline with Middleware Customize the proxy pipeline by adding middleware steps for filtering destinations, modifying requests, or implementing custom logic. ```csharp using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Yarp.ReverseProxy.Configuration; using Yarp.ReverseProxy.Model; var builder = WebApplication.CreateBuilder(args); builder.Services.AddReverseProxy() .LoadFromMemory(GetRoutes(), GetClusters()); var app = builder.Build(); // Custom proxy pipeline with middleware app.MapReverseProxy(proxyPipeline => { // Custom middleware to filter destinations based on request headers proxyPipeline.Use(async (context, next) => { var feature = context.Features.Get<IReverseProxyFeature>(); var useDebug = context.Request.Headers.TryGetValue("X-Debug", out var debugValue) && debugValue == "true"; // Filter destinations based on metadata feature.AvailableDestinations = feature.AvailableDestinations .Where(d => d.Model.Config.Metadata?.ContainsKey("debug") == useDebug) .ToList(); await next(); }); // Include built-in middleware for session affinity and load balancing proxyPipeline.UseSessionAffinity(); proxyPipeline.UseLoadBalancing(); // Passive health check middleware proxyPipeline.UsePassiveHealthChecks(); }); app.Run(); ``` ## Request and Response Transforms Apply transformations to requests before forwarding and responses before returning to clients. Supports path modifications, header manipulation, and custom transforms. ```csharp using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using System.Net.Http.Headers; using Yarp.ReverseProxy.Transforms; var builder = WebApplication.CreateBuilder(args); builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) // Add inline transforms .AddTransforms(context => { // Add path prefix to all requests context.AddPathPrefix("/api/v2"); // Remove a path prefix context.AddPathRemovePrefix("/legacy"); // Set path explicitly context.AddPathSet("/new-path"); // Add/modify request headers context.AddRequestHeader("X-Forwarded-By", "YARP"); context.AddRequestHeader("X-Request-Id", Guid.NewGuid().ToString(), append: false); // Copy headers from original request context.AddOriginalHost(useOriginal: true); // Remove headers context.AddRequestHeaderRemove("X-Internal-Header"); // Add response headers context.AddResponseHeader("X-Proxy", "YARP", append: false); // Conditional transforms based on route if (context.Route.AuthorizationPolicy == "Bearer") { context.AddRequestTransform(async transformContext => { var token = await GetAccessTokenAsync(transformContext.HttpContext); transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); }); } }); var app = builder.Build(); app.MapReverseProxy(); app.Run(); async Task<string> GetAccessTokenAsync(HttpContext context) { // Implement token retrieval logic return "access-token-value"; } ``` ## Direct Forwarding with IHttpForwarder Use `IHttpForwarder` for low-level control over HTTP forwarding when you need custom routing, destination discovery, or fine-grained request manipulation. ```csharp using System.Diagnostics; using System.Net; using System.Net.Http; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Yarp.ReverseProxy.Forwarder; using Yarp.ReverseProxy.Transforms; var builder = WebApplication.CreateBuilder(args); // Register the forwarder service builder.Services.AddHttpForwarder(); var app = builder.Build(); // Configure custom HttpMessageInvoker var httpClient = new HttpMessageInvoker(new SocketsHttpHandler { UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseCookies = false, EnableMultipleHttp2Connections = true, ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), ConnectTimeout = TimeSpan.FromSeconds(15), PooledConnectionLifetime = TimeSpan.FromMinutes(2) }); var requestConfig = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100), Version = HttpVersion.Version20, VersionPolicy = HttpVersionPolicy.RequestVersionOrLower }; // Direct forwarding endpoint with inline transform app.Map("/forward/{**path}", async (HttpContext context, IHttpForwarder forwarder) => { var error = await forwarder.SendAsync(context, "https://api.example.com", httpClient, requestConfig, static (context, proxyRequest) => { // Modify query string var queryContext = new QueryTransformContext(context.Request); queryContext.Collection.Remove("internal-param"); queryContext.Collection["source"] = "yarp-proxy"; // Build destination URI proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress( "https://api.example.com", context.Request.Path, queryContext.QueryString); // Let destination set its own Host header proxyRequest.Headers.Host = null; return default; }); // Handle forwarding errors if (error != ForwarderError.None) { var errorFeature = context.Features.Get<IForwarderErrorFeature>(); Console.WriteLine($"Forwarding error: {error}, Exception: {errorFeature?.Exception?.Message}"); } }); // Simple forwarding with path rewriting app.MapForwarder("/api/{id}", "https://httpbin.org", "/anything/{id}"); app.MapForwarder("/legacy/{**remainder}", "https://old-api.example.com", b => b.AddPathRemovePrefix("/legacy")); app.Run(); ``` ## Custom HttpTransformer Class Create reusable custom transformers by extending the `HttpTransformer` base class for complex request/response modifications. ```csharp using System.Net.Http; using Microsoft.AspNetCore.Http; using Yarp.ReverseProxy.Forwarder; using Yarp.ReverseProxy.Transforms; public sealed class CustomTransformer : HttpTransformer { private readonly string _apiKey; public CustomTransformer(string apiKey) { _apiKey = apiKey; } public override async ValueTask TransformRequestAsync( HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) { // Call base to copy headers (excluding protocol-specific headers) await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); // Add API key header proxyRequest.Headers.Add("X-Api-Key", _apiKey); // Modify query string var queryContext = new QueryTransformContext(httpContext.Request); queryContext.Collection["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); // Build custom destination URI proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress( destinationPrefix, httpContext.Request.Path, queryContext.QueryString); // Override Host header proxyRequest.Headers.Host = new Uri(destinationPrefix).Host; } public override async ValueTask<bool> TransformResponseAsync( HttpContext httpContext, HttpResponseMessage? proxyResponse, CancellationToken cancellationToken) { // Call base to copy response headers and status code var result = await base.TransformResponseAsync(httpContext, proxyResponse, cancellationToken); // Add custom response headers httpContext.Response.Headers["X-Processed-By"] = "CustomTransformer"; httpContext.Response.Headers["X-Response-Time"] = DateTime.UtcNow.ToString("O"); return result; } } // Usage in Program.cs var transformer = new CustomTransformer("my-api-key"); app.MapForwarder("/{**catch-all}", "https://api.example.com", requestConfig, transformer, httpClient); ``` ## Authentication and Authorization Integration Integrate YARP with ASP.NET Core authentication and authorization to protect routes and transform requests with authentication tokens. ```csharp using System.Net.Http.Headers; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Yarp.ReverseProxy.Transforms; var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(); builder.Services.AddAuthorization(options => { // Custom policy requiring specific claims options.AddPolicy("AdminOnly", policy => policy .RequireClaim("role", "admin") .RequireAuthenticatedUser()); options.AddPolicy("ApiAccess", policy => policy .RequireClaim("scope", "api.read", "api.write") .RequireAuthenticatedUser()); // Default fallback for routes without explicit policy options.FallbackPolicy = null; // Allow anonymous by default }); builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) .AddTransforms(context => { // Add auth transforms only for protected routes if (!string.IsNullOrEmpty(context.Route.AuthorizationPolicy)) { context.AddRequestTransform(async transformContext => { var authResult = await transformContext.HttpContext.AuthenticateAsync(); if (!authResult.Succeeded) { transformContext.HttpContext.Response.StatusCode = 401; return; } // Get token from authentication ticket or token service var userId = authResult.Principal?.FindFirst("sub")?.Value; var accessToken = await GetBackendTokenAsync(userId); if (string.IsNullOrEmpty(accessToken)) { transformContext.HttpContext.Response.StatusCode = 403; return; } transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); }); } }); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapReverseProxy(); app.Run(); async Task<string?> GetBackendTokenAsync(string? userId) { // Implement token exchange or retrieval logic return userId != null ? $"backend-token-for-{userId}" : null; } ``` ## Health Check Configuration Configure active and passive health checks to monitor destination health and automatically route traffic away from unhealthy servers. ```json // appsettings.json - Health Check Configuration { "ReverseProxy": { "Clusters": { "backend-cluster": { "Destinations": { "server1": { "Address": "https://server1.example.com", "Health": "https://server1.example.com/health" }, "server2": { "Address": "https://server2.example.com", "Health": "https://server2.example.com/health" } }, "HealthCheck": { "Active": { "Enabled": true, "Interval": "00:00:10", "Timeout": "00:00:05", "Policy": "ConsecutiveFailures", "Path": "/health" }, "Passive": { "Enabled": true, "Policy": "TransportFailureRate", "ReactivationPeriod": "00:05:00" }, "AvailableDestinationsPolicy": "HealthyAndUnknown" } } } } } ``` ```csharp // Programmatic health check configuration var cluster = new ClusterConfig { ClusterId = "monitored-cluster", Destinations = new Dictionary<string, DestinationConfig> { { "primary", new DestinationConfig { Address = "https://primary.example.com", Health = "https://primary.example.com/api/health" }}, { "secondary", new DestinationConfig { Address = "https://secondary.example.com", Health = "https://secondary.example.com/api/health" }} }, HealthCheck = new HealthCheckConfig { Active = new ActiveHealthCheckConfig { Enabled = true, Interval = TimeSpan.FromSeconds(15), Timeout = TimeSpan.FromSeconds(10), Policy = "ConsecutiveFailures", Path = "/api/health", Query = "?detailed=false" }, Passive = new PassiveHealthCheckConfig { Enabled = true, Policy = "TransportFailureRate", ReactivationPeriod = TimeSpan.FromMinutes(2) }, AvailableDestinationsPolicy = "HealthyAndUnknown" } }; ``` ## Telemetry and Metrics Collection Collect detailed telemetry and metrics about proxy operations using YARP's built-in telemetry consumers for monitoring and debugging. ```csharp using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Yarp.Telemetry.Consumption; var builder = WebApplication.CreateBuilder(args); builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); // Register telemetry consumers builder.Services.AddTelemetryConsumer<ProxyTelemetryConsumer>(); builder.Services.AddTelemetryConsumer<HttpClientTelemetryConsumer>(); builder.Services.AddMetricsConsumer<ProxyMetricsConsumer>(); var app = builder.Build(); app.UseWebSocketsTelemetry(); // For WebSocket telemetry app.MapReverseProxy(); app.Run(); // Forwarder telemetry consumer public class ProxyTelemetryConsumer : IForwarderTelemetryConsumer { public void OnForwarderStart(DateTime timestamp, string destinationPrefix) { Console.WriteLine($"[{timestamp:O}] Forwarding to: {destinationPrefix}"); } public void OnForwarderStop(DateTime timestamp, int statusCode) { Console.WriteLine($"[{timestamp:O}] Response status: {statusCode}"); } public void OnForwarderFailed(DateTime timestamp, ForwarderError error) { Console.WriteLine($"[{timestamp:O}] Forwarding failed: {error}"); } public void OnContentTransferring(DateTime timestamp, bool isRequest, long contentLength, long iops, TimeSpan readTime, TimeSpan writeTime) { var direction = isRequest ? "Request" : "Response"; Console.WriteLine($"[{timestamp:O}] {direction} transfer: {contentLength} bytes, {iops} IOPS"); } public void OnContentTransferred(DateTime timestamp, bool isRequest, long contentLength, long iops, TimeSpan readTime, TimeSpan writeTime, TimeSpan firstReadTime) { var direction = isRequest ? "Request" : "Response"; Console.WriteLine($"[{timestamp:O}] {direction} completed: {contentLength} bytes"); } public void OnForwarderStage(DateTime timestamp, ForwarderStage stage) { } public void OnForwarderInvoke(DateTime timestamp, string clusterId, string routeId, string destinationId) { } } // HTTP client telemetry consumer public class HttpClientTelemetryConsumer : IHttpTelemetryConsumer { public void OnRequestStart(DateTime timestamp, string scheme, string host, int port, string pathAndQuery, int versionMajor, int versionMinor, HttpVersionPolicy versionPolicy) { Console.WriteLine($"HTTP request starting: {scheme}://{host}:{port}{pathAndQuery}"); } public void OnRequestStop(DateTime timestamp) { } public void OnRequestFailed(DateTime timestamp) { } public void OnConnectionEstablished(DateTime timestamp, int versionMajor, int versionMinor) { } public void OnRequestLeftQueue(DateTime timestamp, TimeSpan timeOnQueue, int versionMajor, int versionMinor) { } public void OnRequestHeadersStart(DateTime timestamp) { } public void OnRequestHeadersStop(DateTime timestamp) { } public void OnRequestContentStart(DateTime timestamp) { } public void OnRequestContentStop(DateTime timestamp, long contentLength) { } public void OnResponseHeadersStart(DateTime timestamp) { } public void OnResponseHeadersStop(DateTime timestamp) { } public void OnResponseContentStart(DateTime timestamp) { } public void OnResponseContentStop(DateTime timestamp) { } } // Metrics consumer for aggregate statistics public class ProxyMetricsConsumer : IForwarderMetricsConsumer { public void OnForwarderMetrics(ForwarderMetrics oldMetrics, ForwarderMetrics newMetrics) { var requestsDelta = newMetrics.RequestsStarted - oldMetrics.RequestsStarted; var failuresDelta = newMetrics.RequestsFailed - oldMetrics.RequestsFailed; Console.WriteLine($"Requests: {requestsDelta}, Failures: {failuresDelta}"); } } ``` ## Summary YARP is designed for scenarios requiring a programmable, high-performance reverse proxy in .NET environments. Common use cases include API gateways that aggregate multiple backend services, microservices architectures requiring intelligent routing and load balancing, edge proxies handling authentication and request transformation, and migration scenarios where traffic needs to be gradually shifted between systems. The framework excels in environments where the proxy behavior needs to be tightly integrated with application logic or external configuration systems. Integration patterns typically involve combining YARP with ASP.NET Core's dependency injection, authentication, and middleware pipeline. For simple deployments, configuration-based setup with appsettings.json provides quick results. For dynamic environments, the `InMemoryConfigProvider` enables runtime configuration updates without restarts. Advanced scenarios can leverage `IHttpForwarder` for complete control over forwarding logic, or implement custom `HttpTransformer` classes for complex request/response modifications. The telemetry infrastructure enables comprehensive monitoring and debugging in production environments.