Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Gateway Path Mapping Problem #112

Closed
feinstein opened this issue Jan 30, 2018 · 19 comments
Closed

API Gateway Path Mapping Problem #112

feinstein opened this issue Jan 30, 2018 · 19 comments
Assignees
Milestone

Comments

@feinstein
Copy link
Contributor

  • Framework version: 0.9
  • Implementations: Jersey (but I believe this affects all versions)

Scenario

All the details of this issue can be found on this AWS Forums thread.

Basically, regular URLs and Custom Domain Name URLs From API Gateway, inconsistently map their URL paths to the invoked a Lambda Function.

Here's my invocation logs, from bot a regular API Gateway URL and a Custom Domain Name URL:

Custom Domain Name URL:

"resource":"/{proxy+}","path":"/service/prod/test", 
"Host":"api.myservice.com", 
"pathParameters":{"proxy":"test"}
"path":"/service/prod/test",
"stage":"prod",

Regular URL:

"resource":"/{proxy+}","path":"/test", 
"Host":"aaaaaaaaaaa.execute-api.us-east-1.amazonaws.com", 
"pathParameters":{"proxy":"test"},
"path":"/prod/test",
"stage":"prod",

The same Lambda Function, invoked with both URLs will produce different resutls, since the path being passed is different, the Lambda Function invoked with:

https://aaaaaaaaaaa.execute-api.us-east-1.amazonaws.com/prod/test

will execute normaly, but the same Lambda Function invoked with:

https://api.myservice.com/service/prod/test

will return 404 since the path being passed to Jersey by this framework will be an invalid one.

Expected behavior

Calling both APIs will invoke the Lambda Function consistently with the same path and both will execute on the same way.

Actual behavior

https://aaaaaaaaaaa.execute-api.us-east-1.amazonaws.com/prod/test

will execute normaly, but the same Lambda Function invoked with:

https://api.myservice.com/service/prod/test

will return 404 since the path being passed to Jersey by this framework will be an invalid one.

Steps to reproduce

1 - Create a new API Gateway {proxy+} method that invokes a Lambda Function running Jersey
2 - Create a Custom Domain Name and a basepath mapping for that Lambda Function
3 - Invoke the API using both URLs


From the logs:

Custom Domain Name URL:

"resource":"/{proxy+}","path":"/service/prod/test", 
"Host":"api.myservice.com", 
"pathParameters":{"proxy":"test"}
"path":"/service/prod/test",
"stage":"prod",

Regular URL:

"resource":"/{proxy+}","path":"/test", 
"Host":"aaaaaaaaaaa.execute-api.us-east-1.amazonaws.com", 
"pathParameters":{"proxy":"test"},
"path":"/prod/test",
"stage":"prod",

I can see the pathParameters are the same on both cases, but the path is different.

Is this library using the path and this is the source of the problem? Could it change to using the pathParameters instead?

@sapessi
Copy link
Collaborator

sapessi commented Jan 30, 2018

Hey @feinstein, you can use the stripBasePath option of the ContainerConfig object. We have a shorthand syntax in the handler to set this:

handler.setStripBasePath("/service/prod");

Alternatively, you can grab the current config and modify it handler.getContainerConfig()

I'll add this to the docs.

@feinstein
Copy link
Contributor Author

Oh, interesting, I will take a look at this, thanks!

@feinstein
Copy link
Contributor Author

@sapessi I can't find the setStripBasePath or the getContainerConfig on the JerseyLambdaContainerHandler, is this the handler you are referring to?

My StreamLambdaHandler is almost a direct copy from the docs and the JerseyLambdaContainerHandler is the only handler I can see.

@sapessi
Copy link
Collaborator

sapessi commented Jan 31, 2018

Yeah, I can find it in the sample. My handler would change like this:

public class StreamLambdaHandler implements RequestStreamHandler {
    private final ResourceConfig jerseyApplication = new ResourceConfig()
                                                             .packages("com.amazonaws.serverless.sample.jersey")
                                                             .register(JacksonFeature.class);
    private final JerseyLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler
            = JerseyLambdaContainerHandler.getAwsProxyHandler(jerseyApplication);

    public StreamLambdaHandler() {
        // we enable the timer for debugging. This SHOULD NOT be enabled in production.
        Timer.enable();
    }

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
            throws IOException {
        handler.stripBasePath("/service/prod");
        AwsProxyRequest request = LambdaContainerHandler.getObjectMapper().readValue(inputStream, AwsProxyRequest.class);

        AwsProxyResponse resp = handler.proxy(request, context);

        LambdaContainerHandler.getObjectMapper().writeValue(outputStream, resp);

        System.err.println(LambdaContainerHandler.getObjectMapper().writeValueAsString(Timer.getTimers()));

        // just in case it wasn't closed by the mapper
        outputStream.close();
    }
}

