FallbackRoute not being caught if like predefined route

I’m trying to write a plugin that will act almost like a reverse proxy sitting infront of a number of services (there are various other ServiceStack plugins in the pipeline so it’s not just a reverse proxy but that is one of it’s jobs). The approach I’m looking at is:

  1. Take incoming request
  2. Find DTO type from incoming request
  3. Lookup Consul using DTO type (services are tagged with this in Consul) to find uri of service that will fulfil this request.
  4. Redirect request to appropriate service.

This is currently in it’s infancy and I’m trying to figure out the best way to achieve this. I am thinking that I will use the pre-defined routes only as it will give a convention that can be used to identify DTO type etc (e.g. Request.RawUrl.SplitOnLast("/")[0]; ) should give DTO type. I’m using the DTO type rather than serializing to strongly typed DTO to avoid needing to add a reference to every possible downstream service.

I’m trying to use a FallbackRoute to catch the requests using the following:

[FallbackRoute("/{Path*}")]
public class Fallback
{
  public string Path { get; set; }
}

public class FallBackService : Service
{
  public void Any(Fallback fallback){ ... }
}

While testing this I’ve found that any routes that have [xml|json|html|jsv|csv] as first slug and [reply|oneway] as second don’t fallback but instead return a 405. Is there a way round this?

One option that I’ve looked at is having [FallbackRoute(“api/{Path*}”)] as this seems to play well with it but wondering if there’s a way to use the ‘normal’ predefined routes.

Fallback routes are only for providing a fallback to handle unmatched routes which doesn’t sound like it’s what you want.

Why aren’t you just using a normal Global Request Filter? The Request DTO for the service is already matched and populated so you already have the Request DTO for the service which you can use to find the url to direct to, then you can just use the ReirectToUrl extension method to redirect to it, e.g:

this.GlobalRequestFilters.Add((req, res, requestDto) => {
    var redirectUrl = ...
    res.RedirectToUrl(redirectUrl);
});

As I’m using this as a gateway I was looking to use the fallback route to capture all routes and then redirect them to the downstream service, the alternative I was looking at was having a request filter and having a whitelist to ignore specific routes (e.g. /metadata).

I was trying to avoid this gateway having references to every individual service as there will be many and I want to avoid having to update the gateway if I update an individual service which is why I was looking at a fallback route and using DTO name (rather than serialised DTO object) to lookup the appropriate service using Consul for service discovery.

E.g. this service will run as api.mysite.com and there will be 2 downstream services:

If I then get a request for api.mysite.com/jsv/reply/person I want to use the “person” element to lookup Consul, which will return “ds1.mysite.com”, I can then redirect to ds1.mysite.com/jsv/reply/person (along with request headers + body).

I don’t think the GlobalRequestFilter will work in this case as the gateway won’t know about the PersonDTO object for serialisation?

ok so you’re calling a Service on an instance where there are no Services defined on it?

You can use a pre-request filter then to catch all requests where you can check for pre-defined routes and redirect accordingly, e.g:

PreRequestFilters.Add((req, res) =>
{
    var parts = req.PathInfo.Split('/');
    if (parts.Length < 3 || parts[parts.Length - 2] != "reply")
        return;

    var operation = parts[parts.Length - 1];
    string redirectUrl = "";
    res.RedirectToUrl(redirectUrl);
});

Actually you want this for unknown requests where PreRequestFilters only gets called for matching requests, so you’d need to use a Raw HttpHandler, e.g:

RawHttpHandlers.Add(req => req.PathInfo.Contains("/reply/")
    ? new CustomActionHandler((httpReq, httpRes) =>
        {
            var parts = req.PathInfo.Split('/');
            var operation = parts[parts.Length - 1];
            string redirectUrl = "";
            httpRes.RedirectToUrl(redirectUrl);
        })
    : null);

The different hooks and filters available throughout the Request Pipeline is documented in the Order of Operations docs.

Thanks Demis, I’ll have a look at those options.

I need to run some other plugins as part of the request so the RawHttpHandler might not be an option.

I’ve got something running with a FallbackRoute of [FallbackRoute(“api/{Path*}”)] which should allow me to achieve the proxy/gateway functionality I need.

1 Like