Question regarding AWS Api Gateway and ServiceStack

Hey mythz,

Thanks again for the support the other day, that got us where we needed to be. In regards to API Gateway and ServiceStack, I wanted to know what you thought the best way to deal with API Gateway with a custom domain and ServiceStack services running from the root of that domain. Here is a brief explanation of the setup.

  • We have a custom domain setup, i.e: mydomain.com
  • We have 100+ microservices that run off of the root, i.e: mydomain.com/microservice1
    /apply
    /customer-service
    /online-account
    /admin
    etc …

I would love to run these each from the root but due to the complexities of custom domains and other factors, this setup makes the most sense when it comes to our build environment. Can you tell me what configurations would need to be set to properly run these services from these virtual directories (in essence, these work exactly like applications in IIS). We have some issues where we are getting 404 where we don’t think we should be.

I’ve tried a few scenarios but none of them are working 100%. I’ve tried using the proxy feature to forward the request to the microservices raw (AWS generated) URL. I’ve tried modifying the route attributes by overriding the GetRouteAttributes method. Let me know if I can provide you with any additional information if this isn’t enough.

Thanks

I’m not really following, are these running on different ServiceStack instances? If you’re running different processes that you want to expose under the same domain name you’d typically use a reverse proxy like nginx to proxy the external path to the internal url where the instance is running on, e.g:

location /apply/ {
    proxy_pass http://127.0.0.1:8001/;
}

This works similar to proxying sub domains but proxies by path instead.

If this is the same ServiceStack instance then these are just different routes to the same instance.

Yea, after thinking about it, it wasn’t very clear.

Each of the microservices run in a separate ServiceStack instance. I have a deployed instance that is a good example. This is a default installation of the razor .net core template. Here is the main issue that I’d like to solve:

The raw endpoint loads correctly:

The endpoint behind the api gateway custom domain receives a 404:

The custom domain that I refered to takes care of mapping these separate servicestack instances as you can see below:

If configured properly the downstream server shouldn’t care or know that it’s behind a proxy, i.e. it should be working transparently since it’s just sending normal HTTP Requests.

What’s the actual HTTP Request ServiceStack receives?

I agree completely. Here are the log for the failed request:

https://dev.api.enumis.co.uk/apply/default
||Log|| ||PostCreateContext|| Request:
{
“resource”: “/default”,
“path”: “/apply/default”,
“httpMethod”: “GET”,
“headers”: {
“Accept”: “text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8”,
“Accept-Encoding”: “gzip, deflate, br”,
“Accept-Language”: “en-US,en;q=0.9”,
“cache-control”: “max-age=0”,
“CloudFront-Forwarded-Proto”: “https”,
“CloudFront-Is-Desktop-Viewer”: “true”,
“CloudFront-Is-Mobile-Viewer”: “false”,
“CloudFront-Is-SmartTV-Viewer”: “false”,
“CloudFront-Is-Tablet-Viewer”: “false”,
“CloudFront-Viewer-Country”: “US”,
“Cookie”: “ga=GA1.3.126550162.1489663941; ss-id=LO4sRKqUZy55ZxR34jR8; ss-pid=LG1F2VjF4ZkLPKyMmZew",
“dnt”: “1”,
“Host”: “dev.api.enumis.co.uk”,
“upgrade-insecure-requests”: “1”,
“User-Agent”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36”,
“Via”: “2.0 679a4de27906d10e22d8f04cf2f6ad76.cloudfront.net (CloudFront)”,
“X-Amz-Cf-Id”: "sRD_ltirfZBopGXr7XEn86FxZARpmMBrHs5IA49_I4eJ
-aApkvj0w==”,
“X-Amzn-Trace-Id”: “Root=1-5a9148f0-a49c980c9045a864608e193c”,
“X-Forwarded-For”: “216.196.242.118, 52.46.22.91”,
“X-Forwarded-Port”: “443”,
“X-Forwarded-Proto”: “https”
},
“requestContext”: {
“path”: “/apply/default”,
“accountId”: “213343987366”,
“resourceId”: “gkdlbu”,
“stage”: “v2dot2”,
“requestId”: “cbe0891c-1953-11e8-bd61-812f420f276e”,
“identity”: {
“sourceIp”: “216.196.242.118”,
“userAgent”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36”
},
“resourcePath”: “/default”,
“httpMethod”: “GET”,
“apiId”: “gt3poh7827”
},
“isBase64Encoded”: false
}

