Creating a Reverse Proxy in Dotnet Core

Published: Sat Apr 25 2020

After hearing about Project YARP, an initiative from Microsoft to create a super fast .Net reverse proxy, I decided to give it a try in one of my private projects. This is a write up of what I learned.

YARP

YARP is an ongoing project at Mircrosoft where the goal is to create a super fast .Net based reverse proxy. I am intrigued by this idea so I decided to give it a try in one of my personal projects.

The project is still in its early stages, but you can check out their Github repo if you are interested in playing with it. I suspect some this will be subject to change, so watch the Microsoft repo for updates.

Reverse Proxy

Reverse proxy sounds like a complicated concept, but in principle it refers to an entry point to your network that knows how to route traffic to internal servers. In my experience there are three main reasons for using a reverse proxy:

Load Balancing

A reverse proxy can act as a load balancer and distribute load across multiple instances of the same service. This leads to a nice performance benefit since it allows you to scale your application seamlessly by adding more backend servers as incoming traffic increases.

Request Interception

Since the original request travels through the reverse proxy you have an opportunity to intercept the request before passing it to your backend service. In my demo application I will show how to add a custom header to the original request.

Cross Domain Requests

A Reverse proxy is also a common approach for avoiding cross domain requests in single page (web) applications. SPAs typically make xhr requests directly from the browser to some backend api, but this presents a challenge if the api is hosted on a different domain than the domain used to serve the SPA. The reason for this is security concerns from allowing uncontrolled cross domain requests from web browsers.

A reverse proxy can solve this problem since it offers a single entry point for both serving the SPA and making xhr requests. Behind the scenes the api traffic may be routed to different servers, but to the browser it appears as a single domain, so no more cross domain requests.

An alternative to a reverse proxy in this scenario is CORS, a protocol for allowing browsers to make controlled cross domain requests if it’s supported by the api. Both approaches are valid, but a reverse proxy may often be simpler since CORS must be configured for every api.

Demo

The application I am using for this experiment is a simple personal project that I created a while back. However, this project is not important here since you can expose any project through the reverse proxy.

You can read more about my sample project in a previous article, but in summary it breaks down to an Angular application with a .Net core backend.

Un unrelated, but perhaps still interesting note, is that the demo project is built using Bazel. This allows me to build both the Angular code and the .Net code in the same build process.

Configuring the Reverse Proxy

In the following section I will show the highlights of the configuration needed to stand up a reverse proxy in front of my demo application.

As a starting point I used one of the sample projects in the YARP Github project. This gives you a working sample that you can tweak to meet your specific requirements.

For people familiar with .Net core the setup of the .Net reverse proxy will likely seem familiar.

Let’s start by taking a look at Startup.cs

using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.ReverseProxy.Core.Configuration.DependencyInjection; namespace Microsoft.ReverseProxy.Sample { public class Startup { private readonly IConfiguration _configuration; public Startup (IConfiguration configuration) { _configuration = configuration; } public void ConfigureServices (IServiceCollection services) { services.AddControllers (); services.AddReverseProxy ().LoadFromConfig (_configuration.GetSection ("ReverseProxy"), reloadOnChange : true); } public void Configure (IApplicationBuilder app) { app.UseHttpsRedirection (); app.UseRouting (); app.UseAuthorization (); app.UseEndpoints (endpoints => { endpoints.MapControllers (); endpoints.MapReverseProxy (proxyPipeline => { proxyPipeline.UseProxyLoadBalancing (); proxyPipeline.Use ((context, next) => { var connection = context.Connection; context.Request.Headers.AppendCommaSeparatedValues ("Best-Friend", "Buddy"); return next (); }); }); }); } } }

As you can see above the setup follows the typical middleware configurations you may be used to from most .Net core applications.

I mentioned request interception as one of the reasons for using a reverse proxy. Take a look at the proxyPipeline above since I am using that to add a custom header called “Best-Friend” to the original request.

AppSettings

The startup code loads the actual reverse proxy mappings from a config section in appsettings.json. I have included the relevant section below:

"ReverseProxy": { "Backends": { "friends-api": { "LoadBalancing": { "Mode": "PowerOfTwoChoices" }, "Endpoints": { "host1": { "Address": "http://localhost:6050" }, "host2": { "Address": "http://localhost:7050" } } }, "ui": { "Endpoints": { "host": { "Address": "http://localhost:5050/" } } } }, "Routes": [ { "RouteId": "api/route1", "BackendId": "friends-api", "Match": { "Methods": [ "GET" ], "Host": "localhost", "Path": "/api/friends" } }, { "RouteId": "ui/route1", "BackendId": "ui", "Match": { "Methods": [ "GET" ], "Host": "localhost", "Path": "/{**catchall}" } } ] }

In my example I have configured two endpoints. The first one called ui is for serving the Angular application with routing. Notice the catchall wild card to accommodate the client side routes.

The second route configuration is for the api. This maps up the api routes, but it also allows me to specify load balancing across two different server instances of the same services.

Now if I run my application through a browser I am able to serve the UI through the reverse proxy as well as making api requests back to it from the Angular application.

Source Code

You can download my source code from the links below:

Demo Project

Modified Proxy Code

Follow me on twitter @MoreTechStories