laitimes

Build an API gateway in C# with YARP and .Net

author:opendotnet
Build an API gateway in C# with YARP and .Net

Chapter 1 Getting Started

When we use many applications in the platform, we have many configurations that may need to communicate with each other. In the community, we have a few patterns to address this situation, and one of them is the API Gateway pattern. This solution sits between the client and the application, and it is responsible for routing requests from the requesting application, authenticating and authorizing the request, and many other functions.

Now we have a lot of API gateway solutions, such as Kong, KrakenD, Tyk, and Apache APISIX, which can be open source or licensed, each with its own characteristics. In this article, we chose YARP for a number of reasons that are familiar with the code language developed and that it is an open-source library maintained by Microsoft.

We intend to maintain this like a series of articles that describe the process of implementing an api gateway using .Net and YARP, with some important features like firewalls, rate limiting, feature flags, authentication, etc.

To start this project, we need to install dotnet 8 and docker.

To start our development, the first step we want to do is to create a simple application to test the suitability of our routing and our features.

dotnet new web -o RoutingTestAPI
           

Now, in the generated program file, we'll add some environment variables to help us visualize some of the scenarios when testing:

var defaultPath = Environment.GetEnvironmentVariable("ROOT_PATH"); 
var destination = Environment.GetEnvironmentVariable("DESTINATION"); 
var app = Environment.GetEnvironmentVariable("APP_NAME");
           

To complete our code, we'll add an endpoint to return what we can see when routing the request:

app.MapGet($"{defaultPath}/simulate", () => $"Successfull request for destination {destination} from {app} app\n");
           

At this point, we've completed the minimal routing API that allows us to test our API gateway. Now, to make it more convenient, we'll create a docker compose file using this routing API, and we'll be able to replicate and manipulate the deployment as needed, but first we need to publish this API:

dotnet publish --os linux --arch arm64 -p:PublishProfile=DefaultContainer
           

If you're running this code in Windows or Linux, remove the arch directive. This command will publish the docker image in the docker daemon

After that, we can create a docker compose file with the following specifications:

services:
 fooapp:
 image: simple-routing-app:latest
 restart: always
 environment:
 - ROOT_PATH=/api/foo
 - APP_NAME=foo
 - DESTINATION=destination1
 ports:
 - 8080:80
           

Now that we can start building our API gateway, first we need to create another dotnet API and add the YARP library to this API:

dotnet new web -o Gateway 
dotnet add package Yarp.ReverseProxy
           

To configure a reverse proxy in our project, we need to add the following services to the configuration:

public void ConfigureServices(IServiceCollection services) 
{ 
 services.AddReverseProxy(); 
} 
public void Configure(IApplicationBuilder app) 
{ 
 app.UseRouting(); 
 app.UseEndpoints(); 
}
           

Now we need to add some routing configurations, for which we have a lot of options:

  1. Add a configuration called ReverseProxy to our appsettings.json
  2. 创建 InMemoryConfigProvider
  3. 创建自定义 IProxyConfigProvider

We will choose the third way, because it will be the hook for the next chapter and how we want to get closest to the real situation, which is the best way for me.

To do this, we need to create a new class called "CustomProxyConfigProvider", which requires an extension interface called IProxyConfigProvider, which implements the GetConfig() method and a "CustomMemoryConfig" class that extends IProxyConfig.

public class CustomProxyConfigProvider: IProxyConfigProvider
{
private CustomMemoryConfig _config;

public IProxyConfig GetConfig() => _config;
}

public class CustomMemoryConfig: IProxyConfig
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();

public CustomMemoryConfig(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
 {
 Routes = routes;
 Clusters = clusters;
 ChangeToken = new CancellationChangeToken(_cts.Token);
 }

public IReadOnlyList<RouteConfig> Routes { get; }
public IReadOnlyList<ClusterConfig> Clusters { get; }
public IChangeToken ChangeToken { get; }

internal void SignalChange()
 {
 _cts.Cancel();
 }
}
           

In order to route requests to our application, we need to provide some configuration to our API Gateway, YARP uses a standard configuration consisting of routes and clusters, and we'll create a method in the CustomProxyConfigProvider to create this configuration:

public void LoadConfig() 
{
var routeConfig = new RouteConfig 
 { 
 RouteId = "route1",
 ClusterId = "cluster1", 
 Match = new RouteMatch
 {
 Path = "/api/foo/{**catch-all}"
 }
 };
var clusterConfig = new ClusterConfig
 {
 ClusterId = "cluster1",
 LoadBalancingPolicy = LoadBalancingPolicy.RoundRobin,
 Destinations = new Dictionary<string, DestinationConfig>()
 {
"destination1", new DestinationConfig
 {
 Address = "http://localhost:8080"
 }
 }
 }

var routes = new List<RouteConfig>();
 routes.add(routeConfig);
var clusters = new List<ClusterConfig>();
 clusters.add(clusterConfig);

 _config = new CustomMemoryConfig(routes, clusters);
}
           

We need to add this provider to the configuration of the Startup class:

public void ConfigureServices(IServiceCollection services) 
{ 
 services 
 .AddSingleton<IProxyConfigProvider>(new CustomProxyConfigProvider()) 
 .AddReverseProxy(); 
}
           

With this configuration, we can run the application and test the routing of the gateway, so first, we'll run the docker compose created at the beginning of this article:

docker compose up -d
           

And run our Gateway project:

dotnet run .
           

Now that we've run the application and the configured API gateway, we can call to test the route, and when I run the gateway, select port 5025 that I want to expose:

curl http://localhost:5025/api/foo/simulate
           

This will respond with the following message:

Successfull request for destination destination1 from foo app
           

At the end of this chapter, we created a simple application with a simple configuration of our gateway and looked at the requests routed through our API gateway. In the next chapter, we'll create a control plane that will allow our configuration to be changed and customized at runtime.

If you like my article, please give me a like! Thank you

Read on