From a5f5e2c6b9440054e6b61c6393f52240c016f52a Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 19 Oct 2018 11:42:22 +0200 Subject: [PATCH] fix(aws-apigateway): make LambdaRestApi proxy by default (#963) 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. --- packages/@aws-cdk/aws-apigateway/README.md | 12 +++--- .../@aws-cdk/aws-apigateway/lib/lambda-api.ts | 40 +++++++++++------- .../@aws-cdk/aws-apigateway/lib/resource.ts | 13 ++++++ .../aws-apigateway/test/test.lambda-api.ts | 41 +++++-------------- 4 files changed, 54 insertions(+), 52 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index 771dfe0d6cce8..c7e5b244ec8a2 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -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'); diff --git a/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts b/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts index 1b08b2533b165..bf2488a3f2531 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts @@ -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 { @@ -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. @@ -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`); } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index ca469e7f5bb04..c4c1d62724e2e 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -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, @@ -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) { diff --git a/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts b/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts index 2c9ebfb2f21a8..45f1ec60e1479 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts @@ -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+}" })); @@ -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(); @@ -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'); @@ -162,5 +141,5 @@ export = { }), /Cannot specify \"options\.defaultIntegration\" since Lambda integration is automatically defined/); test.done(); - } + }, }; \ No newline at end of file