top of page
Search

YARP: Yet Another Reverse Proxy

Writer's picture: TechTutorTechTutor

Updated: Dec 26, 2024

ASP.Net + YARP
ASP.Net + YARP

YARP (Yet Another Reverse Proxy) is a high-performance reverse proxy and load balancer built in .NET. It allows developers to build and configure reverse proxy capabilities directly within .NET applications. YARP provides advanced routing, load balancing, and request forwarding features, and it's highly customizable, leveraging the full power of the .NET ecosystem for scalability and performance.


Key Features of YARP:

  • Built for .NET: Fully integrated with the .NET ecosystem, allowing for easy customization and extension using C# and other .NET libraries.

  • Advanced Routing: YARP allows you to implement custom routing logic to forward requests to different backends based on conditions like URL paths, query strings, headers, etc.

  • Load Balancing: Supports various load balancing algorithms, such as round-robin, random, and others.

  • High Performance: Optimized for performance with asynchronous I/O operations, making it suitable for modern cloud-native applications.


Nginx, on the other hand, is a widely-used open-source web server that also functions as a reverse proxy, load balancer, and HTTP cache. It is a powerful and flexible tool used to handle incoming HTTP/S traffic and route it to appropriate backend servers.


Differences Between YARP and Nginx:

Technology Stack:

  • YARP: Built specifically for .NET applications and integrates seamlessly with .NET Core and ASP.NET Core.

  • Nginx: A general-purpose reverse proxy that can be used with any technology stack (not limited to .NET).

Customization:

  • YARP: Highly customizable within the .NET ecosystem. Developers can write custom logic for routing, logging, and even extend how YARP handles requests.

  • Nginx: Configuration is done via text-based configuration files, which offer a lot of flexibility but with less direct integration for custom application logic compared to YARP.

Platform Dependency:

  • YARP: Runs within a .NET application, so it's ideal for scenarios where your application is built on the .NET stack, especially if you're using microservices in a .NET environment.

  • Nginx: A standalone application that can run independently of any specific technology stack, and is commonly used for serving static files, proxying, and load balancing across various technologies.

Deployment:

  • YARP: Requires a .NET runtime to run, meaning it's embedded within a .NET application.

  • Nginx: Runs as a separate service, typically outside of your application, and can be configured to serve multiple applications from different technology stacks.

Ecosystem Support:

  • YARP: Ideal for .NET-based microservices or services that need to integrate with the wider .NET ecosystem (e.g., .NET security, logging, etc.).

  • Nginx: Broadly used in many tech stacks, from simple web hosting to large-scale enterprise systems, and has extensive community support and documentation.


Lets create a sample application.

Open Visual Studio IDE and create a new Asp.net Core Web API.


Program.cs

using Microsoft.AspNetCore.Hosting;
using OrderApplication;
public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
        host.Run();
    }
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Startup.cs

using Microsoft.OpenApi.Models;

namespace OrderApplication
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            // Add Swagger services
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Title = "Order API",
                    Version = "v1",
                    Description = "A simple example REST API for managing orders",
                });
            });
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            // Enable Swagger middleware
            app.UseSwagger();
            // Enable Swagger UI
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "Order API V1");
                c.RoutePrefix = string.Empty; // Serve Swagger UI at the app's root
            });
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

OrderController.cs

using log4net;
using log4net.Config;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Reflection;
using OrderApplication.model;

namespace OrderApplication.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class OrderController : ControllerBase
    {
        private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        static OrderController()
        {
            var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
            XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config"));
        }
        private static readonly List<Order> OrderPlaced = new List<Order>();
        [HttpPost("PlaceOrder")]
        public IActionResult PlaceOrder([FromBody] Order order)
        {
            if (order == null || string.IsNullOrWhiteSpace(order.ProductName) || order.Quantity <= 0)
            {
                Logger.Error("Invalid order received.");
                return BadRequest("Invalid order details.");
            }
            OrderPlaced.Add(order);
            Logger.Info($"Order placed: {order.ProductName}, Quantity: {order.Quantity}");
            return Ok("Order placed successfully.");
        }
        [HttpGet("OrderLists")]
        public IActionResult OrderLists()
        {
            Logger.Info("Fetching all placed orders.");
            return Ok(OrderPlaced);
        }
    }
}

model\Order.cs

namespace OrderApplication.model
{
    public class Order
    {
        public string ProductName { get; set; }
        public int Quantity { get; set; }
    }
}

appSettings.json

{
  "Kestrel": {
    "Endpoints": {
      "Http": {
      }
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Run the Application


Set up the application on your local web server (Docker or IIS) and ensure at least two instances of the application are running. Update the running ports to 5001 and 5002.


Create YARP Reverse Proxy :

Lets create a new project (WebAPI) in Visual studio IDE.


program.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace YarpOrderReverseProxyApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace YarpOrderReverseProxyApplication
{
    public class Startup
    {
        private readonly IConfiguration _configuration;
        public Startup(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddReverseProxy()
                .LoadFromConfig(_configuration.GetSection("ReverseProxy"));
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapReverseProxy(); // Enable the reverse proxy
            });
        }
    }
}

appSettings.json

{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://localhost:5000"
      }
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ReverseProxy": {
    "Clusters": {
      "orderCluster": {
        "Destinations": {
          "orderApp1": {
            "Address": "http://localhost:5001"
          },
          "orderApp2": {
            "Address": "http://localhost:5002"
          }
        }
      }
    },
    "Routes": {
      "orderRoute": {
        "ClusterId": "orderCluster",
        "Match": {
          "Path": "{**catch-all}"
        }
      }
    }
  }
}