These aren’t the same requests, your first route:

Is requesting /default, using requestinfo:

https://gt3poh7827.execute-api.us-east-2.amazonaws.com/v2dot2/default?debug=requestinfo

Whilst your second request:

https://dev.api.enumis.co.uk/apply/default

Is requesting /apply/default, see:

https://dev.api.enumis.co.uk/apply/default?debug=requestinfo

I’m assuming your Route is defined as [Route("/default")] which is only going to work for the first request.

When you deploy an API, it generates you an API ID (“gt3poh7827” in this case). You also supply a stage when deploying and that is the “v2dot2” in the URL below.

So when I originally deployed this API, I was given the following base url:

https://gt3poh7827.execute-api.us-east-2.amazonaws.com/v2dot2/

Next, you can setup a custom domain (not required but preferred), We use the following:

dev.api.enumis.co.uk

Next, inside of your customer domain configuration, you have “Base Path Mappings”. This is where you can map all of the API’s ( gt3poh7827) to a base path off of your custom domain (/apply).

So in this example, the mapping looks like:

https://gt3poh7827.execute-api.us-east-2.amazonaws.com/v2dot2/ => /apply

So:

https://dev.api.enumis.co.uk/apply/default

equals

https://gt3poh7827.execute-api.us-east-2.amazonaws.com/v2dot2/default


A bit more interesting is, I added this Proxy and it seemed to get me more in the direction of where I want to go:

//   Variable reference:
// =============================================================================
// - apiHost = dev.api.enumis.co.uk
// - apiPath = /apply
// - apiGatewayHost = gt3poh7827.execute-api.us-east-2.amazonaws.com
// - apiGatewayStage = v2dot2
// =============================================================================

Plugins.Add(new ProxyFeature(
    matchingRequests: req => req.Headers["Host"] == apiHost,
    resolveUrl: req => "https://{apiGatewayHost}/{apiGatewayStage}{req.PathInfo.Replace(apiPath, "")}"
));

On another note, I respect what you said in your last post the other day. If you don’t want to deal with this issue right now, that’s fine. I know that this is not your priority. However, I am going to nail these issues out one-by-one and document them. Once everything is working flawlessly, I’ll certainly deliver what I have to you. I really think people will find this lambda hosting feature very useful, especially for things like managed MQ subscribers among other things.

What is the Route Definition that you’re trying to match? From the requestinfo pages I can see that the path info for the first request is /default whilst the 2nd request is /apply/default. Since the /default request is returning a 200 Response I’ll assume that’s what you’re trying to match on.

If that’s the case you need to make the /apply/default Request map to /default which you can try by specifying a HandlerFactoryPath, e.g:

SetConfig(new HostConfig { 
    HandlerFactoryPath = "apply"
});

Otherwise have a look at overriding ResolvePathInfo in your AppHost to strip the /apply from the resolved /path/info.

As usual, you end up a rockstar. After overriding ResolvePathInfo, everything works as expected. I’ll probably wrap this up as a feature for future AWS Developers.

Here is all it took after all:

    public override string ResolvePathInfo(IRequest request, string originalPathInfo)
    {
        var apiPath = AppSettings.GetRequiredString("API_PATH");
        var pathInfo = NormalizePathInfo(originalPathInfo, Config.HandlerFactoryPath);

        return pathInfo.StartsWithIgnoreCase(apiPath) ? pathInfo.Replace(apiPath, "") : pathInfo;
    }

Thanks again!

1 Like