0

I have an Angular app and a number of APIs, all exposed through a Yarp reverse proxy.

The app is accessed at https://example.com, and the APIs at https://example.com/api/name-of-api.

My problem is that the catch-all address for the app is also picking any invalid APIs and routing to the app.

How can I set up my route so that https://example.com/api/{*any} will return a 404, unless there is a more specific route for the API?

This is what the existing config looks like:

[
    {
        "RouteId": "app",
        "Match": { "Path": "/{**any}" },
        "ClusterId": "app",
        "Transforms": []
    },
    {
        "RouteId": "first-api",
        "Match": { "Path": "/api/first-api/{*any}" },
        "ClusterId": "first-api",
        "Transforms": []
    }
]
Cobus Kruger
  • 8,338
  • 3
  • 61
  • 106

1 Answers1

0

Per the documentation, you can use middleware to accomplish this:

If a middleware inspects a request and determines that it should not be proxied, it may generate its own response and return control to the server without calling next().

The CheckAllowedRequest function in the example does not provide much value, however. Here is an example on how to make this work.

Assuming your route with RouteId "app" is the route you want to return a 404 on, you can update the configuration with the MetaData field and whatever key/value pairs you want. For this example, we'll use the "UnsuccessfulResponseStatusCode" key and a value of "404":

{
    "ReverseProxy": {
        "Routes": {
          "app": {
            "ClusterId": "app",
            "Match": {
              "Path": "/{**any}"
            },
            "MetaData": {
              "UnsuccessfulResponseStatusCode": "404"
            }
          },
          "first-api": {
            "ClusterId": "first-api",
            "Match": {
              "Path": "/api/first-api/{*any}"
            }
          }
        },
        "Clusters": {
            "app": {
                // Config removed for brevity.
            },
            "first-api": {
                // Config removed for brevity.
            }
        }
    }
}

Next you should encapsulate the logic for your middleware in a class:

namespace SomeNamespace
{
    public class SomeMiddleware
    {
        private readonly RequestDelegate _next;

        public SomeMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            // Assign your nullable meta data from the config for the
            // current requested route to a variable
            var metaData = context.GetReverseProxyFeature().Route.Config.Metadata;

            // If metaData is not null and a string value, this includes null 
            // and whitespace, exists for the key "UnsuccessfulResponseStatusCode",
            // then short circuit the middleware
            if (metaData?.TryGetValue("UnsuccessfulResponseStatusCode"), 
                out var unsuccessfulResponseStatusCode) ?? false)
            {
                // Adding a switch case here allows our 
                // UnsuccessfulResponseStatusCode key to be robust enough to 
                // handle multiple different unsuccessful response status 
                // codes if you have different cases for different routes.
                switch (unsuccessfulResponseStatusCode)
                {
                    case "404":
                        context.Response.StatusCode = 
                            StatusCodes.Status404NotFound;
                        break;
                    case "500":
                    default:
                        context.Response.StatusCode = 
                            StatusCodes.Status500InternalServerError;
                        break;
                }
            }
            // Otherwise, invoke the next middleware delegate
            else
            {
                await _next(context);
            }
        }
    }
}

Finally, we can register this middleware with your proxy pipeline:

app.MapReverseProxy(proxyPipeline =>
{
    // I would register this above most or all of the the other middleware
    // to avoid unnecessary processing when attempting to short circuit
    // routes you don't want to proxy. This really depends on what other
    // middleware you have and what it does. Just be careful here.
    proxyPipeline.UseMiddleware<SomeMiddleware>();
});
Aaron
  • 1
  • 2