ReverseProxy

This section is related to configuring the reverse proxy functionality, most likely powered by YARP.

Clusters

  • Clusters: Defines clusters of destination servers.

    • orderCluster: Represents a logical grouping of two destination servers (for load balancing or failover).

Routes

  • Routes: Defines how incoming requests are routed to clusters.

    • orderRoute: A route configuration.

      • ClusterId: Specifies the cluster (orderCluster) to forward the requests to.

      • Match: Describes how incoming requests are matched.

        • Path: {**catch-all} is a wildcard pattern that matches all paths.


Purpose

This configuration achieves the following:

  1. The application itself runs on port 5000.

  2. A reverse proxy is set up:

    • Incoming requests are forwarded to one of two backend applications (orderApp1 or orderApp2) running on ports 5001 and 5002.

    • The cluster (orderCluster) is responsible for managing these two applications.

  3. The routing rule (orderRoute) forwards all incoming requests ({**catch-all}) to the orderCluster, distributing the load or providing redundancy.


Lets run the application

When you run the application http://localhost:5000., It will redirect to any one of the configured reverse proxy (example either it will connect to orderApp1 (http://localhost:5001.) OR orderApp2:( http://localhost:5002)


Invoke the PlaceOrder() method to place 10 orders while simultaneously triggering the OrderLists() method. You will notice a discrepancy in the orders placed, as some orders are processed by OrderApp2 and others by OrderApp1. For further insights, check the logs to identify which request was handled by each order application.


LoadBalancingPolicy

Whenever there are multiple healthy destinations available, YARP has to decide which one to use for a given request. YARP ships with built-in load-balancing algorithms, but also offers extensibility for any custom load balancing approach.

YARP ships with the following built-in policies:

  • FirstAlphabetical : Select the alphabetically first available destination without considering load. This is useful for dual destination fail-over systems.

  • Random : Select a destination randomly.

  • PowerOfTwoChoices (default) : Select two random destinations and then select the one with the least assigned requests. This avoids the overhead of LeastRequests and the worst case for Random where it selects a busy destination.

  • RoundRobin : Select a destination by cycling through them in order.

  • LeastRequests : Select the destination with the least assigned requests. This requires examining all destinations.


If you don't specify a LoadBalancingPolicy, YARP defaults to PowerOfTwoChoices, which provides a good balance between simplicity and load distribution.


"Clusters": { 
	"order_cluster": 
	{ 
		"LoadBalancingPolicy": "RoundRobin", 
		"Destinations": { 
			"order1": { 
				"Address": "http://localhost:5001" 
			}, 
			"order2": { 
				"Address": "http://localhost:5002" 
			} 
		} 
	}
}


High Availability (HA) in a Reverse Proxy

High Availability (HA) in a Reverse Proxy ensures that traffic can still be routed effectively even if the reverse proxy itself fails or becomes unavailable. Here are strategies to handle HA for a reverse proxy like YARP

Deploy Multiple Reverse Proxies

  • Setup: Deploy multiple instances of the reverse proxy in your environment (e.g., across different servers or zones).

  • Load Balancer: Use a higher-level load balancer (e.g., Azure Load Balancer, AWS ELB, or a DNS-based solution like AWS Route 53 or Azure Traffic Manager) to distribute traffic across these proxy instances.

  • Failover: If one reverse proxy goes down, the load balancer automatically reroutes traffic to the remaining instances.


Example:

  • Deploy YARP on multiple servers (proxy1, proxy2, etc.).

  • Use a load balancer with health checks to monitor YARP instances and reroute traffic when one is unavailable


Enable Active-Passive Failover

  • Set up an active instance of your reverse proxy and a standby (passive) instance.

  • Use a health monitoring system to switch traffic to the standby instance if the active instance fails.

Implementation:

  • Use tools like Keepalived, Heartbeat, or Pacemaker for automatic failover.

  • Standby proxy becomes active when the primary fails.




Summary :

YARP (Yet Another Reverse Proxy) is a flexible and customizable reverse proxy library built on ASP.NET Core. It serves as a powerful tool for routing HTTP requests to backend services, making it particularly valuable in microservices architectures and distributed systems. By leveraging the middleware pipeline of ASP.NET Core, YARP allows developers to handle complex routing, traffic management, and service aggregation tasks while maintaining high performance and reliability. Its seamless integration with .NET applications provides developers with a robust framework to build scalable solutions tailored to their specific needs.


One of YARP's key benefits is its customizability, allowing developers to modify routing and request-handling logic through .NET code. It supports various load-balancing strategies, such as Round Robin and Least Requests, to optimize traffic distribution across services. YARP also includes support for HTTP/2, WebSockets, and connection affinity, making it suitable for handling modern web application requirements. With its extensibility and built-in features, YARP simplifies the development of reverse proxy functionality while ensuring low latency and high throughput. For .NET developers, YARP offers a familiar environment to create secure, efficient, and maintainable proxy solutions without extensive overhead.


Reference :

22 views0 comments

Recent Posts

See All

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating

TechTutorTips.com


SUBSCRIBE 


Thanks for submitting!

© 2035 by FEEDs & GRIDs. Powered and secured by Wix

bottom of page