Skip to content

Commit

Permalink
fix(aws-apigateway): make LambdaRestApi proxy by default (#963)
Browse files Browse the repository at this point in the history
The LambdaRestApi construct now proxies all paths and methods by
default, which can be switched off using `proxy: false`.

BREAKING CHANGE: specifying a path no longer works. If you used to
provide a '/', remove it. Otherwise, you will have to supply `proxy: false`
and construct more complex resource paths yourself.

Fixes #959.
  • Loading branch information
rix0rrr committed Oct 19, 2018
1 parent d238ffd commit a5f5e2c
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 52 deletions.
12 changes: 6 additions & 6 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,24 @@ book.addMethod('DELETE');
A very common practice is to use Amazon API Gateway with AWS Lambda as the
backend integration. The `LambdaRestApi` construct makes it easy:

The following code defines a REST API that uses a greedy `{proxy+}` resource
mounted under `/api/v1` and integrates all methods (`"ANY"`) with the specified
AWS Lambda function:
The following code defines a REST API that routes all requests to the
specified AWS Lambda function:

```ts
const backend = new lambda.Function(...);
new apigateway.LambdaRestApi(this, 'myapi', {
handler: backend,
proxyPath: '/api/v1'
});
```

If `proxyPath` is not defined, you will have to explicitly define the API model:
You can also supply `proxy: false`, in which case you will have to explicitly
define the API model:

```ts
const backend = new lambda.Function(...);
const api = new apigateway.LambdaRestApi(this, 'myapi', {
handler: backend
handler: backend,
proxy: false
});

const items = api.root.addResource('items');
Expand Down
40 changes: 25 additions & 15 deletions packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import lambda = require('@aws-cdk/aws-lambda');
import cdk = require('@aws-cdk/cdk');
import { LambdaIntegration } from './integrations';
import { Method } from './method';
import { ProxyResource, Resource } from './resource';
import { RestApi, RestApiProps } from './restapi';

export interface LambdaRestApiProps {
Expand All @@ -13,15 +15,14 @@ export interface LambdaRestApiProps {
handler: lambda.Function;

/**
* An API path for a greedy proxy with an "ANY" method, which will route all
* requests under that path to the defined handler.
* If true, route all requests to the Lambda Function
*
* If not defined, you will need to explicitly define the API model using
* If set to false, you will need to explicitly define the API model using
* `addResource` and `addMethod` (or `addProxy`).
*
* @default undefined
* @default true
*/
proxyPath?: string;
proxy?: boolean;

/**
* Further customization of the REST API.
Expand Down Expand Up @@ -49,16 +50,25 @@ export class LambdaRestApi extends RestApi {
...props.options
});

// if proxyPath is specified, add a proxy at the specified path
// we will need to create all resources along the path.
const proxyPath = props.proxyPath;
if (proxyPath) {
const route = proxyPath.split('/').filter(x => x);
let curr = this.root;
for (const part of route) {
curr = curr.addResource(part);
}
curr.addProxy();
if (props.proxy !== false) {
this.root.addProxy();

// Make sure users cannot call any other resource adding function
this.root.addResource = addResourceThrows;
this.root.addMethod = addMethodThrows;
this.root.addProxy = addProxyThrows;
}
}
}

function addResourceThrows(): Resource {
throw new Error(`Cannot call 'addResource' on a proxying LambdaRestApi; set 'proxy' to false`);
}

function addMethodThrows(): Method {
throw new Error(`Cannot call 'addMethod' on a proxying LambdaRestApi; set 'proxy' to false`);
}

function addProxyThrows(): ProxyResource {
throw new Error(`Cannot call 'addProxy' on a proxying LambdaRestApi; set 'proxy' to false`);
}
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ export class ProxyResource extends Resource {
*/
public readonly anyMethod?: Method;

private readonly parentResource: IRestApiResource;

constructor(parent: cdk.Construct, id: string, props: ProxyResourceProps) {
super(parent, id, {
parent: props.parent,
Expand All @@ -179,11 +181,22 @@ export class ProxyResource extends Resource {
defaultMethodOptions: props.defaultMethodOptions,
});

this.parentResource = props.parent;

const anyMethod = props.anyMethod !== undefined ? props.anyMethod : true;
if (anyMethod) {
this.anyMethod = this.addMethod('ANY');
}
}

public addMethod(httpMethod: string, integration?: Integration, options?: MethodOptions): Method {
// In case this proxy is mounted under the root, also add this method to
// the root so that empty paths are proxied as well.
if (this.parentResource.resourcePath === '/') {
this.parentResource.addMethod(httpMethod);
}
return super.addMethod(httpMethod, integration, options);
}
}

function validateResourcePathPart(part: string) {
Expand Down
41 changes: 10 additions & 31 deletions packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ export = {
});

// WHEN
new apigw.LambdaRestApi(stack, 'lambda-rest-api', { handler, proxyPath: '/' });
const api = new apigw.LambdaRestApi(stack, 'lambda-rest-api', { handler });

// THEN
// THEN -- can't customize further
test.throws(() => {
api.root.addResource('cant-touch-this');
});

// THEN -- template proxies everything
expect(stack).to(haveResource('AWS::ApiGateway::Resource', {
"PathPart": "{proxy+}"
}));
Expand Down Expand Up @@ -81,33 +86,7 @@ export = {
test.done();
},

'proxyPath can be used to attach the proxy to any route'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

const handler = new lambda.Function(stack, 'handler', {
handler: 'index.handler',
code: lambda.Code.inline('boom'),
runtime: lambda.Runtime.NodeJS610,
});

// WHEN
new apigw.LambdaRestApi(stack, 'lambda-rest-api', {
handler,
proxyPath: '/backend/v2'
});

// THEN
expect(stack).to(haveResource('AWS::ApiGateway::Method', {
"ResourceId": {
"Ref": "lambdarestapibackendv2proxyC4980BD5"
}
}));

test.done();
},

'when "proxyPath" is not specified, users need to define the model'(test: Test) {
'when "proxy" is set to false, users need to define the model'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

Expand All @@ -118,7 +97,7 @@ export = {
});

// WHEN
const api = new apigw.LambdaRestApi(stack, 'lambda-rest-api', { handler });
const api = new apigw.LambdaRestApi(stack, 'lambda-rest-api', { handler, proxy: false });

const tasks = api.root.addResource('tasks');
tasks.addMethod('GET');
Expand Down Expand Up @@ -162,5 +141,5 @@ export = {
}), /Cannot specify \"options\.defaultIntegration\" since Lambda integration is automatically defined/);

test.done();
}
},
};

0 comments on commit a5f5e2c

Please sign in to comment.