Serverless-express: req.path is different between default API Gateway domain and custom domain

Created on 28 Jun 2017  Â·  23Comments  Â·  Source: vendia/serverless-express

According to this guide:

http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html#how-to-custom-domains-call-api-with-sni

setting base path mappings on a custom domain should produce the same result as the default API Gateway domain. However, this is the result I'm seeing with my router (using {proxy+}):

https://udxjef.execute-api.us-east-1.amazonaws.com/pro/foo => req.path is "/foo"
https://apis.example.com/petstore/foo => req.path is "/petstore/foo"

Since these paths are different, a route will not be able to match both URLs at the same time.

In our case, we have been able to work around this by setting an environment variable in our Lambda function and then telling our routes that they are mounted to a base path:

app.get(process.env.API_PATH + '/foo', myRouteHandler)

We can then include the same base path on the default URL to get the equivalent route as would be seen by the custom domain:

https://udxjef.execute-api.us-east-1.amazonaws.com/pro/petstore/foo => req.path is "/petstore/foo"
https://apis.example.com/petstore/foo => req.path is "/petstore/foo"

This works, but it seems like an unnecessary step and is inconsistent with the expectations outlined in the guide noted above.

enhancement help wanted v4

Most helpful comment

Try to use this code as your handler:

// lambda.js
const awsServerlessExpress = require('aws-serverless-express')
const app = require('./app')
const server = awsServerlessExpress.createServer(app)

exports.handler = (event, context) => { 

    //Remove the stage
    if (event.requestContext && event.requestContext.stage) {
        event.path = event.path.replace('/' + event.requestContext.stage, '');
    }

    awsServerlessExpress.proxy(server, event, context) 

}

All 23 comments

You could probably add Express middleware to detect if the path starts with "/petstore" and add it if necessary. Though it might make more sense to strip out "/petstore" unless you plan on serving multiple APIs at different base paths from the single Lambda?

There's discussion over here in the Java sister lib https://github.com/awslabs/aws-serverless-java-container/issues/34. I'll do something similar here, either embedded in the lib or as middleware.

Any update for this? Really want to use aws-serverless-express without resorting to custom hacks. Thanks. :smile:

It seems that pathParameter.proxy is consistent whether used with custom domains or not, so you can swap that in for the url path. Here is the middleware solution I came up with: https://github.com/Mindflash/koa-lambda-scaffold/blob/master/src/helpers/lambdaMiddleware.js#L8 (It's Koa middleware, so will be slightly different with Express).

I've forked the repo (and created a PR https://github.com/awslabs/aws-serverless-express/pull/98) to add an option for client to decide to strip base path or not, which also adopt @m5m1th's observation to extract the proxy path.

Hi @brettstack , any plan to move on with this issue or with proposal in PR #98? while we can for sure fork the repo and work a solution to use pathParameters.proxy it would be nice to have it from the official project.

I'm using the following middleware snippet.

const customDomainAdaptorMiddleware = (req,res,next)=>{
    if (!! req.headers['x-apigateway-event']){
        const event = JSON.parse(decodeURIComponent(req.headers['x-apigateway-event']));
        const params = event.pathParameters||{};

        let interpolated_resource = Object.keys(params)
            .reduce((acc, k)=>acc.replace('{'+k+'}', params[k]), event.resource)
        // console.log(event.path, event.resource, interpolated_resource)
        if ((!! event.path && !! interpolated_resource) && event.path != interpolated_resource){
            req.url = req.originalUrl = interpolated_resource;
            console.log(`rerouted ${event.path} -> ${interpolated_resource}`);
        }
    }
    next()
}

the usage is simply

app.use(customDomainAdaptorMiddleware)

The idea is that if event.path and event.resource (after param substitution) don't match it is a case of custom domain. And we replace the req.url for event.resource.
Atm this works fine for my apis.
I can contribute a PR if there's some interest, I think this middleware belongs within this project.

In general, I'd prefer a similar middleware approach rather than a config option: something like PR #98 changes all the import signatures. Whereas this way only who's facing this issue has to change the code, and can do so via a simple app.use(customDomainAdaptorMiddleware).

For the time being I have published the above middleware in npm if anyone needs.
https://www.npmjs.com/package/@turinggroup/serverless-express-custom-domain-middleware.

I agree with @m5m1th and @ebarault , that pathParameters.proxy is the consistent path that should be instead of event.path. I'm using feathersjs and the result is same as your conclusions.

I had a discussion on https://gitter.im/claudiajs/claudia with @stojanovic and he suggested looking at/changing this: https://github.com/awslabs/aws-serverless-express/blob/master/src/index.js#L21-L23

I hope this is changed because I believe this is a bug. Is there any particular reason why event.path is still being used instead of pathParameters.proxy ?

Update: Unfortunately due to bug https://github.com/claudiajs/claudia/issues/170 I can't even "patch" aws-serverless-express easily, now my workaround is this: (in lambda.js)

exports.handler = (event, context) => {
  // console.log('EVENT:', event)
  // FIXME: Ugly workaround for https://github.com/awslabs/aws-serverless-express/issues/86 and https://github.com/claudiajs/claudia/issues/170
  if (event.path.startsWith('/profile/v4-stg')) {
    event.path = event.path.replace('/profile/v4-stg', '')
  }
  awsServerlessExpress.proxy(server, event, context)
}

really ugly, but for now it does its job for me...

We can't use pathParameters.proxy as that makes assumptions about your API (i.e. that you have a {proxy} path parameter). We'll fix this by allowing you to specify a base path to strip similar to this https://github.com/awslabs/aws-serverless-java-container/issues/34#issuecomment-309855876

@brettstack Could you give example in what situation there is no pathParameters.proxy?

In my perspective, this project is used for AWS API Gateway which is a proxy.

And even in the case where it isn't used, would you prioritize pathParameters.proxy first before falling back to event.path? This provide the desired behavior for all parties without breaking those who don't use proxy.

I imagine most use the proxy approach, but some create their resources/methods individually in API Gateway so they can take advantage of other features (e.g. Documentation).

Prioritizing pathParameters.proxy and falling back sounds like a reasonable solution, however, this may be a breaking change (e.g. users may already be working around this and expecting path to be a certain value). I'll also need to include the flag discussed before for users not using proxy. I'll probably start with just the flag and include the fallback mechanism you describe in the next major version

Thank you @brettstack for understanding. Yes, the flag for current version and "potentially breaking" default behavior for next version would be a great solution for me.

We ran into this same issue this morning. We went with an admittedly terrible/naive Express middleware to strip the leading basePath from the URL if we see one.

Bump 😄

I made this change in the v4 branch yesterday. I haven't tested it against
a running Lambda, but I'm making progress on v4 which will include major
changes/fixes/features. Until then there are workarounds

On Fri, May 3, 2019, 5:41 AM James Abbott notifications@github.com wrote:

Bump 😄

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/awslabs/aws-serverless-express/issues/86#issuecomment-489081824,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAOLVFY2WIBVXZHZAVHJABTPTQXHHANCNFSM4DQ7DQUQ
.

that snippet helped me when running without custom domain, but i needed to know my real path the express side

/**
 *
 * @param {import("express").Request} request
 * @param {string} stage
 */
function addStagePathToRequest(request, stage) {
    request.originalUrl = `/${stage}${request.originalUrl}`;
    request.url = `/${stage}${request.url}`;
    // @ts-ignore
    request._parsedOriginalUrl = url.parse(request.originalUrl);
    // @ts-ignore
    request._parsedUrl = url.parse(request.url);
}

Try to use this code as your handler:

// lambda.js
const awsServerlessExpress = require('aws-serverless-express')
const app = require('./app')
const server = awsServerlessExpress.createServer(app)

exports.handler = (event, context) => { 

    //Remove the stage
    if (event.requestContext && event.requestContext.stage) {
        event.path = event.path.replace('/' + event.requestContext.stage, '');
    }

    awsServerlessExpress.proxy(server, event, context) 

}

+1

for me the below solution works.

Get the @turinggroup/serverless-express-custom-domain-middleware npm package

const customDomainReroute = require('@turinggroup/serverless-express-custom-domain-middleware').customDomainReroute
const app = express();
app.use(customDomainReroute)

for me the below solution works.

Get the @turinggroup/serverless-express-custom-domain-middleware npm package

const customDomainReroute = require('@turinggroup/serverless-express-custom-domain-middleware').customDomainReroute
const app = express();
app.use(customDomainReroute)

can you explain how you use this npm package? do you add this script to the wrapper code ie. lambda.js? or the myapp?

Fixed in v4

Looks like the fix was implement for API Gateway v1 but not v2.

Looks like the fix was implement for API Gateway v1 but not v2.

Solution

exports.handler = (event, context) => {

    if (event.pathParameters ==null )  {
        event.path = '/';
    }
    else{
        event.path = '/'+event.pathParameters.proxy;
    }

awsServerlessExpress.proxy(server, event, context);

}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Vadorequest picture Vadorequest  Â·  3Comments

denneulin picture denneulin  Â·  8Comments

donny08 picture donny08  Â·  5Comments

simon998yang picture simon998yang  Â·  10Comments

donny08 picture donny08  Â·  8Comments