@feinstein
Copy link
Contributor Author

I am sorry, my bad, it's late in here, I made a typo and auto-complete wasn't working, ops!

@feinstein
Copy link
Contributor Author

feinstein commented Jan 31, 2018

@sapessi now I am getting 404 on both URLs.

I will try to see the path being passed logging request.getPath().

Are there any caveats on using the pathParameters instead of the path passed to the Lambda Function?

@sapessi
Copy link
Collaborator

sapessi commented Jan 31, 2018

For routing, pathParameters only works if you have API Gateway configured with a {proxy+} catch-all path. If you specify the specific resource, for example /orders/{id}, I would only get the id value in the pathParameters object.

I'm looking into the path.

@feinstein
Copy link
Contributor Author

I see, it makes sense.

@feinstein
Copy link
Contributor Author

@sapessi where is the code that actually strips the base path? I want to double check it's logic to see the final path, so I cant figure out why both URLs now return 404.

@sapessi
Copy link
Collaborator

sapessi commented Jan 31, 2018

The path is stripped by the request reader. Here's the call to it and here's the method implementation

@feinstein
Copy link
Contributor Author

Thanks! I will try to figure out what is going on here....

@sapessi
Copy link
Collaborator

sapessi commented Jan 31, 2018

thanks. Ping here if you think there's something wrong

@feinstein
Copy link
Contributor Author

feinstein commented Jan 31, 2018

@sapessi I modified my StreamLambdaHandler to include the following code:

handler.stripBasePath("/service/prod");
AwsProxyRequest request = LambdaContainerHandler.getObjectMapper().readValue(inputStream, AwsProxyRequest.class);
LOGGER.error("Path before: " + request.getPath());

AwsProxyResponse resp = handler.proxy(request, context);
LOGGER.error("Path after: " + request.getPath());

LambdaContainerHandler.getObjectMapper().writeValue(outputStream, resp);
        // just in case it wasn't closed by the mapper
        outputStream.close();

So basically I am just logging the path before and after it's stripped out.

Now is the part that I really don't understand what's going on, in my logs I can see that the path is being stripped out correctly, but I still get a 404!

My Custom Domain Name invocation log looks like this:

Path before: /service/prod/test
Initialize Jersey application handler
Path after: /test 

And my regular URL logs look like this:

Path before: /test
Path after: /test

So the path is /test at end, at both, and I get a 404 on both.

Now, if I remove the line handler.stripBasePath("/service/prod");, My logs become:

Custom Domain Name log:

Path before: /service/prod/test 
Path after: /service/prod/test 

Regular URL:

Path before: /test
Initialize Jersey application handler
Path after: /test

And now I only get a 404 on the Custom Domain Name URL, but the regular URL works fine!

My Jersey application is configured to work correctly when path is /test, as it does for the regular URL. But as soon as I set handler.stripBasePath("/service/prod");, path becomes /test for all the URLs and my regular URL stops working. Since there's no base path to be stripped, stripBasePath should be transparent, but apparently there are side effects and I can't pinpoint where they are since the stripping code seems to be correct.

I don't understand how the regular URL fails and returns 404 in the first case and executes correctly in the second case, when the path is correctly set to /test on both cases.

@sapessi
Copy link
Collaborator

sapessi commented Jan 31, 2018 via email

@feinstein
Copy link
Contributor Author

@sapessi perfect. If you need anything from me, please let me know. Thank you once again!

sapessi added a commit that referenced this issue Jan 31, 2018
… bug introduced with the fixes for #84. Added unit tests
@sapessi sapessi added bug and removed enhancement labels Jan 31, 2018
@sapessi
Copy link
Collaborator

sapessi commented Jan 31, 2018

Hey @feinstein, found the issue and pushed a fix to the jersey branch. I've also added a couple of unit tests for it. The issue was introduced in the latest release. I will push out a path version 0.9.1 today.

@sapessi sapessi added this to the Release 0.9.1 milestone Jan 31, 2018
sapessi added a commit that referenced this issue Jan 31, 2018
Fix routing issue with Jersey when stripBasePath is configured (addresses #112)
@feinstein
Copy link
Contributor Author

@sapessi great! let me know when it's out so I can test it.

@sapessi
Copy link
Collaborator

sapessi commented Jan 31, 2018

@feinstein 0.9.1 is making its way to maven central. Should be there within the hour.

@feinstein
Copy link
Contributor Author

@sapessi it works perfectly now! I will probably make some sort of configuration file to provide the URL to be stripped out, since there are prod and dev URLs for the same Lambda Function.

Thank you for all the help, you are the best!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants