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

Add Lambda proxy functionality for API Gateway #2185

Merged
merged 8 commits into from
Oct 5, 2016
71 changes: 69 additions & 2 deletions docs/02-providers/aws/events/01-apigateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,75 @@ functions:
method: post
```

## Integration types

Serverless supports the following integration types:

- `lambda`
- `lambda-proxy`

Here's a simple example which demonstrates how you can set the `integration` type for your `http` event:

```yml
# serverless.yml
functions:
get:
handler: users.get
events:
- http:
path: users
method: get
integration: lambda
```

### `lambda-proxy`

**Important:** Serverless defaults to this integration type if you don't setup another one.
Furthermore any `request` or `response` configuration will be ignored if this `integration` type is used.

`lambda-proxy` simply passes the whole request as is (regardless of the content type, the headers, etc.) directly to the
Lambda function. This means that you don't have to setup custom request / response configuration (such as templates, the
passthrough behavior, etc.).

Your function needs to return corresponding response information.

Here's an example for a JavaScript / Node.js function which shows how this might look like:

```javascript
'use strict';

exports.handler = function(event, context) {
const responseBody = {
message: "Hello World!",
input: event
};

const response = {
statusCode: responseCode,
headers: {
"x-custom-header" : "My Header Value"
},
body: JSON.stringify(responseBody)
};

context.succeed(response);
};
```

Take a look at the [AWS documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html)
for more information about this.

### `lambda`

The `lambda` integration type should be used if you want more control over the `request` and `response` configurations.

Serverless ships with defaults for the request / response configuration (such as request templates, error code mappings,
default passthrough behaviour) but you can always configure those accordingly when you set the `integration` type to `lambda`.

## Request templates

**Note:** The request configuration can only be used when the integration type is set to `lambda`.

### Default request templates

Serverless ships with the following default request templates you can use out of the box:
Expand Down Expand Up @@ -133,6 +200,8 @@ See the [api gateway documentation](https://docs.aws.amazon.com/apigateway/lates

## Responses

**Note:** The response configuration can only be used when the integration type is set to `lambda`.

Serverless lets you setup custom headers and a response template for your `http` event.

### Using custom response headers
Expand Down Expand Up @@ -316,7 +385,6 @@ Please note that those are the API keys names, not the actual values. Once you d

Clients connecting to this Rest API will then need to set any of these API keys values in the `x-api-key` header of their request. This is only necessary for functions where the `private` property is set to true.


## Enabling CORS for your endpoints
To set CORS configurations for your HTTP endpoints, simply modify your event configurations as follows:

Expand Down Expand Up @@ -359,7 +427,6 @@ This example is the default setting and is exactly the same as the previous exam
To set up an HTTP proxy, you'll need two CloudFormation templates, one for the endpoint (known as resource in CF), and
one for method. These two templates will work together to construct your proxy. So if you want to set `your-app.com/serverless` as a proxy for `serverless.com`, you'll need the following two templates in your `serverless.yml`:


```yml
# serverless.yml
service: service-name
Expand Down
46 changes: 45 additions & 1 deletion lib/plugins/aws/deploy/compile/events/apiGateway/lib/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
let method;
let path;
let requestPassThroughBehavior = 'NEVER';
let integrationType = 'AWS_PROXY';

if (typeof event.http === 'object') {
method = event.http.method;
Expand Down Expand Up @@ -342,6 +343,49 @@ module.exports = {
const normalizedFunctionName = functionName[0].toUpperCase()
+ functionName.substr(1);

// check if LAMBDA or LAMBDA-PROXY was used for the integration type
if (typeof event.http === 'object') {
if (Boolean(event.http.integration) === true) {
// normalize the integration for further processing
const normalizedIntegration = event.http.integration.toUpperCase();
// check if the user has entered a non-valid integration
const allowedIntegrations = [
'LAMBDA', 'LAMBDA-PROXY',
];
if (allowedIntegrations.indexOf(normalizedIntegration) === -1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!! 👍

const errorMessage = [
`Invalid APIG integration "${event.http.integration}"`,
` in function "${functionName}".`,
' Supported integrations are: lambda, lambda-proxy.',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
// map the Serverless integration to the corresponding CloudFormation types
// LAMBDA --> AWS
// LAMBDA-PROXY --> AWS_PROXY
if (normalizedIntegration === 'LAMBDA') {
integrationType = 'AWS';
} else if (normalizedIntegration === 'LAMBDA-PROXY') {
integrationType = 'AWS_PROXY';
} else {
// default to AWS_PROXY (just in case...)
integrationType = 'AWS_PROXY';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems that the official name is AWS_PROXY, why are we using LAMBDA_PROXY in code and then change it later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mapping is used so that users are not confused. In the console AWS calls it Lambda Proxy and Lambda whereas in CF they call it AWS and AWS_PROXY.

/cc @flomotlik came up with this mapping which might be confusing when you come from CF but makes sense if you played around with it on the console / lambda in general.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but the user never sees LAMBDA_PROXY or AWS_PROXY when working with Serverless, right? they can only see integration: lambda and we take care of the rest. It's only in this JS file, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's true, then I don't see why we call it LAMBDA_PROXY then rename it AWS_PROXY again. It's a bit redundant imo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aaah wait nvm 😄 ... I just looked at it again and noticed the normalize logic. Now it make sense. So the user can do integration: lambda or integration: lambda_proxy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It basically uppercases everything before performing the comparison.

You can do

integration: LAMBDA
integration: lambda
integration: LAMBDA-PROXY
integration: lambda-proxy

or a mixture. I'm more in favor of uppercasing since AWS does the same but don't want to force the user to do this as we don't do this with the Methods either.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

integration: proxy might have been usefully ambiguous

this is great. can't wait for this!

}
}
}

// show a warning when request / response config is used with AWS_PROXY (LAMBDA-PROXY)
if (integrationType === 'AWS_PROXY' && (
(!!event.http.request) || (!!event.http.response)
)) {
const warningMessage = [
'Warning! You\'re using the LAMBDA-PROXY in combination with request / response',
` configuration in your function "${functionName}".`,
' This configuration will be ignored during deployment.',
].join('');
this.serverless.cli.log(warningMessage);
}

const methodTemplate = `
{
"Type" : "AWS::ApiGateway::Method",
Expand All @@ -352,7 +396,7 @@ module.exports = {
"RequestParameters" : {},
"Integration" : {
"IntegrationHttpMethod" : "POST",
"Type" : "AWS",
"Type" : "${integrationType}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't Template Literals need to be wrapped in back-ticks?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entire template is wrapped in back-ticks as if it's a complete JSON file, so this would work 😊

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good one 👍 (acutally I was thinking the same when implementing this).

Turns out that the whole JSON String is wrapped in back-ticks (if you look a few lines above).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To tell you the truth I'm not a big fan of this approach though when working with objects. It's hard to debug if you're missing a comma or something (because it's treated as JSON), plus we're gonna parse anyway to merge it with the entire CF template. I hope we can switch all our templates to just plain JS objects unless there's a pressing reason not to

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eahefnawy I've converted to "Objects" here (vs. Strings): #2262 agreed it's much better.

"Uri" : {
"Fn::Join": [ "",
[
Expand Down