Search code examples
c#asp.net-coreminimal-apis

Proxy Request with Minimal API


So, I'm being forced into creating a gateway by the fact that an API I want to consume in my Blazor WASM app doesn't play nicely with CORS.

So I want to create the quickest, laziest gateway API I can. This is categorically not going to be used for production code so I don't care about security or anything of the sort.

The shortest I've been able to come up with so far is:

var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("appsettings.json")
       .AddEnvironmentVariables();
builder.Services.AddCors(options =>
                 {
                     options.AddPolicy(name: "cors",
                                       policy =>
                                       {
                                           policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
                                       });
                 });


var app = builder.Build();
app.UseCors("cors");

app.MapGet("/discos-proxy/{*discosRoute}", async (string discosRoute, HttpContext context) =>
                                           {
                                               HttpClient    client  = new();
                                               client.BaseAddress                         = new(app.Configuration.GetSection("DiscosOptions:DiscosApiUrl").Value);
                                               Console.WriteLine(context.Request.Headers.Authorization);
                                               client.DefaultRequestHeaders.Authorization = new("bearer", context.Request.Headers.Authorization.ToString().Split(' ')[1]);
                                               HttpResponseMessage res = await client.GetAsync(discosRoute);
                                               res.EnsureSuccessStatusCode();
                                               Stream             contentStream = await res.Content.ReadAsStreamAsync();
                                               using StreamReader reader        = new(contentStream);
                                               string             content       = await reader.ReadToEndAsync();
                                               return content;
                                           });

app.Run();

I tried returning just the HttpResponseMessage directly but that doesn't seem to pull the content through.


So there are some obvious issues with this:

  • If there's an error on the request, it'll 500
  • It doesn't preserve any response headers (particularly important given the API I'm proxying here has a rate limiter)
  • Many more

So am I missing a trick here or is there really no simple way to just proxy a request using these minimal APIs?


Solution

  • It seems as though the suggestion from @CodingMytra in the comments was the easiest solution.

    To create this gateway with ocelot I need to install the Ocelot package from Nuget and then have the following in my Program.cs.

    using Ocelot.DependencyInjection;
    using Ocelot.Middleware;
    
    WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
    builder.Configuration
           .AddJsonFile("appsettings.json")
           .AddJsonFile("ocelot.json")
           .AddEnvironmentVariables();
    
    builder.Services.AddCors(options =>
                     {
                         options.AddPolicy("CorsPolicy",
                                           builder => builder.AllowAnyOrigin()
                                                             .AllowAnyMethod()
                                                             .AllowAnyHeader());
                     });
    
    
    builder.Services.AddOcelot();
    
    WebApplication app = builder.Build();
    
    app.UseCors("CorsPolicy");
    
    await app.UseOcelot();
    
    await app.RunAsync();
    

    It goes without saying that the very free CORS policy in this example should be tightened to suit your use case.

    Essentially, I've just added CORS options (with .UseCors(...) and .AddCors(...)), and Ocelot options (with .AddOcelot() and .UseOcelot()).

    Then, in my ocelot.json:

    {
      "Routes": [
        {
          "DownstreamPathTemplate": "/api/{everything}",
          "DownstreamScheme": "https",
          "DownstreamHostAndPorts": [
            {
              "Host": "discosweb.esoc.esa.int",
              "Port": 443
            }
          ],
          "UpstreamPathTemplate": "/{everything}",
          "UpstreamHttpMethod": [ "Get" ]
        }
      ],
      "GlobalConfiguration": {
        "BaseUrl": "https://localhost:5002/"
      }
    }
    

    This configuration takes every get request sent to the gateway and reroutes it to the downstream host (and prepends /api/ to the path).