YARP Yet Another Reverse Proxy

YARP Yet Another Reverse Proxy

What is YARP?

YARP is a .NET library that implements HTTP proxy functionality and can be hosted in any .NET application, including ASP.NET services, Windows Services, Console Apps, and more. Compared to other proxies like HAProxy or nginx, which support both the Application layer (HTTP) and Transport layer (TCP), YARP supports only the Application layer, specifically HTTP/1.1 and HTTP/2, including gRPC. Therefore, it is mostly suitable for use with web applications and services.

So why not use YARP instead of HAProxy or nginx?

Since YARP is a .NET package, it allows you to use C# to configure the proxy, which is an extremely powerful tool for customization. With HAProxy or nginx, you would need to learn their configuration DSL (Domain Specific Language) or use Lua scripts to implement customization. With YARP, you can use C# to write code that allows you not only to configure but also to mutate traffic, such as request headers, query parameters and etc.

If you still prefer configuration files, YARP supports .NET Configuration Providers, which is a powerful configuration abstraction. You can use one of the built-in JSON providers to fully configure YARP with a simple JSON file. You can also create your own custom configuration providers to load configuration from a database or ZooKeeper, or use any other open-source configuration provider.

YARP supports multiple scenarios, including routing, load balancing, rate limiting, HTTP header or query parameters routing, session affinity, and many more. You can read the documentation and examine examples for each scenario.

In this post, we will cover scenarios that are not well-documented and are not trivial to implement.

Custom scenarios where YARP shines

Create YARP project from scratch

To start with YARP, just follow simple step by step guide. However of you are looking for a quick TL;DR; see below.

Given you have .NET 8 installed, run below to create new .NET web application.

dotnet new web -n YarpDemo -f net8.0

Add YARP NuGet package. Check if newer version is available.

dotnet add package Yarp.ReverseProxy --version 2.1.0

Now ensure the project builds.

dotnet build

Now let’s add YARP code to Program.cs

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

var app = builder.Build();

app.MapGet("/", () => "YARP is running!");
app.MapReverseProxy();

app.Run();

One more step is left, let’s add configuration. Open appsettings.json and add “ReverseProxy” section. Below is the simplest configuration. The config file is self-explanatory, additionally there are few inline comments to provide more context.

{
  "Urls": "http://localhost:7777", // Listen on localhost, port 7777
  "AllowedHosts": "*",
  "ReverseProxy": {
    "Routes": {
      "a-route": { // Identical to frontend in HAProxy
        "ClusterId": "a-cluster", // Points to a cluster.
        "Match": {
          // Catches all routes. Can specify comprehensive filters.
          "Path": "{**catch-all}"
        }
      }
    },
    "Clusters": {
      "a-cluster": { // Identical to backend in HAProxy
        "Destinations": {
          "a-destination": {
            "Address": "https://destination.com/"
          }
        }
      }
    }
  }
}

Mutate requests before proxying.

In order to mutate requests, we need to add transformations. You may add, remove and update headers as well a mutate query parameters. See full documentation for details. 

builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
    .AddTransforms(builderCtx =>
    {
        // Add new header
        builderCtx.AddRequestHeader("new-header", "new-header-value");
        builderCtx.AddRequestTransform(transformCtx =>
        {
            if (transformCtx.ProxyRequest?.Content != null)
            {
                // Update header-to-update header
                transformCtx.ProxyRequest.Content.Headers.Remove("header-to-update");
                transformCtx.ProxyRequest.Content.Headers.Add("header-to-update", "value");
            }
            return ValueTask.CompletedTask;
        });
    });

Proxy over proxy?..

YARP - Proxy Over Proxy
YARP – Proxy Over Proxy

You can configure YARP to forward requests through an outbound proxy, which is a unique feature of YARP. You may need this when your outbound traffic needs to cross a network boundary, for example, from an internal network to the internet, and such communication is only allowed through a proxy.

YARP allows you to configure the HTTP client, and thus, configure a proxy that the client will use to forward traffic through. The HTTP client can be configured per Cluster (backend in HAProxy).

  "ReverseProxy": {
    "Routes": {
      ...
    },
    "Clusters": {
      "a-cluster": { // Identical to backend in HAProxy
        "Destinations": {
          ...
        },
        "HttpClient": {
          "WebProxy": {
            "Address": "http://myproxy.com:9876"
          }
        }
      }
    }
  }
}

Host however you prefer.

On Linux, you may run YARP as a daemon, for example, using systemd, a Docker container, Kubernetes, or any other hosting approach of your choice. Not much extra code is needed to accomplish this.

On Windows, you may host YARP on IIS (Internet Information Services, a robust web server) or as a Windows Service. Hosting on IIS is straightforward; however, hosting as Windows Service may require a few changes and there are some gotchas to look out for.

Steps to configure YARP to be hosted as Windows Service.

Add the Microsoft.Extensions.Hosting.WindowsServices NuGet packager

dotnet add package Microsoft.Extensions.Hosting.WindowsServices --version 8.0.

To easily detect in the code whether YARP is running as a Windows Service, you can add an environment variable “RUNNING_AS=WindowsService” to the Windows Service registry and read it in the Program.cs. This is an unambiguous way to determine whether you need to add any extra code to the program to support Windows Service hosting.

var builder = WebApplication.CreateBuilder(args);

bool isRunningAsWindowsService =
    Environment.GetEnvironmentVariable("RUNNING_AS") == "WindowsService";

Next step is to repoint configuration to the path where the application is going to be located.

if (isRunningAsWindowsService)
{
    // Point configuration and content root to a folder
    // where application binaries and files are located.
    var rootFolder = AppContext.BaseDirectory;
    var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ??
                      Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ??
                      string.Empty;
    var configurationBuilder = new ConfigurationBuilder()
        .SetBasePath(rootFolder)
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
        .AddEnvironmentVariables()
        .AddCommandLine(args);

    builder.WebHost
        .UseConfiguration(configurationBuilder.Build())
        .UseContentRoot(rootFolder);
}

Next step is to add Windows Service before building the builder.

if (isRunningAsWindowsService)
{
    // Add Windows Service
    builder.Services.AddWindowsService();
    builder.Services.AddHostedService<SimpleWindowsService>();
}

var app = builder.Build();

Now it’s time to create Windows Service and run YARP. Below is a simplified version of the PowerShell script.

New-Service -Name YarpDemo `
    -BinaryPathName "C:\Yarp\YarpDemo.dll --contentRoot C:\Yarp\" `
    -Credential "Localsystem" `
    -Description "YARP running as Windows Service" `
    -DisplayName "YarpDemo" `
    -StartupType Automatic

Add the “RUNNING_AS=WindowsService” environment variable to the newly created Windows Service registry to let the program know it’s running as a Windows Service. This is how you can do that.

Publish the application by running

dotnet publish

The output can be found in bin/Release/net8.0/publish/, and the entire folder is your application package.

.NET supports multiple deployment models, and you can tweak whether your application is published as a single file, cross-platform binary, framework-dependent, or completely self-contained.

Now you can start the YARP Application as Windows Service with PowerShell or by using Services window.

Start-Service -Name YarpDemo

Conclusion 

There are many more things that YARP can do. Please share your use cases and how YARP helps to resolve them.
Examples code can be found here Pavel Hudau\BlogCodeExamples\YarpDemo.

Posts created 29

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top