From d3107265cb6c7e33c7c4eb537286ff4b867b7bf5 Mon Sep 17 00:00:00 2001 From: Pahud Date: Tue, 25 Feb 2020 13:28:47 +0800 Subject: [PATCH 01/42] (feat)apigatewayv2: initial L2 support --- packages/@aws-cdk/aws-apigatewayv2/README.md | 127 +++++- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 264 ++++++++++++ .../@aws-cdk/aws-apigatewayv2/lib/index.ts | 2 + .../@aws-cdk/aws-apigatewayv2/lib/route.ts | 407 ++++++++++++++++++ .../@aws-cdk/aws-apigatewayv2/package.json | 7 +- .../test/apigatewayv2.test.ts | 318 +++++++++++++- .../aws-apigatewayv2/test/integ.ap.ts | 98 +++++ 7 files changed, 1216 insertions(+), 7 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/api.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/route.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 8368e8c281dcc..0981680f135a6 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -15,9 +15,132 @@ --- - This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. +`aws-apigatewayv2` supports the `HTTP` API for Amazon API Gateway. + +## Examples: + +### HTTP API with Lambda proxy integration as the `$default` route + +```ts +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'ApiagtewayV2HttpApi'); + +const handler = new lambda.Function(stack, 'MyFunc', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code: new lambda.InlineCode(` +import json +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps(event) + }`), +}); + +// Create a HTTP API with Lambda Proxy Integration as $default route +const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { + handler +}); +``` +### HTTP API with HTTP proxy integration as the `$default` route + +```ts +new apigatewayv2.HttpProxyApi(stack, 'HttpProxyApi', { + url: 'https://aws.amazon.com' +}); +``` + +## Root Route + +Create the `root(/)` route of the API + ```ts -import apigatewayv2 = require('@aws-cdk/aws-apigatewayv2'); + +// prepare the root handler function +const rootHandler = new lambda.Function(stack, 'RootFunc', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code: new lambda.InlineCode(` +import json, os +def handler(event, context): + whoami = os.environ['WHOAMI'] + http_path = os.environ['HTTP_PATH'] + return { + 'statusCode': 200, + 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path }) + }`), + environment: { + WHOAMI: 'root', + HTTP_PATH: '/' + }, +}); + + +// create a HTTP API with Lambda Proxy Integration as $default route +const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { + handler +}); + +// create the root route(/) with HTTP ANY method and Lambda integration +api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { + api, + handler: rootHandler, + httpPath: '/', +}); + +// create child routes from the root +api.root + // HTTP GET /foo + .addLambdaRoute('foo', 'Foo', { + target: handler, + method: apigatewayv2.HttpMethod.GET + }) + // HTTP ANY /foo/checkip + .addHttpRoute('checkip', 'FooCheckIp', { + targetUrl: 'https://checkip.amazonaws.com', + method: apigatewayv2.HttpMethod.ANY + }); ``` + +## Create any route with no `root` + +If we just need a specific route like `/some/very/deep/route/path` without the `root(/)` and make all requests to other paths go to the `$default`, we can simply create it like this: + +```ts +// create a HTTP API with HTTP Proxy Integration as the $default +new apigatewayv2.HttpProxyApi(stack, 'HttpProxyApi', { + url: 'https://aws.amazon.com' +}); + + +// create a specific route +const someDeepLambdaRoute = new apigatewayv2.LambdaRoute(stack, 'SomeLambdaRoute', { + api, + handler, + httpPath: '/some/very/deep/route/path', +}); + +// print the full http url for this route +new cdk.CfnOutput(stack, 'RouteURL', { + value: someDeepLambdaRoute.fullUrl +}); + +// and build even more child routes from here +someDeepLambdaRoute + // HTTP ANY /some/very/deep/route/path/bar + .addLambdaRoute('bar', 'SomeDeepPathBar', { + target: handler, + method: apigatewayv2.HttpMethod.GET + }) + // HTTP ANY /some/very/deep/route/path/bar/checkip + .addHttpRoute('checkip', 'SomeDeepPathBarCheckIp', { + targetUrl: 'https://checkip.amazonaws.com', + method: apigatewayv2.HttpMethod.ANY + }); +``` \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts new file mode 100644 index 0000000000000..89913fb14cf9c --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -0,0 +1,264 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as apigatewayv2 from '../lib'; +import { CfnApiProps } from './apigatewayv2.generated'; + +/** + * the HTTP API interface + */ +export interface IApi extends cdk.IResource { + /** + * The ID of this API Gateway HTTP Api. + * @attribute + */ + readonly apiId: string; +} +/** + * the ApiBase interface + */ +export interface IApiBase extends IApi { + /** + * http url of this api + */ + readonly url: string +} + +/** + * HttpProxyApi interface + */ +export interface IHttpProxyApi extends IApi {} +/** + * LambdaProxyApi interface + */ +export interface ILambdaProxyApi extends IApi { } + +abstract class ApiBase extends cdk.Resource implements IApi { + /** + * API ID + */ + public abstract readonly apiId: string; + /** + * API URL + */ + public abstract readonly url: string; +} + +/** + * API properties + */ +export interface ApiProps extends cdk.StackProps { + /** + * API Name + * @default - the logic ID of the API + */ + readonly apiName?: string; + /** + * API protocol + * @default HTTP + */ + readonly protocol?: ApiProtocol; + /** + * the default integration target of this API + */ + readonly target: string; + +} + +/** + * API protocols + */ +export enum ApiProtocol { + /** + * HTTP protocol + */ + HTTP = 'HTTP', + /** + * Websocket protocol + */ + WEBSOCKET = 'WEBSOCKET' +} + +/** + * Api Resource Class + */ +export class Api extends ApiBase implements IApi { + /** + * import from ApiId + */ + public static fromApiId(scope: cdk.Construct, id: string, apiId: string): IApi { + class Import extends cdk.Resource implements IApi { + public readonly apiId = apiId; + } + + return new Import(scope, id); + } + /** + * the API identifer + */ + public readonly apiId: string; + /** + * AWS partition either `aws` or `aws-cn` + */ + public readonly partition: string; + /** + * AWS region of this stack + */ + public readonly region: string; + /** + * AWS account of this stack + */ + public readonly account: string; + /** + * AWS domain name either `amazonaws.com` or `amazonaws.com.cn` + */ + public readonly awsdn: string; + /** + * the full URL of this API + */ + public readonly url: string; + + constructor(scope: cdk.Construct, id: string, props?: ApiProps) { + super(scope, id, { + physicalName: props?.apiName || id, + }); + + this.region = cdk.Stack.of(this).region; + this.partition = this.region.startsWith('cn-') ? 'aws-cn' : 'aws'; + this.account = cdk.Stack.of(this).account; + this.awsdn = this.partition === 'aws-cn' ? 'amazonaws.com.cn' : 'amazonaws.com'; + + const apiProps: CfnApiProps = { + name: this.physicalName, + protocolType: props?.protocol ?? ApiProtocol.HTTP, + target: props?.target + }; + const api = new apigatewayv2.CfnApi(this, 'Resource', apiProps ); + this.apiId = api.ref; + this.url = `https://${this.apiId}.execute-api.${this.region}.${this.awsdn}`; + } + +} + +/** + * LambdaProxyApi properties interface + */ +export interface LambdaProxyApiProps { + /** + * API name + * @default - the logic ID of the API + */ + readonly apiName?: string, + /** + * Lambda handler function + */ + readonly handler: lambda.IFunction +} + +/** + * HttpProxyApi properties interface + */ +export interface HttpProxyApiProps { + /** + * API Name + * @default - the logic ID of the API + */ + readonly apiName?: string, + /** + * API URL + */ + readonly url: string +} + +/** + * API with Lambda Proxy integration as the default route + */ +export class LambdaProxyApi extends Api implements ILambdaProxyApi { + /** + * import from api id + */ + public static fromLambdaProxyApiId(scope: cdk.Construct, id: string, lambdaProxyApiId: string): ILambdaProxyApi { + class Import extends cdk.Resource implements ILambdaProxyApi { + public readonly apiId = lambdaProxyApiId; + } + return new Import(scope, id); + } + /** + * the root route of the API + */ + public root?: apigatewayv2.IRouteBase; + constructor(scope: cdk.Construct, id: string, props: LambdaProxyApiProps) { + super(scope, id, { + target: props.handler.functionArn + }); + + new lambda.CfnPermission(this, 'Permission', { + action: 'lambda:InvokeFunction', + principal: 'apigateway.amazonaws.com', + functionName: props.handler.functionName, + sourceArn: `arn:${this.partition}:execute-api:${this.region}:${this.account}:${this.apiId}/*/*`, + }); + + // this.root = new RootRoute(this, 'RootRoute', this, { + // target: props.handler.functionArn, + // }); + + new cdk.CfnOutput(this, 'LambdaProxyApiUrl', { + value: this.url + }); + } +} + +/** + * API with HTTP Proxy integration as the default route + */ +export class HttpProxyApi extends Api implements IHttpProxyApi { + /** + * import from api id + */ + public static fromHttpProxyApiId(scope: cdk.Construct, id: string, httpProxyApiId: string): IHttpProxyApi { + class Import extends cdk.Resource implements IHttpProxyApi { + public readonly apiId = httpProxyApiId; + } + return new Import(scope, id); + } + + /** + * the root route of the API + */ + public root?: apigatewayv2.IRouteBase; + constructor(scope: cdk.Construct, id: string, props: HttpProxyApiProps) { + super(scope, id, { + target: props.url, + apiName: props.apiName ?? id + }); + + new cdk.CfnOutput(this, 'HttpProxyApiUrl', { + value: this.url + }); + } +} + +// class RootRoute extends apigatewayv2.Route { +// public readonly parentRoute?: apigatewayv2.IRoute; +// public readonly api: apigatewayv2.Api; +// public readonly routeKey: string; +// public readonly httpPath: string; +// public readonly target?: string; +// public readonly fullUrl: string; +// public readonly routeId: string; + +// constructor(scope: cdk.Construct, id: string, api: apigatewayv2.Api, props: ApiProps) { +// super(scope, id, { +// api, +// httpPath: '/', +// target: props.target +// }); +// this.parentRoute = undefined; +// this.api = api; +// this.routeKey = '$default'; +// this.httpPath = '/'; +// this.target = props.target; +// this.fullUrl = this.api.url; +// this.routeId = 'root'; +// } +// } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts index cbfa720284216..0f65ba7e8463a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts @@ -1,2 +1,4 @@ // AWS::APIGatewayv2 CloudFormation Resources: export * from './apigatewayv2.generated'; +export * from './api'; +export * from './route'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts new file mode 100644 index 0000000000000..0390ebba654fd --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -0,0 +1,407 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as apigatewayv2 from '../lib'; +import { CfnRouteProps } from './apigatewayv2.generated'; + +/** + * the interface of the Route of API Gateway HTTP API + */ +export interface IRoute extends cdk.IResource { + /** + * ID of the Route + * @attribute + */ + readonly routeId: string; +} + +/** + * HttpRoute interface + */ +export interface IHttpRoute extends IRoute {} +/** + * LambdaRoute interface + */ +export interface ILambdaRoute extends IRoute { } + +/** + * the interface of the route attributes + */ +export interface RouteAttributes { + /** + * ID of this Route + * @attribute + */ + readonly routeId: string; +} + +/** + * the interface of the RouteBase + */ +export interface IRouteBase extends IRoute { + /** + * the ID of this API Gateway HttpApi. + * @attribute + */ + readonly api: apigatewayv2.IApiBase; + /** + * the key of this route + * @attribute + */ + readonly routeKey: string; + /** + * the parent of this route or undefinied for the root route + */ + readonly parentRoute?: IRoute; + /** + * full path of this route + */ + readonly httpPath: string; + + /** + * full URL of this route + */ + readonly fullUrl: string; + + /** + * add a child route with HTTP integration for this parent route + */ + addHttpRoute(pathPart: string, id: string, options: HttpRouteOptions): Route; + + /** + * add a child route with Lambda integration for this parent route + */ + addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route; +} + +/** + * the interface of the RouteOptionsBase + */ +export interface RouteOptionsBase {} + +/** + * options of HttpRoute + */ +export interface HttpRouteOptions extends RouteOptionsBase { + /** + * URL of the integration target + */ + readonly targetUrl: string + + /** + * HTTP method + * @default HttpMethod.ANY + */ + readonly method?: HttpMethod + + /** + * Integration Method + * @default HttpMethod.ANY + */ + readonly integrationMethod?: HttpMethod; +} + +/** + * Options for the Route with Lambda integration + */ +export interface LambdaRouteOptions extends RouteOptionsBase { + /** + * target lambda function + */ + readonly target: lambda.IFunction + + /** + * HTTP method + * @default HttpMethod.ANY + */ + readonly method?: HttpMethod + + /** + * Integration method + * @default HttpMethod.ANY + */ + readonly integrationMethod?: HttpMethod; +} + +abstract class RouteBase extends cdk.Resource implements IRouteBase { + public abstract readonly api: apigatewayv2.IApiBase; + public abstract readonly routeKey: string; + public abstract readonly httpPath: string; + public abstract readonly routeId: string; + public abstract readonly fullUrl: string; + + /** + * create a child route with Lambda proxy integration + */ + public addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route { + const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; + const httpMethod = options.method; + // const routeKey = `${httpMethod} ${httpPath}`; + + return new LambdaRoute(this, id, { + api: this.api, + handler: options.target, + httpPath, + httpMethod, + parent: this, + pathPart, + }); + } + + /** + * create a child route with HTTP proxy integration + */ + public addHttpRoute(pathPart: string, id: string, options: HttpRouteOptions): Route { + const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; + const httpMethod = options.method; + // const routeKey = `${httpMethod} ${httpPath}`; + + return new HttpRoute(this, id, { + api: this.api, + targetUrl: options.targetUrl, + httpPath, + httpMethod, + parent: this, + pathPart, + }); + } + +} + +/** + * all HTTP methods + */ +export enum HttpMethod { + /** + * HTTP ANY + */ + ANY = 'ANY', + /** + * HTTP GET + */ + GET = 'GET', + /** + * HTTP PUT + */ + PUT = 'PUT', + /** + * HTTP POST + */ + POST = 'POST', + /** + * HTTP DELETE + */ + DELETE = 'DELETE', +} + +/** + * Route base properties + */ +export interface RouteBaseProps extends cdk.StackProps { + /** + * route name + * @default - the logic ID of this route + */ + readonly routeName?: string; + /** + * the API the route is associated with + */ + readonly api: apigatewayv2.IApiBase; + /** + * HTTP method of this route + * @default HttpMethod.ANY + */ + readonly httpMethod?: HttpMethod; + /** + * full http path of this route + */ + readonly httpPath: string; + /** + * parent of this route + * @default - undefinied if no parentroute d + */ + readonly parent?: IRouteBase + /** + * path part of this route + * @default '' + */ + readonly pathPart?: string; + +} + +/** + * Route properties + */ +export interface RouteProps extends RouteBaseProps { + /** + * target of this route + */ + readonly target: string; + +} + +/** + * Route class that creates the Route for API Gateway HTTP API + */ +export class Route extends RouteBase { + + /** + * import from route attributes + */ + public static fromRouteAttributes(scope: cdk.Construct, id: string, attrs: RouteAttributes): IRoute { + class Import extends cdk.Resource implements IRoute { + public routeId = attrs.routeId; + } + return new Import(scope, id); + } + public readonly fullUrl: string; + /** + * the api interface of this route + */ + public readonly api: apigatewayv2.IApiBase; + /** + * the route key of this route + */ + public readonly routeKey: string; + /** + * the full http path of this route + */ + public readonly httpPath: string; + /** + * http method of this route + */ + public readonly httpMethod: HttpMethod; + /** + * route id from the `Ref` function + */ + public readonly routeId: string; + + constructor(scope: cdk.Construct, id: string, props: RouteProps) { + super(scope, id); + + this.api = props.api; + this.httpPath = props.httpPath; + this.httpMethod = props.httpMethod ?? HttpMethod.ANY; + + this.routeKey = `${props.httpMethod} ${props.httpPath}`; + const routeProps: CfnRouteProps = { + apiId: this.api.apiId, + routeKey: this.routeKey, + target: props.target + }; + const route = new apigatewayv2.CfnRoute(this, 'Resoruce', routeProps); + this.routeId = route.ref; + this.fullUrl = `${this.api.url}${this.httpPath}`; + } +} + +/** + * Lambda route properties + */ +export interface LambdaRouteProps extends RouteBaseProps { + /** + * Lambda handler function + */ + readonly handler: lambda.IFunction +} + +/** + * HTTP route properties + */ +export interface HttpRouteProps extends RouteBaseProps { + /** + * target URL + */ + readonly targetUrl: string, + /** + * integration method + * @default HttpMethod.ANY + */ + readonly integrationMethod?: HttpMethod +} + +enum IntegrationType { + AWS_PROXY = 'AWS_PROXY', + HTTP_PROXY = 'HTTP_PROXY' +} + +/** + * Route with Lambda Proxy integration + */ +export class LambdaRoute extends Route implements ILambdaRoute { + /** + * import from lambdaRouteId + */ + public static fromLambdaRouteId(scope: cdk.Construct, id: string, lambdaRouteId: string): ILambdaRoute { + class Import extends cdk.Resource implements ILambdaRoute { + public readonly routeId = lambdaRouteId; + } + return new Import(scope, id); + } + constructor(scope: cdk.Construct, id: string, props: LambdaRouteProps) { + const region = cdk.Stack.of(scope).region; + const account = cdk.Stack.of(scope).account; + const partition = region.startsWith('cn-') ? 'aws-cn' : 'aws'; + const httpMethod = props.httpMethod ?? HttpMethod.ANY; + + // create a Lambda Proxy integration + const integ = new apigatewayv2.CfnIntegration(scope, `Integration-${id}`, { + apiId: props.api.apiId, + integrationMethod: HttpMethod.POST, + integrationType: IntegrationType.AWS_PROXY, + payloadFormatVersion: '1.0', + integrationUri: `arn:${partition}:apigateway:${region}:lambda:path/2015-03-31/functions/${props.handler.functionArn}/invocations` + }); + + // create permission + new lambda.CfnPermission(scope, `Permission-${id}`, { + action: 'lambda:InvokeFunction', + principal: 'apigateway.amazonaws.com', + functionName: props.handler.functionName, + sourceArn: `arn:${partition}:execute-api:${region}:${account}:${props.api.apiId}/*/*`, + }); + + super(scope, id, { + api: props.api, + httpMethod, + httpPath: props.httpPath, + target: `integrations/${integ.ref}`, + parent: props.parent, + pathPart: props.pathPart ?? '' + }); + } +} + +/** + * Route with HTTP Proxy integration + */ +export class HttpRoute extends Route implements IHttpRoute { + /** + * import from httpRouteId + */ + public static fromHttpRouteId(scope: cdk.Construct, id: string, httpRouteId: string): IHttpRoute { + class Import extends cdk.Resource implements IHttpRoute { + public readonly routeId = httpRouteId; + } + return new Import(scope, id); + } + constructor(scope: cdk.Construct, id: string, props: HttpRouteProps) { + const httpMethod = props.httpMethod ?? HttpMethod.ANY; + + // create a HTTP Proxy integration + const integ = new apigatewayv2.CfnIntegration(scope, `Integration${id}`, { + apiId: props.api.apiId, + integrationMethod: props.integrationMethod ?? HttpMethod.ANY, + integrationType: IntegrationType.HTTP_PROXY, + payloadFormatVersion: '1.0', + integrationUri: props.targetUrl + }); + super(scope, id, { + api: props.api, + httpMethod, + httpPath: props.httpPath, + target: `integrations/${integ.ref}`, + parent: props.parent, + pathPart: props.pathPart + }); + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 04a1257e08152..78932144472ee 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -82,14 +82,17 @@ "devDependencies": { "@aws-cdk/assert": "1.25.0", "cdk-build-tools": "1.25.0", + "cdk-integ-tools": "1.25.0", "cfn2ts": "1.25.0", "pkglint": "1.25.0" }, "dependencies": { - "@aws-cdk/core": "1.25.0" + "@aws-cdk/core": "1.25.0", + "@aws-cdk/aws-lambda": "1.25.0" }, "peerDependencies": { - "@aws-cdk/core": "1.25.0" + "@aws-cdk/core": "1.25.0", + "@aws-cdk/aws-lambda": "1.25.0" }, "engines": { "node": ">= 10.3.0" diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts index e394ef336bfb4..864527891ac41 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts @@ -1,6 +1,318 @@ -import '@aws-cdk/assert/jest'; -import {} from '../lib'; +// import '@aws-cdk/assert/jest'; +import { countResources, expect as expectCDK, haveOutput, haveResource } from '@aws-cdk/assert'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as apigatewayv2 from '../lib'; -test('No tests are specified for this package', () => { +// test('No tests are specified for this package', () => { +// expect(true).toBe(true); +// }); + +let stack = new cdk.Stack(); +let handler: lambda.Function; +let rootHandler: lambda.Function; + +beforeEach(() => { + stack = new cdk.Stack(); + handler = new lambda.Function(stack, 'MyFunc', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code: new lambda.InlineCode(` +import json +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps(event) + }`), + reservedConcurrentExecutions: 10, + }); + + rootHandler = new lambda.Function(stack, 'RootFunc', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code: new lambda.InlineCode(` +import json, os +def handler(event, context): + whoami = os.environ['WHOAMI'] + http_path = os.environ['HTTP_PATH'] + return { + 'statusCode': 200, + 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path }) + }`), + environment: { + WHOAMI: 'root', + HTTP_PATH: '/' + }, + }); +}); + +test('create LambdaProxyApi correctly', () => { + // WHEN + + new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { + handler, + }); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + // expectCDK(stack).to(countResources('AWS::Lambda::Function', 1)); + // expectCDK(stack).to(countResources('AWS::Lambda::Permission', 1)); + expectCDK(stack).to(haveOutput({ + outputName: 'LambdaProxyApiLambdaProxyApiUrl2866EF1B' + })); +}); + +test('create HttpProxyApi correctly', () => { + // WHEN + new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { + url: 'https://checkip.amazonaws.com' + }); + // THEN + expectCDK(stack).to(haveResource('AWS::ApiGatewayV2::Api', { + Name: "LambdaProxyApi", + ProtocolType: "HTTP", + Target: "https://checkip.amazonaws.com" + } )); + expectCDK(stack).to(haveOutput({ + outputName: 'LambdaProxyApiHttpProxyApiUrl378A7258' + })); +}); + +test('create LambdaRoute correctly for HttpProxyApi', () => { + // WHEN + const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { + url: 'https://checkip.amazonaws.com' + }); + api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { + api, + handler: rootHandler, + httpPath: '/', + }); + + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); + expectCDK(stack).to(countResources('AWS::Lambda::Permission', 1)); +}); + +test('create HttpRoute correctly for HttpProxyApi', () => { + // WHEN + const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { + url: 'https://checkip.amazonaws.com' + }); + api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { + api, + targetUrl: 'https://www.amazon.com', + httpPath: '/', + }); + + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +}); + +test('create LambdaRoute correctly for LambdaProxyApi', () => { + // WHEN + const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { + handler + }); + api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { + api, + handler: rootHandler, + httpPath: '/', + }); + + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); + expectCDK(stack).to(countResources('AWS::Lambda::Permission', 2)); +}); + +test('create HttpRoute correctly for LambdaProxyApi', () => { + // WHEN + const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { + handler + }); + api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { + api, + targetUrl: 'https://www.amazon.com', + httpPath: '/', + }); + + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +}); + +test('addLambdaRoute and addHttpRoute correctly from an existing route', () => { + // WHEN + const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { + handler + }); + api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { + api, + targetUrl: 'https://www.amazon.com', + httpPath: '/', + }); + + api.root + // HTTP GET /foo + .addLambdaRoute('foo', 'Foo', { + target: handler, + method: apigatewayv2.HttpMethod.GET + }) + // HTTP ANY /foo/checkip + .addHttpRoute('checkip', 'FooCheckIp', { + targetUrl: 'https://checkip.amazonaws.com', + method: apigatewayv2.HttpMethod.ANY + }); + + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 3)); +}); + +test('import API correctly', () => { + // WHEN + const api = apigatewayv2.Api.fromApiId(stack, 'ImportedApi', 'foo'); + + // THEN + expect(api).toHaveProperty('apiId'); +}); + +test('import HttpProxyApi correctly', () => { + // WHEN + const api = apigatewayv2.HttpProxyApi.fromHttpProxyApiId(stack, 'ImportedApi', 'foo'); + + // THEN + expect(api).toHaveProperty('apiId'); +}); + +test('import LambdaProxyApi correctly', () => { + // WHEN + const api = apigatewayv2.LambdaProxyApi.fromLambdaProxyApiId(stack, 'ImportedApi', 'foo'); + + // THEN + expect(api).toHaveProperty('apiId'); +}); + +test('import Route correctly', () => { + // WHEN + const route = apigatewayv2.Route.fromRouteAttributes(stack, 'ImportedRoute', { + routeId: 'foo' + }); + + // THEN + expect(route).toHaveProperty('routeId'); +}); + +test('import LambdaRoute correctly', () => { + // WHEN + const route = apigatewayv2.LambdaRoute.fromLambdaRouteId(stack, 'ImportedRoute', 'foo'); + // THEN + expect(route).toHaveProperty('routeId'); +}); + +test('import HttpRoute correctly', () => { + // WHEN + const route = apigatewayv2.HttpRoute.fromHttpRouteId(stack, 'ImportedRoute', 'foo'); + // THEN + expect(route).toHaveProperty('routeId'); +}); + +test('import LambdaRoute correctly', () => { + // WHEN + const route = apigatewayv2.LambdaRoute.fromLambdaRouteId(stack, 'ImportedRoute', 'foo'); + // THEN + expect(route).toHaveProperty('routeId'); +}); + +test('create api correctly with no props', () => { + // WHEN + new apigatewayv2.Api(stack, 'API'); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +}); + +test('assign aws partition correctly by region for api', () => { + // WHEN + const cnstack = new cdk.Stack(undefined, undefined, { + env: { + region: 'cn-north-1' + } + }); + const api = new apigatewayv2.Api(cnstack, 'API'); + // THEN + expect(api.partition).toBe('aws-cn'); + expect(api.awsdn).toBe('amazonaws.com.cn'); +}); + +test('assign aws partition correctly by region for lambda route', () => { + // WHEN + const cnstack = new cdk.Stack(undefined, undefined, { + env: { + region: 'cn-north-1' + } + }); + const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { + url: 'https://checkip.amazonaws.com' + }); + + new apigatewayv2.LambdaRoute(cnstack, 'API', { + api, + handler, + httpPath: '/foo' + }); + // THEN expect(true).toBe(true); }); + +test('create api correctly with specific protocol specified', () => { + // WHEN + new apigatewayv2.Api(stack, 'API', { + target: '', + protocol: apigatewayv2.ApiProtocol.WEBSOCKET + }); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +}); + +test('create HttpProxyApi correctly with specific api name specified', () => { + // WHEN + new apigatewayv2.HttpProxyApi(stack, 'API', { + apiName: 'foo', + url: 'https://www.amazon.com' + }); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +}); + +test('create Route correctly with no httpMethod specified', () => { + // WHEN + const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { + url: 'https://checkip.amazonaws.com' + }); + new apigatewayv2.Route(stack, 'Route', { + api, + httpPath: '/foo', + target: 'https://www.amazon.com' + }); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +}); + +test('create HttpRoute correctly with specific integrationMethod', () => { + // WHEN + const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { + url: 'https://checkip.amazonaws.com' + }); + + new apigatewayv2.HttpRoute(stack, 'Route', { + api, + httpPath: '/foo', + targetUrl: 'https://www.amazon.com', + integrationMethod: apigatewayv2.HttpMethod.GET + }); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 1)); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts new file mode 100644 index 0000000000000..1c16ac5f5fe62 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts @@ -0,0 +1,98 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as apigatewayv2 from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'ApiagtewayV2HttpApi'); + +const handler = new lambda.Function(stack, 'MyFunc', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code: new lambda.InlineCode(` +import json +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps(event) + }`), +}); + +const rootHandler = new lambda.Function(stack, 'RootFunc', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code: new lambda.InlineCode(` +import json, os +def handler(event, context): + whoami = os.environ['WHOAMI'] + http_path = os.environ['HTTP_PATH'] + return { + 'statusCode': 200, + 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path }) + }`), + environment: { + WHOAMI: 'root', + HTTP_PATH: '/' + }, +}); + +// Create a HTTP API with Lambda Proxy Integration as $default route +const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { + handler +}); + +// Create the root route(/) with HTTP ANY method and Lambda integration +api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { + api, + handler: rootHandler, + httpPath: '/', +}); + +api.root + // HTTP GET /foo + .addLambdaRoute('foo', 'Foo', { + target: handler, + method: apigatewayv2.HttpMethod.GET + }) + // HTTP ANY /foo/checkip + .addHttpRoute('checkip', 'FooCheckIp', { + targetUrl: 'https://checkip.amazonaws.com', + method: apigatewayv2.HttpMethod.ANY + }); + +// api.root +// // HTTP GET /bar +// .addLambdaRoute('bar', 'Bar', { +// target: handler2, +// method: apigatewayv2.HttpMethod.ANY +// }); + +// Create a HTTP API with HTTP Proxy Integration +new apigatewayv2.HttpProxyApi(stack, 'HttpProxyApi', { + url: 'https://aws.amazon.com' +}); + +const someDeepLambdaRoute = new apigatewayv2.LambdaRoute(stack, 'SomeLambdaRoute', { + api, + handler, + httpPath: '/some/very/deep/route/path', +}); + +// new cdk.CfnOutput(stack, 'RouteURL', { +// value: someDeepLambdaRoute.fullUrl +// }); + +someDeepLambdaRoute + // HTTP ANY /some/very/deep/route/path/bar + .addLambdaRoute('bar', 'SomeDeepPathBar', { + target: handler, + method: apigatewayv2.HttpMethod.GET + }) + // HTTP ANY /some/very/deep/route/path/bar/checkip + .addHttpRoute('checkip', 'SomeDeepPathBarCheckIp', { + targetUrl: 'https://checkip.amazonaws.com', + method: apigatewayv2.HttpMethod.ANY + }); + +new cdk.CfnOutput(stack, 'SomeDeepLambdaRouteURL', { + value: someDeepLambdaRoute.fullUrl +}); From 2ba95d85b6617836f6175eed2ef8df723b3a7862 Mon Sep 17 00:00:00 2001 From: Pahud Date: Tue, 25 Feb 2020 13:49:56 +0800 Subject: [PATCH 02/42] add the expected.json for integ test --- .../test/integ.ap.expected.json | 601 ++++++++++++++++++ 1 file changed, 601 insertions(+) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.expected.json diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.expected.json new file mode 100644 index 0000000000000..3d1c9cdc6c3ec --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.expected.json @@ -0,0 +1,601 @@ +{ + "Resources": { + "MyFuncServiceRole54065130": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "MyFunc8A243A2C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "\nimport json\ndef handler(event, context):\n return {\n 'statusCode': 200,\n 'body': json.dumps(event)\n }" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFuncServiceRole54065130", + "Arn" + ] + }, + "Runtime": "python3.7" + }, + "DependsOn": [ + "MyFuncServiceRole54065130" + ] + }, + "RootFuncServiceRoleE4AA9E41": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "RootFuncF39FB174": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "\nimport json, os\ndef handler(event, context):\n whoami = os.environ['WHOAMI']\n http_path = os.environ['HTTP_PATH']\n return {\n 'statusCode': 200,\n 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path })\n }" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "RootFuncServiceRoleE4AA9E41", + "Arn" + ] + }, + "Runtime": "python3.7", + "Environment": { + "Variables": { + "WHOAMI": "root", + "HTTP_PATH": "/" + } + } + }, + "DependsOn": [ + "RootFuncServiceRoleE4AA9E41" + ] + }, + "LambdaProxyApi67594471": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "LambdaProxyApi", + "ProtocolType": "HTTP", + "Target": { + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] + } + } + }, + "LambdaProxyApiPermission126890B4": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunc8A243A2C" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "LambdaProxyApi67594471" + }, + "/*/*" + ] + ] + } + } + }, + "IntegrationRootRoute": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationMethod": "POST", + "IntegrationUri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "RootFuncF39FB174", + "Arn" + ] + }, + "/invocations" + ] + ] + }, + "PayloadFormatVersion": "1.0" + } + }, + "PermissionRootRoute": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "RootFuncF39FB174" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "LambdaProxyApi67594471" + }, + "/*/*" + ] + ] + } + } + }, + "RootRouteResoruceDCB7CC4A": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "RouteKey": "ANY /", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "IntegrationRootRoute" + } + ] + ] + } + } + }, + "RootRouteIntegrationFoo7869F96E": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationMethod": "POST", + "IntegrationUri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] + }, + "/invocations" + ] + ] + }, + "PayloadFormatVersion": "1.0" + } + }, + "RootRoutePermissionFoo06AF804C": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunc8A243A2C" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "LambdaProxyApi67594471" + }, + "/*/*" + ] + ] + } + } + }, + "RootRouteFooResoruceB48AF356": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "RouteKey": "GET /foo", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "RootRouteIntegrationFoo7869F96E" + } + ] + ] + } + } + }, + "RootRouteFooIntegrationFooCheckIp87863B2F": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "IntegrationType": "HTTP_PROXY", + "IntegrationMethod": "ANY", + "IntegrationUri": "https://checkip.amazonaws.com", + "PayloadFormatVersion": "1.0" + } + }, + "RootRouteFooFooCheckIpResoruce7E7874A1": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "RouteKey": "ANY /foo/checkip", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "RootRouteFooIntegrationFooCheckIp87863B2F" + } + ] + ] + } + } + }, + "HttpProxyApiD0217C67": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "HttpProxyApi", + "ProtocolType": "HTTP", + "Target": "https://aws.amazon.com" + } + }, + "IntegrationSomeLambdaRoute": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationMethod": "POST", + "IntegrationUri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] + }, + "/invocations" + ] + ] + }, + "PayloadFormatVersion": "1.0" + } + }, + "PermissionSomeLambdaRoute": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunc8A243A2C" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "LambdaProxyApi67594471" + }, + "/*/*" + ] + ] + } + } + }, + "SomeLambdaRouteResoruce8A88D44E": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "RouteKey": "ANY /some/very/deep/route/path", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "IntegrationSomeLambdaRoute" + } + ] + ] + } + } + }, + "SomeLambdaRouteIntegrationSomeDeepPathBar96AE7930": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationMethod": "POST", + "IntegrationUri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] + }, + "/invocations" + ] + ] + }, + "PayloadFormatVersion": "1.0" + } + }, + "SomeLambdaRoutePermissionSomeDeepPathBar34A20879": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunc8A243A2C" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "LambdaProxyApi67594471" + }, + "/*/*" + ] + ] + } + } + }, + "SomeLambdaRouteSomeDeepPathBarResoruceC800EE14": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "RouteKey": "GET /some/very/deep/route/path/bar", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "SomeLambdaRouteIntegrationSomeDeepPathBar96AE7930" + } + ] + ] + } + } + }, + "SomeLambdaRouteSomeDeepPathBarIntegrationSomeDeepPathBarCheckIp911CF681": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "IntegrationType": "HTTP_PROXY", + "IntegrationMethod": "ANY", + "IntegrationUri": "https://checkip.amazonaws.com", + "PayloadFormatVersion": "1.0" + } + }, + "SomeLambdaRouteSomeDeepPathBarSomeDeepPathBarCheckIpResoruce5039BD41": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "RouteKey": "ANY /some/very/deep/route/path/bar/checkip", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "SomeLambdaRouteSomeDeepPathBarIntegrationSomeDeepPathBarCheckIp911CF681" + } + ] + ] + } + } + } + }, + "Outputs": { + "LambdaProxyApiLambdaProxyApiUrl2866EF1B": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "LambdaProxyApi67594471" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + }, + "HttpProxyApiHttpProxyApiUrl25461D7F": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "HttpProxyApiD0217C67" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + }, + "SomeDeepLambdaRouteURL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "LambdaProxyApi67594471" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com/some/very/deep/route/path" + ] + ] + } + } + } +} \ No newline at end of file From 9f08ed4aa9c6d79f6b7673744f301f919da07735 Mon Sep 17 00:00:00 2001 From: Pahud Date: Tue, 25 Feb 2020 14:05:20 +0800 Subject: [PATCH 03/42] fix package.json --- .../@aws-cdk/aws-apigatewayv2/package.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 6cb684ca64149..01087210b972b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -80,22 +80,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "1.25.0", - "cdk-build-tools": "1.25.0", - "cdk-integ-tools": "1.25.0", - "cfn2ts": "1.25.0", - "pkglint": "1.25.0" + "@aws-cdk/assert": "999.0.0", + "cdk-build-tools": "999.0.0", + "cdk-integ-tools": "999.0.0", + "cfn2ts": "999.0.0", + "pkglint": "999.0.0" }, "dependencies": { - "@aws-cdk/core": "1.25.0", - "@aws-cdk/aws-lambda": "1.25.0" + "@aws-cdk/core": "999.0.0", + "@aws-cdk/aws-lambda": "999.0.0" }, "peerDependencies": { - "@aws-cdk/core": "1.25.0", - "@aws-cdk/aws-lambda": "1.25.0" + "@aws-cdk/core": "999.0.0", + "@aws-cdk/aws-lambda": "999.0.0" }, "engines": { "node": ">= 10.3.0" }, "stability": "experimental" -} \ No newline at end of file +} From febd6a3df8cd683f6f8c0de493d8afcc6dc03b56 Mon Sep 17 00:00:00 2001 From: Pahud Date: Fri, 13 Mar 2020 13:08:52 +0800 Subject: [PATCH 04/42] udpate README --- packages/@aws-cdk/aws-apigatewayv2/README.md | 132 +++++++------------ 1 file changed, 50 insertions(+), 82 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 5f119398d1811..fa496b5ef52ed 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -19,121 +19,83 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. -`aws-apigatewayv2` supports the `HTTP` API for Amazon API Gateway. -## Examples: +## Introduction -### HTTP API with Lambda proxy integration as the `$default` route +Amazon API Gateway is an AWS service for creating, publishing, maintaining, monitoring, and securing REST, HTTP, and WebSocket APIs at any scale. API developers can create APIs that access AWS or other web services, as well as data stored in the AWS Cloud. As an API Gateway API developer, you can create APIs for use in your own client applications. Or you can make your APIs available to third-party app developers. For more infomation, read the [Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html). + + +## API + +This construct library at this moment implements API resources from [AWS::ApiGatewayV2::Api](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-api.html), which supports both `WebSocket APIs` and `HTTP APIs`. + +For more information about WebSocket APIs, see [About WebSocket APIs in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-overview.html) in the _API Gateway Developer Guide_. For more information about HTTP APIs, see [HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) in the _API Gateway Developer Guide_. -```ts -import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; -import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; - -const app = new cdk.App(); -const stack = new cdk.Stack(app, 'ApiagtewayV2HttpApi'); - -const handler = new lambda.Function(stack, 'MyFunc', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code: new lambda.InlineCode(` -import json -def handler(event, context): - return { - 'statusCode': 200, - 'body': json.dumps(event) - }`), -}); -// Create a HTTP API with Lambda Proxy Integration as $default route +### HTTP API + +`CfnApi` is the L1 construct to create either `WebSocket APIs` or `HTTP APIs`. At this moment, `HTTP APIs` supports both `Lambda Proxy Integration` and `HTTP Proxy Integration`. See [Working with AWS Lambda Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) and [Working with HTTP Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html) for more information. + + +To create `HTTP APIs` with `Lambda Proxy Integration`, simply use `LambdaProxyApi`. + + +```ts +// Create a HTTP API with Lambda Proxy Integration as its $default route const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { handler }); ``` -### HTTP API with HTTP proxy integration as the `$default` route + +To create `HTTP APIs` with Lambda proxy integration, use `HttpProxyApi` instead. ```ts +// Create a HTTP API with HTTP Proxy Integration new apigatewayv2.HttpProxyApi(stack, 'HttpProxyApi', { url: 'https://aws.amazon.com' }); ``` -## Root Route -Create the `root(/)` route of the API +### WebSocket API -```ts +The WebSocket APIs are not supported yet in this L2 construct library, however, as `Api` class is provided, it's still possible to create the Websocket APIs with the `Api` class. -// prepare the root handler function -const rootHandler = new lambda.Function(stack, 'RootFunc', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code: new lambda.InlineCode(` -import json, os -def handler(event, context): - whoami = os.environ['WHOAMI'] - http_path = os.environ['HTTP_PATH'] - return { - 'statusCode': 200, - 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path }) - }`), - environment: { - WHOAMI: 'root', - HTTP_PATH: '/' - }, -}); +## Route -// create a HTTP API with Lambda Proxy Integration as $default route -const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { - handler -}); +Routes direct incoming API requests to backend resources. Routes consist of two parts: an HTTP method and a resource path. For example, `GET /pets`. You can define specific HTTP methods for your route, or use the ANY method to match all methods that you haven't defined for a resource. You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See [Working with Routes for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). -// create the root route(/) with HTTP ANY method and Lambda integration -api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { +When you create HTTP APIs with either `Lambda Proxy Integration` or `HTTP Proxy Integration`, the `$default route` will be created as well. + +To create a specific route for an existing `Api` resource, specify the `httpPath` and an optional `httpMethod` property. + +```ts +// create a specific 'GET /some/very/deep/route/path' route with Lambda proxy integration for an existing HTTP API +const someDeepLambdaRoute = new apigatewayv2.LambdaRoute(stack, 'SomeLambdaRoute', { api, - handler: rootHandler, - httpPath: '/', + handler, + httpPath: '/some/very/deep/route/path', + // optional + httpMethod: HttpMethod.GET }); - -// create child routes from the root -api.root - // HTTP GET /foo - .addLambdaRoute('foo', 'Foo', { - target: handler, - method: apigatewayv2.HttpMethod.GET - }) - // HTTP ANY /foo/checkip - .addHttpRoute('checkip', 'FooCheckIp', { - targetUrl: 'https://checkip.amazonaws.com', - method: apigatewayv2.HttpMethod.ANY - }); ``` -## Create any route with no `root` - -If we just need a specific route like `/some/very/deep/route/path` without the `root(/)` and make all requests to other paths go to the `$default`, we can simply create it like this: +### `addLambdaRoute` and `addHttpRoute` -```ts -// create a HTTP API with HTTP Proxy Integration as the $default -new apigatewayv2.HttpProxyApi(stack, 'HttpProxyApi', { - url: 'https://aws.amazon.com' -}); +To extend the routes from an existing HTTP API, use `addLambdaRoute` for Lambda proxy integration or `addHttpRoute` for HTTP Proxy Intgegration. +Consider the providewd example above -// create a specific route +```ts +// create a specific 'GET /some/very/deep/route/path' route with Lambda proxy integration for an existing HTTP API const someDeepLambdaRoute = new apigatewayv2.LambdaRoute(stack, 'SomeLambdaRoute', { api, handler, httpPath: '/some/very/deep/route/path', + httpMethod: HttpMethod.GET }); -// print the full http url for this route -new cdk.CfnOutput(stack, 'RouteURL', { - value: someDeepLambdaRoute.fullUrl -}); - -// and build even more child routes from here someDeepLambdaRoute // HTTP ANY /some/very/deep/route/path/bar .addLambdaRoute('bar', 'SomeDeepPathBar', { @@ -145,4 +107,10 @@ someDeepLambdaRoute targetUrl: 'https://checkip.amazonaws.com', method: apigatewayv2.HttpMethod.ANY }); -``` \ No newline at end of file + +// print the full URL in the Outputs +new cdk.CfnOutput(stack, 'SomeDeepLambdaRouteURL', { + value: someDeepLambdaRoute.fullUrl +}); +``` + From 33dead4d13223606c047c3eed63fe799492e1aeb Mon Sep 17 00:00:00 2001 From: Pahud Date: Fri, 13 Mar 2020 13:18:45 +0800 Subject: [PATCH 05/42] update README --- packages/@aws-cdk/aws-apigatewayv2/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index fa496b5ef52ed..22684e27c8271 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -47,7 +47,7 @@ const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { }); ``` -To create `HTTP APIs` with Lambda proxy integration, use `HttpProxyApi` instead. +To create `HTTP APIs` with `HTTP Proxy Integration`, use `HttpProxyApi` instead. ```ts // Create a HTTP API with HTTP Proxy Integration @@ -59,12 +59,12 @@ new apigatewayv2.HttpProxyApi(stack, 'HttpProxyApi', { ### WebSocket API -The WebSocket APIs are not supported yet in this L2 construct library, however, as `Api` class is provided, it's still possible to create the Websocket APIs with the `Api` class. +The WebSocket API is not implemented yet in this L2 construct library, however, as `Api` class is provided, it's still possible to create the Websocket APIs with the `Api` class. ## Route -Routes direct incoming API requests to backend resources. Routes consist of two parts: an HTTP method and a resource path. For example, `GET /pets`. You can define specific HTTP methods for your route, or use the ANY method to match all methods that you haven't defined for a resource. You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See [Working with Routes for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). +`Routes` direct incoming API requests to backend resources. `Routes` consist of two parts: an HTTP method and a resource path—for example, `GET /pets`. You can define specific HTTP methods for your route, or use the `ANY` method to match all methods that you haven't defined for a resource. You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See [Working with Routes for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). When you create HTTP APIs with either `Lambda Proxy Integration` or `HTTP Proxy Integration`, the `$default route` will be created as well. From 02f96c272aa164998af7a4140e917cfff8bf7b3e Mon Sep 17 00:00:00 2001 From: Pahud Date: Fri, 13 Mar 2020 13:24:43 +0800 Subject: [PATCH 06/42] update README --- packages/@aws-cdk/aws-apigatewayv2/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 22684e27c8271..df5b4229662c6 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -31,6 +31,8 @@ This construct library at this moment implements API resources from [AWS::ApiGat For more information about WebSocket APIs, see [About WebSocket APIs in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-overview.html) in the _API Gateway Developer Guide_. For more information about HTTP APIs, see [HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) in the _API Gateway Developer Guide_. +To create [REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html), use `@aws-cdk/aws-apigateway` instead. + ### HTTP API From 46d3be47ded5ed4d94efeba2cb0835eca6032eaf Mon Sep 17 00:00:00 2001 From: Pahud Date: Fri, 13 Mar 2020 13:46:33 +0800 Subject: [PATCH 07/42] remove comments and minor fix --- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 27 +------------------ .../aws-apigatewayv2/test/integ.ap.ts | 1 + 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 89913fb14cf9c..755172e7f00f4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -236,29 +236,4 @@ export class HttpProxyApi extends Api implements IHttpProxyApi { value: this.url }); } -} - -// class RootRoute extends apigatewayv2.Route { -// public readonly parentRoute?: apigatewayv2.IRoute; -// public readonly api: apigatewayv2.Api; -// public readonly routeKey: string; -// public readonly httpPath: string; -// public readonly target?: string; -// public readonly fullUrl: string; -// public readonly routeId: string; - -// constructor(scope: cdk.Construct, id: string, api: apigatewayv2.Api, props: ApiProps) { -// super(scope, id, { -// api, -// httpPath: '/', -// target: props.target -// }); -// this.parentRoute = undefined; -// this.api = api; -// this.routeKey = '$default'; -// this.httpPath = '/'; -// this.target = props.target; -// this.fullUrl = this.api.url; -// this.routeId = 'root'; -// } -// } \ No newline at end of file +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts index 1c16ac5f5fe62..d3860c3403438 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts @@ -75,6 +75,7 @@ const someDeepLambdaRoute = new apigatewayv2.LambdaRoute(stack, 'SomeLambdaRoute api, handler, httpPath: '/some/very/deep/route/path', + httpMethod: apigatewayv2.HttpMethod.GET }); // new cdk.CfnOutput(stack, 'RouteURL', { From 7265479c1be4059ec7b5c4cf7d91d043c9aeb223 Mon Sep 17 00:00:00 2001 From: Pahud Date: Fri, 13 Mar 2020 17:41:17 +0800 Subject: [PATCH 08/42] fix package.json --- packages/@aws-cdk/aws-apigatewayv2/package.json | 8 ++++++-- .../{integ.ap.expected.json => integ.api.expected.json} | 2 +- .../aws-apigatewayv2/test/{integ.ap.ts => integ.api.ts} | 0 3 files changed, 7 insertions(+), 3 deletions(-) rename packages/@aws-cdk/aws-apigatewayv2/test/{integ.ap.expected.json => integ.api.expected.json} (99%) rename packages/@aws-cdk/aws-apigatewayv2/test/{integ.ap.ts => integ.api.ts} (100%) diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index d082ac26d95b7..dd1af87737dcf 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -82,14 +82,18 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "pkglint": "0.0.0" }, "dependencies": { - "@aws-cdk/core": "0.0.0" + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0" + }, "peerDependencies": { - "@aws-cdk/core": "0.0.0" + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0" }, "engines": { "node": ">= 10.3.0" diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json similarity index 99% rename from packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.expected.json rename to packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json index 3d1c9cdc6c3ec..bfef9a2c651e6 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json @@ -415,7 +415,7 @@ "ApiId": { "Ref": "LambdaProxyApi67594471" }, - "RouteKey": "ANY /some/very/deep/route/path", + "RouteKey": "GET /some/very/deep/route/path", "Target": { "Fn::Join": [ "", diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts similarity index 100% rename from packages/@aws-cdk/aws-apigatewayv2/test/integ.ap.ts rename to packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts From 35b8ddff1012a7b6ce881efdc489e3081f5b0d9f Mon Sep 17 00:00:00 2001 From: Pahud Date: Sun, 15 Mar 2020 00:34:18 +0800 Subject: [PATCH 09/42] add integration and tests --- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 196 ++---- .../@aws-cdk/aws-apigatewayv2/lib/index.ts | 1 + .../aws-apigatewayv2/lib/integration.ts | 207 ++++++ .../@aws-cdk/aws-apigatewayv2/lib/route.ts | 296 +++----- .../test/apigatewayv2.test.ts | 644 +++++++++++++----- .../test/integ.api.expected.json | 343 ++-------- .../aws-apigatewayv2/test/integ.api.ts | 84 +-- 7 files changed, 924 insertions(+), 847 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 755172e7f00f4..b18a2c0d8c5e4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -6,37 +6,19 @@ import { CfnApiProps } from './apigatewayv2.generated'; /** * the HTTP API interface */ -export interface IApi extends cdk.IResource { +export interface IHttpApi extends cdk.IResource { /** * The ID of this API Gateway HTTP Api. * @attribute */ - readonly apiId: string; + readonly httpApiId: string; } -/** - * the ApiBase interface - */ -export interface IApiBase extends IApi { - /** - * http url of this api - */ - readonly url: string -} - -/** - * HttpProxyApi interface - */ -export interface IHttpProxyApi extends IApi {} -/** - * LambdaProxyApi interface - */ -export interface ILambdaProxyApi extends IApi { } -abstract class ApiBase extends cdk.Resource implements IApi { +abstract class ApiBase extends cdk.Resource implements IHttpApi { /** * API ID */ - public abstract readonly apiId: string; + public abstract readonly httpApiId: string; /** * API URL */ @@ -46,28 +28,38 @@ abstract class ApiBase extends cdk.Resource implements IApi { /** * API properties */ -export interface ApiProps extends cdk.StackProps { +export interface HttpApiProps extends cdk.StackProps { /** - * API Name - * @default - the logic ID of the API + * A name for the HTTP API resoruce + * + * @default - ID of the HttpApi construct. */ readonly apiName?: string; /** * API protocol + * * @default HTTP */ - readonly protocol?: ApiProtocol; + readonly protocol?: ProtocolType; /** - * the default integration target of this API + * target lambda function for lambda proxy integration. + * + * @default - None. Specify either `targetHandler` or `targetUrl` */ - readonly target: string; + readonly targetHandler?: lambda.IFunction; + /** + * target URL for HTTP proxy integration. + * + * @default - None. Specify either `targetHandler` or `targetUrl` + */ + readonly targetUrl?: string; } /** - * API protocols + * protocol types for the Amazon API Gateway HTTP API */ -export enum ApiProtocol { +export enum ProtocolType { /** * HTTP protocol */ @@ -79,23 +71,24 @@ export enum ApiProtocol { } /** - * Api Resource Class + * HTTPApi Resource Class + * + * @resource AWS::ApiGatewayV2::Api */ -export class Api extends ApiBase implements IApi { +export class HttpApi extends ApiBase implements IHttpApi { /** * import from ApiId */ - public static fromApiId(scope: cdk.Construct, id: string, apiId: string): IApi { - class Import extends cdk.Resource implements IApi { - public readonly apiId = apiId; + public static fromApiId(scope: cdk.Construct, id: string, httpApiId: string): IHttpApi { + class Import extends cdk.Resource implements IHttpApi { + public readonly httpApiId = httpApiId; } - return new Import(scope, id); } /** * the API identifer */ - public readonly apiId: string; + public readonly httpApiId: string; /** * AWS partition either `aws` or `aws-cn` */ @@ -116,124 +109,49 @@ export class Api extends ApiBase implements IApi { * the full URL of this API */ public readonly url: string; + /** + * root route + */ + public root?: apigatewayv2.IRouteBase; - constructor(scope: cdk.Construct, id: string, props?: ApiProps) { + constructor(scope: cdk.Construct, id: string, props?: HttpApiProps) { super(scope, id, { physicalName: props?.apiName || id, }); + if ((!props) || + (props!.targetHandler && props!.targetUrl) || + (props!.targetHandler === undefined && props!.targetUrl === undefined)) { + throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); + } + this.region = cdk.Stack.of(this).region; - this.partition = this.region.startsWith('cn-') ? 'aws-cn' : 'aws'; + this.partition = this.isChina() ? 'aws-cn' : 'aws'; this.account = cdk.Stack.of(this).account; - this.awsdn = this.partition === 'aws-cn' ? 'amazonaws.com.cn' : 'amazonaws.com'; + this.awsdn = this.isChina() ? 'amazonaws.com.cn' : 'amazonaws.com'; const apiProps: CfnApiProps = { name: this.physicalName, - protocolType: props?.protocol ?? ApiProtocol.HTTP, - target: props?.target + protocolType: props!.protocol ?? ProtocolType.HTTP, + target: props!.targetHandler ? props.targetHandler.functionArn : props!.targetUrl }; const api = new apigatewayv2.CfnApi(this, 'Resource', apiProps ); - this.apiId = api.ref; - this.url = `https://${this.apiId}.execute-api.${this.region}.${this.awsdn}`; - } + this.httpApiId = api.ref; -} + this.url = `https://${this.httpApiId}.execute-api.${this.region}.${this.awsdn}`; -/** - * LambdaProxyApi properties interface - */ -export interface LambdaProxyApiProps { - /** - * API name - * @default - the logic ID of the API - */ - readonly apiName?: string, - /** - * Lambda handler function - */ - readonly handler: lambda.IFunction -} - -/** - * HttpProxyApi properties interface - */ -export interface HttpProxyApiProps { - /** - * API Name - * @default - the logic ID of the API - */ - readonly apiName?: string, - /** - * API URL - */ - readonly url: string -} - -/** - * API with Lambda Proxy integration as the default route - */ -export class LambdaProxyApi extends Api implements ILambdaProxyApi { - /** - * import from api id - */ - public static fromLambdaProxyApiId(scope: cdk.Construct, id: string, lambdaProxyApiId: string): ILambdaProxyApi { - class Import extends cdk.Resource implements ILambdaProxyApi { - public readonly apiId = lambdaProxyApiId; - } - return new Import(scope, id); - } - /** - * the root route of the API - */ - public root?: apigatewayv2.IRouteBase; - constructor(scope: cdk.Construct, id: string, props: LambdaProxyApiProps) { - super(scope, id, { - target: props.handler.functionArn - }); - - new lambda.CfnPermission(this, 'Permission', { - action: 'lambda:InvokeFunction', - principal: 'apigateway.amazonaws.com', - functionName: props.handler.functionName, - sourceArn: `arn:${this.partition}:execute-api:${this.region}:${this.account}:${this.apiId}/*/*`, - }); - - // this.root = new RootRoute(this, 'RootRoute', this, { - // target: props.handler.functionArn, - // }); - - new cdk.CfnOutput(this, 'LambdaProxyApiUrl', { - value: this.url - }); - } -} - -/** - * API with HTTP Proxy integration as the default route - */ -export class HttpProxyApi extends Api implements IHttpProxyApi { - /** - * import from api id - */ - public static fromHttpProxyApiId(scope: cdk.Construct, id: string, httpProxyApiId: string): IHttpProxyApi { - class Import extends cdk.Resource implements IHttpProxyApi { - public readonly apiId = httpProxyApiId; + if (props!.targetHandler) { + new lambda.CfnPermission(this, 'Permission', { + action: 'lambda:InvokeFunction', + principal: 'apigateway.amazonaws.com', + functionName: props!.targetHandler.functionName, + sourceArn: `arn:${this.partition}:execute-api:${this.region}:${this.account}:${this.httpApiId}/*/*`, + }); } - return new Import(scope, id); } - /** - * the root route of the API - */ - public root?: apigatewayv2.IRouteBase; - constructor(scope: cdk.Construct, id: string, props: HttpProxyApiProps) { - super(scope, id, { - target: props.url, - apiName: props.apiName ?? id - }); - - new cdk.CfnOutput(this, 'HttpProxyApiUrl', { - value: this.url - }); + private isChina(): boolean { + const region = this.stack.region; + return !cdk.Token.isUnresolved(region) && region.startsWith('cn-'); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts index 0f65ba7e8463a..4e56ea24d5444 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts @@ -2,3 +2,4 @@ export * from './apigatewayv2.generated'; export * from './api'; export * from './route'; +export * from './integration'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts new file mode 100644 index 0000000000000..96b6b6caf08cc --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts @@ -0,0 +1,207 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as apigatewayv2 from '../lib'; + +/** + * The integration interface + */ +export interface IIntegration extends cdk.IResource { +/** + * The resource ID of the integration + * + * @attribute + */ + readonly integrationId: string +} + +/** + * Integration typ + * + */ +export enum IntegrationType { + /** + * for integrating the route or method request with an AWS service action, includingthe Lambda function-invoking action. + * With the Lambda function-invoking action, this is referred to as the Lambda custom integration. With any other AWS service + * action, this is known as AWS integration. Supported only for WebSocket APIs. + */ + AWS = 'AWS', + /** + * for integrating the route or method request with the Lambda function-invoking action with the client request passed through as-is. + * This integration is also referred to as Lambda proxy integration. + */ + AWS_PROXY = 'AWS_PROXY', + /** + * for integrating the route or method request with an HTTP endpoint. This integration is also referred to as the HTTP custom integration. + * Supported only for WebSocket APIs. + */ + HTTP = 'HTTP', + /** + * for integrating the route or method request with an HTTP endpoint, with the client request passed through as-is. + * This is also referred to as HTTP proxy integration. For HTTP API private integrations, use an `HTTP_PROXY` integration. + */ + HTTP_PROXY = 'HTTP_PROXY', + /** + * for integrating the route or method request with API Gateway as a "loopback" endpoint without invoking any backend. + * Supported only for WebSocket APIs. + */ + MOCK = 'MOCK' +} + +/** + * the integration properties + */ +export interface IntegrationProps extends cdk.StackProps { + /** + * integration name + * @default - the resource ID of the integration + */ + readonly integrationName?: string; + /** + * API ID + */ + readonly apiId: string; + /** + * integration type + */ + readonly integrationType: IntegrationType + /** + * integration URI + */ + readonly integrationUri: string + /** + * integration method + */ + readonly integrationMethod: apigatewayv2.HttpMethod +} + +/** + * Lambda Proxy integration properties + */ +export interface LambdaProxyIntegrationProps extends cdk.StackProps { + /** + * integration name + * @default - the resource id + */ + readonly integrationName?: string; + /** + * The API identifier + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-integration.html#cfn-apigatewayv2-integration-apiid + */ + readonly api: apigatewayv2.IHttpApi; + /** + * The Lambda function for this integration. + */ + readonly targetHandler: lambda.IFunction +} + +/** + * HTTP Proxy integration properties + */ +export interface HttpProxyIntegrationProps extends cdk.StackProps { + /** + * integration name + * @default - the resource id + */ + readonly integrationName?: string + /** + * The API identifier + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-integration.html#cfn-apigatewayv2-integration-apiid + */ + readonly api: apigatewayv2.IHttpApi; + /** + * The full-qualified HTTP URL for the HTTP integration + */ + readonly targetUrl: string + /** + * Specifies the integration's HTTP method type. + * @default - ANY + */ + readonly integrationMethod?: apigatewayv2.HttpMethod +} + +/** + * The integration resource for HTTP API + */ +export class Integration extends cdk.Resource implements IIntegration { + /** + * import from integration ID + */ + public static fromIntegrationId(scope: cdk.Construct, id: string, integrationId: string): IIntegration { + class Import extends cdk.Resource implements IIntegration { + public readonly integrationId = integrationId; + } + + return new Import(scope, id); + } + public readonly integrationId: string; + constructor(scope: cdk.Construct, id: string, props: IntegrationProps) { + super(scope, id); + const integ = new apigatewayv2.CfnIntegration(this, 'Resource', { + apiId: props.apiId, + integrationType: props.integrationType, + integrationMethod: props.integrationMethod, + integrationUri: props.integrationUri, + payloadFormatVersion: '1.0' + }); + this.integrationId = integ.ref; + } +} + +/** + * The Lambda Proxy integration resource for HTTP API + * @resource AWS::ApiGatewayV2::Integration + */ +export class LambdaProxyIntegration extends cdk.Resource implements IIntegration { + public readonly integrationId: string; + constructor(scope: cdk.Construct, id: string, props: LambdaProxyIntegrationProps) { + super(scope, id); + + const partition = this.isChina() ? 'aws-cn' : 'aws'; + const region = this.stack.region; + const account = this.stack.account; + + // create integration + const integ = new apigatewayv2.CfnIntegration(this, 'Resource', { + apiId: props.api.httpApiId, + integrationType: apigatewayv2.IntegrationType.AWS_PROXY, + integrationMethod: apigatewayv2.HttpMethod.POST, + payloadFormatVersion: '1.0', + integrationUri: `arn:${partition}:apigateway:${region}:lambda:path/2015-03-31/functions/${props.targetHandler.functionArn}/invocations`, + }); + this.integrationId = integ.ref; + // create permission + new lambda.CfnPermission(scope, `Permission-${id}`, { + action: 'lambda:InvokeFunction', + principal: 'apigateway.amazonaws.com', + functionName: props.targetHandler.functionName, + sourceArn: `arn:${partition}:execute-api:${region}:${account}:${props.api.httpApiId}/*/*`, + }); + } + private isChina(): boolean { + const region = this.stack.region; + return !cdk.Token.isUnresolved(region) && region.startsWith('cn-'); + } +} + +/** + * The HTTP Proxy integration resource for HTTP API + * + * @resource AWS::ApiGatewayV2::Integration + */ +export class HttpProxyIntegration extends cdk.Resource implements IIntegration { + public readonly integrationId: string; + + constructor(scope: cdk.Construct, id: string, props: HttpProxyIntegrationProps) { + super(scope, id); + + // create integration + const integ = new apigatewayv2.CfnIntegration(this, 'Resource', { + apiId: props.api.httpApiId, + integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, + integrationMethod: props.integrationMethod ?? apigatewayv2.HttpMethod.ANY, + integrationUri: props.targetUrl, + payloadFormatVersion: '1.0' + }); + this.integrationId = integ.ref; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts index 0390ebba654fd..e68325a17bd72 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -1,7 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; -import { CfnRouteProps } from './apigatewayv2.generated'; /** * the interface of the Route of API Gateway HTTP API @@ -14,26 +13,6 @@ export interface IRoute extends cdk.IResource { readonly routeId: string; } -/** - * HttpRoute interface - */ -export interface IHttpRoute extends IRoute {} -/** - * LambdaRoute interface - */ -export interface ILambdaRoute extends IRoute { } - -/** - * the interface of the route attributes - */ -export interface RouteAttributes { - /** - * ID of this Route - * @attribute - */ - readonly routeId: string; -} - /** * the interface of the RouteBase */ @@ -42,7 +21,7 @@ export interface IRouteBase extends IRoute { * the ID of this API Gateway HttpApi. * @attribute */ - readonly api: apigatewayv2.IApiBase; + readonly api: apigatewayv2.IHttpApi; /** * the key of this route * @attribute @@ -57,11 +36,6 @@ export interface IRouteBase extends IRoute { */ readonly httpPath: string; - /** - * full URL of this route - */ - readonly fullUrl: string; - /** * add a child route with HTTP integration for this parent route */ @@ -74,14 +48,20 @@ export interface IRouteBase extends IRoute { } /** - * the interface of the RouteOptionsBase + * the interface of the route attributes */ -export interface RouteOptionsBase {} +export interface RouteAttributes { + /** + * ID of this Route + * @attribute + */ + readonly routeId: string; +} /** * options of HttpRoute */ -export interface HttpRouteOptions extends RouteOptionsBase { +export interface HttpRouteOptions { /** * URL of the integration target */ @@ -103,7 +83,7 @@ export interface HttpRouteOptions extends RouteOptionsBase { /** * Options for the Route with Lambda integration */ -export interface LambdaRouteOptions extends RouteOptionsBase { +export interface LambdaRouteOptions { /** * target lambda function */ @@ -122,51 +102,6 @@ export interface LambdaRouteOptions extends RouteOptionsBase { readonly integrationMethod?: HttpMethod; } -abstract class RouteBase extends cdk.Resource implements IRouteBase { - public abstract readonly api: apigatewayv2.IApiBase; - public abstract readonly routeKey: string; - public abstract readonly httpPath: string; - public abstract readonly routeId: string; - public abstract readonly fullUrl: string; - - /** - * create a child route with Lambda proxy integration - */ - public addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route { - const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; - const httpMethod = options.method; - // const routeKey = `${httpMethod} ${httpPath}`; - - return new LambdaRoute(this, id, { - api: this.api, - handler: options.target, - httpPath, - httpMethod, - parent: this, - pathPart, - }); - } - - /** - * create a child route with HTTP proxy integration - */ - public addHttpRoute(pathPart: string, id: string, options: HttpRouteOptions): Route { - const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; - const httpMethod = options.method; - // const routeKey = `${httpMethod} ${httpPath}`; - - return new HttpRoute(this, id, { - api: this.api, - targetUrl: options.targetUrl, - httpPath, - httpMethod, - parent: this, - pathPart, - }); - } - -} - /** * all HTTP methods */ @@ -194,9 +129,9 @@ export enum HttpMethod { } /** - * Route base properties + * Route properties */ -export interface RouteBaseProps extends cdk.StackProps { +export interface RouteProps extends cdk.StackProps { /** * route name * @default - the logic ID of this route @@ -205,7 +140,7 @@ export interface RouteBaseProps extends cdk.StackProps { /** * the API the route is associated with */ - readonly api: apigatewayv2.IApiBase; + readonly api: apigatewayv2.IHttpApi; /** * HTTP method of this route * @default HttpMethod.ANY @@ -219,45 +154,47 @@ export interface RouteBaseProps extends cdk.StackProps { * parent of this route * @default - undefinied if no parentroute d */ - readonly parent?: IRouteBase + readonly parent?: IRoute /** * path part of this route * @default '' */ readonly pathPart?: string; - -} - -/** - * Route properties - */ -export interface RouteProps extends RouteBaseProps { /** - * target of this route + * HTTP URL target of this route + * @default - None. Specify one of `targetUrl`, `targetHandler` or `integration` */ - readonly target: string; - + readonly targetUrl?: string; + /** + * Lambda handler target of this route + * @default - None. Specify one of `targetUrl`, `targetHandler` or `integration` + */ + readonly targetHandler?: lambda.IFunction; + /** + * Integration + * @default - None. Specify one of `targetUrl`, `targetHandler` or `integration` + */ + readonly integration?: apigatewayv2.IIntegration; } /** * Route class that creates the Route for API Gateway HTTP API */ -export class Route extends RouteBase { - +export class Route extends cdk.Resource implements IRouteBase { /** - * import from route attributes + * import from route id */ - public static fromRouteAttributes(scope: cdk.Construct, id: string, attrs: RouteAttributes): IRoute { + public static fromRouteId(scope: cdk.Construct, id: string, routeId: string): IRoute { class Import extends cdk.Resource implements IRoute { - public routeId = attrs.routeId; + public routeId = routeId; } return new Import(scope, id); } - public readonly fullUrl: string; + // public readonly fullUrl: string; /** - * the api interface of this route + * the api ID of this route */ - public readonly api: apigatewayv2.IApiBase; + public readonly api: apigatewayv2.IHttpApi; /** * the route key of this route */ @@ -274,134 +211,89 @@ export class Route extends RouteBase { * route id from the `Ref` function */ public readonly routeId: string; + /** + * integration ID + */ + public readonly integId: string; constructor(scope: cdk.Construct, id: string, props: RouteProps) { super(scope, id); + if ((props.targetHandler && props.targetUrl) || + (props.targetHandler && props.integration) || + (props.targetUrl && props.integration)) { + throw new Error('You must specify targetHandler, targetUrl or integration, use at most one'); + } + this.api = props.api; this.httpPath = props.httpPath; this.httpMethod = props.httpMethod ?? HttpMethod.ANY; + this.routeKey = `${this.httpMethod} ${this.httpPath}`; + + if (props.integration) { + this.integId = props.integration.integrationId; + } else if (props.targetUrl) { + // create a HTTP Proxy integration + const integ = new apigatewayv2.HttpProxyIntegration(scope, `${id}/HttpProxyIntegration`, { + api: this.api, + targetUrl: props.targetUrl + }); + this.integId = integ.integrationId; + } else if (props.targetHandler) { + // create a Lambda Proxy integration + const integ = new apigatewayv2.LambdaProxyIntegration(scope, `${id}/LambdaProxyIntegration`, { + api: this.api, + targetHandler: props.targetHandler + }); + this.integId = integ.integrationId; + } else { + throw new Error('You must specify either a integration, targetHandler or targetUrl'); + } - this.routeKey = `${props.httpMethod} ${props.httpPath}`; - const routeProps: CfnRouteProps = { - apiId: this.api.apiId, + const routeProps: apigatewayv2.CfnRouteProps = { + apiId: this.api.httpApiId, routeKey: this.routeKey, - target: props.target + target: `integrations/${this.integId}`, }; - const route = new apigatewayv2.CfnRoute(this, 'Resoruce', routeProps); + + const route = new apigatewayv2.CfnRoute(this, 'Resource', routeProps); this.routeId = route.ref; - this.fullUrl = `${this.api.url}${this.httpPath}`; + // this.url = `${this.api.url}${this.httpPath}`; } -} - -/** - * Lambda route properties - */ -export interface LambdaRouteProps extends RouteBaseProps { - /** - * Lambda handler function - */ - readonly handler: lambda.IFunction -} -/** - * HTTP route properties - */ -export interface HttpRouteProps extends RouteBaseProps { - /** - * target URL - */ - readonly targetUrl: string, - /** - * integration method - * @default HttpMethod.ANY - */ - readonly integrationMethod?: HttpMethod -} - -enum IntegrationType { - AWS_PROXY = 'AWS_PROXY', - HTTP_PROXY = 'HTTP_PROXY' -} - -/** - * Route with Lambda Proxy integration - */ -export class LambdaRoute extends Route implements ILambdaRoute { /** - * import from lambdaRouteId + * create a child route with Lambda proxy integration */ - public static fromLambdaRouteId(scope: cdk.Construct, id: string, lambdaRouteId: string): ILambdaRoute { - class Import extends cdk.Resource implements ILambdaRoute { - public readonly routeId = lambdaRouteId; - } - return new Import(scope, id); - } - constructor(scope: cdk.Construct, id: string, props: LambdaRouteProps) { - const region = cdk.Stack.of(scope).region; - const account = cdk.Stack.of(scope).account; - const partition = region.startsWith('cn-') ? 'aws-cn' : 'aws'; - const httpMethod = props.httpMethod ?? HttpMethod.ANY; - - // create a Lambda Proxy integration - const integ = new apigatewayv2.CfnIntegration(scope, `Integration-${id}`, { - apiId: props.api.apiId, - integrationMethod: HttpMethod.POST, - integrationType: IntegrationType.AWS_PROXY, - payloadFormatVersion: '1.0', - integrationUri: `arn:${partition}:apigateway:${region}:lambda:path/2015-03-31/functions/${props.handler.functionArn}/invocations` - }); - - // create permission - new lambda.CfnPermission(scope, `Permission-${id}`, { - action: 'lambda:InvokeFunction', - principal: 'apigateway.amazonaws.com', - functionName: props.handler.functionName, - sourceArn: `arn:${partition}:execute-api:${region}:${account}:${props.api.apiId}/*/*`, - }); + public addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route { + const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; + const httpMethod = options.method; + // const routeKey = `${httpMethod} ${httpPath}`; - super(scope, id, { - api: props.api, + return new Route(this, id, { + api: this.api, + targetHandler: options.target, + httpPath, httpMethod, - httpPath: props.httpPath, - target: `integrations/${integ.ref}`, - parent: props.parent, - pathPart: props.pathPart ?? '' + parent: this, + pathPart, }); } -} -/** - * Route with HTTP Proxy integration - */ -export class HttpRoute extends Route implements IHttpRoute { /** - * import from httpRouteId + * create a child route with HTTP proxy integration */ - public static fromHttpRouteId(scope: cdk.Construct, id: string, httpRouteId: string): IHttpRoute { - class Import extends cdk.Resource implements IHttpRoute { - public readonly routeId = httpRouteId; - } - return new Import(scope, id); - } - constructor(scope: cdk.Construct, id: string, props: HttpRouteProps) { - const httpMethod = props.httpMethod ?? HttpMethod.ANY; + public addHttpRoute(pathPart: string, id: string, options: HttpRouteOptions): Route { + const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; + const httpMethod = options.method; + // const routeKey = `${httpMethod} ${httpPath}`; - // create a HTTP Proxy integration - const integ = new apigatewayv2.CfnIntegration(scope, `Integration${id}`, { - apiId: props.api.apiId, - integrationMethod: props.integrationMethod ?? HttpMethod.ANY, - integrationType: IntegrationType.HTTP_PROXY, - payloadFormatVersion: '1.0', - integrationUri: props.targetUrl - }); - super(scope, id, { - api: props.api, + return new Route(this, id, { + api: this.api, + targetUrl: options.targetUrl, + httpPath, httpMethod, - httpPath: props.httpPath, - target: `integrations/${integ.ref}`, - parent: props.parent, - pathPart: props.pathPart + parent: this, + pathPart, }); } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts index 864527891ac41..686a8e27193c1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts @@ -4,13 +4,11 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; -// test('No tests are specified for this package', () => { -// expect(true).toBe(true); -// }); - let stack = new cdk.Stack(); let handler: lambda.Function; let rootHandler: lambda.Function; +let checkIpUrl: string; +let awsUrl: string; beforeEach(() => { stack = new cdk.Stack(); @@ -44,275 +42,561 @@ def handler(event, context): HTTP_PATH: '/' }, }); + checkIpUrl = 'https://checkip.amazonaws.com'; + awsUrl = 'https://aws.amazon.com'; }); -test('create LambdaProxyApi correctly', () => { +test('create a basic HTTP API correctly', () => { // WHEN - - new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { - handler, + new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetUrl: checkIpUrl, + protocol: apigatewayv2.ProtocolType.HTTP }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - // expectCDK(stack).to(countResources('AWS::Lambda::Function', 1)); - // expectCDK(stack).to(countResources('AWS::Lambda::Permission', 1)); - expectCDK(stack).to(haveOutput({ - outputName: 'LambdaProxyApiLambdaProxyApiUrl2866EF1B' - })); }); -test('create HttpProxyApi correctly', () => { +test('create a basic HTTP API correctly with target handler', () => { // WHEN - new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { - url: 'https://checkip.amazonaws.com' + new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler }); - // THEN - expectCDK(stack).to(haveResource('AWS::ApiGatewayV2::Api', { - Name: "LambdaProxyApi", - ProtocolType: "HTTP", - Target: "https://checkip.amazonaws.com" - } )); - expectCDK(stack).to(haveOutput({ - outputName: 'LambdaProxyApiHttpProxyApiUrl378A7258' - })); -}); - -test('create LambdaRoute correctly for HttpProxyApi', () => { - // WHEN - const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { - url: 'https://checkip.amazonaws.com' - }); - api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { - api, - handler: rootHandler, - httpPath: '/', - }); - // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); - expectCDK(stack).to(countResources('AWS::Lambda::Permission', 1)); }); -test('create HttpRoute correctly for HttpProxyApi', () => { +test('import HTTP API from API ID correctly', () => { // WHEN - const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { - url: 'https://checkip.amazonaws.com' - }); - api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { - api, - targetUrl: 'https://www.amazon.com', - httpPath: '/', - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); + expect(() => + apigatewayv2.HttpApi.fromApiId(stack, 'HttpApi', 'foo') + ).not.toThrowError(); }); -test('create LambdaRoute correctly for LambdaProxyApi', () => { +test('create lambda proxy integration correctly', () => { // WHEN - const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { - handler + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler }); - api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { - api, - handler: rootHandler, - httpPath: '/', + new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { + api: httpApi, + targetHandler: rootHandler }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); - expectCDK(stack).to(countResources('AWS::Lambda::Permission', 2)); }); -test('create HttpRoute correctly for LambdaProxyApi', () => { +test('create HTTP proxy integration correctly with targetUrl', () => { // WHEN - const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { - handler + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler }); - api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { - api, - targetUrl: 'https://www.amazon.com', - httpPath: '/', + new apigatewayv2.HttpProxyIntegration(stack, 'IntegRootHandler', { + api: httpApi, + targetUrl: 'https://aws.amazon.com', + integrationMethod: apigatewayv2.HttpMethod.GET }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); }); -test('addLambdaRoute and addHttpRoute correctly from an existing route', () => { +test('create HTTP proxy integration correctly with Integration', () => { // WHEN - const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { - handler + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler }); - api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { - api, - targetUrl: 'https://www.amazon.com', - httpPath: '/', + new apigatewayv2.Integration(stack, 'IntegRootHandler', { + apiId: httpApi.httpApiId, + integrationMethod: apigatewayv2.HttpMethod.ANY, + integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, + integrationUri: awsUrl }); - - api.root - // HTTP GET /foo - .addLambdaRoute('foo', 'Foo', { - target: handler, - method: apigatewayv2.HttpMethod.GET - }) - // HTTP ANY /foo/checkip - .addHttpRoute('checkip', 'FooCheckIp', { - targetUrl: 'https://checkip.amazonaws.com', - method: apigatewayv2.HttpMethod.ANY - }); - // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 3)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); }); -test('import API correctly', () => { +test('import integration from integration ID correctly', () => { // WHEN - const api = apigatewayv2.Api.fromApiId(stack, 'ImportedApi', 'foo'); - // THEN - expect(api).toHaveProperty('apiId'); + expect(() => + apigatewayv2.Integration.fromIntegrationId(stack, 'Integ', 'foo') + ).not.toThrowError(); }); -test('import HttpProxyApi correctly', () => { +test('create the root route correctly', () => { // WHEN - const api = apigatewayv2.HttpProxyApi.fromHttpProxyApiId(stack, 'ImportedApi', 'foo'); - + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler + }); + const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { + api: httpApi, + targetHandler: rootHandler + }); + httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { + api: httpApi, + httpPath: '/', + integration: integRootHandler + }); // THEN - expect(api).toHaveProperty('apiId'); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 1)); }); -test('import LambdaProxyApi correctly', () => { +test('import route from route ID correctly', () => { // WHEN - const api = apigatewayv2.LambdaProxyApi.fromLambdaProxyApiId(stack, 'ImportedApi', 'foo'); - // THEN - expect(api).toHaveProperty('apiId'); + expect(() => + apigatewayv2.Route.fromRouteId(stack, 'Integ', 'foo') + ).not.toThrowError(); }); -test('import Route correctly', () => { +test('addLambdaRoute correctly', () => { // WHEN - const route = apigatewayv2.Route.fromRouteAttributes(stack, 'ImportedRoute', { - routeId: 'foo' + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler + }); + const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { + api: httpApi, + targetHandler: rootHandler + }); + httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { + api: httpApi, + httpPath: '/', + integration: integRootHandler + }); + httpApi.root.addLambdaRoute('foo', 'Foo', { + target: handler, + method: apigatewayv2.HttpMethod.GET }); - // THEN - expect(route).toHaveProperty('routeId'); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 2)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); }); -test('import LambdaRoute correctly', () => { +test('addHttpRoute correctly', () => { // WHEN - const route = apigatewayv2.LambdaRoute.fromLambdaRouteId(stack, 'ImportedRoute', 'foo'); + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler + }); + const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { + api: httpApi, + targetHandler: rootHandler + }); + httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { + api: httpApi, + httpPath: '/', + integration: integRootHandler + }); + httpApi.root.addHttpRoute('aws', 'AwsPage', { + targetUrl: awsUrl, + method: apigatewayv2.HttpMethod.ANY + }); // THEN - expect(route).toHaveProperty('routeId'); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 2)); + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); }); -test('import HttpRoute correctly', () => { +test('throws when creating HTTP API with no props', () => { // WHEN - const route = apigatewayv2.HttpRoute.fromHttpRouteId(stack, 'ImportedRoute', 'foo'); // THEN - expect(route).toHaveProperty('routeId'); + expect(() => + new apigatewayv2.HttpApi(stack, 'HttpApi') + ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); }); -test('import LambdaRoute correctly', () => { +test('throws when both targetHandler and targetUrl are specified', () => { // WHEN - const route = apigatewayv2.LambdaRoute.fromLambdaRouteId(stack, 'ImportedRoute', 'foo'); // THEN - expect(route).toHaveProperty('routeId'); + expect(() => + new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler, + targetUrl: awsUrl + }) + ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); }); -test('create api correctly with no props', () => { +test('throws when neither targetHandler nor targetUrl are specified', () => { // WHEN - new apigatewayv2.Api(stack, 'API'); // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expect(() => + new apigatewayv2.HttpApi(stack, 'HttpApi', { + }) + ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); }); -test('assign aws partition correctly by region for api', () => { +test('throws when both targetHandler and targetUrl are specified for Route', () => { // WHEN - const cnstack = new cdk.Stack(undefined, undefined, { - env: { - region: 'cn-north-1' - } + const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler }); - const api = new apigatewayv2.Api(cnstack, 'API'); // THEN - expect(api.partition).toBe('aws-cn'); - expect(api.awsdn).toBe('amazonaws.com.cn'); + expect(() => + new apigatewayv2.Route(stack, 'Route', { + api, + httpPath: '/', + targetUrl: awsUrl, + targetHandler: handler + }) + ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); }); -test('assign aws partition correctly by region for lambda route', () => { +test('throws when targetHandler, targetUrl and integration all specified for Route', () => { // WHEN - const cnstack = new cdk.Stack(undefined, undefined, { - env: { - region: 'cn-north-1' - } - }); - const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { - url: 'https://checkip.amazonaws.com' + const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler }); - - new apigatewayv2.LambdaRoute(cnstack, 'API', { - api, - handler, - httpPath: '/foo' + const integ = new apigatewayv2.Integration(stack, 'IntegRootHandler', { + apiId: api.httpApiId, + integrationMethod: apigatewayv2.HttpMethod.ANY, + integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, + integrationUri: awsUrl }); // THEN - expect(true).toBe(true); + expect(() => + new apigatewayv2.Route(stack, 'Route', { + api, + httpPath: '/', + targetUrl: awsUrl, + targetHandler: handler, + integration: integ + }) + ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); }); -test('create api correctly with specific protocol specified', () => { +test('throws when targetHandler, targetUrl and integration all unspecified for Route', () => { // WHEN - new apigatewayv2.Api(stack, 'API', { - target: '', - protocol: apigatewayv2.ApiProtocol.WEBSOCKET + const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler }); // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expect(() => + new apigatewayv2.Route(stack, 'Route', { + api, + httpPath: '/', + }) + ).toThrowError(/You must specify either a integration, targetHandler or targetUrl/); }); -test('create HttpProxyApi correctly with specific api name specified', () => { - // WHEN - new apigatewayv2.HttpProxyApi(stack, 'API', { - apiName: 'foo', - url: 'https://www.amazon.com' +test('create LambdaProxyIntegration correctly in aws china region', () => { + // GIVEN + const app = new cdk.App(); + const stackcn = new cdk.Stack(app, 'stack', { env: { region: 'cn-north-1' } }); + const handlercn = new lambda.Function(stackcn, 'MyFunc', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code: new lambda.InlineCode(` +import json +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps(event) + }`), + reservedConcurrentExecutions: 10, }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -}); - -test('create Route correctly with no httpMethod specified', () => { // WHEN - const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { - url: 'https://checkip.amazonaws.com' + const httpApi = new apigatewayv2.HttpApi(stackcn, 'HttpApi', { + targetUrl: awsUrl }); - new apigatewayv2.Route(stack, 'Route', { - api, - httpPath: '/foo', - target: 'https://www.amazon.com' + // THEN + new apigatewayv2.LambdaProxyIntegration(stackcn, 'IntegRootHandler', { + api: httpApi, + targetHandler: handlercn }); // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stackcn).to(countResources('AWS::ApiGatewayV2::Integration', 1)); }); -test('create HttpRoute correctly with specific integrationMethod', () => { - // WHEN - const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { - url: 'https://checkip.amazonaws.com' - }); +// ''(test: Test); { +// // GIVEN +// const app = new cdk.App(); +// const stack = new cdk.Stack(app, 'stack', { env: { region: 'us-east-1' } }); + +// // WHEN +// const vpc = new ec2.Vpc(stack, 'VPC'); +// new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); +// const layer = KubectlLayer.getOrCreate(stack, {}); + +// // THEN +// expect(stack).to(haveResource('Custom::AWSCDK-EKS-Cluster')); +// expect(stack).to(haveResourceLike('AWS::Serverless::Application', { +// Location: { +// ApplicationId: 'arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl', +// } +// })); +// test.equal(layer.isChina(), false); +// test.done(); +// }, + +// test('create LambdaProxyApi correctly', () => { +// // WHEN +// new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { +// handler, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// // expectCDK(stack).to(countResources('AWS::Lambda::Function', 1)); +// // expectCDK(stack).to(countResources('AWS::Lambda::Permission', 1)); +// expectCDK(stack).to(haveOutput({ +// outputName: 'LambdaProxyApiLambdaProxyApiUrl2866EF1B' +// })); +// }); - new apigatewayv2.HttpRoute(stack, 'Route', { - api, - httpPath: '/foo', - targetUrl: 'https://www.amazon.com', - integrationMethod: apigatewayv2.HttpMethod.GET - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 1)); -}); \ No newline at end of file +// test('create HttpProxyApi correctly', () => { +// // WHEN +// new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { +// url: 'https://checkip.amazonaws.com' +// }); +// // THEN +// expectCDK(stack).to(haveResource('AWS::ApiGatewayV2::Api', { +// Name: "LambdaProxyApi", +// ProtocolType: "HTTP", +// Target: "https://checkip.amazonaws.com" +// } )); +// expectCDK(stack).to(haveOutput({ +// outputName: 'LambdaProxyApiHttpProxyApiUrl378A7258' +// })); +// }); + +// test('create LambdaRoute correctly for HttpProxyApi', () => { +// // WHEN +// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { +// url: 'https://checkip.amazonaws.com' +// }); +// api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { +// api, +// handler: rootHandler, +// httpPath: '/', +// }); + +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +// expectCDK(stack).to(countResources('AWS::Lambda::Permission', 1)); +// }); + +// test('create HttpRoute correctly for HttpProxyApi', () => { +// // WHEN +// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { +// url: 'https://checkip.amazonaws.com' +// }); +// api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { +// api, +// targetUrl: 'https://www.amazon.com', +// httpPath: '/', +// }); + +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +// }); + +// test('create LambdaRoute correctly for LambdaProxyApi', () => { +// // WHEN +// const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { +// handler +// }); +// api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { +// api, +// handler: rootHandler, +// httpPath: '/', +// }); + +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +// expectCDK(stack).to(countResources('AWS::Lambda::Permission', 2)); +// }); + +// test('create HttpRoute correctly for LambdaProxyApi', () => { +// // WHEN +// const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { +// handler +// }); +// api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { +// api, +// targetUrl: 'https://www.amazon.com', +// httpPath: '/', +// }); + +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +// }); + +// test('addLambdaRoute and addHttpRoute correctly from an existing route', () => { +// // WHEN +// const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { +// handler +// }); +// api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { +// api, +// targetUrl: 'https://www.amazon.com', +// httpPath: '/', +// }); + +// api.root +// // HTTP GET /foo +// .addLambdaRoute('foo', 'Foo', { +// target: handler, +// method: apigatewayv2.HttpMethod.GET +// }) +// // HTTP ANY /foo/checkip +// .addHttpRoute('checkip', 'FooCheckIp', { +// targetUrl: 'https://checkip.amazonaws.com', +// method: apigatewayv2.HttpMethod.ANY +// }); + +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 3)); +// }); + +// test('import API correctly', () => { +// // WHEN +// const api = apigatewayv2.Api.fromApiId(stack, 'ImportedApi', 'foo'); + +// // THEN +// expect(api).toHaveProperty('apiId'); +// }); + +// test('import HttpProxyApi correctly', () => { +// // WHEN +// const api = apigatewayv2.HttpProxyApi.fromHttpProxyApiId(stack, 'ImportedApi', 'foo'); + +// // THEN +// expect(api).toHaveProperty('apiId'); +// }); + +// test('import LambdaProxyApi correctly', () => { +// // WHEN +// const api = apigatewayv2.LambdaProxyApi.fromLambdaProxyApiId(stack, 'ImportedApi', 'foo'); + +// // THEN +// expect(api).toHaveProperty('apiId'); +// }); + +// test('import Route correctly', () => { +// // WHEN +// const route = apigatewayv2.Route.fromRouteAttributes(stack, 'ImportedRoute', { +// routeId: 'foo' +// }); + +// // THEN +// expect(route).toHaveProperty('routeId'); +// }); + +// test('import LambdaRoute correctly', () => { +// // WHEN +// const route = apigatewayv2.LambdaRoute.fromLambdaRouteId(stack, 'ImportedRoute', 'foo'); +// // THEN +// expect(route).toHaveProperty('routeId'); +// }); + +// test('import HttpRoute correctly', () => { +// // WHEN +// const route = apigatewayv2.HttpRoute.fromHttpRouteId(stack, 'ImportedRoute', 'foo'); +// // THEN +// expect(route).toHaveProperty('routeId'); +// }); + +// test('import LambdaRoute correctly', () => { +// // WHEN +// const route = apigatewayv2.LambdaRoute.fromLambdaRouteId(stack, 'ImportedRoute', 'foo'); +// // THEN +// expect(route).toHaveProperty('routeId'); +// }); + +// test('create api correctly with no props', () => { +// // WHEN +// new apigatewayv2.Api(stack, 'API'); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// }); + +// test('assign aws partition correctly by region for api', () => { +// // WHEN +// const cnstack = new cdk.Stack(undefined, undefined, { +// env: { +// region: 'cn-north-1' +// } +// }); +// const api = new apigatewayv2.Api(cnstack, 'API'); +// // THEN +// expect(api.partition).toBe('aws-cn'); +// expect(api.awsdn).toBe('amazonaws.com.cn'); +// }); + +// test('assign aws partition correctly by region for lambda route', () => { +// // WHEN +// const cnstack = new cdk.Stack(undefined, undefined, { +// env: { +// region: 'cn-north-1' +// } +// }); +// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { +// url: 'https://checkip.amazonaws.com' +// }); + +// new apigatewayv2.LambdaRoute(cnstack, 'API', { +// api, +// handler, +// httpPath: '/foo' +// }); +// // THEN +// expect(true).toBe(true); +// }); + +// test('create api correctly with specific protocol specified', () => { +// // WHEN +// new apigatewayv2.Api(stack, 'API', { +// target: '', +// protocol: apigatewayv2.ApiProtocol.WEBSOCKET +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// }); + +// test('create HttpProxyApi correctly with specific api name specified', () => { +// // WHEN +// new apigatewayv2.HttpProxyApi(stack, 'API', { +// apiName: 'foo', +// url: 'https://www.amazon.com' +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// }); + +// test('create Route correctly with no httpMethod specified', () => { +// // WHEN +// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { +// url: 'https://checkip.amazonaws.com' +// }); +// new apigatewayv2.Route(stack, 'Route', { +// api, +// httpPath: '/foo', +// target: 'https://www.amazon.com' +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// }); + +// test('create HttpRoute correctly with specific integrationMethod', () => { +// // WHEN +// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { +// url: 'https://checkip.amazonaws.com' +// }); + +// new apigatewayv2.HttpRoute(stack, 'Route', { +// api, +// httpPath: '/foo', +// targetUrl: 'https://www.amazon.com', +// integrationMethod: apigatewayv2.HttpMethod.GET +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 1)); +// }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json index bfef9a2c651e6..d609722dc4d9a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json @@ -106,10 +106,18 @@ "RootFuncServiceRoleE4AA9E41" ] }, - "LambdaProxyApi67594471": { + "HttpApiF5A9A8A7": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { - "Name": "LambdaProxyApi", + "Name": "HttpApi", + "ProtocolType": "HTTP", + "Target": "https://checkip.amazonaws.com" + } + }, + "HttpApi2C094A993": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "HttpApi2", "ProtocolType": "HTTP", "Target": { "Fn::GetAtt": [ @@ -119,7 +127,7 @@ } } }, - "LambdaProxyApiPermission126890B4": { + "HttpApi2Permission5FEFB3CB": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", @@ -131,17 +139,9 @@ "Fn::Join": [ "", [ - "arn:aws:execute-api:", - { - "Ref": "AWS::Region" - }, - ":", + "arn:aws:execute-api:ap-northeast-1:903779448426:", { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, "/*/*" ] @@ -149,11 +149,11 @@ } } }, - "IntegrationRootRoute": { + "IntegRootHandler0E7ABE31": { "Type": "AWS::ApiGatewayV2::Integration", "Properties": { "ApiId": { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, "IntegrationType": "AWS_PROXY", "IntegrationMethod": "POST", @@ -161,11 +161,7 @@ "Fn::Join": [ "", [ - "arn:aws:apigateway:", - { - "Ref": "AWS::Region" - }, - ":lambda:path/2015-03-31/functions/", + "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/", { "Fn::GetAtt": [ "RootFuncF39FB174", @@ -179,7 +175,7 @@ "PayloadFormatVersion": "1.0" } }, - "PermissionRootRoute": { + "PermissionIntegRootHandler": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", @@ -191,17 +187,9 @@ "Fn::Join": [ "", [ - "arn:aws:execute-api:", - { - "Ref": "AWS::Region" - }, - ":", + "arn:aws:execute-api:ap-northeast-1:903779448426:", { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, "/*/*" ] @@ -209,11 +197,11 @@ } } }, - "RootRouteResoruceDCB7CC4A": { + "RootRoute357999D4": { "Type": "AWS::ApiGatewayV2::Route", "Properties": { "ApiId": { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, "RouteKey": "ANY /", "Target": { @@ -222,218 +210,102 @@ [ "integrations/", { - "Ref": "IntegrationRootRoute" + "Ref": "IntegRootHandler0E7ABE31" } ] ] } } }, - "RootRouteIntegrationFoo7869F96E": { - "Type": "AWS::ApiGatewayV2::Integration", + "RootRouteFoo0BA98CEE": { + "Type": "AWS::ApiGatewayV2::Route", "Properties": { "ApiId": { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, - "IntegrationType": "AWS_PROXY", - "IntegrationMethod": "POST", - "IntegrationUri": { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - { - "Ref": "AWS::Region" - }, - ":lambda:path/2015-03-31/functions/", - { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - }, - "/invocations" - ] - ] - }, - "PayloadFormatVersion": "1.0" - } - }, - "RootRoutePermissionFoo06AF804C": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFunc8A243A2C" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { + "RouteKey": "GET /foo", + "Target": { "Fn::Join": [ "", [ - "arn:aws:execute-api:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":", + "integrations/", { - "Ref": "LambdaProxyApi67594471" - }, - "/*/*" + "Ref": "RootRouteFooLambdaProxyIntegration5B1AEF5D" + } ] ] } } }, - "RootRouteFooResoruceB48AF356": { + "RootRouteFooAwsPageB70848AE": { "Type": "AWS::ApiGatewayV2::Route", "Properties": { "ApiId": { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, - "RouteKey": "GET /foo", + "RouteKey": "ANY /foo/aws", "Target": { "Fn::Join": [ "", [ "integrations/", { - "Ref": "RootRouteIntegrationFoo7869F96E" + "Ref": "RootRouteFooAwsPageHttpProxyIntegration9D16D6B6" } ] ] } } }, - "RootRouteFooIntegrationFooCheckIp87863B2F": { - "Type": "AWS::ApiGatewayV2::Integration", - "Properties": { - "ApiId": { - "Ref": "LambdaProxyApi67594471" - }, - "IntegrationType": "HTTP_PROXY", - "IntegrationMethod": "ANY", - "IntegrationUri": "https://checkip.amazonaws.com", - "PayloadFormatVersion": "1.0" - } - }, - "RootRouteFooFooCheckIpResoruce7E7874A1": { + "RootRouteFooAwsPageCheckIp644AED0C": { "Type": "AWS::ApiGatewayV2::Route", "Properties": { "ApiId": { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, - "RouteKey": "ANY /foo/checkip", + "RouteKey": "ANY /foo/aws/checkip", "Target": { "Fn::Join": [ "", [ "integrations/", { - "Ref": "RootRouteFooIntegrationFooCheckIp87863B2F" + "Ref": "RootRouteFooAwsPageCheckIpHttpProxyIntegration4E30792F" } ] ] } } }, - "HttpProxyApiD0217C67": { - "Type": "AWS::ApiGatewayV2::Api", - "Properties": { - "Name": "HttpProxyApi", - "ProtocolType": "HTTP", - "Target": "https://aws.amazon.com" - } - }, - "IntegrationSomeLambdaRoute": { + "RootRouteFooAwsPageCheckIpHttpProxyIntegration4E30792F": { "Type": "AWS::ApiGatewayV2::Integration", "Properties": { "ApiId": { - "Ref": "LambdaProxyApi67594471" - }, - "IntegrationType": "AWS_PROXY", - "IntegrationMethod": "POST", - "IntegrationUri": { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - { - "Ref": "AWS::Region" - }, - ":lambda:path/2015-03-31/functions/", - { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - }, - "/invocations" - ] - ] + "Ref": "HttpApi2C094A993" }, + "IntegrationType": "HTTP_PROXY", + "IntegrationMethod": "ANY", + "IntegrationUri": "https://checkip.amazonaws.com", "PayloadFormatVersion": "1.0" } }, - "PermissionSomeLambdaRoute": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFunc8A243A2C" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Join": [ - "", - [ - "arn:aws:execute-api:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "LambdaProxyApi67594471" - }, - "/*/*" - ] - ] - } - } - }, - "SomeLambdaRouteResoruce8A88D44E": { - "Type": "AWS::ApiGatewayV2::Route", + "RootRouteFooAwsPageHttpProxyIntegration9D16D6B6": { + "Type": "AWS::ApiGatewayV2::Integration", "Properties": { "ApiId": { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, - "RouteKey": "GET /some/very/deep/route/path", - "Target": { - "Fn::Join": [ - "", - [ - "integrations/", - { - "Ref": "IntegrationSomeLambdaRoute" - } - ] - ] - } + "IntegrationType": "HTTP_PROXY", + "IntegrationMethod": "ANY", + "IntegrationUri": "https://aws.amazon.com", + "PayloadFormatVersion": "1.0" } }, - "SomeLambdaRouteIntegrationSomeDeepPathBar96AE7930": { + "RootRouteFooLambdaProxyIntegration5B1AEF5D": { "Type": "AWS::ApiGatewayV2::Integration", "Properties": { "ApiId": { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, "IntegrationType": "AWS_PROXY", "IntegrationMethod": "POST", @@ -441,11 +313,7 @@ "Fn::Join": [ "", [ - "arn:aws:apigateway:", - { - "Ref": "AWS::Region" - }, - ":lambda:path/2015-03-31/functions/", + "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/", { "Fn::GetAtt": [ "MyFunc8A243A2C", @@ -459,7 +327,7 @@ "PayloadFormatVersion": "1.0" } }, - "SomeLambdaRoutePermissionSomeDeepPathBar34A20879": { + "RootRoutePermissionFooLambdaProxyIntegrationE3A30900": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", @@ -471,131 +339,48 @@ "Fn::Join": [ "", [ - "arn:aws:execute-api:", - { - "Ref": "AWS::Region" - }, - ":", + "arn:aws:execute-api:ap-northeast-1:903779448426:", { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "LambdaProxyApi67594471" + "Ref": "HttpApi2C094A993" }, "/*/*" ] ] } } - }, - "SomeLambdaRouteSomeDeepPathBarResoruceC800EE14": { - "Type": "AWS::ApiGatewayV2::Route", - "Properties": { - "ApiId": { - "Ref": "LambdaProxyApi67594471" - }, - "RouteKey": "GET /some/very/deep/route/path/bar", - "Target": { - "Fn::Join": [ - "", - [ - "integrations/", - { - "Ref": "SomeLambdaRouteIntegrationSomeDeepPathBar96AE7930" - } - ] - ] - } - } - }, - "SomeLambdaRouteSomeDeepPathBarIntegrationSomeDeepPathBarCheckIp911CF681": { - "Type": "AWS::ApiGatewayV2::Integration", - "Properties": { - "ApiId": { - "Ref": "LambdaProxyApi67594471" - }, - "IntegrationType": "HTTP_PROXY", - "IntegrationMethod": "ANY", - "IntegrationUri": "https://checkip.amazonaws.com", - "PayloadFormatVersion": "1.0" - } - }, - "SomeLambdaRouteSomeDeepPathBarSomeDeepPathBarCheckIpResoruce5039BD41": { - "Type": "AWS::ApiGatewayV2::Route", - "Properties": { - "ApiId": { - "Ref": "LambdaProxyApi67594471" - }, - "RouteKey": "ANY /some/very/deep/route/path/bar/checkip", - "Target": { - "Fn::Join": [ - "", - [ - "integrations/", - { - "Ref": "SomeLambdaRouteSomeDeepPathBarIntegrationSomeDeepPathBarCheckIp911CF681" - } - ] - ] - } - } } }, "Outputs": { - "LambdaProxyApiLambdaProxyApiUrl2866EF1B": { + "URL": { "Value": { "Fn::Join": [ "", [ "https://", { - "Ref": "LambdaProxyApi67594471" - }, - ".execute-api.", - { - "Ref": "AWS::Region" + "Ref": "HttpApiF5A9A8A7" }, - ".amazonaws.com" + ".execute-api.ap-northeast-1.amazonaws.com" ] ] } }, - "HttpProxyApiHttpProxyApiUrl25461D7F": { + "URL2": { "Value": { "Fn::Join": [ "", [ "https://", { - "Ref": "HttpProxyApiD0217C67" - }, - ".execute-api.", - { - "Ref": "AWS::Region" + "Ref": "HttpApi2C094A993" }, - ".amazonaws.com" + ".execute-api.ap-northeast-1.amazonaws.com" ] ] } }, - "SomeDeepLambdaRouteURL": { - "Value": { - "Fn::Join": [ - "", - [ - "https://", - { - "Ref": "LambdaProxyApi67594471" - }, - ".execute-api.", - { - "Ref": "AWS::Region" - }, - ".amazonaws.com/some/very/deep/route/path" - ] - ] - } + "Region": { + "Value": "ap-northeast-1" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index d3860c3403438..eea5ab9544ee9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -3,7 +3,13 @@ import * as cdk from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; const app = new cdk.App(); -const stack = new cdk.Stack(app, 'ApiagtewayV2HttpApi'); + +const env = { + region: process.env.CDK_DEFAULT_REGION, + account: process.env.CDK_DEFAULT_ACCOUNT +}; + +const stack = new cdk.Stack(app, 'ApiagtewayV2HttpApi', { env }); const handler = new lambda.Function(stack, 'MyFunc', { runtime: lambda.Runtime.PYTHON_3_7, @@ -35,65 +41,49 @@ def handler(event, context): }, }); -// Create a HTTP API with Lambda Proxy Integration as $default route -const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { - handler +const checkIpUrl = 'https://checkip.amazonaws.com'; +const awsUrl = 'https://aws.amazon.com'; + +// Create a basic HTTP API +const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetUrl: checkIpUrl +}); +new cdk.CfnOutput(stack, 'URL', { value: httpApi.url} ); + +const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { + targetHandler: handler }); +new cdk.CfnOutput(stack, 'URL2', { value: httpApi2.url }); -// Create the root route(/) with HTTP ANY method and Lambda integration -api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { - api, - handler: rootHandler, +const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { + api: httpApi2, + targetHandler: rootHandler +}); + +// create a root route for the API +httpApi2.root = new apigatewayv2.Route(stack, 'RootRoute', { + api: httpApi2, httpPath: '/', + integration: integRootHandler }); -api.root +httpApi2.root // HTTP GET /foo .addLambdaRoute('foo', 'Foo', { target: handler, method: apigatewayv2.HttpMethod.GET }) - // HTTP ANY /foo/checkip - .addHttpRoute('checkip', 'FooCheckIp', { - targetUrl: 'https://checkip.amazonaws.com', + // HTTP ANY /foo/aws + .addHttpRoute('aws', 'AwsPage', { + targetUrl: awsUrl, method: apigatewayv2.HttpMethod.ANY - }); - -// api.root -// // HTTP GET /bar -// .addLambdaRoute('bar', 'Bar', { -// target: handler2, -// method: apigatewayv2.HttpMethod.ANY -// }); - -// Create a HTTP API with HTTP Proxy Integration -new apigatewayv2.HttpProxyApi(stack, 'HttpProxyApi', { - url: 'https://aws.amazon.com' -}); - -const someDeepLambdaRoute = new apigatewayv2.LambdaRoute(stack, 'SomeLambdaRoute', { - api, - handler, - httpPath: '/some/very/deep/route/path', - httpMethod: apigatewayv2.HttpMethod.GET -}); - -// new cdk.CfnOutput(stack, 'RouteURL', { -// value: someDeepLambdaRoute.fullUrl -// }); - -someDeepLambdaRoute - // HTTP ANY /some/very/deep/route/path/bar - .addLambdaRoute('bar', 'SomeDeepPathBar', { - target: handler, - method: apigatewayv2.HttpMethod.GET }) - // HTTP ANY /some/very/deep/route/path/bar/checkip - .addHttpRoute('checkip', 'SomeDeepPathBarCheckIp', { - targetUrl: 'https://checkip.amazonaws.com', + // HTTP ANY /foo/aws/checkip + .addHttpRoute('checkip', 'CheckIp', { + targetUrl: checkIpUrl, method: apigatewayv2.HttpMethod.ANY }); -new cdk.CfnOutput(stack, 'SomeDeepLambdaRouteURL', { - value: someDeepLambdaRoute.fullUrl +new cdk.CfnOutput(stack, 'Region', { + value: stack.region }); From 66c502228220d896cbed4363e123834afd6c33bd Mon Sep 17 00:00:00 2001 From: Pahud Date: Sun, 15 Mar 2020 02:03:38 +0800 Subject: [PATCH 10/42] update README --- packages/@aws-cdk/aws-apigatewayv2/README.md | 218 +++++++++++++++---- 1 file changed, 172 insertions(+), 46 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index df5b4229662c6..a027ff56c759c 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -22,97 +22,223 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw ## Introduction -Amazon API Gateway is an AWS service for creating, publishing, maintaining, monitoring, and securing REST, HTTP, and WebSocket APIs at any scale. API developers can create APIs that access AWS or other web services, as well as data stored in the AWS Cloud. As an API Gateway API developer, you can create APIs for use in your own client applications. Or you can make your APIs available to third-party app developers. For more infomation, read the [Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html). +Amazon API Gateway is an AWS service for creating, publishing, maintaining, monitoring, and securing REST, HTTP, and WebSocket +APIs at any scale. API developers can create APIs that access AWS or other web services, as well as data stored in the AWS Cloud. +As an API Gateway API developer, you can create APIs for use in your own client applications. Read the +[Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html). +This module supports features under [API Gateway v2](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_ApiGatewayV2.html) +that lets users set up Websocket and HTTP APIs. ## API -This construct library at this moment implements API resources from [AWS::ApiGatewayV2::Api](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-api.html), which supports both `WebSocket APIs` and `HTTP APIs`. +Amazon API Gateway supports `HTTP APIs`, `WebSocket APIs` and `REST APIs`. For more information about `WebSocket APIs` and `HTTP APIs`, +see [About WebSocket APIs in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-overview.html) +and [HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html). -For more information about WebSocket APIs, see [About WebSocket APIs in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-overview.html) in the _API Gateway Developer Guide_. For more information about HTTP APIs, see [HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) in the _API Gateway Developer Guide_. - -To create [REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html), use `@aws-cdk/aws-apigateway` instead. +For more information about `REST APIs`, see [Working with REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html). To create [REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) with AWS CDK, use `@aws-cdk/aws-apigateway` instead. ### HTTP API -`CfnApi` is the L1 construct to create either `WebSocket APIs` or `HTTP APIs`. At this moment, `HTTP APIs` supports both `Lambda Proxy Integration` and `HTTP Proxy Integration`. See [Working with AWS Lambda Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) and [Working with HTTP Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html) for more information. +HTTP APIs enable you to create RESTful APIs with lower latency and lower cost than REST APIs. + +You can use HTTP APIs to send requests to AWS Lambda functions or to any routable HTTP endpoint. + +For example, you can create an HTTP API that integrates with a Lambda function on the backend. When a client calls your API, +API Gateway sends the request to the Lambda function and returns the function's response to the client. + +HTTP API supports both `Lambda Proxy Integration` and `HTTP Proxy Integration`. +See [Working with AWS Lambda Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) +and [Working with HTTP Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html) +for more information. -To create `HTTP APIs` with `Lambda Proxy Integration`, simply use `LambdaProxyApi`. + + +Use `HttpApi` to create HTTP APIs with Lambda or HTTP proxy integration. ```ts // Create a HTTP API with Lambda Proxy Integration as its $default route -const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { - handler +const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler +}); + +// Create a HTTP API with HTTP Proxy Integration as its $default route +const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { + targetUrl: checkIpUrl }); ``` -To create `HTTP APIs` with `HTTP Proxy Integration`, use `HttpProxyApi` instead. + +## Route + +`Routes` direct incoming API requests to backend resources. `Routes` consist of two parts: an HTTP method and a resource path—for example, +`GET /pets`. You can define specific HTTP methods for your route, or use the `ANY` method to match all methods that you haven't defined for a resource. +You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See +[Working with Routes for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). + +When you create HTTP APIs with either `Lambda Proxy Integration` or `HTTP Proxy Integration`, the `$default route` will be created as well. + +To create a root route for an existing `Api` resource ```ts -// Create a HTTP API with HTTP Proxy Integration -new apigatewayv2.HttpProxyApi(stack, 'HttpProxyApi', { - url: 'https://aws.amazon.com' +// create a root route for the API +httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { + api: httpApi, + httpPath: '/', + integration: integRootHandler }); ``` +And extend different routes from the root route +```ts +const checkIpUrl = 'https://checkip.amazonaws.com'; +const awsUrl = 'https://aws.amazon.com'; -### WebSocket API +httpApi.root + // HTTP GET /foo + .addLambdaRoute('foo', 'Foo', { + target: handler, + method: apigatewayv2.HttpMethod.GET + }) + // HTTP ANY /foo/aws + .addHttpRoute('aws', 'AwsPage', { + targetUrl: awsUrl, + method: apigatewayv2.HttpMethod.ANY + }) + // HTTP ANY /foo/aws/checkip + .addHttpRoute('checkip', 'CheckIp', { + targetUrl: checkIpUrl, + method: apigatewayv2.HttpMethod.ANY + }); +``` -The WebSocket API is not implemented yet in this L2 construct library, however, as `Api` class is provided, it's still possible to create the Websocket APIs with the `Api` class. +To create a specific route directly rather than building it from the root +```ts +// Option 1. create a specific 'GET /some/very/deep/route/path' route with Lambda proxy integration for an existing HTTP API +const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { + api: httpApi, + httpPath: '/some/very/deep/route/path', + targetHandler +}); -## Route +// Option 2. with HTTP proxy integration +const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { + api: httpApi, + httpPath: '/some/very/deep/route/path', + targetUrl +}); -`Routes` direct incoming API requests to backend resources. `Routes` consist of two parts: an HTTP method and a resource path—for example, `GET /pets`. You can define specific HTTP methods for your route, or use the `ANY` method to match all methods that you haven't defined for a resource. You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See [Working with Routes for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). +// Option 3. with existing integration resource +const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { + api: httpApi, + httpPath: '/some/very/deep/route/path', + integration +}); +``` -When you create HTTP APIs with either `Lambda Proxy Integration` or `HTTP Proxy Integration`, the `$default route` will be created as well. +## Integration + +Integrations connect a route to backend resources. HTTP APIs support Lambda proxy and HTTP proxy integrations. +For example, you can configure a POST request to the /signup route of your API to integrate with a Lambda function +that handles signing up customers. -To create a specific route for an existing `Api` resource, specify the `httpPath` and an optional `httpMethod` property. +Use `Integration` to create the integration resources ```ts -// create a specific 'GET /some/very/deep/route/path' route with Lambda proxy integration for an existing HTTP API -const someDeepLambdaRoute = new apigatewayv2.LambdaRoute(stack, 'SomeLambdaRoute', { - api, - handler, +// create API +const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler +}); +// create Integration +const integ = new apigatewayv2.Integration(stack, 'IntegRootHandler', { + apiId: api.httpApiId, + integrationMethod: apigatewayv2.HttpMethod.ANY, + integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, + integrationUri: awsUrl +}); +// create a specific route with the integration above for the API +new apigatewayv2.Route(stack, 'SomeDeepRoute', { + api: httpApi, httpPath: '/some/very/deep/route/path', - // optional - httpMethod: HttpMethod.GET + integration }); + ``` -### `addLambdaRoute` and `addHttpRoute` +You may also use `LambdaProxyIntegraion` or `HttpProxyIntegration` to easily create the integrations. + +```ts +// create a Lambda proxy integration +new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { + api + targetHandler +}); + +// or create a Http proxy integration +new apigatewayv2.HttpProxyIntegration(stack, 'IntegRootHandler', { + api + targetUrl +}); +``` -To extend the routes from an existing HTTP API, use `addLambdaRoute` for Lambda proxy integration or `addHttpRoute` for HTTP Proxy Intgegration. +## Samples -Consider the providewd example above ```ts -// create a specific 'GET /some/very/deep/route/path' route with Lambda proxy integration for an existing HTTP API -const someDeepLambdaRoute = new apigatewayv2.LambdaRoute(stack, 'SomeLambdaRoute', { - api, - handler, - httpPath: '/some/very/deep/route/path', - httpMethod: HttpMethod.GET +// create a HTTP API with HTTP proxy integration as the $default route +const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetUrl: checkIpUrl +}); +// print the API URL +new cdk.CfnOutput(stack, 'URL', { value: httpApi.url} ); + +// create another HTTP API with Lambda proxy integration as the $default route +const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { + targetHandler: handler +}); +// print the API URL +new cdk.CfnOutput(stack, 'URL2', { value: httpApi2.url }); + +// create a Lambda proxy integration +const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { + api: httpApi2, + targetHandler: rootHandler +}); + +// create a root route for the API with the integration we created above and assign the route resource +// as a 'root' property to the API +httpApi2.root = new apigatewayv2.Route(stack, 'RootRoute', { + api: httpApi2, + httpPath: '/', + integration: integRootHandler, }); -someDeepLambdaRoute - // HTTP ANY /some/very/deep/route/path/bar - .addLambdaRoute('bar', 'SomeDeepPathBar', { +// Now, extend the route tree from the root +httpApi2.root + // HTTP ANY /foo + .addLambdaRoute('foo', 'Foo', { target: handler, - method: apigatewayv2.HttpMethod.GET }) - // HTTP ANY /some/very/deep/route/path/bar/checkip - .addHttpRoute('checkip', 'SomeDeepPathBarCheckIp', { - targetUrl: 'https://checkip.amazonaws.com', + // HTTP ANY /foo/aws + .addHttpRoute('aws', 'AwsPage', { + targetUrl: awsUrl, method: apigatewayv2.HttpMethod.ANY + }) + // HTTP GET /foo/aws/checkip + .addHttpRoute('checkip', 'CheckIp', { + targetUrl: checkIpUrl, + method: apigatewayv2.HttpMethod.GET }); -// print the full URL in the Outputs -new cdk.CfnOutput(stack, 'SomeDeepLambdaRouteURL', { - value: someDeepLambdaRoute.fullUrl +// And create a specific route for it as well +// HTTP ANY /some/very/deep/route/path +new apigatewayv2.Route(stack, 'SomeDeepRoute', { + api: httpApi, + httpPath: '/some/very/deep/route/path', + targetHandler }); -``` - +``` \ No newline at end of file From 87fbe4ba0c2077295efe18097da6778444eb11e7 Mon Sep 17 00:00:00 2001 From: Pahud Date: Sun, 15 Mar 2020 02:04:43 +0800 Subject: [PATCH 11/42] minor fix --- packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts index 686a8e27193c1..24ead1927cd96 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts @@ -1,5 +1,5 @@ // import '@aws-cdk/assert/jest'; -import { countResources, expect as expectCDK, haveOutput, haveResource } from '@aws-cdk/assert'; +import { countResources, expect as expectCDK } from '@aws-cdk/assert'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; From d26ad33ee2bf311018a6927071a9813fa191f96a Mon Sep 17 00:00:00 2001 From: Pahud Date: Sun, 15 Mar 2020 02:23:17 +0800 Subject: [PATCH 12/42] support HttpApi with no integration and no route --- packages/@aws-cdk/aws-apigatewayv2/README.md | 8 +- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 16 +- .../test/apigatewayv2.test.ts | 317 +----------------- 3 files changed, 23 insertions(+), 318 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index a027ff56c759c..59451e4a097d1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -60,18 +60,20 @@ Use `HttpApi` to create HTTP APIs with Lambda or HTTP proxy integration. ```ts +// Create a vanilla HTTP API with no integration targets +const httpApi1 = new apigatewayv2.HttpApi(stack, 'HttpApi1'); + // Create a HTTP API with Lambda Proxy Integration as its $default route -const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { targetHandler: handler }); // Create a HTTP API with HTTP Proxy Integration as its $default route -const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { +const httpApi3 = new apigatewayv2.HttpApi(stack, 'HttpApi3', { targetUrl: checkIpUrl }); ``` - ## Route `Routes` direct incoming API requests to backend resources. `Routes` consist of two parts: an HTTP method and a resource path—for example, diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index b18a2c0d8c5e4..1fc7f6b46c8b7 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -119,9 +119,13 @@ export class HttpApi extends ApiBase implements IHttpApi { physicalName: props?.apiName || id, }); - if ((!props) || - (props!.targetHandler && props!.targetUrl) || - (props!.targetHandler === undefined && props!.targetUrl === undefined)) { + // if ((!props) || + // (props!.targetHandler && props!.targetUrl) || + // (props!.targetHandler === undefined && props!.targetUrl === undefined)) { + // throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); + // } + + if (props?.targetHandler && props?.targetUrl) { throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); } @@ -132,15 +136,15 @@ export class HttpApi extends ApiBase implements IHttpApi { const apiProps: CfnApiProps = { name: this.physicalName, - protocolType: props!.protocol ?? ProtocolType.HTTP, - target: props!.targetHandler ? props.targetHandler.functionArn : props!.targetUrl + protocolType: props?.protocol ?? ProtocolType.HTTP, + target: props?.targetHandler ? props?.targetHandler.functionArn : props?.targetUrl ?? undefined }; const api = new apigatewayv2.CfnApi(this, 'Resource', apiProps ); this.httpApiId = api.ref; this.url = `https://${this.httpApiId}.execute-api.${this.region}.${this.awsdn}`; - if (props!.targetHandler) { + if (props?.targetHandler) { new lambda.CfnPermission(this, 'Permission', { action: 'lambda:InvokeFunction', principal: 'apigateway.amazonaws.com', diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts index 24ead1927cd96..3f15ec4b38682 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts @@ -46,6 +46,13 @@ def handler(event, context): awsUrl = 'https://aws.amazon.com'; }); +test('create a HTTP API with no props correctly', () => { + // WHEN + new apigatewayv2.HttpApi(stack, 'HttpApi'); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +}); + test('create a basic HTTP API correctly', () => { // WHEN new apigatewayv2.HttpApi(stack, 'HttpApi', { @@ -200,14 +207,6 @@ test('addHttpRoute correctly', () => { expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); }); -test('throws when creating HTTP API with no props', () => { - // WHEN - // THEN - expect(() => - new apigatewayv2.HttpApi(stack, 'HttpApi') - ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); -}); - test('throws when both targetHandler and targetUrl are specified', () => { // WHEN // THEN @@ -219,15 +218,6 @@ test('throws when both targetHandler and targetUrl are specified', () => { ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); }); -test('throws when neither targetHandler nor targetUrl are specified', () => { - // WHEN - // THEN - expect(() => - new apigatewayv2.HttpApi(stack, 'HttpApi', { - }) - ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); -}); - test('throws when both targetHandler and targetUrl are specified for Route', () => { // WHEN const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { @@ -308,295 +298,4 @@ def handler(event, context): }); // THEN expectCDK(stackcn).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -}); - -// ''(test: Test); { -// // GIVEN -// const app = new cdk.App(); -// const stack = new cdk.Stack(app, 'stack', { env: { region: 'us-east-1' } }); - -// // WHEN -// const vpc = new ec2.Vpc(stack, 'VPC'); -// new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); -// const layer = KubectlLayer.getOrCreate(stack, {}); - -// // THEN -// expect(stack).to(haveResource('Custom::AWSCDK-EKS-Cluster')); -// expect(stack).to(haveResourceLike('AWS::Serverless::Application', { -// Location: { -// ApplicationId: 'arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl', -// } -// })); -// test.equal(layer.isChina(), false); -// test.done(); -// }, - -// test('create LambdaProxyApi correctly', () => { -// // WHEN -// new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { -// handler, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// // expectCDK(stack).to(countResources('AWS::Lambda::Function', 1)); -// // expectCDK(stack).to(countResources('AWS::Lambda::Permission', 1)); -// expectCDK(stack).to(haveOutput({ -// outputName: 'LambdaProxyApiLambdaProxyApiUrl2866EF1B' -// })); -// }); - -// test('create HttpProxyApi correctly', () => { -// // WHEN -// new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { -// url: 'https://checkip.amazonaws.com' -// }); -// // THEN -// expectCDK(stack).to(haveResource('AWS::ApiGatewayV2::Api', { -// Name: "LambdaProxyApi", -// ProtocolType: "HTTP", -// Target: "https://checkip.amazonaws.com" -// } )); -// expectCDK(stack).to(haveOutput({ -// outputName: 'LambdaProxyApiHttpProxyApiUrl378A7258' -// })); -// }); - -// test('create LambdaRoute correctly for HttpProxyApi', () => { -// // WHEN -// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { -// url: 'https://checkip.amazonaws.com' -// }); -// api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { -// api, -// handler: rootHandler, -// httpPath: '/', -// }); - -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -// expectCDK(stack).to(countResources('AWS::Lambda::Permission', 1)); -// }); - -// test('create HttpRoute correctly for HttpProxyApi', () => { -// // WHEN -// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { -// url: 'https://checkip.amazonaws.com' -// }); -// api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { -// api, -// targetUrl: 'https://www.amazon.com', -// httpPath: '/', -// }); - -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -// }); - -// test('create LambdaRoute correctly for LambdaProxyApi', () => { -// // WHEN -// const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { -// handler -// }); -// api.root = new apigatewayv2.LambdaRoute(stack, 'RootRoute', { -// api, -// handler: rootHandler, -// httpPath: '/', -// }); - -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -// expectCDK(stack).to(countResources('AWS::Lambda::Permission', 2)); -// }); - -// test('create HttpRoute correctly for LambdaProxyApi', () => { -// // WHEN -// const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { -// handler -// }); -// api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { -// api, -// targetUrl: 'https://www.amazon.com', -// httpPath: '/', -// }); - -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -// }); - -// test('addLambdaRoute and addHttpRoute correctly from an existing route', () => { -// // WHEN -// const api = new apigatewayv2.LambdaProxyApi(stack, 'LambdaProxyApi', { -// handler -// }); -// api.root = new apigatewayv2.HttpRoute(stack, 'RootRoute', { -// api, -// targetUrl: 'https://www.amazon.com', -// httpPath: '/', -// }); - -// api.root -// // HTTP GET /foo -// .addLambdaRoute('foo', 'Foo', { -// target: handler, -// method: apigatewayv2.HttpMethod.GET -// }) -// // HTTP ANY /foo/checkip -// .addHttpRoute('checkip', 'FooCheckIp', { -// targetUrl: 'https://checkip.amazonaws.com', -// method: apigatewayv2.HttpMethod.ANY -// }); - -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 3)); -// }); - -// test('import API correctly', () => { -// // WHEN -// const api = apigatewayv2.Api.fromApiId(stack, 'ImportedApi', 'foo'); - -// // THEN -// expect(api).toHaveProperty('apiId'); -// }); - -// test('import HttpProxyApi correctly', () => { -// // WHEN -// const api = apigatewayv2.HttpProxyApi.fromHttpProxyApiId(stack, 'ImportedApi', 'foo'); - -// // THEN -// expect(api).toHaveProperty('apiId'); -// }); - -// test('import LambdaProxyApi correctly', () => { -// // WHEN -// const api = apigatewayv2.LambdaProxyApi.fromLambdaProxyApiId(stack, 'ImportedApi', 'foo'); - -// // THEN -// expect(api).toHaveProperty('apiId'); -// }); - -// test('import Route correctly', () => { -// // WHEN -// const route = apigatewayv2.Route.fromRouteAttributes(stack, 'ImportedRoute', { -// routeId: 'foo' -// }); - -// // THEN -// expect(route).toHaveProperty('routeId'); -// }); - -// test('import LambdaRoute correctly', () => { -// // WHEN -// const route = apigatewayv2.LambdaRoute.fromLambdaRouteId(stack, 'ImportedRoute', 'foo'); -// // THEN -// expect(route).toHaveProperty('routeId'); -// }); - -// test('import HttpRoute correctly', () => { -// // WHEN -// const route = apigatewayv2.HttpRoute.fromHttpRouteId(stack, 'ImportedRoute', 'foo'); -// // THEN -// expect(route).toHaveProperty('routeId'); -// }); - -// test('import LambdaRoute correctly', () => { -// // WHEN -// const route = apigatewayv2.LambdaRoute.fromLambdaRouteId(stack, 'ImportedRoute', 'foo'); -// // THEN -// expect(route).toHaveProperty('routeId'); -// }); - -// test('create api correctly with no props', () => { -// // WHEN -// new apigatewayv2.Api(stack, 'API'); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// }); - -// test('assign aws partition correctly by region for api', () => { -// // WHEN -// const cnstack = new cdk.Stack(undefined, undefined, { -// env: { -// region: 'cn-north-1' -// } -// }); -// const api = new apigatewayv2.Api(cnstack, 'API'); -// // THEN -// expect(api.partition).toBe('aws-cn'); -// expect(api.awsdn).toBe('amazonaws.com.cn'); -// }); - -// test('assign aws partition correctly by region for lambda route', () => { -// // WHEN -// const cnstack = new cdk.Stack(undefined, undefined, { -// env: { -// region: 'cn-north-1' -// } -// }); -// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { -// url: 'https://checkip.amazonaws.com' -// }); - -// new apigatewayv2.LambdaRoute(cnstack, 'API', { -// api, -// handler, -// httpPath: '/foo' -// }); -// // THEN -// expect(true).toBe(true); -// }); - -// test('create api correctly with specific protocol specified', () => { -// // WHEN -// new apigatewayv2.Api(stack, 'API', { -// target: '', -// protocol: apigatewayv2.ApiProtocol.WEBSOCKET -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// }); - -// test('create HttpProxyApi correctly with specific api name specified', () => { -// // WHEN -// new apigatewayv2.HttpProxyApi(stack, 'API', { -// apiName: 'foo', -// url: 'https://www.amazon.com' -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// }); - -// test('create Route correctly with no httpMethod specified', () => { -// // WHEN -// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { -// url: 'https://checkip.amazonaws.com' -// }); -// new apigatewayv2.Route(stack, 'Route', { -// api, -// httpPath: '/foo', -// target: 'https://www.amazon.com' -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// }); - -// test('create HttpRoute correctly with specific integrationMethod', () => { -// // WHEN -// const api = new apigatewayv2.HttpProxyApi(stack, 'LambdaProxyApi', { -// url: 'https://checkip.amazonaws.com' -// }); - -// new apigatewayv2.HttpRoute(stack, 'Route', { -// api, -// httpPath: '/foo', -// targetUrl: 'https://www.amazon.com', -// integrationMethod: apigatewayv2.HttpMethod.GET -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 1)); -// }); \ No newline at end of file +}); \ No newline at end of file From 4b278c0bbb04d64453c713ef5044f8b2b94d0748 Mon Sep 17 00:00:00 2001 From: Pahud Date: Sun, 15 Mar 2020 02:38:43 +0800 Subject: [PATCH 13/42] update tests --- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 4 ++-- .../aws-apigatewayv2/test/apigatewayv2.test.ts | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 1fc7f6b46c8b7..6f2ae1947192b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -125,7 +125,7 @@ export class HttpApi extends ApiBase implements IHttpApi { // throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); // } - if (props?.targetHandler && props?.targetUrl) { + if (props?.targetHandler && props.targetUrl) { throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); } @@ -137,7 +137,7 @@ export class HttpApi extends ApiBase implements IHttpApi { const apiProps: CfnApiProps = { name: this.physicalName, protocolType: props?.protocol ?? ProtocolType.HTTP, - target: props?.targetHandler ? props?.targetHandler.functionArn : props?.targetUrl ?? undefined + target: props?.targetHandler ? props.targetHandler.functionArn : props?.targetUrl ?? undefined }; const api = new apigatewayv2.CfnApi(this, 'Resource', apiProps ); this.httpApiId = api.ref; diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts index 3f15ec4b38682..4d02969d3904c 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts @@ -53,7 +53,23 @@ test('create a HTTP API with no props correctly', () => { expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); }); -test('create a basic HTTP API correctly', () => { +test('create a HTTP API with empty props correctly', () => { + // WHEN + new apigatewayv2.HttpApi(stack, 'HttpApi', {}); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +}); + +test('create a basic HTTP API correctly with targetUrl', () => { + // WHEN + new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetUrl: checkIpUrl, + }); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +}); + +test('create a basic HTTP API correctly with targetUrl and protocol', () => { // WHEN new apigatewayv2.HttpApi(stack, 'HttpApi', { targetUrl: checkIpUrl, From bef8974920bd7c8da1c3448d383d7902fe779f5f Mon Sep 17 00:00:00 2001 From: Pahud Date: Sun, 15 Mar 2020 02:54:25 +0800 Subject: [PATCH 14/42] remove unused type --- packages/@aws-cdk/aws-apigatewayv2/lib/route.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts index e68325a17bd72..cfc78a23840a4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -47,17 +47,6 @@ export interface IRouteBase extends IRoute { addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route; } -/** - * the interface of the route attributes - */ -export interface RouteAttributes { - /** - * ID of this Route - * @attribute - */ - readonly routeId: string; -} - /** * options of HttpRoute */ From 068b476e1ca15fcf9995a34a88b22eeae1c6dcc7 Mon Sep 17 00:00:00 2001 From: Pahud Date: Sun, 15 Mar 2020 03:26:24 +0800 Subject: [PATCH 15/42] update integ test --- .../test/integ.api.expected.json | 75 +++++++++---------- .../aws-apigatewayv2/test/integ.api.ts | 17 +---- 2 files changed, 40 insertions(+), 52 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json index d609722dc4d9a..49bb17ea3ada2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json @@ -139,7 +139,15 @@ "Fn::Join": [ "", [ - "arn:aws:execute-api:ap-northeast-1:903779448426:", + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", { "Ref": "HttpApi2C094A993" }, @@ -161,7 +169,11 @@ "Fn::Join": [ "", [ - "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/", + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", { "Fn::GetAtt": [ "RootFuncF39FB174", @@ -187,7 +199,15 @@ "Fn::Join": [ "", [ - "arn:aws:execute-api:ap-northeast-1:903779448426:", + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", { "Ref": "HttpApi2C094A993" }, @@ -313,7 +333,11 @@ "Fn::Join": [ "", [ - "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/", + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", { "Fn::GetAtt": [ "MyFunc8A243A2C", @@ -339,7 +363,15 @@ "Fn::Join": [ "", [ - "arn:aws:execute-api:ap-northeast-1:903779448426:", + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", { "Ref": "HttpApi2C094A993" }, @@ -349,38 +381,5 @@ } } } - }, - "Outputs": { - "URL": { - "Value": { - "Fn::Join": [ - "", - [ - "https://", - { - "Ref": "HttpApiF5A9A8A7" - }, - ".execute-api.ap-northeast-1.amazonaws.com" - ] - ] - } - }, - "URL2": { - "Value": { - "Fn::Join": [ - "", - [ - "https://", - { - "Ref": "HttpApi2C094A993" - }, - ".execute-api.ap-northeast-1.amazonaws.com" - ] - ] - } - }, - "Region": { - "Value": "ap-northeast-1" - } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index eea5ab9544ee9..2c15578137f84 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -4,12 +4,7 @@ import * as apigatewayv2 from '../lib'; const app = new cdk.App(); -const env = { - region: process.env.CDK_DEFAULT_REGION, - account: process.env.CDK_DEFAULT_ACCOUNT -}; - -const stack = new cdk.Stack(app, 'ApiagtewayV2HttpApi', { env }); +const stack = new cdk.Stack(app, 'ApiagtewayV2HttpApi'); const handler = new lambda.Function(stack, 'MyFunc', { runtime: lambda.Runtime.PYTHON_3_7, @@ -45,15 +40,13 @@ const checkIpUrl = 'https://checkip.amazonaws.com'; const awsUrl = 'https://aws.amazon.com'; // Create a basic HTTP API -const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +new apigatewayv2.HttpApi(stack, 'HttpApi', { targetUrl: checkIpUrl }); -new cdk.CfnOutput(stack, 'URL', { value: httpApi.url} ); const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { targetHandler: handler }); -new cdk.CfnOutput(stack, 'URL2', { value: httpApi2.url }); const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { api: httpApi2, @@ -82,8 +75,4 @@ httpApi2.root .addHttpRoute('checkip', 'CheckIp', { targetUrl: checkIpUrl, method: apigatewayv2.HttpMethod.ANY - }); - -new cdk.CfnOutput(stack, 'Region', { - value: stack.region -}); + }); \ No newline at end of file From 6f8f64d06e797e2aaa0ba803fb62bacef189669f Mon Sep 17 00:00:00 2001 From: Pahud Date: Sun, 15 Mar 2020 13:11:35 +0800 Subject: [PATCH 16/42] support addLambdaRoute and addHttpRoute from HttpApi --- packages/@aws-cdk/aws-apigatewayv2/README.md | 13 +++ packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 33 +++++++- .../test/apigatewayv2.test.ts | 66 ++++++++++++++- .../test/integ.api.expected.json | 80 +++++++++++++++++++ .../aws-apigatewayv2/test/integ.api.ts | 8 +- 5 files changed, 192 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 59451e4a097d1..89cb2211252a1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -83,6 +83,8 @@ You can create a `$default route` that acts as a catch-all for requests that don When you create HTTP APIs with either `Lambda Proxy Integration` or `HTTP Proxy Integration`, the `$default route` will be created as well. + + To create a root route for an existing `Api` resource ```ts @@ -140,6 +142,17 @@ const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { httpPath: '/some/very/deep/route/path', integration }); + +// Option 3. with addLambdaRoute or addHttpRoute from the HttpApi resource +httpApi.addLambdaRoute('/foo/bar', 'FooBar', { + target: handler, + method: apigatewayv2.HttpMethod.GET +}); + +httpApi.addHttpRoute('/foo/bar', 'FooBar', { + targetUrl: awsUrl, + method: apigatewayv2.HttpMethod.ANY +}); ``` ## Integration diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 6f2ae1947192b..474ea4a7cf581 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -1,7 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; -import * as apigatewayv2 from '../lib'; -import { CfnApiProps } from './apigatewayv2.generated'; +import { CfnApi, CfnApiProps, HttpRouteOptions, IRouteBase, LambdaRouteOptions, Route } from '../lib'; /** * the HTTP API interface @@ -112,7 +111,7 @@ export class HttpApi extends ApiBase implements IHttpApi { /** * root route */ - public root?: apigatewayv2.IRouteBase; + public root?: IRouteBase; constructor(scope: cdk.Construct, id: string, props?: HttpApiProps) { super(scope, id, { @@ -139,7 +138,7 @@ export class HttpApi extends ApiBase implements IHttpApi { protocolType: props?.protocol ?? ProtocolType.HTTP, target: props?.targetHandler ? props.targetHandler.functionArn : props?.targetUrl ?? undefined }; - const api = new apigatewayv2.CfnApi(this, 'Resource', apiProps ); + const api = new CfnApi(this, 'Resource', apiProps ); this.httpApiId = api.ref; this.url = `https://${this.httpApiId}.execute-api.${this.region}.${this.awsdn}`; @@ -154,6 +153,32 @@ export class HttpApi extends ApiBase implements IHttpApi { } } + /** + * create a child route with Lambda proxy integration + */ + public addLambdaRoute(httpPath: string, id: string, options: LambdaRouteOptions): Route { + const httpMethod = options.method; + return new Route(this, id, { + api: this, + targetHandler: options.target, + httpPath, + httpMethod + }); + } + + /** + * create a child route with HTTP proxy integration + */ + public addHttpRoute(httpPath: string, id: string, options: HttpRouteOptions): Route { + const httpMethod = options.method; + return new Route(this, id, { + api: this, + targetUrl: options.targetUrl, + httpPath, + httpMethod + }); + } + private isChina(): boolean { const region = this.stack.region; return !cdk.Token.isUnresolved(region) && region.startsWith('cn-'); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts index 4d02969d3904c..f18b22fc85b3c 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts @@ -1,5 +1,5 @@ // import '@aws-cdk/assert/jest'; -import { countResources, expect as expectCDK } from '@aws-cdk/assert'; +import { countResources, expect as expectCDK, haveResourceLike } from '@aws-cdk/assert'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; @@ -175,7 +175,7 @@ test('import route from route ID correctly', () => { ).not.toThrowError(); }); -test('addLambdaRoute correctly', () => { +test('addLambdaRoute correctly from a Route', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { targetHandler: handler @@ -199,7 +199,7 @@ test('addLambdaRoute correctly', () => { expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); }); -test('addHttpRoute correctly', () => { +test('addHttpRoute correctly from a Route', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { targetHandler: handler @@ -223,6 +223,66 @@ test('addHttpRoute correctly', () => { expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); }); +test('addLambdaRoute correctly from a HttpApi', () => { + // WHEN + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler + }); + httpApi.addLambdaRoute('/foo/bar', 'FooBar', { + target: handler, + method: apigatewayv2.HttpMethod.GET + }); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { + ApiId: { + Ref: "HttpApiF5A9A8A7" + }, + RouteKey: "GET /foo/bar", + Target: { + "Fn::Join": [ + "", + [ + "integrations/", + { + Ref: "HttpApiFooBarLambdaProxyIntegration2FFCA7FC" + } + ] + ] + } + })); +}); + +test('addHttpRoute correctly from a HttpApi', () => { + // WHEN + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler + }); + httpApi.addHttpRoute('/foo/bar', 'FooBar', { + targetUrl: awsUrl, + method: apigatewayv2.HttpMethod.ANY + }); + // THEN + expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { + ApiId: { + Ref: "HttpApiF5A9A8A7" + }, + RouteKey: "ANY /foo/bar", + Target: { + "Fn::Join": [ + "", + [ + "integrations/", + { + Ref: "HttpApiFooBarHttpProxyIntegration80C34C6B" + } + ] + ] + } + })); +}); + test('throws when both targetHandler and targetUrl are specified', () => { // WHEN // THEN diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json index 49bb17ea3ada2..aff315d2c2bd6 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json @@ -114,6 +114,86 @@ "Target": "https://checkip.amazonaws.com" } }, + "HttpApiLambdaRoute41C718EF": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "HttpApiF5A9A8A7" + }, + "RouteKey": "ANY /fn", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "HttpApiLambdaRouteLambdaProxyIntegration9CDB3CA1" + } + ] + ] + } + } + }, + "HttpApiLambdaRouteLambdaProxyIntegration9CDB3CA1": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "HttpApiF5A9A8A7" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationMethod": "POST", + "IntegrationUri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] + }, + "/invocations" + ] + ] + }, + "PayloadFormatVersion": "1.0" + } + }, + "HttpApiPermissionLambdaRouteLambdaProxyIntegrationB7913F51": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunc8A243A2C" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "HttpApiF5A9A8A7" + }, + "/*/*" + ] + ] + } + } + }, "HttpApi2C094A993": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index 2c15578137f84..13141695337c4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -40,10 +40,16 @@ const checkIpUrl = 'https://checkip.amazonaws.com'; const awsUrl = 'https://aws.amazon.com'; // Create a basic HTTP API -new apigatewayv2.HttpApi(stack, 'HttpApi', { +const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { targetUrl: checkIpUrl }); +// addLambdaRoute from this API +// HTTP ANY /fn +httpApi.addLambdaRoute('/fn', 'LambdaRoute', { + target: handler +}); + const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { targetHandler: handler }); From 9d986d5d012d5b0c7e7b1a379aec89c057fa9a07 Mon Sep 17 00:00:00 2001 From: Pahud Date: Mon, 16 Mar 2020 08:59:13 +0800 Subject: [PATCH 17/42] update README sample --- packages/@aws-cdk/aws-apigatewayv2/README.md | 36 ++++++++------------ 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 89cb2211252a1..654777d2673c8 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -119,31 +119,35 @@ httpApi.root }); ``` -To create a specific route directly rather than building it from the root +To create a specific route directly rather than building it from the root, just create the `Route` resource with `targetHandler`, `targetUrl` or `integration`. ```ts -// Option 1. create a specific 'GET /some/very/deep/route/path' route with Lambda proxy integration for an existing HTTP API +// create a specific 'GET /some/very/deep/route/path' route with Lambda proxy integration for an existing HTTP API const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { api: httpApi, httpPath: '/some/very/deep/route/path', targetHandler }); -// Option 2. with HTTP proxy integration +// with HTTP proxy integration const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { api: httpApi, httpPath: '/some/very/deep/route/path', targetUrl }); -// Option 3. with existing integration resource +// with existing integration resource const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { api: httpApi, httpPath: '/some/very/deep/route/path', integration }); +``` + +You may also `addLambdaRoute` or `addHttpRoute` from the `HttpAppi` resource. -// Option 3. with addLambdaRoute or addHttpRoute from the HttpApi resource +```ts +// addLambdaRoute or addHttpRoute from the HttpApi resource httpApi.addLambdaRoute('/foo/bar', 'FooBar', { target: handler, method: apigatewayv2.HttpMethod.GET @@ -184,7 +188,7 @@ new apigatewayv2.Route(stack, 'SomeDeepRoute', { ``` -You may also use `LambdaProxyIntegraion` or `HttpProxyIntegration` to easily create the integrations. +You may use `LambdaProxyIntegraion` or `HttpProxyIntegration` to easily create the integrations. ```ts // create a Lambda proxy integration @@ -218,19 +222,11 @@ const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { // print the API URL new cdk.CfnOutput(stack, 'URL2', { value: httpApi2.url }); -// create a Lambda proxy integration -const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { - api: httpApi2, - targetHandler: rootHandler -}); - // create a root route for the API with the integration we created above and assign the route resource // as a 'root' property to the API -httpApi2.root = new apigatewayv2.Route(stack, 'RootRoute', { - api: httpApi2, - httpPath: '/', - integration: integRootHandler, -}); +httpApi2.root = httpApi2.addLambdaRoute('/', 'RootRoute', { + target: rootHandler +}) // Now, extend the route tree from the root httpApi2.root @@ -251,9 +247,7 @@ httpApi2.root // And create a specific route for it as well // HTTP ANY /some/very/deep/route/path -new apigatewayv2.Route(stack, 'SomeDeepRoute', { - api: httpApi, - httpPath: '/some/very/deep/route/path', - targetHandler +httpApi2.addLambdaRoute('/some/very/deep/route/path', 'FooBar', { + target: handler }); ``` \ No newline at end of file From b200c9f1f8ce7952c67e2bf936e8a411cb0196c4 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Wed, 18 Mar 2020 19:57:42 +0800 Subject: [PATCH 18/42] Update packages/@aws-cdk/aws-apigatewayv2/README.md Co-Authored-By: Niranjan Jayakar --- packages/@aws-cdk/aws-apigatewayv2/README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 654777d2673c8..ed9f72a6576bf 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -42,12 +42,7 @@ For more information about `REST APIs`, see [Working with REST APIs](https://doc ### HTTP API -HTTP APIs enable you to create RESTful APIs with lower latency and lower cost than REST APIs. - -You can use HTTP APIs to send requests to AWS Lambda functions or to any routable HTTP endpoint. - -For example, you can create an HTTP API that integrates with a Lambda function on the backend. When a client calls your API, -API Gateway sends the request to the Lambda function and returns the function's response to the client. +HTTP APIs enable you to create RESTful APIs that integrate with AWS Lambda functions or to any routable HTTP endpoint. HTTP API supports both `Lambda Proxy Integration` and `HTTP Proxy Integration`. See [Working with AWS Lambda Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) @@ -250,4 +245,4 @@ httpApi2.root httpApi2.addLambdaRoute('/some/very/deep/route/path', 'FooBar', { target: handler }); -``` \ No newline at end of file +``` From 0bea7fc0b5cf0919a2692b3ef071427a14663cc3 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Wed, 18 Mar 2020 19:58:04 +0800 Subject: [PATCH 19/42] Update packages/@aws-cdk/aws-apigatewayv2/README.md Co-Authored-By: Niranjan Jayakar --- packages/@aws-cdk/aws-apigatewayv2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index ed9f72a6576bf..010c99a56462e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -44,7 +44,7 @@ For more information about `REST APIs`, see [Working with REST APIs](https://doc HTTP APIs enable you to create RESTful APIs that integrate with AWS Lambda functions or to any routable HTTP endpoint. -HTTP API supports both `Lambda Proxy Integration` and `HTTP Proxy Integration`. +HTTP API supports both Lambda proxy integration and HTTP proxy integration. See [Working with AWS Lambda Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) and [Working with HTTP Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html) for more information. From 822cfe83e8e433b9f69d059fdf2293babf6e29ac Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Wed, 18 Mar 2020 19:58:44 +0800 Subject: [PATCH 20/42] Update packages/@aws-cdk/aws-apigatewayv2/README.md Co-Authored-By: Niranjan Jayakar --- packages/@aws-cdk/aws-apigatewayv2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 010c99a56462e..78a36fa54e753 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -56,7 +56,7 @@ Use `HttpApi` to create HTTP APIs with Lambda or HTTP proxy integration. ```ts // Create a vanilla HTTP API with no integration targets -const httpApi1 = new apigatewayv2.HttpApi(stack, 'HttpApi1'); +new apigatewayv2.HttpApi(stack, 'HttpApi1'); // Create a HTTP API with Lambda Proxy Integration as its $default route const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { From c20a448203bed358f7e62536156bf82c9d791885 Mon Sep 17 00:00:00 2001 From: Pahud Date: Wed, 18 Mar 2020 22:44:38 +0800 Subject: [PATCH 21/42] minor fix --- packages/@aws-cdk/aws-apigatewayv2/README.md | 8 +- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 106 ++++-------------- .../aws-apigatewayv2/lib/integration.ts | 59 +++++----- .../@aws-cdk/aws-apigatewayv2/lib/route.ts | 18 ++- .../test/apigatewayv2.test.ts | 11 +- .../aws-apigatewayv2/test/integ.api.ts | 5 +- 6 files changed, 71 insertions(+), 136 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 78a36fa54e753..54a726da0a3c7 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -36,12 +36,12 @@ Amazon API Gateway supports `HTTP APIs`, `WebSocket APIs` and `REST APIs`. For m see [About WebSocket APIs in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-overview.html) and [HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html). -For more information about `REST APIs`, see [Working with REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html). To create [REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) with AWS CDK, use `@aws-cdk/aws-apigateway` instead. +For more information about `REST APIs`, see [Working with REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html). +To create [REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) with AWS CDK, use `@aws-cdk/aws-apigateway` instead. ### HTTP API - HTTP APIs enable you to create RESTful APIs that integrate with AWS Lambda functions or to any routable HTTP endpoint. HTTP API supports both Lambda proxy integration and HTTP proxy integration. @@ -51,7 +51,7 @@ for more information. -Use `HttpApi` to create HTTP APIs with Lambda or HTTP proxy integration. +Use `HttpApi` to create HTTP APIs with Lambda or HTTP proxy integration. The $default route will be created as well that acts as a catch-all for requests that don’t match any other routes. ```ts @@ -71,7 +71,7 @@ const httpApi3 = new apigatewayv2.HttpApi(stack, 'HttpApi3', { ## Route -`Routes` direct incoming API requests to backend resources. `Routes` consist of two parts: an HTTP method and a resource path—for example, +Routes direct incoming API requests to backend resources. Routes consist of two parts: an HTTP method and a resource path—for example, `GET /pets`. You can define specific HTTP methods for your route, or use the `ANY` method to match all methods that you haven't defined for a resource. You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See [Working with Routes for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 474ea4a7cf581..91360d75604a0 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -1,11 +1,12 @@ +import { ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; +import { Construct, IResource, Resource, Stack, } from '@aws-cdk/core'; import { CfnApi, CfnApiProps, HttpRouteOptions, IRouteBase, LambdaRouteOptions, Route } from '../lib'; /** * the HTTP API interface */ -export interface IHttpApi extends cdk.IResource { +export interface IHttpApi extends IResource { /** * The ID of this API Gateway HTTP Api. * @attribute @@ -13,33 +14,16 @@ export interface IHttpApi extends cdk.IResource { readonly httpApiId: string; } -abstract class ApiBase extends cdk.Resource implements IHttpApi { - /** - * API ID - */ - public abstract readonly httpApiId: string; - /** - * API URL - */ - public abstract readonly url: string; -} - /** * API properties */ -export interface HttpApiProps extends cdk.StackProps { +export interface HttpApiProps { /** * A name for the HTTP API resoruce * * @default - ID of the HttpApi construct. */ readonly apiName?: string; - /** - * API protocol - * - * @default HTTP - */ - readonly protocol?: ProtocolType; /** * target lambda function for lambda proxy integration. * @@ -55,31 +39,17 @@ export interface HttpApiProps extends cdk.StackProps { readonly targetUrl?: string; } -/** - * protocol types for the Amazon API Gateway HTTP API - */ -export enum ProtocolType { - /** - * HTTP protocol - */ - HTTP = 'HTTP', - /** - * Websocket protocol - */ - WEBSOCKET = 'WEBSOCKET' -} - /** * HTTPApi Resource Class * * @resource AWS::ApiGatewayV2::Api */ -export class HttpApi extends ApiBase implements IHttpApi { +export class HttpApi extends Resource implements IHttpApi { /** * import from ApiId */ - public static fromApiId(scope: cdk.Construct, id: string, httpApiId: string): IHttpApi { - class Import extends cdk.Resource implements IHttpApi { + public static fromApiId(scope: Construct, id: string, httpApiId: string): IHttpApi { + class Import extends Resource implements IHttpApi { public readonly httpApiId = httpApiId; } return new Import(scope, id); @@ -88,71 +58,46 @@ export class HttpApi extends ApiBase implements IHttpApi { * the API identifer */ public readonly httpApiId: string; - /** - * AWS partition either `aws` or `aws-cn` - */ - public readonly partition: string; - /** - * AWS region of this stack - */ - public readonly region: string; - /** - * AWS account of this stack - */ - public readonly account: string; - /** - * AWS domain name either `amazonaws.com` or `amazonaws.com.cn` - */ - public readonly awsdn: string; - /** - * the full URL of this API - */ - public readonly url: string; /** * root route */ public root?: IRouteBase; - constructor(scope: cdk.Construct, id: string, props?: HttpApiProps) { + constructor(scope: Construct, id: string, props?: HttpApiProps) { super(scope, id, { physicalName: props?.apiName || id, }); - // if ((!props) || - // (props!.targetHandler && props!.targetUrl) || - // (props!.targetHandler === undefined && props!.targetUrl === undefined)) { - // throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); - // } - if (props?.targetHandler && props.targetUrl) { throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); } - this.region = cdk.Stack.of(this).region; - this.partition = this.isChina() ? 'aws-cn' : 'aws'; - this.account = cdk.Stack.of(this).account; - this.awsdn = this.isChina() ? 'amazonaws.com.cn' : 'amazonaws.com'; - const apiProps: CfnApiProps = { name: this.physicalName, - protocolType: props?.protocol ?? ProtocolType.HTTP, + protocolType: 'HTTP', target: props?.targetHandler ? props.targetHandler.functionArn : props?.targetUrl ?? undefined }; const api = new CfnApi(this, 'Resource', apiProps ); this.httpApiId = api.ref; - this.url = `https://${this.httpApiId}.execute-api.${this.region}.${this.awsdn}`; - if (props?.targetHandler) { - new lambda.CfnPermission(this, 'Permission', { - action: 'lambda:InvokeFunction', - principal: 'apigateway.amazonaws.com', - functionName: props!.targetHandler.functionName, - sourceArn: `arn:${this.partition}:execute-api:${this.region}:${this.account}:${this.httpApiId}/*/*`, - }); + const desc = `${this.node.uniqueId}.'ANY'`; + props.targetHandler.addPermission(`ApiPermission.${desc}`, { + scope, + principal: new ServicePrincipal('apigateway.amazonaws.com'), + sourceArn: `arn:${Stack.of(this).partition}:execute-api:${Stack.of(this).region}:${Stack.of(this).account}:${this.httpApiId}/*/*`, + } ); } } + /** + * The HTTP URL of this API. + * HTTP API auto deploys the default stage and this just returns the URL from the default stage. + */ + public get url() { + return `https://${this.httpApiId}.execute-api.${Stack.of(this).region}.${Stack.of(this).urlSuffix}`; + } + /** * create a child route with Lambda proxy integration */ @@ -178,9 +123,4 @@ export class HttpApi extends ApiBase implements IHttpApi { httpMethod }); } - - private isChina(): boolean { - const region = this.stack.region; - return !cdk.Token.isUnresolved(region) && region.startsWith('cn-'); - } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts index 96b6b6caf08cc..421b95404133e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts @@ -1,11 +1,12 @@ +import { ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; +import { Construct, IResource, Resource, Stack, } from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; /** * The integration interface */ -export interface IIntegration extends cdk.IResource { +export interface IIntegration extends IResource { /** * The resource ID of the integration * @@ -20,9 +21,9 @@ export interface IIntegration extends cdk.IResource { */ export enum IntegrationType { /** - * for integrating the route or method request with an AWS service action, includingthe Lambda function-invoking action. - * With the Lambda function-invoking action, this is referred to as the Lambda custom integration. With any other AWS service - * action, this is known as AWS integration. Supported only for WebSocket APIs. + * for integrating the route or method request with an AWS service action, includingthe Lambda function-invoking + * action. With the Lambda function-invoking action, this is referred to as the Lambda custom integration. With any + * other AWS service action, this is known as AWS integration. Supported only for WebSocket APIs. */ AWS = 'AWS', /** @@ -50,7 +51,7 @@ export enum IntegrationType { /** * the integration properties */ -export interface IntegrationProps extends cdk.StackProps { +export interface IntegrationProps { /** * integration name * @default - the resource ID of the integration @@ -77,7 +78,7 @@ export interface IntegrationProps extends cdk.StackProps { /** * Lambda Proxy integration properties */ -export interface LambdaProxyIntegrationProps extends cdk.StackProps { +export interface LambdaProxyIntegrationProps { /** * integration name * @default - the resource id @@ -97,7 +98,7 @@ export interface LambdaProxyIntegrationProps extends cdk.StackProps { /** * HTTP Proxy integration properties */ -export interface HttpProxyIntegrationProps extends cdk.StackProps { +export interface HttpProxyIntegrationProps { /** * integration name * @default - the resource id @@ -122,19 +123,19 @@ export interface HttpProxyIntegrationProps extends cdk.StackProps { /** * The integration resource for HTTP API */ -export class Integration extends cdk.Resource implements IIntegration { +export class Integration extends Resource implements IIntegration { /** * import from integration ID */ - public static fromIntegrationId(scope: cdk.Construct, id: string, integrationId: string): IIntegration { - class Import extends cdk.Resource implements IIntegration { + public static fromIntegrationId(scope: Construct, id: string, integrationId: string): IIntegration { + class Import extends Resource implements IIntegration { public readonly integrationId = integrationId; } return new Import(scope, id); } public readonly integrationId: string; - constructor(scope: cdk.Construct, id: string, props: IntegrationProps) { + constructor(scope: Construct, id: string, props: IntegrationProps) { super(scope, id); const integ = new apigatewayv2.CfnIntegration(this, 'Resource', { apiId: props.apiId, @@ -151,36 +152,30 @@ export class Integration extends cdk.Resource implements IIntegration { * The Lambda Proxy integration resource for HTTP API * @resource AWS::ApiGatewayV2::Integration */ -export class LambdaProxyIntegration extends cdk.Resource implements IIntegration { +export class LambdaProxyIntegration extends Resource implements IIntegration { public readonly integrationId: string; - constructor(scope: cdk.Construct, id: string, props: LambdaProxyIntegrationProps) { + constructor(scope: Construct, id: string, props: LambdaProxyIntegrationProps) { super(scope, id); - const partition = this.isChina() ? 'aws-cn' : 'aws'; - const region = this.stack.region; - const account = this.stack.account; - // create integration + const integrationType = apigatewayv2.IntegrationType.AWS_PROXY; + const integrationMethod = apigatewayv2.HttpMethod.POST; const integ = new apigatewayv2.CfnIntegration(this, 'Resource', { apiId: props.api.httpApiId, - integrationType: apigatewayv2.IntegrationType.AWS_PROXY, - integrationMethod: apigatewayv2.HttpMethod.POST, + integrationType, + integrationMethod, payloadFormatVersion: '1.0', - integrationUri: `arn:${partition}:apigateway:${region}:lambda:path/2015-03-31/functions/${props.targetHandler.functionArn}/invocations`, + integrationUri: `arn:${Stack.of(this).partition}:apigateway:${Stack.of(this).region}:lambda:path/2015-03-31/functions/${props.targetHandler.functionArn}/invocations`, }); this.integrationId = integ.ref; // create permission - new lambda.CfnPermission(scope, `Permission-${id}`, { - action: 'lambda:InvokeFunction', - principal: 'apigateway.amazonaws.com', - functionName: props.targetHandler.functionName, - sourceArn: `arn:${partition}:execute-api:${region}:${account}:${props.api.httpApiId}/*/*`, + const desc = `${this.node.uniqueId}.${integrationType}.${integrationMethod}`; + props.targetHandler.addPermission(`IntegPermission.${desc}`, { + scope, + principal: new ServicePrincipal('apigateway.amazonaws.com'), + sourceArn: `arn:${Stack.of(this).partition}:execute-api:${Stack.of(this).region}:${Stack.of(this).account}:${props.api.httpApiId}/*/*`, }); } - private isChina(): boolean { - const region = this.stack.region; - return !cdk.Token.isUnresolved(region) && region.startsWith('cn-'); - } } /** @@ -188,10 +183,10 @@ export class LambdaProxyIntegration extends cdk.Resource implements IIntegratio * * @resource AWS::ApiGatewayV2::Integration */ -export class HttpProxyIntegration extends cdk.Resource implements IIntegration { +export class HttpProxyIntegration extends Resource implements IIntegration { public readonly integrationId: string; - constructor(scope: cdk.Construct, id: string, props: HttpProxyIntegrationProps) { + constructor(scope: Construct, id: string, props: HttpProxyIntegrationProps) { super(scope, id); // create integration diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts index cfc78a23840a4..6b3f9560b58ae 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -1,11 +1,11 @@ import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; +import { Construct, IResource, Resource } from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; /** * the interface of the Route of API Gateway HTTP API */ -export interface IRoute extends cdk.IResource { +export interface IRoute extends IResource { /** * ID of the Route * @attribute @@ -120,7 +120,7 @@ export enum HttpMethod { /** * Route properties */ -export interface RouteProps extends cdk.StackProps { +export interface RouteProps { /** * route name * @default - the logic ID of this route @@ -169,17 +169,16 @@ export interface RouteProps extends cdk.StackProps { /** * Route class that creates the Route for API Gateway HTTP API */ -export class Route extends cdk.Resource implements IRouteBase { +export class Route extends Resource implements IRouteBase { /** * import from route id */ - public static fromRouteId(scope: cdk.Construct, id: string, routeId: string): IRoute { - class Import extends cdk.Resource implements IRoute { + public static fromRouteId(scope: Construct, id: string, routeId: string): IRoute { + class Import extends Resource implements IRoute { public routeId = routeId; } return new Import(scope, id); } - // public readonly fullUrl: string; /** * the api ID of this route */ @@ -205,7 +204,7 @@ export class Route extends cdk.Resource implements IRouteBase { */ public readonly integId: string; - constructor(scope: cdk.Construct, id: string, props: RouteProps) { + constructor(scope: Construct, id: string, props: RouteProps) { super(scope, id); if ((props.targetHandler && props.targetUrl) || @@ -247,7 +246,6 @@ export class Route extends cdk.Resource implements IRouteBase { const route = new apigatewayv2.CfnRoute(this, 'Resource', routeProps); this.routeId = route.ref; - // this.url = `${this.api.url}${this.httpPath}`; } /** @@ -256,7 +254,6 @@ export class Route extends cdk.Resource implements IRouteBase { public addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route { const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; const httpMethod = options.method; - // const routeKey = `${httpMethod} ${httpPath}`; return new Route(this, id, { api: this.api, @@ -274,7 +271,6 @@ export class Route extends cdk.Resource implements IRouteBase { public addHttpRoute(pathPart: string, id: string, options: HttpRouteOptions): Route { const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; const httpMethod = options.method; - // const routeKey = `${httpMethod} ${httpPath}`; return new Route(this, id, { api: this.api, diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts index f18b22fc85b3c..8af6d2496e6f1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts @@ -1,5 +1,4 @@ -// import '@aws-cdk/assert/jest'; -import { countResources, expect as expectCDK, haveResourceLike } from '@aws-cdk/assert'; +import { countResources, expect as expectCDK, haveOutput, haveResourceLike } from '@aws-cdk/assert'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; @@ -22,7 +21,6 @@ def handler(event, context): 'statusCode': 200, 'body': json.dumps(event) }`), - reservedConcurrentExecutions: 10, }); rootHandler = new lambda.Function(stack, 'RootFunc', { @@ -48,9 +46,13 @@ def handler(event, context): test('create a HTTP API with no props correctly', () => { // WHEN - new apigatewayv2.HttpApi(stack, 'HttpApi'); + const api = new apigatewayv2.HttpApi(stack, 'HttpApi'); + new cdk.CfnOutput(stack, 'URL', { value: api.url }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); + expectCDK(stack).to(haveOutput({ + outputName: 'URL' + })); }); test('create a HTTP API with empty props correctly', () => { @@ -73,7 +75,6 @@ test('create a basic HTTP API correctly with targetUrl and protocol', () => { // WHEN new apigatewayv2.HttpApi(stack, 'HttpApi', { targetUrl: checkIpUrl, - protocol: apigatewayv2.ProtocolType.HTTP }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index 13141695337c4..643f943f489c9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -81,4 +81,7 @@ httpApi2.root .addHttpRoute('checkip', 'CheckIp', { targetUrl: checkIpUrl, method: apigatewayv2.HttpMethod.ANY - }); \ No newline at end of file + }); + +new cdk.CfnOutput(stack, 'URL', { value: httpApi.url }); +new cdk.CfnOutput(stack, 'URL2', { value: httpApi2.url }); \ No newline at end of file From 1965f59ba685b6069d2d0f68d1615ec278de292e Mon Sep 17 00:00:00 2001 From: Pahud Date: Wed, 18 Mar 2020 23:17:15 +0800 Subject: [PATCH 22/42] update package.json --- packages/@aws-cdk/aws-apigatewayv2/package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 95f485ba29b4a..57a4f3238c26f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -87,13 +87,15 @@ "pkglint": "0.0.0" }, "dependencies": { - "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/core": "0.0.0", "constructs": "^1.1.2" }, "peerDependencies": { - "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/core": "0.0.0", "constructs": "^1.1.2" }, "engines": { From 6547860694d949a2790fd6ca21880add2a93df52 Mon Sep 17 00:00:00 2001 From: Pahud Date: Wed, 18 Mar 2020 23:54:31 +0800 Subject: [PATCH 23/42] update integ --- .../test/integ.api.expected.json | 70 +++++++++++++++---- .../aws-apigatewayv2/test/integ.api.ts | 3 - 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json index aff315d2c2bd6..251d6101958ec 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json @@ -146,7 +146,11 @@ "Fn::Join": [ "", [ - "arn:aws:apigateway:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", { "Ref": "AWS::Region" }, @@ -164,19 +168,26 @@ "PayloadFormatVersion": "1.0" } }, - "HttpApiPermissionLambdaRouteLambdaProxyIntegrationB7913F51": { + "HttpApiIntegPermissionApiagtewayV2HttpApiLambdaRouteLambdaProxyIntegration13D61555AWSPROXYPOST97C13A63": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyFunc8A243A2C" + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Join": [ "", [ - "arn:aws:execute-api:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", { "Ref": "AWS::Region" }, @@ -207,19 +218,26 @@ } } }, - "HttpApi2Permission5FEFB3CB": { + "ApiPermissionApiagtewayV2HttpApiHttpApi212E01E71ANY": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyFunc8A243A2C" + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Join": [ "", [ - "arn:aws:execute-api:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", { "Ref": "AWS::Region" }, @@ -249,7 +267,11 @@ "Fn::Join": [ "", [ - "arn:aws:apigateway:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", { "Ref": "AWS::Region" }, @@ -267,19 +289,26 @@ "PayloadFormatVersion": "1.0" } }, - "PermissionIntegRootHandler": { + "IntegPermissionApiagtewayV2HttpApiIntegRootHandler3D54DAE8AWSPROXYPOST": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "RootFuncF39FB174" + "Fn::GetAtt": [ + "RootFuncF39FB174", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Join": [ "", [ - "arn:aws:execute-api:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", { "Ref": "AWS::Region" }, @@ -413,7 +442,11 @@ "Fn::Join": [ "", [ - "arn:aws:apigateway:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", { "Ref": "AWS::Region" }, @@ -431,19 +464,26 @@ "PayloadFormatVersion": "1.0" } }, - "RootRoutePermissionFooLambdaProxyIntegrationE3A30900": { + "RootRouteIntegPermissionApiagtewayV2HttpApiRootRouteFooLambdaProxyIntegration5622B5E6AWSPROXYPOST4694F774": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyFunc8A243A2C" + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Join": [ "", [ - "arn:aws:execute-api:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", { "Ref": "AWS::Region" }, diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index 643f943f489c9..51068b5c2f96b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -82,6 +82,3 @@ httpApi2.root targetUrl: checkIpUrl, method: apigatewayv2.HttpMethod.ANY }); - -new cdk.CfnOutput(stack, 'URL', { value: httpApi.url }); -new cdk.CfnOutput(stack, 'URL2', { value: httpApi2.url }); \ No newline at end of file From 09895853e668883ab9eeeee4f75729cc23e6f6ea Mon Sep 17 00:00:00 2001 From: Pahud Date: Mon, 23 Mar 2020 20:43:18 +0800 Subject: [PATCH 24/42] support api.addRootRoute() --- packages/@aws-cdk/aws-apigatewayv2/README.md | 18 ++--- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 23 +++++- .../@aws-cdk/aws-apigatewayv2/lib/route.ts | 8 +++ .../aws-apigatewayv2/test/integ.api.ts | 72 +++++++++++++------ 4 files changed, 85 insertions(+), 36 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 54a726da0a3c7..f06c3d43f446a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -79,24 +79,20 @@ You can create a `$default route` that acts as a catch-all for requests that don When you create HTTP APIs with either `Lambda Proxy Integration` or `HTTP Proxy Integration`, the `$default route` will be created as well. +## Defining APIs -To create a root route for an existing `Api` resource +APIs are defined as a hierarchy of routes. `addLambdaRoute` and `addHttpRoute` can be used to build this hierarchy. The root resource is `api.root`. -```ts -// create a root route for the API -httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { - api: httpApi, - httpPath: '/', - integration: integRootHandler -}); -``` -And extend different routes from the root route +For example, the following code defines an API that includes the following HTTP endpoints: ANY /, GET /books, POST /books, GET /books/{book_id}, DELETE /books/{book_id}. ```ts const checkIpUrl = 'https://checkip.amazonaws.com'; const awsUrl = 'https://aws.amazon.com'; -httpApi.root +const api = new apigatewayv2.HttpApi(this, 'HttpApi'); +api.root + +api.root // HTTP GET /foo .addLambdaRoute('foo', 'Foo', { target: handler, diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 91360d75604a0..f8b91a6f30558 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -2,6 +2,8 @@ import { ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, IResource, Resource, Stack, } from '@aws-cdk/core'; import { CfnApi, CfnApiProps, HttpRouteOptions, IRouteBase, LambdaRouteOptions, Route } from '../lib'; +import { IIntegration, Integration, IntegrationProps } from './integration'; +import { HttpMethod, RouteProps } from './route'; /** * the HTTP API interface @@ -25,14 +27,14 @@ export interface HttpApiProps { */ readonly apiName?: string; /** - * target lambda function for lambda proxy integration. + * target lambda function of lambda proxy integration for the $default route * * @default - None. Specify either `targetHandler` or `targetUrl` */ readonly targetHandler?: lambda.IFunction; /** - * target URL for HTTP proxy integration. + * target URL of the HTTP proxy integration for the $default route * * @default - None. Specify either `targetHandler` or `targetUrl` */ @@ -98,6 +100,23 @@ export class HttpApi extends Resource implements IHttpApi { return `https://${this.httpApiId}.execute-api.${Stack.of(this).region}.${Stack.of(this).urlSuffix}`; } + // public get root(): IRouteBase { + // return new Route(this, `RootRoute${this.httpApiId}`, { + // api: this, + // httpPath: '/', + // }); + // } + + public addRootRoute(integration?: IIntegration, httpMethod?: HttpMethod): IRouteBase { + this.root = new Route(this, `RootRoute${this.httpApiId}`, { + api: this, + httpPath: '/', + integration, + httpMethod + }); + return this.root; + } + /** * create a child route with Lambda proxy integration */ diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts index 6b3f9560b58ae..43afb081dbba1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -1,6 +1,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, IResource, Resource } from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; +import { IIntegration } from './integration'; /** * the interface of the Route of API Gateway HTTP API @@ -281,4 +282,11 @@ export class Route extends Resource implements IRouteBase { pathPart, }); } + + /** + * add integration for this route + */ + public addIntegration(integration:IIntegration): Route { + + } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index 51068b5c2f96b..c64f8aa8de6a4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -1,6 +1,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; +import { HttpMethod, Integration } from '../lib'; const app = new cdk.App(); @@ -39,34 +40,20 @@ def handler(event, context): const checkIpUrl = 'https://checkip.amazonaws.com'; const awsUrl = 'https://aws.amazon.com'; -// Create a basic HTTP API -const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetUrl: checkIpUrl +// create a basic HTTP API with http proxy integration as the $default route +const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetUrl: checkIpUrl, }); -// addLambdaRoute from this API -// HTTP ANY /fn -httpApi.addLambdaRoute('/fn', 'LambdaRoute', { - target: handler -}); - -const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { - targetHandler: handler -}); - -const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { - api: httpApi2, +// create a lambda proxy integration for the api +const rootIntegration = new apigatewayv2.LambdaProxyIntegration(stack, 'RootIntegration', { + api, targetHandler: rootHandler }); -// create a root route for the API -httpApi2.root = new apigatewayv2.Route(stack, 'RootRoute', { - api: httpApi2, - httpPath: '/', - integration: integRootHandler -}); - -httpApi2.root +// pass the rootIntegration to addRootRoute() to initialize the root route +// HTTP GET / +api.addRootRoute(rootIntegration, HttpMethod.GET) // HTTP GET /foo .addLambdaRoute('foo', 'Foo', { target: handler, @@ -82,3 +69,42 @@ httpApi2.root targetUrl: checkIpUrl, method: apigatewayv2.HttpMethod.ANY }); + +// // addLambdaRoute from this API +// // HTTP ANY /fn +// httpApi.addLambdaRoute('/fn', 'LambdaRoute', { +// target: handler +// }); + +// const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { +// targetHandler: handler +// }); + +// const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { +// api: httpApi2, +// targetHandler: rootHandler +// }); + +// // create a root route for the API +// httpApi2.root = new apigatewayv2.Route(stack, 'RootRoute', { +// api: httpApi2, +// httpPath: '/', +// integration: integRootHandler +// }); + +// httpApi2.root +// // HTTP GET /foo +// .addLambdaRoute('foo', 'Foo', { +// target: handler, +// method: apigatewayv2.HttpMethod.GET +// }) +// // HTTP ANY /foo/aws +// .addHttpRoute('aws', 'AwsPage', { +// targetUrl: awsUrl, +// method: apigatewayv2.HttpMethod.ANY +// }) +// // HTTP ANY /foo/aws/checkip +// .addHttpRoute('checkip', 'CheckIp', { +// targetUrl: checkIpUrl, +// method: apigatewayv2.HttpMethod.ANY +// }); From 90204bb897a7840c882ede043ae347767e9505bd Mon Sep 17 00:00:00 2001 From: Pahud Date: Thu, 26 Mar 2020 20:57:14 +0800 Subject: [PATCH 25/42] support addRoutes() fro `HttpApi` and `Route` resources. --- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 19 ++++- .../@aws-cdk/aws-apigatewayv2/lib/route.ts | 58 +++++++++++-- .../aws-apigatewayv2/test/integ.api.ts | 81 +++++++------------ 3 files changed, 99 insertions(+), 59 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index f8b91a6f30558..74c3b29166111 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -3,7 +3,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, IResource, Resource, Stack, } from '@aws-cdk/core'; import { CfnApi, CfnApiProps, HttpRouteOptions, IRouteBase, LambdaRouteOptions, Route } from '../lib'; import { IIntegration, Integration, IntegrationProps } from './integration'; -import { HttpMethod, RouteProps } from './route'; +import { HttpMethod, RouteOptions } from './route'; /** * the HTTP API interface @@ -117,6 +117,23 @@ export class HttpApi extends Resource implements IHttpApi { return this.root; } + /** + * add routes on this API + */ + public addRoutes(pathPart: string, id: string, options: RouteOptions): Route[] { + const routes: Route[] = []; + const methods = options.methods ?? [ HttpMethod.ANY ]; + for (const m of methods) { + routes.push(new Route(this, `${id}${m}`, { + api: this, + integration: options.integration, + httpMethod: m, + httpPath: pathPart + })); + } + return routes; + } + /** * create a child route with Lambda proxy integration */ diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts index 43afb081dbba1..071db090c7cd9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -1,7 +1,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, IResource, Resource } from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; -import { IIntegration } from './integration'; +import { IIntegration, Integration } from './integration'; /** * the interface of the Route of API Gateway HTTP API @@ -46,6 +46,11 @@ export interface IRouteBase extends IRoute { * add a child route with Lambda integration for this parent route */ addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route; + + /** + * add a child route with integration resource for this parent route + */ + addRoutes(pathPart: string, id: string, options: RouteOptions): Route[]; } /** @@ -92,6 +97,28 @@ export interface LambdaRouteOptions { readonly integrationMethod?: HttpMethod; } +/** + * Options for the Route with Integration resoruce + */ +export interface RouteOptions { + /** + * HTTP methods + * @default HttpMethod.ANY + */ + readonly methods?: HttpMethod[] + + /** + * Integration method + * @default HttpMethod.ANY + */ + readonly integrationMethod?: HttpMethod; + + /** + * Integration + */ + readonly integration: Integration; +} + /** * all HTTP methods */ @@ -249,6 +276,23 @@ export class Route extends Resource implements IRouteBase { this.routeId = route.ref; } + /** + * create child routes + */ + public addRoutes(pathPart: string, id: string, options: RouteOptions): Route[] { + const routes: Route[] = []; + const methods = options.methods ?? [ HttpMethod.ANY ]; + for (const m of methods) { + routes.push(new Route(this, `${id}${m}`, { + api: this.api, + integration: options.integration, + httpMethod: m, + httpPath: pathPart + })); + } + return routes; + } + /** * create a child route with Lambda proxy integration */ @@ -283,10 +327,10 @@ export class Route extends Resource implements IRouteBase { }); } - /** - * add integration for this route - */ - public addIntegration(integration:IIntegration): Route { - - } + // /** + // * add integration for this route + // */ + // public addIntegration(integration:IIntegration): Route { + + // } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index c64f8aa8de6a4..02891994ec57f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -1,13 +1,13 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; -import { HttpMethod, Integration } from '../lib'; +import { HttpMethod, HttpProxyIntegration, LambdaProxyIntegration } from '../lib'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'ApiagtewayV2HttpApi'); -const handler = new lambda.Function(stack, 'MyFunc', { +const getbooksHandler = new lambda.Function(stack, 'MyFunc', { runtime: lambda.Runtime.PYTHON_3_7, handler: 'index.handler', code: new lambda.InlineCode(` @@ -19,7 +19,7 @@ def handler(event, context): }`), }); -const rootHandler = new lambda.Function(stack, 'RootFunc', { +const getbookReviewsHandler = new lambda.Function(stack, 'RootFunc', { runtime: lambda.Runtime.PYTHON_3_7, handler: 'index.handler', code: new lambda.InlineCode(` @@ -37,62 +37,41 @@ def handler(event, context): }, }); -const checkIpUrl = 'https://checkip.amazonaws.com'; -const awsUrl = 'https://aws.amazon.com'; +const rootUrl = 'https://checkip.amazonaws.com'; +const defaultUrl = 'https://aws.amazon.com'; // create a basic HTTP API with http proxy integration as the $default route const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetUrl: checkIpUrl, + targetUrl: defaultUrl, }); -// create a lambda proxy integration for the api -const rootIntegration = new apigatewayv2.LambdaProxyIntegration(stack, 'RootIntegration', { - api, - targetHandler: rootHandler -}); - -// pass the rootIntegration to addRootRoute() to initialize the root route -// HTTP GET / -api.addRootRoute(rootIntegration, HttpMethod.GET) - // HTTP GET /foo - .addLambdaRoute('foo', 'Foo', { - target: handler, - method: apigatewayv2.HttpMethod.GET - }) - // HTTP ANY /foo/aws - .addHttpRoute('aws', 'AwsPage', { - targetUrl: awsUrl, - method: apigatewayv2.HttpMethod.ANY +api.addRoutes('/', 'RootRoute', { + methods: [HttpMethod.GET, HttpMethod.POST], + integration: new HttpProxyIntegration(stack, 'RootInteg', { + api, + targetUrl: rootUrl }) - // HTTP ANY /foo/aws/checkip - .addHttpRoute('checkip', 'CheckIp', { - targetUrl: checkIpUrl, - method: apigatewayv2.HttpMethod.ANY - }); - -// // addLambdaRoute from this API -// // HTTP ANY /fn -// httpApi.addLambdaRoute('/fn', 'LambdaRoute', { -// target: handler -// }); - -// const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { -// targetHandler: handler -// }); +}); -// const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { -// api: httpApi2, -// targetHandler: rootHandler -// }); +api.addRoutes('/books', 'GetBooksRoute', { + methods: [HttpMethod.GET], + integration: new LambdaProxyIntegration(stack, 'getbooksInteg', { + api, + targetHandler: getbooksHandler, + }), +}); -// // create a root route for the API -// httpApi2.root = new apigatewayv2.Route(stack, 'RootRoute', { -// api: httpApi2, -// httpPath: '/', -// integration: integRootHandler -// }); +api.addRoutes('/books/reviews', 'GetBookReviewRoute', { + methods: [HttpMethod.GET], + integration: new LambdaProxyIntegration(stack, 'getBookReviewInteg', { + api, + targetHandler: getbookReviewsHandler + }) +}); -// httpApi2.root +// // pass the rootIntegration to addRootRoute() to initialize the root route +// // HTTP GET / +// api.addRootRoute(rootIntegration, HttpMethod.GET) // // HTTP GET /foo // .addLambdaRoute('foo', 'Foo', { // target: handler, @@ -107,4 +86,4 @@ api.addRootRoute(rootIntegration, HttpMethod.GET) // .addHttpRoute('checkip', 'CheckIp', { // targetUrl: checkIpUrl, // method: apigatewayv2.HttpMethod.ANY -// }); +// }); \ No newline at end of file From 3568779f5dd18a818cb83fdbe846e23bc884f70a Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 27 Apr 2020 11:55:01 +0100 Subject: [PATCH 26/42] trying to move this forward --- packages/@aws-cdk/aws-apigatewayv2/README.md | 56 +++----- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 40 +++--- .../aws-apigatewayv2/lib/integration.ts | 6 +- .../@aws-cdk/aws-apigatewayv2/lib/route.ts | 14 +- .../test/apigatewayv2.test.ts | 124 +++++++++--------- .../aws-apigatewayv2/test/integ.api.ts | 15 +-- 6 files changed, 120 insertions(+), 135 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 1fb0eec0173a8..7cb44a6aa54da 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -11,40 +11,28 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. - ## Introduction -Amazon API Gateway is an AWS service for creating, publishing, maintaining, monitoring, and securing REST, HTTP, and WebSocket -APIs at any scale. API developers can create APIs that access AWS or other web services, as well as data stored in the AWS Cloud. -As an API Gateway API developer, you can create APIs for use in your own client applications. Read the +Amazon API Gateway is an AWS service for creating, publishing, maintaining, monitoring, and securing REST, HTTP, and WebSocket +APIs at any scale. API developers can create APIs that access AWS or other web services, as well as data stored in the AWS Cloud. +As an API Gateway API developer, you can create APIs for use in your own client applications. Read the [Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html). -This module supports features under [API Gateway v2](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_ApiGatewayV2.html) +This module supports features under [API Gateway v2](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_ApiGatewayV2.html) that lets users set up Websocket and HTTP APIs. +REST APIs can be created using the `@aws-cdk/aws-apigateway` module. -## API - -Amazon API Gateway supports `HTTP APIs`, `WebSocket APIs` and `REST APIs`. For more information about `WebSocket APIs` and `HTTP APIs`, -see [About WebSocket APIs in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-overview.html) -and [HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html). - -For more information about `REST APIs`, see [Working with REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html). -To create [REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) with AWS CDK, use `@aws-cdk/aws-apigateway` instead. - - -### HTTP API +## HTTP API -HTTP APIs enable you to create RESTful APIs that integrate with AWS Lambda functions or to any routable HTTP endpoint. +HTTP APIs enable creation of RESTful APIs that integrate with AWS Lambda functions, known as Lambda proxy integration, +or to any routable HTTP endpoint, known as HTTP proxy integration. -HTTP API supports both Lambda proxy integration and HTTP proxy integration. -See [Working with AWS Lambda Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) -and [Working with HTTP Proxy Integrations for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html) -for more information. - - - -Use `HttpApi` to create HTTP APIs with Lambda or HTTP proxy integration. The $default route will be created as well that acts as a catch-all for requests that don’t match any other routes. +Read more about [Working with AWS Lambda Proxy Integrations for HTTP +APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) +and [Working with HTTP Proxy Integrations for HTTP +APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html). +Use `HttpApi` to create HTTP APIs with Lambda or HTTP proxy integration. The $default route will be created as well that acts as a catch-all for requests that don’t match any other routes. ```ts // Create a vanilla HTTP API with no integration targets @@ -63,14 +51,13 @@ const httpApi3 = new apigatewayv2.HttpApi(stack, 'HttpApi3', { ## Route -Routes direct incoming API requests to backend resources. Routes consist of two parts: an HTTP method and a resource path—for example, -`GET /pets`. You can define specific HTTP methods for your route, or use the `ANY` method to match all methods that you haven't defined for a resource. -You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See +Routes direct incoming API requests to backend resources. Routes consist of two parts: an HTTP method and a resource path—for example, +`GET /pets`. You can define specific HTTP methods for your route, or use the `ANY` method to match all methods that you haven't defined for a resource. +You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See [Working with Routes for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). When you create HTTP APIs with either `Lambda Proxy Integration` or `HTTP Proxy Integration`, the `$default route` will be created as well. - ## Defining APIs APIs are defined as a hierarchy of routes. `addLambdaRoute` and `addHttpRoute` can be used to build this hierarchy. The root resource is `api.root`. @@ -144,9 +131,9 @@ httpApi.addHttpRoute('/foo/bar', 'FooBar', { ## Integration -Integrations connect a route to backend resources. HTTP APIs support Lambda proxy and HTTP proxy integrations. -For example, you can configure a POST request to the /signup route of your API to integrate with a Lambda function -that handles signing up customers. +Integrations connect a route to backend resources. HTTP APIs support Lambda proxy and HTTP proxy integrations. +For example, you can configure a POST request to the /signup route of your API to integrate with a Lambda function +that handles signing up customers. Use `Integration` to create the integration resources @@ -189,20 +176,19 @@ new apigatewayv2.HttpProxyIntegration(stack, 'IntegRootHandler', { ## Samples - ```ts // create a HTTP API with HTTP proxy integration as the $default route const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { targetUrl: checkIpUrl }); -// print the API URL +// print the API URL new cdk.CfnOutput(stack, 'URL', { value: httpApi.url} ); // create another HTTP API with Lambda proxy integration as the $default route const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { targetHandler: handler }); -// print the API URL +// print the API URL new cdk.CfnOutput(stack, 'URL2', { value: httpApi2.url }); // create a root route for the API with the integration we created above and assign the route resource diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 74c3b29166111..13c07b0cf54ed 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -1,12 +1,12 @@ import { ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, IResource, Resource, Stack, } from '@aws-cdk/core'; +import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; import { CfnApi, CfnApiProps, HttpRouteOptions, IRouteBase, LambdaRouteOptions, Route } from '../lib'; -import { IIntegration, Integration, IntegrationProps } from './integration'; +import { IIntegration } from './integration'; import { HttpMethod, RouteOptions } from './route'; /** - * the HTTP API interface + * Represents an HTTP API */ export interface IHttpApi extends IResource { /** @@ -17,13 +17,13 @@ export interface IHttpApi extends IResource { } /** - * API properties + * Properties to initialize an instance of `HttpApi`. */ export interface HttpApiProps { /** - * A name for the HTTP API resoruce + * Name for the HTTP API resoruce * - * @default - ID of the HttpApi construct. + * @default - id of the HttpApi construct. */ readonly apiName?: string; /** @@ -48,7 +48,7 @@ export interface HttpApiProps { */ export class HttpApi extends Resource implements IHttpApi { /** - * import from ApiId + * Import an existing HTTP API into this CDK app. */ public static fromApiId(scope: Construct, id: string, httpApiId: string): IHttpApi { class Import extends Resource implements IHttpApi { @@ -66,20 +66,20 @@ export class HttpApi extends Resource implements IHttpApi { public root?: IRouteBase; constructor(scope: Construct, id: string, props?: HttpApiProps) { - super(scope, id, { - physicalName: props?.apiName || id, - }); + super(scope, id); - if (props?.targetHandler && props.targetUrl) { - throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); - } + const apiName = props?.apiName ?? id; + + // if (props?.targetHandler && props.targetUrl) { + // throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); + // } const apiProps: CfnApiProps = { - name: this.physicalName, + name: apiName, protocolType: 'HTTP', - target: props?.targetHandler ? props.targetHandler.functionArn : props?.targetUrl ?? undefined + target: props?.targetHandler ? props.targetHandler.functionArn : props?.targetUrl ?? undefined, }; - const api = new CfnApi(this, 'Resource', apiProps ); + const api = new CfnApi(this, 'Resource', apiProps); this.httpApiId = api.ref; if (props?.targetHandler) { @@ -112,7 +112,7 @@ export class HttpApi extends Resource implements IHttpApi { api: this, httpPath: '/', integration, - httpMethod + httpMethod, }); return this.root; } @@ -128,7 +128,7 @@ export class HttpApi extends Resource implements IHttpApi { api: this, integration: options.integration, httpMethod: m, - httpPath: pathPart + httpPath: pathPart, })); } return routes; @@ -143,7 +143,7 @@ export class HttpApi extends Resource implements IHttpApi { api: this, targetHandler: options.target, httpPath, - httpMethod + httpMethod, }); } @@ -156,7 +156,7 @@ export class HttpApi extends Resource implements IHttpApi { api: this, targetUrl: options.targetUrl, httpPath, - httpMethod + httpMethod, }); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts index 421b95404133e..49a17652cf025 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts @@ -1,6 +1,6 @@ import { ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, IResource, Resource, Stack, } from '@aws-cdk/core'; +import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; /** @@ -142,7 +142,7 @@ export class Integration extends Resource implements IIntegration { integrationType: props.integrationType, integrationMethod: props.integrationMethod, integrationUri: props.integrationUri, - payloadFormatVersion: '1.0' + payloadFormatVersion: '1.0', }); this.integrationId = integ.ref; } @@ -195,7 +195,7 @@ export class HttpProxyIntegration extends Resource implements IIntegration { integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, integrationMethod: props.integrationMethod ?? apigatewayv2.HttpMethod.ANY, integrationUri: props.targetUrl, - payloadFormatVersion: '1.0' + payloadFormatVersion: '1.0', }); this.integrationId = integ.ref; } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts index 071db090c7cd9..79e1064f3bf20 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -1,7 +1,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, IResource, Resource } from '@aws-cdk/core'; import * as apigatewayv2 from '../lib'; -import { IIntegration, Integration } from './integration'; +import { Integration } from './integration'; /** * the interface of the Route of API Gateway HTTP API @@ -249,17 +249,17 @@ export class Route extends Resource implements IRouteBase { if (props.integration) { this.integId = props.integration.integrationId; } else if (props.targetUrl) { - // create a HTTP Proxy integration + // create a HTTP Proxy integration const integ = new apigatewayv2.HttpProxyIntegration(scope, `${id}/HttpProxyIntegration`, { api: this.api, - targetUrl: props.targetUrl + targetUrl: props.targetUrl, }); this.integId = integ.integrationId; } else if (props.targetHandler) { // create a Lambda Proxy integration const integ = new apigatewayv2.LambdaProxyIntegration(scope, `${id}/LambdaProxyIntegration`, { api: this.api, - targetHandler: props.targetHandler + targetHandler: props.targetHandler, }); this.integId = integ.integrationId; } else { @@ -287,7 +287,7 @@ export class Route extends Resource implements IRouteBase { api: this.api, integration: options.integration, httpMethod: m, - httpPath: pathPart + httpPath: pathPart, })); } return routes; @@ -297,7 +297,7 @@ export class Route extends Resource implements IRouteBase { * create a child route with Lambda proxy integration */ public addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route { - const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; + const httpPath = `${this.httpPath.replace(/\/+$/, '')}/${pathPart}`; const httpMethod = options.method; return new Route(this, id, { @@ -314,7 +314,7 @@ export class Route extends Resource implements IRouteBase { * create a child route with HTTP proxy integration */ public addHttpRoute(pathPart: string, id: string, options: HttpRouteOptions): Route { - const httpPath = `${this.httpPath.replace(/\/+$/, "")}/${pathPart}`; + const httpPath = `${this.httpPath.replace(/\/+$/, '')}/${pathPart}`; const httpMethod = options.method; return new Route(this, id, { diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts index 8af6d2496e6f1..6609e926c8f89 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts @@ -37,7 +37,7 @@ def handler(event, context): }`), environment: { WHOAMI: 'root', - HTTP_PATH: '/' + HTTP_PATH: '/', }, }); checkIpUrl = 'https://checkip.amazonaws.com'; @@ -51,7 +51,7 @@ test('create a HTTP API with no props correctly', () => { // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); expectCDK(stack).to(haveOutput({ - outputName: 'URL' + outputName: 'URL', })); }); @@ -83,7 +83,7 @@ test('create a basic HTTP API correctly with targetUrl and protocol', () => { test('create a basic HTTP API correctly with target handler', () => { // WHEN new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); @@ -93,18 +93,18 @@ test('import HTTP API from API ID correctly', () => { // WHEN // THEN expect(() => - apigatewayv2.HttpApi.fromApiId(stack, 'HttpApi', 'foo') + apigatewayv2.HttpApi.fromApiId(stack, 'HttpApi', 'foo'), ).not.toThrowError(); }); test('create lambda proxy integration correctly', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { api: httpApi, - targetHandler: rootHandler + targetHandler: rootHandler, }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); @@ -113,12 +113,12 @@ test('create lambda proxy integration correctly', () => { test('create HTTP proxy integration correctly with targetUrl', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); new apigatewayv2.HttpProxyIntegration(stack, 'IntegRootHandler', { api: httpApi, targetUrl: 'https://aws.amazon.com', - integrationMethod: apigatewayv2.HttpMethod.GET + integrationMethod: apigatewayv2.HttpMethod.GET, }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); @@ -127,13 +127,13 @@ test('create HTTP proxy integration correctly with targetUrl', () => { test('create HTTP proxy integration correctly with Integration', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); new apigatewayv2.Integration(stack, 'IntegRootHandler', { apiId: httpApi.httpApiId, integrationMethod: apigatewayv2.HttpMethod.ANY, integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, - integrationUri: awsUrl + integrationUri: awsUrl, }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); @@ -144,23 +144,23 @@ test('import integration from integration ID correctly', () => { // WHEN // THEN expect(() => - apigatewayv2.Integration.fromIntegrationId(stack, 'Integ', 'foo') + apigatewayv2.Integration.fromIntegrationId(stack, 'Integ', 'foo'), ).not.toThrowError(); }); test('create the root route correctly', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { api: httpApi, - targetHandler: rootHandler + targetHandler: rootHandler, }); httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { api: httpApi, httpPath: '/', - integration: integRootHandler + integration: integRootHandler, }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); @@ -172,27 +172,27 @@ test('import route from route ID correctly', () => { // WHEN // THEN expect(() => - apigatewayv2.Route.fromRouteId(stack, 'Integ', 'foo') + apigatewayv2.Route.fromRouteId(stack, 'Integ', 'foo'), ).not.toThrowError(); }); test('addLambdaRoute correctly from a Route', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { api: httpApi, - targetHandler: rootHandler + targetHandler: rootHandler, }); httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { api: httpApi, httpPath: '/', - integration: integRootHandler + integration: integRootHandler, }); httpApi.root.addLambdaRoute('foo', 'Foo', { target: handler, - method: apigatewayv2.HttpMethod.GET + method: apigatewayv2.HttpMethod.GET, }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); @@ -203,20 +203,20 @@ test('addLambdaRoute correctly from a Route', () => { test('addHttpRoute correctly from a Route', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { api: httpApi, - targetHandler: rootHandler + targetHandler: rootHandler, }); httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { api: httpApi, httpPath: '/', - integration: integRootHandler + integration: integRootHandler, }); httpApi.root.addHttpRoute('aws', 'AwsPage', { targetUrl: awsUrl, - method: apigatewayv2.HttpMethod.ANY + method: apigatewayv2.HttpMethod.ANY, }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); @@ -227,60 +227,60 @@ test('addHttpRoute correctly from a Route', () => { test('addLambdaRoute correctly from a HttpApi', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); httpApi.addLambdaRoute('/foo/bar', 'FooBar', { target: handler, - method: apigatewayv2.HttpMethod.GET + method: apigatewayv2.HttpMethod.GET, }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { ApiId: { - Ref: "HttpApiF5A9A8A7" + Ref: 'HttpApiF5A9A8A7', }, - RouteKey: "GET /foo/bar", + RouteKey: 'GET /foo/bar', Target: { - "Fn::Join": [ - "", + 'Fn::Join': [ + '', [ - "integrations/", + 'integrations/', { - Ref: "HttpApiFooBarLambdaProxyIntegration2FFCA7FC" - } - ] - ] - } + Ref: 'HttpApiFooBarLambdaProxyIntegration2FFCA7FC', + }, + ], + ], + }, })); }); test('addHttpRoute correctly from a HttpApi', () => { // WHEN const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); httpApi.addHttpRoute('/foo/bar', 'FooBar', { targetUrl: awsUrl, - method: apigatewayv2.HttpMethod.ANY + method: apigatewayv2.HttpMethod.ANY, }); // THEN expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { ApiId: { - Ref: "HttpApiF5A9A8A7" + Ref: 'HttpApiF5A9A8A7', }, - RouteKey: "ANY /foo/bar", + RouteKey: 'ANY /foo/bar', Target: { - "Fn::Join": [ - "", + 'Fn::Join': [ + '', [ - "integrations/", + 'integrations/', { - Ref: "HttpApiFooBarHttpProxyIntegration80C34C6B" - } - ] - ] - } + Ref: 'HttpApiFooBarHttpProxyIntegration80C34C6B', + }, + ], + ], + }, })); }); @@ -288,17 +288,17 @@ test('throws when both targetHandler and targetUrl are specified', () => { // WHEN // THEN expect(() => - new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - targetUrl: awsUrl - }) + new apigatewayv2.HttpApi(stack, 'HttpApi', { + targetHandler: handler, + targetUrl: awsUrl, + }), ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); }); test('throws when both targetHandler and targetUrl are specified for Route', () => { // WHEN const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); // THEN expect(() => @@ -306,21 +306,21 @@ test('throws when both targetHandler and targetUrl are specified for Route', () api, httpPath: '/', targetUrl: awsUrl, - targetHandler: handler - }) + targetHandler: handler, + }), ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); }); test('throws when targetHandler, targetUrl and integration all specified for Route', () => { // WHEN const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); const integ = new apigatewayv2.Integration(stack, 'IntegRootHandler', { apiId: api.httpApiId, integrationMethod: apigatewayv2.HttpMethod.ANY, integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, - integrationUri: awsUrl + integrationUri: awsUrl, }); // THEN expect(() => @@ -329,22 +329,22 @@ test('throws when targetHandler, targetUrl and integration all specified for Rou httpPath: '/', targetUrl: awsUrl, targetHandler: handler, - integration: integ - }) + integration: integ, + }), ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); }); test('throws when targetHandler, targetUrl and integration all unspecified for Route', () => { // WHEN const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler + targetHandler: handler, }); // THEN expect(() => new apigatewayv2.Route(stack, 'Route', { api, httpPath: '/', - }) + }), ).toThrowError(/You must specify either a integration, targetHandler or targetUrl/); }); @@ -366,12 +366,12 @@ def handler(event, context): }); // WHEN const httpApi = new apigatewayv2.HttpApi(stackcn, 'HttpApi', { - targetUrl: awsUrl + targetUrl: awsUrl, }); // THEN new apigatewayv2.LambdaProxyIntegration(stackcn, 'IntegRootHandler', { api: httpApi, - targetHandler: handlercn + targetHandler: handlercn, }); // THEN expectCDK(stackcn).to(countResources('AWS::ApiGatewayV2::Integration', 1)); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index 02891994ec57f..cc9819ba09c27 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -1,7 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; -import * as apigatewayv2 from '../lib'; -import { HttpMethod, HttpProxyIntegration, LambdaProxyIntegration } from '../lib'; +import { HttpApi, HttpMethod, HttpProxyIntegration, LambdaProxyIntegration } from '../lib'; const app = new cdk.App(); @@ -33,7 +32,7 @@ def handler(event, context): }`), environment: { WHOAMI: 'root', - HTTP_PATH: '/' + HTTP_PATH: '/', }, }); @@ -41,7 +40,7 @@ const rootUrl = 'https://checkip.amazonaws.com'; const defaultUrl = 'https://aws.amazon.com'; // create a basic HTTP API with http proxy integration as the $default route -const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { +const api = new HttpApi(stack, 'HttpApi', { targetUrl: defaultUrl, }); @@ -49,8 +48,8 @@ api.addRoutes('/', 'RootRoute', { methods: [HttpMethod.GET, HttpMethod.POST], integration: new HttpProxyIntegration(stack, 'RootInteg', { api, - targetUrl: rootUrl - }) + targetUrl: rootUrl, + }), }); api.addRoutes('/books', 'GetBooksRoute', { @@ -65,8 +64,8 @@ api.addRoutes('/books/reviews', 'GetBookReviewRoute', { methods: [HttpMethod.GET], integration: new LambdaProxyIntegration(stack, 'getBookReviewInteg', { api, - targetHandler: getbookReviewsHandler - }) + targetHandler: getbookReviewsHandler, + }), }); // // pass the rootIntegration to addRootRoute() to initialize the root route From 7b64fd0a6e85c4968408688d3fc22c182b0c365d Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 28 Apr 2020 13:01:23 +0100 Subject: [PATCH 27/42] working basic routes and lambda integration --- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 83 +- .../@aws-cdk/aws-apigatewayv2/lib/index.ts | 1 + .../aws-apigatewayv2/lib/integration.ts | 169 +---- .../aws-apigatewayv2/lib/integrations/http.ts | 57 ++ .../lib/integrations/index.ts | 2 + .../lib/integrations/lambda.ts | 49 ++ .../@aws-cdk/aws-apigatewayv2/lib/route.ts | 324 ++------ .../test/apigatewayv2.test.ts | 708 +++++++++--------- .../aws-apigatewayv2/test/integ.api.ts | 71 +- 9 files changed, 597 insertions(+), 867 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/integrations/http.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/integrations/index.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 13c07b0cf54ed..4d7b56483cb88 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -1,9 +1,8 @@ import { ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; -import { CfnApi, CfnApiProps, HttpRouteOptions, IRouteBase, LambdaRouteOptions, Route } from '../lib'; -import { IIntegration } from './integration'; -import { HttpMethod, RouteOptions } from './route'; +import { CfnApi, CfnApiProps } from './apigatewayv2.generated'; +// import { AddRoutesOptions, HttpMethod, Route } from './route'; /** * Represents an HTTP API @@ -26,6 +25,7 @@ export interface HttpApiProps { * @default - id of the HttpApi construct. */ readonly apiName?: string; + /** * target lambda function of lambda proxy integration for the $default route * @@ -42,7 +42,7 @@ export interface HttpApiProps { } /** - * HTTPApi Resource Class + * Create a new API Gateway HTTP API endpoint. * * @resource AWS::ApiGatewayV2::Api */ @@ -60,10 +60,6 @@ export class HttpApi extends Resource implements IHttpApi { * the API identifer */ public readonly httpApiId: string; - /** - * root route - */ - public root?: IRouteBase; constructor(scope: Construct, id: string, props?: HttpApiProps) { super(scope, id); @@ -97,66 +93,23 @@ export class HttpApi extends Resource implements IHttpApi { * HTTP API auto deploys the default stage and this just returns the URL from the default stage. */ public get url() { - return `https://${this.httpApiId}.execute-api.${Stack.of(this).region}.${Stack.of(this).urlSuffix}`; - } - - // public get root(): IRouteBase { - // return new Route(this, `RootRoute${this.httpApiId}`, { - // api: this, - // httpPath: '/', - // }); - // } - - public addRootRoute(integration?: IIntegration, httpMethod?: HttpMethod): IRouteBase { - this.root = new Route(this, `RootRoute${this.httpApiId}`, { - api: this, - httpPath: '/', - integration, - httpMethod, - }); - return this.root; + return `https://${this.httpApiId}.execute-api.${Stack.of(this).region}.${Stack.of(this).urlSuffix}/`; } /** * add routes on this API */ - public addRoutes(pathPart: string, id: string, options: RouteOptions): Route[] { - const routes: Route[] = []; - const methods = options.methods ?? [ HttpMethod.ANY ]; - for (const m of methods) { - routes.push(new Route(this, `${id}${m}`, { - api: this, - integration: options.integration, - httpMethod: m, - httpPath: pathPart, - })); - } - return routes; - } - - /** - * create a child route with Lambda proxy integration - */ - public addLambdaRoute(httpPath: string, id: string, options: LambdaRouteOptions): Route { - const httpMethod = options.method; - return new Route(this, id, { - api: this, - targetHandler: options.target, - httpPath, - httpMethod, - }); - } - - /** - * create a child route with HTTP proxy integration - */ - public addHttpRoute(httpPath: string, id: string, options: HttpRouteOptions): Route { - const httpMethod = options.method; - return new Route(this, id, { - api: this, - targetUrl: options.targetUrl, - httpPath, - httpMethod, - }); - } + // public addRoutes(pathPart: string, id: string, options: AddRoutesOptions): Route[] { + // const routes: Route[] = []; + // const methods = options.methods ?? [ HttpMethod.ANY ]; + // for (const m of methods) { + // routes.push(new Route(this, `${id}${m}`, { + // api: this, + // integration: options.integration, + // httpMethod: m, + // httpPath: pathPart, + // })); + // } + // return routes; + // } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts index 4e56ea24d5444..3dafb144fa901 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts @@ -3,3 +3,4 @@ export * from './apigatewayv2.generated'; export * from './api'; export * from './route'; export * from './integration'; +export * from './integrations'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts index 49a17652cf025..0439f953d0041 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts @@ -1,129 +1,53 @@ -import { ServicePrincipal } from '@aws-cdk/aws-iam'; -import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; -import * as apigatewayv2 from '../lib'; +import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IHttpApi } from './api'; +import { CfnIntegration } from './apigatewayv2.generated'; +import { HttpMethod, Route } from './route'; /** - * The integration interface + * Represents an integration to a HTTP API Route. */ export interface IIntegration extends IResource { -/** - * The resource ID of the integration - * - * @attribute - */ - readonly integrationId: string + /** + * The resource ID of the integration + * @attribute + */ + readonly integrationId: string; } /** - * Integration typ - * + * Supported integration types */ export enum IntegrationType { - /** - * for integrating the route or method request with an AWS service action, includingthe Lambda function-invoking - * action. With the Lambda function-invoking action, this is referred to as the Lambda custom integration. With any - * other AWS service action, this is known as AWS integration. Supported only for WebSocket APIs. - */ - AWS = 'AWS', - /** - * for integrating the route or method request with the Lambda function-invoking action with the client request passed through as-is. - * This integration is also referred to as Lambda proxy integration. - */ AWS_PROXY = 'AWS_PROXY', - /** - * for integrating the route or method request with an HTTP endpoint. This integration is also referred to as the HTTP custom integration. - * Supported only for WebSocket APIs. - */ - HTTP = 'HTTP', - /** - * for integrating the route or method request with an HTTP endpoint, with the client request passed through as-is. - * This is also referred to as HTTP proxy integration. For HTTP API private integrations, use an `HTTP_PROXY` integration. - */ HTTP_PROXY = 'HTTP_PROXY', - /** - * for integrating the route or method request with API Gateway as a "loopback" endpoint without invoking any backend. - * Supported only for WebSocket APIs. - */ - MOCK = 'MOCK' } /** - * the integration properties + * The integration properties */ export interface IntegrationProps { - /** - * integration name - * @default - the resource ID of the integration - */ - readonly integrationName?: string; /** * API ID */ - readonly apiId: string; + readonly httpApi: IHttpApi; /** * integration type */ - readonly integrationType: IntegrationType + readonly integrationType: IntegrationType; /** * integration URI */ - readonly integrationUri: string + readonly integrationUri: string; /** * integration method */ - readonly integrationMethod: apigatewayv2.HttpMethod -} - -/** - * Lambda Proxy integration properties - */ -export interface LambdaProxyIntegrationProps { - /** - * integration name - * @default - the resource id - */ - readonly integrationName?: string; - /** - * The API identifier - * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-integration.html#cfn-apigatewayv2-integration-apiid - */ - readonly api: apigatewayv2.IHttpApi; - /** - * The Lambda function for this integration. - */ - readonly targetHandler: lambda.IFunction -} - -/** - * HTTP Proxy integration properties - */ -export interface HttpProxyIntegrationProps { - /** - * integration name - * @default - the resource id - */ - readonly integrationName?: string - /** - * The API identifier - * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-integration.html#cfn-apigatewayv2-integration-apiid - */ - readonly api: apigatewayv2.IHttpApi; - /** - * The full-qualified HTTP URL for the HTTP integration - */ - readonly targetUrl: string - /** - * Specifies the integration's HTTP method type. - * @default - ANY - */ - readonly integrationMethod?: apigatewayv2.HttpMethod + readonly integrationMethod: HttpMethod; } /** * The integration resource for HTTP API */ -export class Integration extends Resource implements IIntegration { +export class Integration extends Resource implements IResource { /** * import from integration ID */ @@ -134,11 +58,13 @@ export class Integration extends Resource implements IIntegration { return new Import(scope, id); } + public readonly integrationId: string; + constructor(scope: Construct, id: string, props: IntegrationProps) { super(scope, id); - const integ = new apigatewayv2.CfnIntegration(this, 'Resource', { - apiId: props.apiId, + const integ = new CfnIntegration(this, 'Resource', { + apiId: props.httpApi.httpApiId, integrationType: props.integrationType, integrationMethod: props.integrationMethod, integrationUri: props.integrationUri, @@ -148,55 +74,12 @@ export class Integration extends Resource implements IIntegration { } } -/** - * The Lambda Proxy integration resource for HTTP API - * @resource AWS::ApiGatewayV2::Integration - */ -export class LambdaProxyIntegration extends Resource implements IIntegration { - public readonly integrationId: string; - constructor(scope: Construct, id: string, props: LambdaProxyIntegrationProps) { - super(scope, id); - - // create integration - const integrationType = apigatewayv2.IntegrationType.AWS_PROXY; - const integrationMethod = apigatewayv2.HttpMethod.POST; - const integ = new apigatewayv2.CfnIntegration(this, 'Resource', { - apiId: props.api.httpApiId, - integrationType, - integrationMethod, - payloadFormatVersion: '1.0', - integrationUri: `arn:${Stack.of(this).partition}:apigateway:${Stack.of(this).region}:lambda:path/2015-03-31/functions/${props.targetHandler.functionArn}/invocations`, - }); - this.integrationId = integ.ref; - // create permission - const desc = `${this.node.uniqueId}.${integrationType}.${integrationMethod}`; - props.targetHandler.addPermission(`IntegPermission.${desc}`, { - scope, - principal: new ServicePrincipal('apigateway.amazonaws.com'), - sourceArn: `arn:${Stack.of(this).partition}:execute-api:${Stack.of(this).region}:${Stack.of(this).account}:${props.api.httpApiId}/*/*`, - }); - } +export interface IRouteIntegration { + bind(route: Route): RouteIntegrationConfig; } -/** - * The HTTP Proxy integration resource for HTTP API - * - * @resource AWS::ApiGatewayV2::Integration - */ -export class HttpProxyIntegration extends Resource implements IIntegration { - public readonly integrationId: string; - - constructor(scope: Construct, id: string, props: HttpProxyIntegrationProps) { - super(scope, id); +export interface RouteIntegrationConfig { + readonly type: IntegrationType; - // create integration - const integ = new apigatewayv2.CfnIntegration(this, 'Resource', { - apiId: props.api.httpApiId, - integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, - integrationMethod: props.integrationMethod ?? apigatewayv2.HttpMethod.ANY, - integrationUri: props.targetUrl, - payloadFormatVersion: '1.0', - }); - this.integrationId = integ.ref; - } + readonly uri: string; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/http.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/http.ts new file mode 100644 index 0000000000000..ad8b1afafb527 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/http.ts @@ -0,0 +1,57 @@ +import { Construct, Resource } from '@aws-cdk/core'; +import { IHttpApi } from '../api'; +import { CfnIntegration } from '../apigatewayv2.generated'; +import { IIntegration } from '../integration'; +import { HttpMethod } from '../route'; + +/** + * HTTP Proxy integration properties + */ +export interface HttpProxyIntegrationOptions { + /** + * integration name + * @default - the resource id + */ + readonly integrationName?: string + + /** + * The full-qualified HTTP URL for the HTTP integration + */ + readonly targetUrl: string + /** + * Specifies the integration's HTTP method type. + * @default - ANY + */ + readonly integrationMethod?: HttpMethod +} + +export interface HttpProxyIntegrationProps extends HttpProxyIntegrationOptions { + /** + * The API identifier + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-integration.html#cfn-apigatewayv2-integration-apiid + */ + readonly api: IHttpApi; +} + +/** + * The HTTP Proxy integration resource for HTTP API + * + * @resource AWS::ApiGatewayV2::Integration + */ +export class HttpProxyIntegration extends Resource implements IIntegration { + public readonly integrationId: string; + + constructor(scope: Construct, id: string, props: HttpProxyIntegrationProps) { + super(scope, id); + + // create integration + const integ = new CfnIntegration(this, 'Resource', { + apiId: props.api.httpApiId, + integrationType: 'HTTP_PROXY', + integrationMethod: props.integrationMethod ?? HttpMethod.ANY, + integrationUri: props.targetUrl, + payloadFormatVersion: '1.0', + }); + this.integrationId = integ.ref; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/index.ts new file mode 100644 index 0000000000000..7797fe6ea8b03 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/index.ts @@ -0,0 +1,2 @@ +export * from './http'; +export * from './lambda'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts new file mode 100644 index 0000000000000..cb3b63ac4542d --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts @@ -0,0 +1,49 @@ +import { ServicePrincipal } from '@aws-cdk/aws-iam'; +import { IFunction } from '@aws-cdk/aws-lambda'; +import { Stack } from '@aws-cdk/core'; +import { IntegrationType, IRouteIntegration, RouteIntegrationConfig } from '../integration'; +import { Route } from '../route'; + +/** + * Lambda Proxy integration properties + */ +export interface LambdaProxyIntegrationProps { + /** + * The Lambda function for this integration. + */ + readonly handler: IFunction + + /** + * Version of the payload sent to the lambda handler. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + * @default '2.0' + */ + readonly payloadFormatVersion?: string; +} + +/** + * The Lambda Proxy integration resource for HTTP API + * @resource AWS::ApiGatewayV2::Integration + */ +export class LambdaProxyIntegration implements IRouteIntegration { + + constructor(private readonly props: LambdaProxyIntegrationProps) { + } + + public bind(route: Route): RouteIntegrationConfig { + // create permission + this.props.handler.addPermission(`${route.node.uniqueId}-Permission`, { + principal: new ServicePrincipal('apigateway.amazonaws.com'), + sourceArn: Stack.of(route).formatArn({ + service: 'execute-api', + resource: route.httpApi.httpApiId, + resourceName: `*/*${route.path}`, + }), + }); + + return { + type: IntegrationType.AWS_PROXY, + uri: this.props.handler.functionArn, + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts index 79e1064f3bf20..2dcc0788c08f4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -1,7 +1,7 @@ -import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, IResource, Resource } from '@aws-cdk/core'; -import * as apigatewayv2 from '../lib'; -import { Integration } from './integration'; +import { IHttpApi } from './api'; +import { CfnRoute, CfnRouteProps } from './apigatewayv2.generated'; +import { Integration, IRouteIntegration } from './integration'; /** * the interface of the Route of API Gateway HTTP API @@ -15,189 +15,73 @@ export interface IRoute extends IResource { } /** - * the interface of the RouteBase + * all HTTP methods */ -export interface IRouteBase extends IRoute { - /** - * the ID of this API Gateway HttpApi. - * @attribute - */ - readonly api: apigatewayv2.IHttpApi; - /** - * the key of this route - * @attribute - */ - readonly routeKey: string; - /** - * the parent of this route or undefinied for the root route - */ - readonly parentRoute?: IRoute; - /** - * full path of this route - */ - readonly httpPath: string; - - /** - * add a child route with HTTP integration for this parent route - */ - addHttpRoute(pathPart: string, id: string, options: HttpRouteOptions): Route; - - /** - * add a child route with Lambda integration for this parent route - */ - addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route; - - /** - * add a child route with integration resource for this parent route - */ - addRoutes(pathPart: string, id: string, options: RouteOptions): Route[]; +export enum HttpMethod { + /** HTTP ANY */ + ANY = 'ANY', + /** HTTP DELETE */ + DELETE = 'DELETE', + /** HTTP GET */ + GET = 'GET', + /** HTTP HEAD */ + HEAD = 'HEAD', + /** HTTP OPTIONS */ + OPTIONS = 'OPTIONS', + /** HTTP PATCH */ + PATCH = 'PATCH', + /** HTTP POST */ + POST = 'POST', + /** HTTP PUT */ + PUT = 'PUT', } /** - * options of HttpRoute + * Route properties */ -export interface HttpRouteOptions { - /** - * URL of the integration target - */ - readonly targetUrl: string - +export interface RouteProps { /** - * HTTP method - * @default HttpMethod.ANY + * the API the route is associated with */ - readonly method?: HttpMethod + readonly httpApi: IHttpApi; /** - * Integration Method + * HTTP method of this route * @default HttpMethod.ANY */ - readonly integrationMethod?: HttpMethod; -} - -/** - * Options for the Route with Lambda integration - */ -export interface LambdaRouteOptions { - /** - * target lambda function - */ - readonly target: lambda.IFunction + readonly method?: HttpMethod; /** - * HTTP method - * @default HttpMethod.ANY + * full http path of this route */ - readonly method?: HttpMethod + readonly path: string; /** - * Integration method - * @default HttpMethod.ANY + * Integration */ - readonly integrationMethod?: HttpMethod; + readonly integration?: IRouteIntegration; } /** * Options for the Route with Integration resoruce */ -export interface RouteOptions { +export interface AddRoutesOptions { /** * HTTP methods * @default HttpMethod.ANY */ - readonly methods?: HttpMethod[] - - /** - * Integration method - * @default HttpMethod.ANY - */ - readonly integrationMethod?: HttpMethod; + readonly methods?: HttpMethod[]; /** - * Integration + * The integration for this path */ readonly integration: Integration; } -/** - * all HTTP methods - */ -export enum HttpMethod { - /** - * HTTP ANY - */ - ANY = 'ANY', - /** - * HTTP GET - */ - GET = 'GET', - /** - * HTTP PUT - */ - PUT = 'PUT', - /** - * HTTP POST - */ - POST = 'POST', - /** - * HTTP DELETE - */ - DELETE = 'DELETE', -} - -/** - * Route properties - */ -export interface RouteProps { - /** - * route name - * @default - the logic ID of this route - */ - readonly routeName?: string; - /** - * the API the route is associated with - */ - readonly api: apigatewayv2.IHttpApi; - /** - * HTTP method of this route - * @default HttpMethod.ANY - */ - readonly httpMethod?: HttpMethod; - /** - * full http path of this route - */ - readonly httpPath: string; - /** - * parent of this route - * @default - undefinied if no parentroute d - */ - readonly parent?: IRoute - /** - * path part of this route - * @default '' - */ - readonly pathPart?: string; - /** - * HTTP URL target of this route - * @default - None. Specify one of `targetUrl`, `targetHandler` or `integration` - */ - readonly targetUrl?: string; - /** - * Lambda handler target of this route - * @default - None. Specify one of `targetUrl`, `targetHandler` or `integration` - */ - readonly targetHandler?: lambda.IFunction; - /** - * Integration - * @default - None. Specify one of `targetUrl`, `targetHandler` or `integration` - */ - readonly integration?: apigatewayv2.IIntegration; -} - /** * Route class that creates the Route for API Gateway HTTP API */ -export class Route extends Resource implements IRouteBase { +export class Route extends Resource implements IRoute { /** * import from route id */ @@ -207,130 +91,58 @@ export class Route extends Resource implements IRouteBase { } return new Import(scope, id); } - /** - * the api ID of this route - */ - public readonly api: apigatewayv2.IHttpApi; - /** - * the route key of this route - */ - public readonly routeKey: string; - /** - * the full http path of this route - */ - public readonly httpPath: string; - /** - * http method of this route - */ - public readonly httpMethod: HttpMethod; - /** - * route id from the `Ref` function - */ + public readonly routeId: string; - /** - * integration ID - */ - public readonly integId: string; + public readonly httpApi: IHttpApi; + public readonly method: HttpMethod; + public readonly path: string; constructor(scope: Construct, id: string, props: RouteProps) { super(scope, id); - if ((props.targetHandler && props.targetUrl) || - (props.targetHandler && props.integration) || - (props.targetUrl && props.integration)) { - throw new Error('You must specify targetHandler, targetUrl or integration, use at most one'); + if (props.path !== '/' && (!props.path.startsWith('/') || props.path.endsWith('/'))) { + throw new Error('path must always start with a "/" and not end with a "/"'); } - this.api = props.api; - this.httpPath = props.httpPath; - this.httpMethod = props.httpMethod ?? HttpMethod.ANY; - this.routeKey = `${this.httpMethod} ${this.httpPath}`; + this.httpApi = props.httpApi; + this.method = props.method ?? HttpMethod.ANY; + this.path = props.path; + let integration: Integration | undefined; if (props.integration) { - this.integId = props.integration.integrationId; - } else if (props.targetUrl) { - // create a HTTP Proxy integration - const integ = new apigatewayv2.HttpProxyIntegration(scope, `${id}/HttpProxyIntegration`, { - api: this.api, - targetUrl: props.targetUrl, + const config = props.integration.bind(this); + integration = new Integration(this, `${this.node.id}-Integration`, { + httpApi: props.httpApi, + integrationMethod: this.method, + integrationType: config.type, + integrationUri: config.uri, }); - this.integId = integ.integrationId; - } else if (props.targetHandler) { - // create a Lambda Proxy integration - const integ = new apigatewayv2.LambdaProxyIntegration(scope, `${id}/LambdaProxyIntegration`, { - api: this.api, - targetHandler: props.targetHandler, - }); - this.integId = integ.integrationId; - } else { - throw new Error('You must specify either a integration, targetHandler or targetUrl'); } - const routeProps: apigatewayv2.CfnRouteProps = { - apiId: this.api.httpApiId, - routeKey: this.routeKey, - target: `integrations/${this.integId}`, + const routeProps: CfnRouteProps = { + apiId: props.httpApi.httpApiId, + routeKey: `${this.method} ${this.path}`, + target: integration ? `integrations/${integration.integrationId}` : undefined, }; - const route = new apigatewayv2.CfnRoute(this, 'Resource', routeProps); + const route = new CfnRoute(this, 'Resource', routeProps); this.routeId = route.ref; } /** * create child routes */ - public addRoutes(pathPart: string, id: string, options: RouteOptions): Route[] { - const routes: Route[] = []; - const methods = options.methods ?? [ HttpMethod.ANY ]; - for (const m of methods) { - routes.push(new Route(this, `${id}${m}`, { - api: this.api, - integration: options.integration, - httpMethod: m, - httpPath: pathPart, - })); - } - return routes; - } - - /** - * create a child route with Lambda proxy integration - */ - public addLambdaRoute(pathPart: string, id: string, options: LambdaRouteOptions): Route { - const httpPath = `${this.httpPath.replace(/\/+$/, '')}/${pathPart}`; - const httpMethod = options.method; - - return new Route(this, id, { - api: this.api, - targetHandler: options.target, - httpPath, - httpMethod, - parent: this, - pathPart, - }); - } - - /** - * create a child route with HTTP proxy integration - */ - public addHttpRoute(pathPart: string, id: string, options: HttpRouteOptions): Route { - const httpPath = `${this.httpPath.replace(/\/+$/, '')}/${pathPart}`; - const httpMethod = options.method; - - return new Route(this, id, { - api: this.api, - targetUrl: options.targetUrl, - httpPath, - httpMethod, - parent: this, - pathPart, - }); - } - - // /** - // * add integration for this route - // */ - // public addIntegration(integration:IIntegration): Route { - + // public addRoutes(pathPart: string, id: string, options: AddRoutesOptions): Route[] { + // const routes: Route[] = []; + // const methods = options.methods ?? [ HttpMethod.ANY ]; + // for (const m of methods) { + // routes.push(new Route(this, `${id}${m}`, { + // api: this.httpApi, + // integration: options.integration, + // httpMethod: m, + // httpPath: pathPart, + // })); + // } + // return routes; // } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts index 6609e926c8f89..fa54f2fe80a37 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts @@ -1,378 +1,378 @@ -import { countResources, expect as expectCDK, haveOutput, haveResourceLike } from '@aws-cdk/assert'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; -import * as apigatewayv2 from '../lib'; +// import { countResources, expect as expectCDK, haveOutput, haveResourceLike } from '@aws-cdk/assert'; +// import * as lambda from '@aws-cdk/aws-lambda'; +// import * as cdk from '@aws-cdk/core'; +// import * as apigatewayv2 from '../lib'; -let stack = new cdk.Stack(); -let handler: lambda.Function; -let rootHandler: lambda.Function; -let checkIpUrl: string; -let awsUrl: string; +// let stack = new cdk.Stack(); +// let handler: lambda.Function; +// let rootHandler: lambda.Function; +// let checkIpUrl: string; +// let awsUrl: string; -beforeEach(() => { - stack = new cdk.Stack(); - handler = new lambda.Function(stack, 'MyFunc', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code: new lambda.InlineCode(` -import json -def handler(event, context): - return { - 'statusCode': 200, - 'body': json.dumps(event) - }`), - }); +// beforeEach(() => { +// stack = new cdk.Stack(); +// handler = new lambda.Function(stack, 'MyFunc', { +// runtime: lambda.Runtime.PYTHON_3_7, +// handler: 'index.handler', +// code: new lambda.InlineCode(` +// import json +// def handler(event, context): +// return { +// 'statusCode': 200, +// 'body': json.dumps(event) +// }`), +// }); - rootHandler = new lambda.Function(stack, 'RootFunc', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code: new lambda.InlineCode(` -import json, os -def handler(event, context): - whoami = os.environ['WHOAMI'] - http_path = os.environ['HTTP_PATH'] - return { - 'statusCode': 200, - 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path }) - }`), - environment: { - WHOAMI: 'root', - HTTP_PATH: '/', - }, - }); - checkIpUrl = 'https://checkip.amazonaws.com'; - awsUrl = 'https://aws.amazon.com'; -}); +// rootHandler = new lambda.Function(stack, 'RootFunc', { +// runtime: lambda.Runtime.PYTHON_3_7, +// handler: 'index.handler', +// code: new lambda.InlineCode(` +// import json, os +// def handler(event, context): +// whoami = os.environ['WHOAMI'] +// http_path = os.environ['HTTP_PATH'] +// return { +// 'statusCode': 200, +// 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path }) +// }`), +// environment: { +// WHOAMI: 'root', +// HTTP_PATH: '/', +// }, +// }); +// checkIpUrl = 'https://checkip.amazonaws.com'; +// awsUrl = 'https://aws.amazon.com'; +// }); -test('create a HTTP API with no props correctly', () => { - // WHEN - const api = new apigatewayv2.HttpApi(stack, 'HttpApi'); - new cdk.CfnOutput(stack, 'URL', { value: api.url }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(haveOutput({ - outputName: 'URL', - })); -}); +// test('create a HTTP API with no props correctly', () => { +// // WHEN +// const api = new apigatewayv2.HttpApi(stack, 'HttpApi'); +// new cdk.CfnOutput(stack, 'URL', { value: api.url }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(haveOutput({ +// outputName: 'URL', +// })); +// }); -test('create a HTTP API with empty props correctly', () => { - // WHEN - new apigatewayv2.HttpApi(stack, 'HttpApi', {}); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -}); +// test('create a HTTP API with empty props correctly', () => { +// // WHEN +// new apigatewayv2.HttpApi(stack, 'HttpApi', {}); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// }); -test('create a basic HTTP API correctly with targetUrl', () => { - // WHEN - new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetUrl: checkIpUrl, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -}); +// test('create a basic HTTP API correctly with targetUrl', () => { +// // WHEN +// new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetUrl: checkIpUrl, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// }); -test('create a basic HTTP API correctly with targetUrl and protocol', () => { - // WHEN - new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetUrl: checkIpUrl, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -}); +// test('create a basic HTTP API correctly with targetUrl and protocol', () => { +// // WHEN +// new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetUrl: checkIpUrl, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// }); -test('create a basic HTTP API correctly with target handler', () => { - // WHEN - new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -}); +// test('create a basic HTTP API correctly with target handler', () => { +// // WHEN +// new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// }); -test('import HTTP API from API ID correctly', () => { - // WHEN - // THEN - expect(() => - apigatewayv2.HttpApi.fromApiId(stack, 'HttpApi', 'foo'), - ).not.toThrowError(); -}); +// test('import HTTP API from API ID correctly', () => { +// // WHEN +// // THEN +// expect(() => +// apigatewayv2.HttpApi.fromApiId(stack, 'HttpApi', 'foo'), +// ).not.toThrowError(); +// }); -test('create lambda proxy integration correctly', () => { - // WHEN - const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { - api: httpApi, - targetHandler: rootHandler, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -}); +// test('create lambda proxy integration correctly', () => { +// // WHEN +// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { +// api: httpApi, +// targetHandler: rootHandler, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +// }); -test('create HTTP proxy integration correctly with targetUrl', () => { - // WHEN - const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - new apigatewayv2.HttpProxyIntegration(stack, 'IntegRootHandler', { - api: httpApi, - targetUrl: 'https://aws.amazon.com', - integrationMethod: apigatewayv2.HttpMethod.GET, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -}); +// test('create HTTP proxy integration correctly with targetUrl', () => { +// // WHEN +// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// new apigatewayv2.HttpProxyIntegration(stack, 'IntegRootHandler', { +// api: httpApi, +// targetUrl: 'https://aws.amazon.com', +// integrationMethod: apigatewayv2.HttpMethod.GET, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +// }); -test('create HTTP proxy integration correctly with Integration', () => { - // WHEN - const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - new apigatewayv2.Integration(stack, 'IntegRootHandler', { - apiId: httpApi.httpApiId, - integrationMethod: apigatewayv2.HttpMethod.ANY, - integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, - integrationUri: awsUrl, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -}); +// test('create HTTP proxy integration correctly with Integration', () => { +// // WHEN +// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// new apigatewayv2.Integration(stack, 'IntegRootHandler', { +// apiId: httpApi.httpApiId, +// integrationMethod: apigatewayv2.HttpMethod.ANY, +// integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, +// integrationUri: awsUrl, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +// }); -test('import integration from integration ID correctly', () => { - // WHEN - // THEN - expect(() => - apigatewayv2.Integration.fromIntegrationId(stack, 'Integ', 'foo'), - ).not.toThrowError(); -}); +// test('import integration from integration ID correctly', () => { +// // WHEN +// // THEN +// expect(() => +// apigatewayv2.Integration.fromIntegrationId(stack, 'Integ', 'foo'), +// ).not.toThrowError(); +// }); -test('create the root route correctly', () => { - // WHEN - const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { - api: httpApi, - targetHandler: rootHandler, - }); - httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { - api: httpApi, - httpPath: '/', - integration: integRootHandler, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 1)); -}); +// test('create the root route correctly', () => { +// // WHEN +// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { +// api: httpApi, +// targetHandler: rootHandler, +// }); +// httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { +// api: httpApi, +// httpPath: '/', +// integration: integRootHandler, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 1)); +// }); -test('import route from route ID correctly', () => { - // WHEN - // THEN - expect(() => - apigatewayv2.Route.fromRouteId(stack, 'Integ', 'foo'), - ).not.toThrowError(); -}); +// test('import route from route ID correctly', () => { +// // WHEN +// // THEN +// expect(() => +// apigatewayv2.Route.fromRouteId(stack, 'Integ', 'foo'), +// ).not.toThrowError(); +// }); -test('addLambdaRoute correctly from a Route', () => { - // WHEN - const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { - api: httpApi, - targetHandler: rootHandler, - }); - httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { - api: httpApi, - httpPath: '/', - integration: integRootHandler, - }); - httpApi.root.addLambdaRoute('foo', 'Foo', { - target: handler, - method: apigatewayv2.HttpMethod.GET, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 2)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); -}); +// test('addLambdaRoute correctly from a Route', () => { +// // WHEN +// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { +// api: httpApi, +// targetHandler: rootHandler, +// }); +// httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { +// api: httpApi, +// httpPath: '/', +// integration: integRootHandler, +// }); +// httpApi.root.addLambdaRoute('foo', 'Foo', { +// target: handler, +// method: apigatewayv2.HttpMethod.GET, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 2)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); +// }); -test('addHttpRoute correctly from a Route', () => { - // WHEN - const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { - api: httpApi, - targetHandler: rootHandler, - }); - httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { - api: httpApi, - httpPath: '/', - integration: integRootHandler, - }); - httpApi.root.addHttpRoute('aws', 'AwsPage', { - targetUrl: awsUrl, - method: apigatewayv2.HttpMethod.ANY, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 2)); - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); -}); +// test('addHttpRoute correctly from a Route', () => { +// // WHEN +// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { +// api: httpApi, +// targetHandler: rootHandler, +// }); +// httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { +// api: httpApi, +// httpPath: '/', +// integration: integRootHandler, +// }); +// httpApi.root.addHttpRoute('aws', 'AwsPage', { +// targetUrl: awsUrl, +// method: apigatewayv2.HttpMethod.ANY, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 2)); +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); +// }); -test('addLambdaRoute correctly from a HttpApi', () => { - // WHEN - const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - httpApi.addLambdaRoute('/foo/bar', 'FooBar', { - target: handler, - method: apigatewayv2.HttpMethod.GET, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { - ApiId: { - Ref: 'HttpApiF5A9A8A7', - }, - RouteKey: 'GET /foo/bar', - Target: { - 'Fn::Join': [ - '', - [ - 'integrations/', - { - Ref: 'HttpApiFooBarLambdaProxyIntegration2FFCA7FC', - }, - ], - ], - }, - })); -}); +// test('addLambdaRoute correctly from a HttpApi', () => { +// // WHEN +// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// httpApi.addLambdaRoute('/foo/bar', 'FooBar', { +// target: handler, +// method: apigatewayv2.HttpMethod.GET, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { +// ApiId: { +// Ref: 'HttpApiF5A9A8A7', +// }, +// RouteKey: 'GET /foo/bar', +// Target: { +// 'Fn::Join': [ +// '', +// [ +// 'integrations/', +// { +// Ref: 'HttpApiFooBarLambdaProxyIntegration2FFCA7FC', +// }, +// ], +// ], +// }, +// })); +// }); -test('addHttpRoute correctly from a HttpApi', () => { - // WHEN - const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - httpApi.addHttpRoute('/foo/bar', 'FooBar', { - targetUrl: awsUrl, - method: apigatewayv2.HttpMethod.ANY, - }); - // THEN - expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); - expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { - ApiId: { - Ref: 'HttpApiF5A9A8A7', - }, - RouteKey: 'ANY /foo/bar', - Target: { - 'Fn::Join': [ - '', - [ - 'integrations/', - { - Ref: 'HttpApiFooBarHttpProxyIntegration80C34C6B', - }, - ], - ], - }, - })); -}); +// test('addHttpRoute correctly from a HttpApi', () => { +// // WHEN +// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// httpApi.addHttpRoute('/foo/bar', 'FooBar', { +// targetUrl: awsUrl, +// method: apigatewayv2.HttpMethod.ANY, +// }); +// // THEN +// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); +// expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { +// ApiId: { +// Ref: 'HttpApiF5A9A8A7', +// }, +// RouteKey: 'ANY /foo/bar', +// Target: { +// 'Fn::Join': [ +// '', +// [ +// 'integrations/', +// { +// Ref: 'HttpApiFooBarHttpProxyIntegration80C34C6B', +// }, +// ], +// ], +// }, +// })); +// }); -test('throws when both targetHandler and targetUrl are specified', () => { - // WHEN - // THEN - expect(() => - new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - targetUrl: awsUrl, - }), - ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); -}); +// test('throws when both targetHandler and targetUrl are specified', () => { +// // WHEN +// // THEN +// expect(() => +// new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// targetUrl: awsUrl, +// }), +// ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); +// }); -test('throws when both targetHandler and targetUrl are specified for Route', () => { - // WHEN - const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - // THEN - expect(() => - new apigatewayv2.Route(stack, 'Route', { - api, - httpPath: '/', - targetUrl: awsUrl, - targetHandler: handler, - }), - ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); -}); +// test('throws when both targetHandler and targetUrl are specified for Route', () => { +// // WHEN +// const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// // THEN +// expect(() => +// new apigatewayv2.Route(stack, 'Route', { +// api, +// httpPath: '/', +// targetUrl: awsUrl, +// targetHandler: handler, +// }), +// ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); +// }); -test('throws when targetHandler, targetUrl and integration all specified for Route', () => { - // WHEN - const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - const integ = new apigatewayv2.Integration(stack, 'IntegRootHandler', { - apiId: api.httpApiId, - integrationMethod: apigatewayv2.HttpMethod.ANY, - integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, - integrationUri: awsUrl, - }); - // THEN - expect(() => - new apigatewayv2.Route(stack, 'Route', { - api, - httpPath: '/', - targetUrl: awsUrl, - targetHandler: handler, - integration: integ, - }), - ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); -}); +// test('throws when targetHandler, targetUrl and integration all specified for Route', () => { +// // WHEN +// const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// const integ = new apigatewayv2.Integration(stack, 'IntegRootHandler', { +// apiId: api.httpApiId, +// integrationMethod: apigatewayv2.HttpMethod.ANY, +// integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, +// integrationUri: awsUrl, +// }); +// // THEN +// expect(() => +// new apigatewayv2.Route(stack, 'Route', { +// api, +// httpPath: '/', +// targetUrl: awsUrl, +// targetHandler: handler, +// integration: integ, +// }), +// ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); +// }); -test('throws when targetHandler, targetUrl and integration all unspecified for Route', () => { - // WHEN - const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler: handler, - }); - // THEN - expect(() => - new apigatewayv2.Route(stack, 'Route', { - api, - httpPath: '/', - }), - ).toThrowError(/You must specify either a integration, targetHandler or targetUrl/); -}); +// test('throws when targetHandler, targetUrl and integration all unspecified for Route', () => { +// // WHEN +// const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { +// targetHandler: handler, +// }); +// // THEN +// expect(() => +// new apigatewayv2.Route(stack, 'Route', { +// api, +// httpPath: '/', +// }), +// ).toThrowError(/You must specify either a integration, targetHandler or targetUrl/); +// }); -test('create LambdaProxyIntegration correctly in aws china region', () => { - // GIVEN - const app = new cdk.App(); - const stackcn = new cdk.Stack(app, 'stack', { env: { region: 'cn-north-1' } }); - const handlercn = new lambda.Function(stackcn, 'MyFunc', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code: new lambda.InlineCode(` -import json -def handler(event, context): - return { - 'statusCode': 200, - 'body': json.dumps(event) - }`), - reservedConcurrentExecutions: 10, - }); - // WHEN - const httpApi = new apigatewayv2.HttpApi(stackcn, 'HttpApi', { - targetUrl: awsUrl, - }); - // THEN - new apigatewayv2.LambdaProxyIntegration(stackcn, 'IntegRootHandler', { - api: httpApi, - targetHandler: handlercn, - }); - // THEN - expectCDK(stackcn).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -}); \ No newline at end of file +// test('create LambdaProxyIntegration correctly in aws china region', () => { +// // GIVEN +// const app = new cdk.App(); +// const stackcn = new cdk.Stack(app, 'stack', { env: { region: 'cn-north-1' } }); +// const handlercn = new lambda.Function(stackcn, 'MyFunc', { +// runtime: lambda.Runtime.PYTHON_3_7, +// handler: 'index.handler', +// code: new lambda.InlineCode(` +// import json +// def handler(event, context): +// return { +// 'statusCode': 200, +// 'body': json.dumps(event) +// }`), +// reservedConcurrentExecutions: 10, +// }); +// // WHEN +// const httpApi = new apigatewayv2.HttpApi(stackcn, 'HttpApi', { +// targetUrl: awsUrl, +// }); +// // THEN +// new apigatewayv2.LambdaProxyIntegration(stackcn, 'IntegRootHandler', { +// api: httpApi, +// targetHandler: handlercn, +// }); +// // THEN +// expectCDK(stackcn).to(countResources('AWS::ApiGatewayV2::Integration', 1)); +// }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index cc9819ba09c27..09c8c37961398 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -1,12 +1,12 @@ import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; -import { HttpApi, HttpMethod, HttpProxyIntegration, LambdaProxyIntegration } from '../lib'; +import { App, Stack } from '@aws-cdk/core'; +import { HttpApi, HttpMethod, LambdaProxyIntegration, Route } from '../lib'; -const app = new cdk.App(); +const app = new App(); -const stack = new cdk.Stack(app, 'ApiagtewayV2HttpApi'); +const stack = new Stack(app, 'ApiagtewayV2HttpApi'); -const getbooksHandler = new lambda.Function(stack, 'MyFunc', { +const handler = new lambda.Function(stack, 'MyFunc', { runtime: lambda.Runtime.PYTHON_3_7, handler: 'index.handler', code: new lambda.InlineCode(` @@ -18,55 +18,28 @@ def handler(event, context): }`), }); -const getbookReviewsHandler = new lambda.Function(stack, 'RootFunc', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code: new lambda.InlineCode(` -import json, os -def handler(event, context): - whoami = os.environ['WHOAMI'] - http_path = os.environ['HTTP_PATH'] - return { - 'statusCode': 200, - 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path }) - }`), - environment: { - WHOAMI: 'root', - HTTP_PATH: '/', - }, -}); - -const rootUrl = 'https://checkip.amazonaws.com'; -const defaultUrl = 'https://aws.amazon.com'; +// const rootUrl = 'https://checkip.amazonaws.com'; +// const defaultUrl = 'https://aws.amazon.com'; // create a basic HTTP API with http proxy integration as the $default route -const api = new HttpApi(stack, 'HttpApi', { - targetUrl: defaultUrl, -}); - -api.addRoutes('/', 'RootRoute', { - methods: [HttpMethod.GET, HttpMethod.POST], - integration: new HttpProxyIntegration(stack, 'RootInteg', { - api, - targetUrl: rootUrl, - }), -}); - -api.addRoutes('/books', 'GetBooksRoute', { - methods: [HttpMethod.GET], - integration: new LambdaProxyIntegration(stack, 'getbooksInteg', { - api, - targetHandler: getbooksHandler, +const api = new HttpApi(stack, 'HttpApi'); + +new Route(api, 'allroutes', { + httpApi: api, + path: '/', + method: HttpMethod.ANY, + integration: new LambdaProxyIntegration({ + handler, }), }); -api.addRoutes('/books/reviews', 'GetBookReviewRoute', { - methods: [HttpMethod.GET], - integration: new LambdaProxyIntegration(stack, 'getBookReviewInteg', { - api, - targetHandler: getbookReviewsHandler, - }), -}); +// api.addRoutes('/books/reviews', 'GetBookReviewRoute', { +// methods: [HttpMethod.GET], +// integration: new LambdaProxyIntegration(stack, 'getBookReviewInteg', { +// api, +// targetHandler: getbookReviewsHandler, +// }), +// }); // // pass the rootIntegration to addRootRoute() to initialize the root route // // HTTP GET / From bb8857fab1f17c340b5b5f512c1b487bbdfb6b96 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 28 Apr 2020 16:24:54 +0100 Subject: [PATCH 28/42] Working stage --- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 70 +++++++++---------- .../@aws-cdk/aws-apigatewayv2/lib/index.ts | 3 +- .../@aws-cdk/aws-apigatewayv2/lib/stage.ts | 63 +++++++++++++++++ .../aws-apigatewayv2/test/integ.api.ts | 27 ++++--- 4 files changed, 116 insertions(+), 47 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 4d7b56483cb88..ce366c91ee8ca 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -1,7 +1,6 @@ -import { ServicePrincipal } from '@aws-cdk/aws-iam'; -import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; +import { Construct, IResource, Resource } from '@aws-cdk/core'; import { CfnApi, CfnApiProps } from './apigatewayv2.generated'; +import { Stage, StageName } from './stage'; // import { AddRoutesOptions, HttpMethod, Route } from './route'; /** @@ -9,7 +8,7 @@ import { CfnApi, CfnApiProps } from './apigatewayv2.generated'; */ export interface IHttpApi extends IResource { /** - * The ID of this API Gateway HTTP Api. + * The identifier of this API Gateway HTTP API. * @attribute */ readonly httpApiId: string; @@ -21,24 +20,22 @@ export interface IHttpApi extends IResource { export interface HttpApiProps { /** * Name for the HTTP API resoruce - * * @default - id of the HttpApi construct. */ readonly apiName?: string; - /** - * target lambda function of lambda proxy integration for the $default route - * - * @default - None. Specify either `targetHandler` or `targetUrl` - */ - readonly targetHandler?: lambda.IFunction; + // /** + // * target lambda function of lambda proxy integration for the $default route + // * + // * @default - None. Specify either `targetHandler` or `targetUrl` + // */ + // readonly targetHandler?: lambda.IFunction; /** - * target URL of the HTTP proxy integration for the $default route - * - * @default - None. Specify either `targetHandler` or `targetUrl` + * Whether a default stage and deployment should be automatically created. + * @default true */ - readonly targetUrl?: string; + readonly createDefaultStage?: boolean; } /** @@ -56,44 +53,45 @@ export class HttpApi extends Resource implements IHttpApi { } return new Import(scope, id); } - /** - * the API identifer - */ + public readonly httpApiId: string; + private readonly defaultStage: Stage | undefined; constructor(scope: Construct, id: string, props?: HttpApiProps) { super(scope, id); const apiName = props?.apiName ?? id; - // if (props?.targetHandler && props.targetUrl) { - // throw new Error('You must specify either a targetHandler or targetUrl, use at most one'); - // } - const apiProps: CfnApiProps = { name: apiName, protocolType: 'HTTP', - target: props?.targetHandler ? props.targetHandler.functionArn : props?.targetUrl ?? undefined, }; - const api = new CfnApi(this, 'Resource', apiProps); - this.httpApiId = api.ref; + const resource = new CfnApi(this, 'Resource', apiProps); + this.httpApiId = resource.ref; - if (props?.targetHandler) { - const desc = `${this.node.uniqueId}.'ANY'`; - props.targetHandler.addPermission(`ApiPermission.${desc}`, { - scope, - principal: new ServicePrincipal('apigateway.amazonaws.com'), - sourceArn: `arn:${Stack.of(this).partition}:execute-api:${Stack.of(this).region}:${Stack.of(this).account}:${this.httpApiId}/*/*`, - } ); + if (props?.createDefaultStage === undefined || props.createDefaultStage === true) { + this.defaultStage = new Stage(this, 'DefaultStage', { + httpApi: this, + stageName: StageName.DEFAULT, + }); } + + // if (props?.targetHandler) { + // const desc = `${this.node.uniqueId}.'ANY'`; + // props.targetHandler.addPermission(`ApiPermission.${desc}`, { + // scope, + // principal: new ServicePrincipal('apigateway.amazonaws.com'), + // sourceArn: `arn:${Stack.of(this).partition}:execute-api:${Stack.of(this).region}:${Stack.of(this).account}:${this.httpApiId}/*/*`, + // } ); + // } } /** - * The HTTP URL of this API. - * HTTP API auto deploys the default stage and this just returns the URL from the default stage. + * Get the URL to the default stage of this API. + * Returns `undefined` if `createDefaultStage` is unset. */ - public get url() { - return `https://${this.httpApiId}.execute-api.${Stack.of(this).region}.${Stack.of(this).urlSuffix}/`; + public get url(): string | undefined { + return this.defaultStage ? this.defaultStage.url : undefined; } /** diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts index 3dafb144fa901..f8b6279513d69 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts @@ -3,4 +3,5 @@ export * from './apigatewayv2.generated'; export * from './api'; export * from './route'; export * from './integration'; -export * from './integrations'; \ No newline at end of file +export * from './integrations'; +export * from './stage'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts new file mode 100644 index 0000000000000..9f34364ca7633 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts @@ -0,0 +1,63 @@ +import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; +import { IHttpApi } from './api'; +import { CfnStage } from './apigatewayv2.generated'; + +export interface IStage extends IResource { + readonly stageName: string; +} + +export class StageName { + public static readonly DEFAULT = new StageName('$default'); + + public static of(stageName: string) { + return new StageName(stageName); + } + + private constructor(public readonly stageName: string) { + } +} + +export interface StageOptions { + readonly stageName?: StageName; + readonly autoDeploy?: boolean; +} + +export interface StageProps extends StageOptions { + readonly httpApi: IHttpApi; +} + +export class Stage extends Resource implements IStage { + public static fromStageName(scope: Construct, id: string, stageName: string): IStage { + class Import extends Resource implements IStage { + public readonly stageName = stageName; + } + return new Import(scope, id); + } + + public readonly stageName: string; + private httpApi: IHttpApi; + + constructor(scope: Construct, id: string, props: StageProps) { + super(scope, id, { + physicalName: props.stageName ? props.stageName.stageName : StageName.DEFAULT.stageName, + }); + + const resource = new CfnStage(this, 'Resource', { + apiId: props.httpApi.httpApiId, + stageName: this.physicalName, + autoDeploy: props.autoDeploy ?? true, + }); + + this.stageName = resource.ref; + this.httpApi = props.httpApi; + } + + /** + * The URL to this stage. + */ + public get url() { + const s = Stack.of(this); + const urlPath = this.stageName === StageName.DEFAULT.stageName ? '' : this.stageName; + return `https://${this.httpApi.httpApiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index 09c8c37961398..106dc88bd7998 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -1,6 +1,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; -import { HttpApi, HttpMethod, LambdaProxyIntegration, Route } from '../lib'; +// import { HttpApi, HttpMethod, LambdaProxyIntegration, Route, Stage } from '../lib'; +import { HttpApi } from '../lib'; const app = new App(); @@ -22,17 +23,23 @@ def handler(event, context): // const defaultUrl = 'https://aws.amazon.com'; // create a basic HTTP API with http proxy integration as the $default route -const api = new HttpApi(stack, 'HttpApi'); - -new Route(api, 'allroutes', { - httpApi: api, - path: '/', - method: HttpMethod.ANY, - integration: new LambdaProxyIntegration({ - handler, - }), +new HttpApi(stack, 'HttpApi', { + targetHandler: handler, }); +// new Route(api, 'allroutes', { +// httpApi: api, +// path: '/', +// method: HttpMethod.ANY, +// integration: new LambdaProxyIntegration({ +// handler, +// }), +// }); + +// new Stage(api, 'mystage', { +// api, +// }); + // api.addRoutes('/books/reviews', 'GetBookReviewRoute', { // methods: [HttpMethod.GET], // integration: new LambdaProxyIntegration(stack, 'getBookReviewInteg', { From 5f44349bf66225d8466583a050885a7ead4bcf39 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 28 Apr 2020 17:23:13 +0100 Subject: [PATCH 29/42] default integration --- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 31 +++++++------ .../aws-apigatewayv2/lib/integration.ts | 7 +-- .../lib/integrations/lambda.ts | 4 +- .../@aws-cdk/aws-apigatewayv2/lib/route.ts | 45 +++++++++++-------- .../aws-apigatewayv2/test/integ.api.ts | 7 +-- 5 files changed, 48 insertions(+), 46 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index ce366c91ee8ca..1ca2145558a72 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -1,7 +1,8 @@ import { Construct, IResource, Resource } from '@aws-cdk/core'; import { CfnApi, CfnApiProps } from './apigatewayv2.generated'; +import { IRouteIntegration } from './integration'; +import { Route, RouteKey } from './route'; import { Stage, StageName } from './stage'; -// import { AddRoutesOptions, HttpMethod, Route } from './route'; /** * Represents an HTTP API @@ -24,12 +25,11 @@ export interface HttpApiProps { */ readonly apiName?: string; - // /** - // * target lambda function of lambda proxy integration for the $default route - // * - // * @default - None. Specify either `targetHandler` or `targetUrl` - // */ - // readonly targetHandler?: lambda.IFunction; + /** + * An integration that will be configured on the catch-all route ($default). + * @default - none + */ + readonly defaultIntegration?: IRouteIntegration; /** * Whether a default stage and deployment should be automatically created. @@ -69,21 +69,20 @@ export class HttpApi extends Resource implements IHttpApi { const resource = new CfnApi(this, 'Resource', apiProps); this.httpApiId = resource.ref; + if (props?.defaultIntegration) { + new Route(this, 'DefaultRoute', { + httpApi: this, + routeKey: RouteKey.DEFAULT, + integration: props.defaultIntegration, + }); + } + if (props?.createDefaultStage === undefined || props.createDefaultStage === true) { this.defaultStage = new Stage(this, 'DefaultStage', { httpApi: this, stageName: StageName.DEFAULT, }); } - - // if (props?.targetHandler) { - // const desc = `${this.node.uniqueId}.'ANY'`; - // props.targetHandler.addPermission(`ApiPermission.${desc}`, { - // scope, - // principal: new ServicePrincipal('apigateway.amazonaws.com'), - // sourceArn: `arn:${Stack.of(this).partition}:execute-api:${Stack.of(this).region}:${Stack.of(this).account}:${this.httpApiId}/*/*`, - // } ); - // } } /** diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts index 0439f953d0041..6fc9766294e53 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts @@ -1,7 +1,7 @@ import { Construct, IResource, Resource } from '@aws-cdk/core'; import { IHttpApi } from './api'; import { CfnIntegration } from './apigatewayv2.generated'; -import { HttpMethod, Route } from './route'; +import { Route } from './route'; /** * Represents an integration to a HTTP API Route. @@ -38,10 +38,6 @@ export interface IntegrationProps { * integration URI */ readonly integrationUri: string; - /** - * integration method - */ - readonly integrationMethod: HttpMethod; } /** @@ -66,7 +62,6 @@ export class Integration extends Resource implements IResource { const integ = new CfnIntegration(this, 'Resource', { apiId: props.httpApi.httpApiId, integrationType: props.integrationType, - integrationMethod: props.integrationMethod, integrationUri: props.integrationUri, payloadFormatVersion: '1.0', }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts index cb3b63ac4542d..1d01d491f27c1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts @@ -23,7 +23,6 @@ export interface LambdaProxyIntegrationProps { /** * The Lambda Proxy integration resource for HTTP API - * @resource AWS::ApiGatewayV2::Integration */ export class LambdaProxyIntegration implements IRouteIntegration { @@ -31,13 +30,12 @@ export class LambdaProxyIntegration implements IRouteIntegration { } public bind(route: Route): RouteIntegrationConfig { - // create permission this.props.handler.addPermission(`${route.node.uniqueId}-Permission`, { principal: new ServicePrincipal('apigateway.amazonaws.com'), sourceArn: Stack.of(route).formatArn({ service: 'execute-api', resource: route.httpApi.httpApiId, - resourceName: `*/*${route.path}`, + resourceName: `*/*${route.path ?? ''}`, // empty string in the case of the catch-all route $default }), }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts index 2dcc0788c08f4..01c835308bab5 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -36,6 +36,28 @@ export enum HttpMethod { PUT = 'PUT', } +/** + * The key + */ +export class RouteKey { + public static readonly DEFAULT = new RouteKey('$default'); + + public static with(path: string, method?: HttpMethod) { + if (path !== '/' && (!path.startsWith('/') || path.endsWith('/'))) { + throw new Error('path must always start with a "/" and not end with a "/"'); + } + return new RouteKey(`${method ?? 'ANY'} ${path}`, path); + } + + public readonly key: string; + public readonly path?: string; + + private constructor(key: string, path?: string) { + this.key = key; + this.path = path; + } +} + /** * Route properties */ @@ -46,15 +68,9 @@ export interface RouteProps { readonly httpApi: IHttpApi; /** - * HTTP method of this route - * @default HttpMethod.ANY + * The key to this route. This is a combination of an HTTP method and an HTTP path. */ - readonly method?: HttpMethod; - - /** - * full http path of this route - */ - readonly path: string; + readonly routeKey: RouteKey; /** * Integration @@ -94,26 +110,19 @@ export class Route extends Resource implements IRoute { public readonly routeId: string; public readonly httpApi: IHttpApi; - public readonly method: HttpMethod; - public readonly path: string; + public readonly path: string | undefined; constructor(scope: Construct, id: string, props: RouteProps) { super(scope, id); - if (props.path !== '/' && (!props.path.startsWith('/') || props.path.endsWith('/'))) { - throw new Error('path must always start with a "/" and not end with a "/"'); - } - this.httpApi = props.httpApi; - this.method = props.method ?? HttpMethod.ANY; - this.path = props.path; + this.path = props.routeKey.path; let integration: Integration | undefined; if (props.integration) { const config = props.integration.bind(this); integration = new Integration(this, `${this.node.id}-Integration`, { httpApi: props.httpApi, - integrationMethod: this.method, integrationType: config.type, integrationUri: config.uri, }); @@ -121,7 +130,7 @@ export class Route extends Resource implements IRoute { const routeProps: CfnRouteProps = { apiId: props.httpApi.httpApiId, - routeKey: `${this.method} ${this.path}`, + routeKey: props.routeKey.key, target: integration ? `integrations/${integration.integrationId}` : undefined, }; diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index 106dc88bd7998..959077ce8b1f8 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -1,7 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; -// import { HttpApi, HttpMethod, LambdaProxyIntegration, Route, Stage } from '../lib'; -import { HttpApi } from '../lib'; +import { HttpApi, LambdaProxyIntegration } from '../lib'; const app = new App(); @@ -24,7 +23,9 @@ def handler(event, context): // create a basic HTTP API with http proxy integration as the $default route new HttpApi(stack, 'HttpApi', { - targetHandler: handler, + defaultIntegration: new LambdaProxyIntegration({ + handler, + }), }); // new Route(api, 'allroutes', { From 9e4deb8bba0c6f0f23fb964c1c8896440658ec78 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 29 Apr 2020 09:08:26 +0100 Subject: [PATCH 30/42] docs updated & tweaks --- packages/@aws-cdk/aws-apigatewayv2/lib/api.ts | 14 ++++- .../aws-apigatewayv2/lib/integration.ts | 20 ++++++- .../lib/integrations/lambda.ts | 2 +- .../@aws-cdk/aws-apigatewayv2/lib/route.ts | 57 ++++++++++++------- .../@aws-cdk/aws-apigatewayv2/lib/stage.ts | 42 +++++++++++++- 5 files changed, 108 insertions(+), 27 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts index 1ca2145558a72..abc609e2977b1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts @@ -2,7 +2,7 @@ import { Construct, IResource, Resource } from '@aws-cdk/core'; import { CfnApi, CfnApiProps } from './apigatewayv2.generated'; import { IRouteIntegration } from './integration'; import { Route, RouteKey } from './route'; -import { Stage, StageName } from './stage'; +import { Stage, StageName, StageOptions } from './stage'; /** * Represents an HTTP API @@ -40,7 +40,6 @@ export interface HttpApiProps { /** * Create a new API Gateway HTTP API endpoint. - * * @resource AWS::ApiGatewayV2::Api */ export class HttpApi extends Resource implements IHttpApi { @@ -81,6 +80,7 @@ export class HttpApi extends Resource implements IHttpApi { this.defaultStage = new Stage(this, 'DefaultStage', { httpApi: this, stageName: StageName.DEFAULT, + autoDeploy: true, }); } } @@ -93,6 +93,16 @@ export class HttpApi extends Resource implements IHttpApi { return this.defaultStage ? this.defaultStage.url : undefined; } + /** + * Add a new stage. + */ + public addStage(id: string, options: StageOptions): Stage { + return new Stage(this, id, { + httpApi: this, + ...options, + }); + } + /** * add routes on this API */ diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts index 6fc9766294e53..88204ee95a92b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts @@ -41,11 +41,12 @@ export interface IntegrationProps { } /** - * The integration resource for HTTP API + * The integration for an API route. + * @resource AWS::ApiGatewayV2::Integration */ export class Integration extends Resource implements IResource { /** - * import from integration ID + * Import an existing integration using integration id */ public static fromIntegrationId(scope: Construct, id: string, integrationId: string): IIntegration { class Import extends Resource implements IIntegration { @@ -69,12 +70,27 @@ export class Integration extends Resource implements IResource { } } +/** + * The interface that various route integration classes will inherit. + */ export interface IRouteIntegration { + /** + * Bind this integration to the route. + */ bind(route: Route): RouteIntegrationConfig; } +/** + * Config returned back as a result of the bind. + */ export interface RouteIntegrationConfig { + /** + * Integration type. + */ readonly type: IntegrationType; + /** + * Integration URI + */ readonly uri: string; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts index 1d01d491f27c1..c0fbd4c78d8f0 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts @@ -9,7 +9,7 @@ import { Route } from '../route'; */ export interface LambdaProxyIntegrationProps { /** - * The Lambda function for this integration. + * The handler for this integration. */ readonly handler: IFunction diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts index 01c835308bab5..90dc169e6bc8b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts @@ -37,11 +37,19 @@ export enum HttpMethod { } /** - * The key + * HTTP route in APIGateway is a combination of the HTTP method and the path component. + * This class models that combination. */ export class RouteKey { + /** + * The catch-all route of the API, i.e., when no other routes match + */ public static readonly DEFAULT = new RouteKey('$default'); + /** + * Create a route key with the combination of the path and the method. + * @param method default is 'ANY' + */ public static with(path: string, method?: HttpMethod) { if (path !== '/' && (!path.startsWith('/') || path.endsWith('/'))) { throw new Error('path must always start with a "/" and not end with a "/"'); @@ -49,7 +57,12 @@ export class RouteKey { return new RouteKey(`${method ?? 'ANY'} ${path}`, path); } + /** The key to the RouteKey as recognized by APIGateway */ public readonly key: string; + /** + * The path part of this RouteKey. + * Returns `undefined` when `RouteKey.DEFAULT` is used. + */ public readonly path?: string; private constructor(key: string, path?: string) { @@ -59,7 +72,7 @@ export class RouteKey { } /** - * Route properties + * Properties to initialize a new Route */ export interface RouteProps { /** @@ -73,26 +86,26 @@ export interface RouteProps { readonly routeKey: RouteKey; /** - * Integration + * The integration to be configured on this route. */ readonly integration?: IRouteIntegration; } -/** - * Options for the Route with Integration resoruce - */ -export interface AddRoutesOptions { - /** - * HTTP methods - * @default HttpMethod.ANY - */ - readonly methods?: HttpMethod[]; - - /** - * The integration for this path - */ - readonly integration: Integration; -} +// /** +// * Options for the Route with Integration resoruce +// */ +// export interface AddRoutesOptions { +// /** +// * HTTP methods +// * @default HttpMethod.ANY +// */ +// readonly methods?: HttpMethod[]; + +// /** +// * The integration for this path +// */ +// readonly integration: Integration; +// } /** * Route class that creates the Route for API Gateway HTTP API @@ -109,7 +122,12 @@ export class Route extends Resource implements IRoute { } public readonly routeId: string; + /** The HTTP API on which this route is configured. */ public readonly httpApi: IHttpApi; + /** + * The path to which this Route is configured. + * This is `undefined` when using the catch-all route + */ public readonly path: string | undefined; constructor(scope: Construct, id: string, props: RouteProps) { @@ -138,9 +156,6 @@ export class Route extends Resource implements IRoute { this.routeId = route.ref; } - /** - * create child routes - */ // public addRoutes(pathPart: string, id: string, options: AddRoutesOptions): Route[] { // const routes: Route[] = []; // const methods = options.methods ?? [ HttpMethod.ANY ]; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts index 9f34364ca7633..1274fb3858a62 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts @@ -2,13 +2,31 @@ import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; import { IHttpApi } from './api'; import { CfnStage } from './apigatewayv2.generated'; +/** + * Represents a APIGatewayV2 Stage for the HTTP API. + */ export interface IStage extends IResource { + /** + * The name of the stage; its primary identifier. + */ readonly stageName: string; } +/** + * A type to be used while specifying stage name for a new Stage. + */ export class StageName { + /** + * The default stage of the HTTP API. + * This stage will have the URL at the root of the API endpoint. + */ public static readonly DEFAULT = new StageName('$default'); + /** + * The name of this stage. + * This will be used to both the primary identifier of this stage, as well as, + * the path component of the API endpoint - 'https://.execute-api..amazonaws.com/'. + */ public static of(stageName: string) { return new StageName(stageName); } @@ -17,16 +35,38 @@ export class StageName { } } +/** + * Options to create a new stage. + */ export interface StageOptions { + /** + * The name of the stage. See `StageName` class for more details. + * @default StageName.DEFAULT + */ readonly stageName?: StageName; + + /** + * Whether updates to an API automatically trigger a new deployment. + * @default false + */ readonly autoDeploy?: boolean; } +/** + * Properties to initialize an instance of `Stage`. + */ export interface StageProps extends StageOptions { readonly httpApi: IHttpApi; } +/** + * Represents a stage where an instance of the API is deployed. + * @resource AWS::ApiGatewayV2::Stage + */ export class Stage extends Resource implements IStage { + /** + * Import an existing stage into this CDK app. + */ public static fromStageName(scope: Construct, id: string, stageName: string): IStage { class Import extends Resource implements IStage { public readonly stageName = stageName; @@ -45,7 +85,7 @@ export class Stage extends Resource implements IStage { const resource = new CfnStage(this, 'Resource', { apiId: props.httpApi.httpApiId, stageName: this.physicalName, - autoDeploy: props.autoDeploy ?? true, + autoDeploy: props.autoDeploy, }); this.stageName = resource.ref; From 35d63416524a699ca8c20f2bb32fe8ac70c4174f Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 29 Apr 2020 10:03:34 +0100 Subject: [PATCH 31/42] the massive restructure --- .../aws-apigatewayv2/lib/common/index.ts | 3 + .../lib/common/integration.ts | 12 ++ .../aws-apigatewayv2/lib/common/route.ts | 12 ++ .../aws-apigatewayv2/lib/common/stage.ts | 59 ++++++++++ .../aws-apigatewayv2/lib/{ => http}/api.ts | 23 ++-- .../aws-apigatewayv2/lib/http/index.ts | 5 + .../lib/{ => http}/integration.ts | 50 +++++---- .../lib/{ => http}/integrations/http.ts | 4 +- .../lib/{ => http}/integrations/index.ts | 0 .../lib/{ => http}/integrations/lambda.ts | 10 +- .../aws-apigatewayv2/lib/{ => http}/route.ts | 47 ++++---- .../aws-apigatewayv2/lib/http/stage.ts | 63 +++++++++++ .../@aws-cdk/aws-apigatewayv2/lib/index.ts | 8 +- .../@aws-cdk/aws-apigatewayv2/lib/stage.ts | 103 ------------------ 14 files changed, 222 insertions(+), 177 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/common/index.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/common/integration.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/common/route.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/common/stage.ts rename packages/@aws-cdk/aws-apigatewayv2/lib/{ => http}/api.ts (80%) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts rename packages/@aws-cdk/aws-apigatewayv2/lib/{ => http}/integration.ts (53%) rename packages/@aws-cdk/aws-apigatewayv2/lib/{ => http}/integrations/http.ts (93%) rename packages/@aws-cdk/aws-apigatewayv2/lib/{ => http}/integrations/index.ts (100%) rename packages/@aws-cdk/aws-apigatewayv2/lib/{ => http}/integrations/lambda.ts (78%) rename packages/@aws-cdk/aws-apigatewayv2/lib/{ => http}/route.ts (78%) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts delete mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/index.ts new file mode 100644 index 0000000000000..5995c40125978 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/index.ts @@ -0,0 +1,3 @@ +export * from './integration'; +export * from './route'; +export * from './stage'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/integration.ts new file mode 100644 index 0000000000000..7255607639468 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/integration.ts @@ -0,0 +1,12 @@ +import { IResource } from '@aws-cdk/core'; + +/** + * Represents an integration to an API Route. + */ +export interface IIntegration extends IResource { + /** + * Id of the integration. + * @attribute + */ + readonly integrationId: string; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/route.ts new file mode 100644 index 0000000000000..7419328256220 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/route.ts @@ -0,0 +1,12 @@ +import { IResource } from '@aws-cdk/core'; + +/** + * Represents a route. + */ +export interface IRoute extends IResource { + /** + * Id of the Route + * @attribute + */ + readonly routeId: string; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/stage.ts new file mode 100644 index 0000000000000..db7b87606c969 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/stage.ts @@ -0,0 +1,59 @@ +import { IResource } from '@aws-cdk/core'; + +/** + * Represents a Stage. + */ +export interface IStage extends IResource { + /** + * The name of the stage; its primary identifier. + * @attribute + */ + readonly stageName: string; +} + +/** + * A type to be used while specifying the stage name for a new Stage. + */ +export class StageName { + /** + * The default stage of the API. + * This stage will have the URL at the root of the API endpoint. + */ + public static readonly DEFAULT = new StageName('$default'); + + /** + * The name of this stage. + * This will be used to both the primary identifier of this stage, as well as, + * the path component of the API endpoint - 'https://.execute-api..amazonaws.com/'. + */ + public static of(stageName: string) { + return new StageName(stageName); + } + + /** + * The name of this stage as a string, as recognized by the API Gateway service. + */ + public readonly stageName: string; + + private constructor(stageName: string) { + this.stageName = stageName; + } +} + +/** + * Options required to create a new stage. + * Options that are common between HTTP and Websocket APIs. + */ +export interface CommonStageOptions { + /** + * The name of the stage. See `StageName` class for more details. + * @default StageName.DEFAULT + */ + readonly stageName?: StageName; + + /** + * Whether updates to an API automatically trigger a new deployment. + * @default false + */ + readonly autoDeploy?: boolean; +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts similarity index 80% rename from packages/@aws-cdk/aws-apigatewayv2/lib/api.ts rename to packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index abc609e2977b1..43b7fe68bf724 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -1,8 +1,9 @@ import { Construct, IResource, Resource } from '@aws-cdk/core'; -import { CfnApi, CfnApiProps } from './apigatewayv2.generated'; -import { IRouteIntegration } from './integration'; -import { Route, RouteKey } from './route'; -import { Stage, StageName, StageOptions } from './stage'; +import { CfnApi, CfnApiProps } from '../apigatewayv2.generated'; +import { StageName } from '../common'; +import { IHttpRouteIntegration } from './integration'; +import { HttpRoute, HttpRouteKey } from './route'; +import { HttpStage, HttpStageOptions } from './stage'; /** * Represents an HTTP API @@ -29,7 +30,7 @@ export interface HttpApiProps { * An integration that will be configured on the catch-all route ($default). * @default - none */ - readonly defaultIntegration?: IRouteIntegration; + readonly defaultIntegration?: IHttpRouteIntegration; /** * Whether a default stage and deployment should be automatically created. @@ -54,7 +55,7 @@ export class HttpApi extends Resource implements IHttpApi { } public readonly httpApiId: string; - private readonly defaultStage: Stage | undefined; + private readonly defaultStage: HttpStage | undefined; constructor(scope: Construct, id: string, props?: HttpApiProps) { super(scope, id); @@ -69,15 +70,15 @@ export class HttpApi extends Resource implements IHttpApi { this.httpApiId = resource.ref; if (props?.defaultIntegration) { - new Route(this, 'DefaultRoute', { + new HttpRoute(this, 'DefaultRoute', { httpApi: this, - routeKey: RouteKey.DEFAULT, + routeKey: HttpRouteKey.DEFAULT, integration: props.defaultIntegration, }); } if (props?.createDefaultStage === undefined || props.createDefaultStage === true) { - this.defaultStage = new Stage(this, 'DefaultStage', { + this.defaultStage = new HttpStage(this, 'DefaultStage', { httpApi: this, stageName: StageName.DEFAULT, autoDeploy: true, @@ -96,8 +97,8 @@ export class HttpApi extends Resource implements IHttpApi { /** * Add a new stage. */ - public addStage(id: string, options: StageOptions): Stage { - return new Stage(this, id, { + public addStage(id: string, options: HttpStageOptions): HttpStage { + return new HttpStage(this, id, { httpApi: this, ...options, }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts new file mode 100644 index 0000000000000..4fbb1c4e76f6a --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts @@ -0,0 +1,5 @@ +export * from './api'; +export * from './route'; +export * from './integration'; +export * from './integrations'; +export * from './stage'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts similarity index 53% rename from packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts rename to packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index 88204ee95a92b..565255d96a38b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -1,31 +1,35 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { Construct, Resource } from '@aws-cdk/core'; +import { CfnIntegration } from '../apigatewayv2.generated'; +import { IIntegration } from '../common'; import { IHttpApi } from './api'; -import { CfnIntegration } from './apigatewayv2.generated'; -import { Route } from './route'; +import { HttpRoute } from './route'; /** - * Represents an integration to a HTTP API Route. + * Supported integration types */ -export interface IIntegration extends IResource { +export enum HttpIntegrationType { /** - * The resource ID of the integration - * @attribute + * Integration type is a Lambda proxy + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + */ + LAMBDA_PROXY = 'AWS_PROXY', + /** + * Integration type is an HTTP proxy + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html */ - readonly integrationId: string; -} - -/** - * Supported integration types - */ -export enum IntegrationType { - AWS_PROXY = 'AWS_PROXY', HTTP_PROXY = 'HTTP_PROXY', + + /** + * Private integrations. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-private.html + */ + PRIVATE = 'HTTP_PROXY', } /** * The integration properties */ -export interface IntegrationProps { +export interface HttpIntegrationProps { /** * API ID */ @@ -33,7 +37,7 @@ export interface IntegrationProps { /** * integration type */ - readonly integrationType: IntegrationType; + readonly integrationType: HttpIntegrationType; /** * integration URI */ @@ -44,7 +48,7 @@ export interface IntegrationProps { * The integration for an API route. * @resource AWS::ApiGatewayV2::Integration */ -export class Integration extends Resource implements IResource { +export class HttpIntegration extends Resource implements IIntegration { /** * Import an existing integration using integration id */ @@ -58,7 +62,7 @@ export class Integration extends Resource implements IResource { public readonly integrationId: string; - constructor(scope: Construct, id: string, props: IntegrationProps) { + constructor(scope: Construct, id: string, props: HttpIntegrationProps) { super(scope, id); const integ = new CfnIntegration(this, 'Resource', { apiId: props.httpApi.httpApiId, @@ -73,21 +77,21 @@ export class Integration extends Resource implements IResource { /** * The interface that various route integration classes will inherit. */ -export interface IRouteIntegration { +export interface IHttpRouteIntegration { /** * Bind this integration to the route. */ - bind(route: Route): RouteIntegrationConfig; + bind(route: HttpRoute): HttpRouteIntegrationConfig; } /** * Config returned back as a result of the bind. */ -export interface RouteIntegrationConfig { +export interface HttpRouteIntegrationConfig { /** * Integration type. */ - readonly type: IntegrationType; + readonly type: HttpIntegrationType; /** * Integration URI diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/http.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts similarity index 93% rename from packages/@aws-cdk/aws-apigatewayv2/lib/integrations/http.ts rename to packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts index ad8b1afafb527..fa99d1c2a8b2f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/http.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts @@ -1,7 +1,7 @@ import { Construct, Resource } from '@aws-cdk/core'; +import { CfnIntegration } from '../../apigatewayv2.generated'; +import { IIntegration } from '../../common'; import { IHttpApi } from '../api'; -import { CfnIntegration } from '../apigatewayv2.generated'; -import { IIntegration } from '../integration'; import { HttpMethod } from '../route'; /** diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/index.ts similarity index 100% rename from packages/@aws-cdk/aws-apigatewayv2/lib/integrations/index.ts rename to packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/index.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts similarity index 78% rename from packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts rename to packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts index c0fbd4c78d8f0..6f9fe9478cd13 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts @@ -1,8 +1,8 @@ import { ServicePrincipal } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; import { Stack } from '@aws-cdk/core'; -import { IntegrationType, IRouteIntegration, RouteIntegrationConfig } from '../integration'; -import { Route } from '../route'; +import { HttpIntegrationType, HttpRouteIntegrationConfig, IHttpRouteIntegration } from '../integration'; +import { HttpRoute } from '../route'; /** * Lambda Proxy integration properties @@ -24,12 +24,12 @@ export interface LambdaProxyIntegrationProps { /** * The Lambda Proxy integration resource for HTTP API */ -export class LambdaProxyIntegration implements IRouteIntegration { +export class LambdaProxyIntegration implements IHttpRouteIntegration { constructor(private readonly props: LambdaProxyIntegrationProps) { } - public bind(route: Route): RouteIntegrationConfig { + public bind(route: HttpRoute): HttpRouteIntegrationConfig { this.props.handler.addPermission(`${route.node.uniqueId}-Permission`, { principal: new ServicePrincipal('apigateway.amazonaws.com'), sourceArn: Stack.of(route).formatArn({ @@ -40,7 +40,7 @@ export class LambdaProxyIntegration implements IRouteIntegration { }); return { - type: IntegrationType.AWS_PROXY, + type: HttpIntegrationType.LAMBDA_PROXY, uri: this.props.handler.functionArn, }; } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts similarity index 78% rename from packages/@aws-cdk/aws-apigatewayv2/lib/route.ts rename to packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts index 90dc169e6bc8b..ae4df68ba6fde 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts @@ -1,21 +1,11 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { Construct, Resource } from '@aws-cdk/core'; +import { CfnRoute, CfnRouteProps } from '../apigatewayv2.generated'; +import { IRoute } from '../common'; import { IHttpApi } from './api'; -import { CfnRoute, CfnRouteProps } from './apigatewayv2.generated'; -import { Integration, IRouteIntegration } from './integration'; +import { HttpIntegration, IHttpRouteIntegration } from './integration'; /** - * the interface of the Route of API Gateway HTTP API - */ -export interface IRoute extends IResource { - /** - * ID of the Route - * @attribute - */ - readonly routeId: string; -} - -/** - * all HTTP methods + * Supported HTTP methods */ export enum HttpMethod { /** HTTP ANY */ @@ -40,11 +30,11 @@ export enum HttpMethod { * HTTP route in APIGateway is a combination of the HTTP method and the path component. * This class models that combination. */ -export class RouteKey { +export class HttpRouteKey { /** * The catch-all route of the API, i.e., when no other routes match */ - public static readonly DEFAULT = new RouteKey('$default'); + public static readonly DEFAULT = new HttpRouteKey('$default'); /** * Create a route key with the combination of the path and the method. @@ -54,10 +44,12 @@ export class RouteKey { if (path !== '/' && (!path.startsWith('/') || path.endsWith('/'))) { throw new Error('path must always start with a "/" and not end with a "/"'); } - return new RouteKey(`${method ?? 'ANY'} ${path}`, path); + return new HttpRouteKey(`${method ?? 'ANY'} ${path}`, path); } - /** The key to the RouteKey as recognized by APIGateway */ + /** + * The key to the RouteKey as recognized by APIGateway + */ public readonly key: string; /** * The path part of this RouteKey. @@ -74,7 +66,7 @@ export class RouteKey { /** * Properties to initialize a new Route */ -export interface RouteProps { +export interface HttpRouteProps { /** * the API the route is associated with */ @@ -83,12 +75,12 @@ export interface RouteProps { /** * The key to this route. This is a combination of an HTTP method and an HTTP path. */ - readonly routeKey: RouteKey; + readonly routeKey: HttpRouteKey; /** * The integration to be configured on this route. */ - readonly integration?: IRouteIntegration; + readonly integration: IHttpRouteIntegration; } // /** @@ -109,10 +101,11 @@ export interface RouteProps { /** * Route class that creates the Route for API Gateway HTTP API + * @resource AWS::ApiGatewayV2::Route */ -export class Route extends Resource implements IRoute { +export class HttpRoute extends Resource implements IRoute { /** - * import from route id + * Import from route id */ public static fromRouteId(scope: Construct, id: string, routeId: string): IRoute { class Import extends Resource implements IRoute { @@ -130,16 +123,16 @@ export class Route extends Resource implements IRoute { */ public readonly path: string | undefined; - constructor(scope: Construct, id: string, props: RouteProps) { + constructor(scope: Construct, id: string, props: HttpRouteProps) { super(scope, id); this.httpApi = props.httpApi; this.path = props.routeKey.path; - let integration: Integration | undefined; + let integration: HttpIntegration | undefined; if (props.integration) { const config = props.integration.bind(this); - integration = new Integration(this, `${this.node.id}-Integration`, { + integration = new HttpIntegration(this, `${this.node.id}-Integration`, { httpApi: props.httpApi, integrationType: config.type, integrationUri: config.uri, diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts new file mode 100644 index 0000000000000..1681c2051dddf --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts @@ -0,0 +1,63 @@ +import { Construct, Resource, Stack } from '@aws-cdk/core'; +import { CfnStage } from '../apigatewayv2.generated'; +import { CommonStageOptions, IStage, StageName } from '../common'; +import { IHttpApi } from './api'; + +/** + * Options to create a new stage for an HTTP API. + */ +export interface HttpStageOptions extends CommonStageOptions { +} + +/** + * Properties to initialize an instance of `HttpStage`. + */ +export interface HttpStageProps extends HttpStageOptions { + /** + * The HTTP API to which this stage is associated. + */ + readonly httpApi: IHttpApi; +} + +/** + * Represents a stage where an instance of the API is deployed. + * @resource AWS::ApiGatewayV2::Stage + */ +export class HttpStage extends Resource implements IStage { + /** + * Import an existing stage into this CDK app. + */ + public static fromStageName(scope: Construct, id: string, stageName: string): IStage { + class Import extends Resource implements IStage { + public readonly stageName = stageName; + } + return new Import(scope, id); + } + + public readonly stageName: string; + private httpApi: IHttpApi; + + constructor(scope: Construct, id: string, props: HttpStageProps) { + super(scope, id, { + physicalName: props.stageName ? props.stageName.stageName : StageName.DEFAULT.stageName, + }); + + const resource = new CfnStage(this, 'Resource', { + apiId: props.httpApi.httpApiId, + stageName: this.physicalName, + autoDeploy: props.autoDeploy, + }); + + this.stageName = resource.ref; + this.httpApi = props.httpApi; + } + + /** + * The URL to this stage. + */ + public get url() { + const s = Stack.of(this); + const urlPath = this.stageName === StageName.DEFAULT.stageName ? '' : this.stageName; + return `https://${this.httpApi.httpApiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts index f8b6279513d69..31ea86b4a91c2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts @@ -1,7 +1,3 @@ -// AWS::APIGatewayv2 CloudFormation Resources: export * from './apigatewayv2.generated'; -export * from './api'; -export * from './route'; -export * from './integration'; -export * from './integrations'; -export * from './stage'; \ No newline at end of file +export * from './common'; +export * from './http'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts deleted file mode 100644 index 1274fb3858a62..0000000000000 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/stage.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; -import { IHttpApi } from './api'; -import { CfnStage } from './apigatewayv2.generated'; - -/** - * Represents a APIGatewayV2 Stage for the HTTP API. - */ -export interface IStage extends IResource { - /** - * The name of the stage; its primary identifier. - */ - readonly stageName: string; -} - -/** - * A type to be used while specifying stage name for a new Stage. - */ -export class StageName { - /** - * The default stage of the HTTP API. - * This stage will have the URL at the root of the API endpoint. - */ - public static readonly DEFAULT = new StageName('$default'); - - /** - * The name of this stage. - * This will be used to both the primary identifier of this stage, as well as, - * the path component of the API endpoint - 'https://.execute-api..amazonaws.com/'. - */ - public static of(stageName: string) { - return new StageName(stageName); - } - - private constructor(public readonly stageName: string) { - } -} - -/** - * Options to create a new stage. - */ -export interface StageOptions { - /** - * The name of the stage. See `StageName` class for more details. - * @default StageName.DEFAULT - */ - readonly stageName?: StageName; - - /** - * Whether updates to an API automatically trigger a new deployment. - * @default false - */ - readonly autoDeploy?: boolean; -} - -/** - * Properties to initialize an instance of `Stage`. - */ -export interface StageProps extends StageOptions { - readonly httpApi: IHttpApi; -} - -/** - * Represents a stage where an instance of the API is deployed. - * @resource AWS::ApiGatewayV2::Stage - */ -export class Stage extends Resource implements IStage { - /** - * Import an existing stage into this CDK app. - */ - public static fromStageName(scope: Construct, id: string, stageName: string): IStage { - class Import extends Resource implements IStage { - public readonly stageName = stageName; - } - return new Import(scope, id); - } - - public readonly stageName: string; - private httpApi: IHttpApi; - - constructor(scope: Construct, id: string, props: StageProps) { - super(scope, id, { - physicalName: props.stageName ? props.stageName.stageName : StageName.DEFAULT.stageName, - }); - - const resource = new CfnStage(this, 'Resource', { - apiId: props.httpApi.httpApiId, - stageName: this.physicalName, - autoDeploy: props.autoDeploy, - }); - - this.stageName = resource.ref; - this.httpApi = props.httpApi; - } - - /** - * The URL to this stage. - */ - public get url() { - const s = Stack.of(this); - const urlPath = this.stageName === StageName.DEFAULT.stageName ? '' : this.stageName; - return `https://${this.httpApi.httpApiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`; - } -} \ No newline at end of file From cce22c11907e2a569f656cdbca5951b962fdef12 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 29 Apr 2020 11:47:27 +0100 Subject: [PATCH 32/42] Added IHttpRoute and fixed up HttpProxyIntegration --- .../aws-apigatewayv2/lib/http/integration.ts | 4 +- .../lib/http/integrations/http.ts | 56 +++++-------------- .../lib/http/integrations/lambda.ts | 4 +- .../aws-apigatewayv2/lib/http/route.ts | 34 +++++------ .../@aws-cdk/aws-apigatewayv2/package.json | 8 +++ 5 files changed, 42 insertions(+), 64 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index 565255d96a38b..dc8dd8567df28 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -2,7 +2,7 @@ import { Construct, Resource } from '@aws-cdk/core'; import { CfnIntegration } from '../apigatewayv2.generated'; import { IIntegration } from '../common'; import { IHttpApi } from './api'; -import { HttpRoute } from './route'; +import { IHttpRoute } from './route'; /** * Supported integration types @@ -81,7 +81,7 @@ export interface IHttpRouteIntegration { /** * Bind this integration to the route. */ - bind(route: HttpRoute): HttpRouteIntegrationConfig; + bind(route: IHttpRoute): HttpRouteIntegrationConfig; } /** diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts index fa99d1c2a8b2f..6b7f6c9741716 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts @@ -1,57 +1,27 @@ -import { Construct, Resource } from '@aws-cdk/core'; -import { CfnIntegration } from '../../apigatewayv2.generated'; -import { IIntegration } from '../../common'; -import { IHttpApi } from '../api'; -import { HttpMethod } from '../route'; +import { HttpIntegrationType, HttpRouteIntegrationConfig, IHttpRouteIntegration } from '../integration'; +import { IHttpRoute } from '../route'; /** - * HTTP Proxy integration properties + * Properties to initialize a new `HttpProxyIntegration`. */ -export interface HttpProxyIntegrationOptions { - /** - * integration name - * @default - the resource id - */ - readonly integrationName?: string - +export interface HttpProxyIntegrationProps { /** * The full-qualified HTTP URL for the HTTP integration */ - readonly targetUrl: string - /** - * Specifies the integration's HTTP method type. - * @default - ANY - */ - readonly integrationMethod?: HttpMethod -} - -export interface HttpProxyIntegrationProps extends HttpProxyIntegrationOptions { - /** - * The API identifier - * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-integration.html#cfn-apigatewayv2-integration-apiid - */ - readonly api: IHttpApi; + readonly url: string } /** * The HTTP Proxy integration resource for HTTP API - * - * @resource AWS::ApiGatewayV2::Integration */ -export class HttpProxyIntegration extends Resource implements IIntegration { - public readonly integrationId: string; - - constructor(scope: Construct, id: string, props: HttpProxyIntegrationProps) { - super(scope, id); +export class HttpProxyIntegration implements IHttpRouteIntegration { + constructor(private readonly props: HttpProxyIntegrationProps) { + } - // create integration - const integ = new CfnIntegration(this, 'Resource', { - apiId: props.api.httpApiId, - integrationType: 'HTTP_PROXY', - integrationMethod: props.integrationMethod ?? HttpMethod.ANY, - integrationUri: props.targetUrl, - payloadFormatVersion: '1.0', - }); - this.integrationId = integ.ref; + public bind(_: IHttpRoute): HttpRouteIntegrationConfig { + return { + type: HttpIntegrationType.HTTP_PROXY, + uri: this.props.url, + }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts index 6f9fe9478cd13..a40098565f212 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts @@ -2,7 +2,7 @@ import { ServicePrincipal } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; import { Stack } from '@aws-cdk/core'; import { HttpIntegrationType, HttpRouteIntegrationConfig, IHttpRouteIntegration } from '../integration'; -import { HttpRoute } from '../route'; +import { IHttpRoute } from '../route'; /** * Lambda Proxy integration properties @@ -29,7 +29,7 @@ export class LambdaProxyIntegration implements IHttpRouteIntegration { constructor(private readonly props: LambdaProxyIntegrationProps) { } - public bind(route: HttpRoute): HttpRouteIntegrationConfig { + public bind(route: IHttpRoute): HttpRouteIntegrationConfig { this.props.handler.addPermission(`${route.node.uniqueId}-Permission`, { principal: new ServicePrincipal('apigateway.amazonaws.com'), sourceArn: Stack.of(route).formatArn({ diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts index ae4df68ba6fde..19fcfc18756b4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts @@ -4,6 +4,21 @@ import { IRoute } from '../common'; import { IHttpApi } from './api'; import { HttpIntegration, IHttpRouteIntegration } from './integration'; +/** + * Represents a Route for an HTTP API. + */ +export interface IHttpRoute extends IRoute { + /** + * The HTTP API associated with this route. + */ + readonly httpApi: IHttpApi; + + /** + * Returns the path component of this HTTP route, `undefined` if the path is the catch-all route. + */ + readonly path?: string; +} + /** * Supported HTTP methods */ @@ -103,25 +118,10 @@ export interface HttpRouteProps { * Route class that creates the Route for API Gateway HTTP API * @resource AWS::ApiGatewayV2::Route */ -export class HttpRoute extends Resource implements IRoute { - /** - * Import from route id - */ - public static fromRouteId(scope: Construct, id: string, routeId: string): IRoute { - class Import extends Resource implements IRoute { - public routeId = routeId; - } - return new Import(scope, id); - } - +export class HttpRoute extends Resource implements IHttpRoute { public readonly routeId: string; - /** The HTTP API on which this route is configured. */ public readonly httpApi: IHttpApi; - /** - * The path to which this Route is configured. - * This is `undefined` when using the catch-all route - */ - public readonly path: string | undefined; + public readonly path?: string; constructor(scope: Construct, id: string, props: HttpRouteProps) { super(scope, id); diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index ecf7f0e2aa65d..5319067f0d88c 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -101,6 +101,14 @@ "engines": { "node": ">= 10.12.0" }, + "awslint": { + "exclude": [ + "from-method:@aws-cdk/aws-apigatewayv2.HttpRoute", + "props-physical-name-type:@aws-cdk/aws-apigatewayv2.HttpStageProps.stageName", + "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpIntegrationProps", + "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpRouteProps" + ] + }, "stability": "experimental", "maturity": "cfn-only", "awscdkio": { From edba63c6c6de2ab694ae1fff8316543e3567f1a0 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 29 Apr 2020 15:20:01 +0100 Subject: [PATCH 33/42] http proxy now works --- .../aws-apigatewayv2/lib/http/integration.ts | 68 ++++++++++++++++--- .../lib/http/integrations/http.ts | 12 +++- .../lib/http/integrations/lambda.ts | 7 +- .../aws-apigatewayv2/lib/http/route.ts | 2 + .../aws-apigatewayv2/test/integ.api.ts | 19 +++++- 5 files changed, 92 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index dc8dd8567df28..20f8b90416735 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -2,7 +2,7 @@ import { Construct, Resource } from '@aws-cdk/core'; import { CfnIntegration } from '../apigatewayv2.generated'; import { IIntegration } from '../common'; import { IHttpApi } from './api'; -import { IHttpRoute } from './route'; +import { HttpMethod, IHttpRoute } from './route'; /** * Supported integration types @@ -18,12 +18,32 @@ export enum HttpIntegrationType { * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html */ HTTP_PROXY = 'HTTP_PROXY', +} + +/** + * Payload format version for lambda proxy integration + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + */ +export class PayloadFormatVersion { + /** Version 1.0 */ + public static readonly VERSION_1_0 = new PayloadFormatVersion('1.0'); + /** Version 2.0 */ + public static readonly VERSION_2_0 = new PayloadFormatVersion('2.0'); /** - * Private integrations. - * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-private.html + * A custom payload version. + * Typically used if there is a version number that the CDK doesn't support yet */ - PRIVATE = 'HTTP_PROXY', + public static custom(version: string) { + return new PayloadFormatVersion(version); + } + + /** version as a string */ + public readonly version: string; + + private constructor(version: string) { + this.version = version; + } } /** @@ -31,17 +51,34 @@ export enum HttpIntegrationType { */ export interface HttpIntegrationProps { /** - * API ID + * The HTTP API to which this integration should be bound. */ readonly httpApi: IHttpApi; + /** - * integration type + * Integration type */ readonly integrationType: HttpIntegrationType; + /** - * integration URI + * Integration URI. + * This will be the function ARN in the case of `HttpIntegrationType.LAMBDA_PROXY`, + * or HTTP URL in the case of `HttpIntegrationType.HTTP_PROXY`. */ readonly integrationUri: string; + + /** + * The HTTP method to use when calling the underlying HTTP proxy + * @default - none. required if the integration type is `HttpIntegrationType.HTTP_PROXY`. + */ + readonly method?: HttpMethod; + + /** + * The version of the payload format + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + * @default - defaults to latest in the case of HttpIntegrationType.LAMBDA_PROXY`, irrelevant otherwise. + */ + readonly payloadFormatVersion?: PayloadFormatVersion; } /** @@ -68,7 +105,8 @@ export class HttpIntegration extends Resource implements IIntegration { apiId: props.httpApi.httpApiId, integrationType: props.integrationType, integrationUri: props.integrationUri, - payloadFormatVersion: '1.0', + integrationMethod: props.method, + payloadFormatVersion: props.payloadFormatVersion?.version, }); this.integrationId = integ.ref; } @@ -97,4 +135,18 @@ export interface HttpRouteIntegrationConfig { * Integration URI */ readonly uri: string; + + /** + * The HTTP method that must be used to invoke the underlying proxy. + * Required for `HttpIntegrationType.HTTP_PROXY` + * @default - undefined + */ + readonly method?: HttpMethod; + + /** + * Payload format version in the case of lambda proxy integration + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + * @default - undefined + */ + readonly payloadFormatVersion: PayloadFormatVersion; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts index 6b7f6c9741716..b514b63f8056f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/http.ts @@ -1,5 +1,5 @@ -import { HttpIntegrationType, HttpRouteIntegrationConfig, IHttpRouteIntegration } from '../integration'; -import { IHttpRoute } from '../route'; +import { HttpIntegrationType, HttpRouteIntegrationConfig, IHttpRouteIntegration, PayloadFormatVersion } from '../integration'; +import { HttpMethod, IHttpRoute } from '../route'; /** * Properties to initialize a new `HttpProxyIntegration`. @@ -9,6 +9,12 @@ export interface HttpProxyIntegrationProps { * The full-qualified HTTP URL for the HTTP integration */ readonly url: string + + /** + * The HTTP method that must be used to invoke the underlying HTTP proxy. + * @default HttpMethod.ANY + */ + readonly method?: HttpMethod; } /** @@ -20,6 +26,8 @@ export class HttpProxyIntegration implements IHttpRouteIntegration { public bind(_: IHttpRoute): HttpRouteIntegrationConfig { return { + method: this.props.method ?? HttpMethod.ANY, + payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, // 1.0 is required and is the only supported format type: HttpIntegrationType.HTTP_PROXY, uri: this.props.url, }; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts index a40098565f212..155b671fa79c2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts @@ -1,7 +1,7 @@ import { ServicePrincipal } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; import { Stack } from '@aws-cdk/core'; -import { HttpIntegrationType, HttpRouteIntegrationConfig, IHttpRouteIntegration } from '../integration'; +import { HttpIntegrationType, HttpRouteIntegrationConfig, IHttpRouteIntegration, PayloadFormatVersion } from '../integration'; import { IHttpRoute } from '../route'; /** @@ -16,9 +16,9 @@ export interface LambdaProxyIntegrationProps { /** * Version of the payload sent to the lambda handler. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html - * @default '2.0' + * @default PayloadFormatVersion.VERSION_2_0 */ - readonly payloadFormatVersion?: string; + readonly payloadFormatVersion?: PayloadFormatVersion; } /** @@ -42,6 +42,7 @@ export class LambdaProxyIntegration implements IHttpRouteIntegration { return { type: HttpIntegrationType.LAMBDA_PROXY, uri: this.props.handler.functionArn, + payloadFormatVersion: this.props.payloadFormatVersion ?? PayloadFormatVersion.VERSION_2_0, }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts index 19fcfc18756b4..6b29b7006c22e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts @@ -136,6 +136,8 @@ export class HttpRoute extends Resource implements IHttpRoute { httpApi: props.httpApi, integrationType: config.type, integrationUri: config.uri, + method: config.method, + payloadFormatVersion: config.payloadFormatVersion, }); } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts index 959077ce8b1f8..6b529d2099ff8 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts @@ -1,6 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; -import { App, Stack } from '@aws-cdk/core'; -import { HttpApi, LambdaProxyIntegration } from '../lib'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { HttpApi, HttpProxyIntegration, LambdaProxyIntegration } from '../lib'; const app = new App(); @@ -22,12 +22,25 @@ def handler(event, context): // const defaultUrl = 'https://aws.amazon.com'; // create a basic HTTP API with http proxy integration as the $default route -new HttpApi(stack, 'HttpApi', { +const firstone = new HttpApi(stack, 'HttpApi', { defaultIntegration: new LambdaProxyIntegration({ handler, }), }); +const secondone = new HttpApi(stack, 'AnotherHttpApi', { + defaultIntegration: new HttpProxyIntegration({ + url: firstone.url!, + }), +}); + +new CfnOutput(stack, 'firsturl', { + value: firstone.url!, +}); + +new CfnOutput(stack, 'secondurl', { + value: secondone.url!, +}); // new Route(api, 'allroutes', { // httpApi: api, // path: '/', From 0b404dc4664ef3d6dc9b7fd7996552d137eddef2 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 29 Apr 2020 18:13:10 +0100 Subject: [PATCH 34/42] move stage name back to string --- .../aws-apigatewayv2/lib/common/stage.ts | 33 ++----------------- .../@aws-cdk/aws-apigatewayv2/lib/http/api.ts | 2 -- .../aws-apigatewayv2/lib/http/stage.ts | 14 ++++---- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/stage.ts index db7b87606c969..b608a7a34ad97 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/common/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/stage.ts @@ -11,35 +11,6 @@ export interface IStage extends IResource { readonly stageName: string; } -/** - * A type to be used while specifying the stage name for a new Stage. - */ -export class StageName { - /** - * The default stage of the API. - * This stage will have the URL at the root of the API endpoint. - */ - public static readonly DEFAULT = new StageName('$default'); - - /** - * The name of this stage. - * This will be used to both the primary identifier of this stage, as well as, - * the path component of the API endpoint - 'https://.execute-api..amazonaws.com/'. - */ - public static of(stageName: string) { - return new StageName(stageName); - } - - /** - * The name of this stage as a string, as recognized by the API Gateway service. - */ - public readonly stageName: string; - - private constructor(stageName: string) { - this.stageName = stageName; - } -} - /** * Options required to create a new stage. * Options that are common between HTTP and Websocket APIs. @@ -47,9 +18,9 @@ export class StageName { export interface CommonStageOptions { /** * The name of the stage. See `StageName` class for more details. - * @default StageName.DEFAULT + * @default '$default' the default stage of the API. This stage will have the URL at the root of the API endpoint. */ - readonly stageName?: StageName; + readonly stageName?: string; /** * Whether updates to an API automatically trigger a new deployment. diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index 43b7fe68bf724..577a11d20c998 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -1,6 +1,5 @@ import { Construct, IResource, Resource } from '@aws-cdk/core'; import { CfnApi, CfnApiProps } from '../apigatewayv2.generated'; -import { StageName } from '../common'; import { IHttpRouteIntegration } from './integration'; import { HttpRoute, HttpRouteKey } from './route'; import { HttpStage, HttpStageOptions } from './stage'; @@ -80,7 +79,6 @@ export class HttpApi extends Resource implements IHttpApi { if (props?.createDefaultStage === undefined || props.createDefaultStage === true) { this.defaultStage = new HttpStage(this, 'DefaultStage', { httpApi: this, - stageName: StageName.DEFAULT, autoDeploy: true, }); } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts index 1681c2051dddf..1335cbf839984 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts @@ -1,8 +1,10 @@ import { Construct, Resource, Stack } from '@aws-cdk/core'; import { CfnStage } from '../apigatewayv2.generated'; -import { CommonStageOptions, IStage, StageName } from '../common'; +import { CommonStageOptions, IStage } from '../common'; import { IHttpApi } from './api'; +const DEFAULT_STAGE_NAME = '$default'; + /** * Options to create a new stage for an HTTP API. */ @@ -39,25 +41,25 @@ export class HttpStage extends Resource implements IStage { constructor(scope: Construct, id: string, props: HttpStageProps) { super(scope, id, { - physicalName: props.stageName ? props.stageName.stageName : StageName.DEFAULT.stageName, + physicalName: props.stageName ? props.stageName : DEFAULT_STAGE_NAME, }); - const resource = new CfnStage(this, 'Resource', { + new CfnStage(this, 'Resource', { apiId: props.httpApi.httpApiId, stageName: this.physicalName, autoDeploy: props.autoDeploy, }); - this.stageName = resource.ref; + this.stageName = this.physicalName; this.httpApi = props.httpApi; } /** * The URL to this stage. */ - public get url() { + public get url(): string { const s = Stack.of(this); - const urlPath = this.stageName === StageName.DEFAULT.stageName ? '' : this.stageName; + const urlPath = this.stageName === DEFAULT_STAGE_NAME ? '' : this.stageName; return `https://${this.httpApi.httpApiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`; } } \ No newline at end of file From d9cd3359e1d6aad40bd89dc19b6551080a533a4c Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 29 Apr 2020 19:06:34 +0100 Subject: [PATCH 35/42] unit and integ tests --- .../aws-apigatewayv2/lib/http/route.ts | 20 +- .../test/apigatewayv2.test.ts | 378 ------------- .../aws-apigatewayv2/test/http/api.test.ts | 59 ++ .../integ.http-proxy.expected.json | 236 ++++++++ .../http/integrations/integ.http-proxy.ts | 39 ++ .../integ.lambda-proxy.expected.json | 168 ++++++ .../http/integrations/integ.lambda-proxy.ts | 28 + .../aws-apigatewayv2/test/http/route.test.ts | 66 +++ .../aws-apigatewayv2/test/http/stage.test.ts | 40 ++ .../test/integ.api.expected.json | 505 ------------------ .../aws-apigatewayv2/test/integ.api.ts | 82 --- 11 files changed, 645 insertions(+), 976 deletions(-) delete mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.http-proxy.expected.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.http-proxy.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.lambda-proxy.expected.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.lambda-proxy.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts delete mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json delete mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts index 6b29b7006c22e..c5662824addc4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts @@ -130,21 +130,19 @@ export class HttpRoute extends Resource implements IHttpRoute { this.path = props.routeKey.path; let integration: HttpIntegration | undefined; - if (props.integration) { - const config = props.integration.bind(this); - integration = new HttpIntegration(this, `${this.node.id}-Integration`, { - httpApi: props.httpApi, - integrationType: config.type, - integrationUri: config.uri, - method: config.method, - payloadFormatVersion: config.payloadFormatVersion, - }); - } + const config = props.integration.bind(this); + integration = new HttpIntegration(this, `${this.node.id}-Integration`, { + httpApi: props.httpApi, + integrationType: config.type, + integrationUri: config.uri, + method: config.method, + payloadFormatVersion: config.payloadFormatVersion, + }); const routeProps: CfnRouteProps = { apiId: props.httpApi.httpApiId, routeKey: props.routeKey.key, - target: integration ? `integrations/${integration.integrationId}` : undefined, + target: `integrations/${integration.integrationId}`, }; const route = new CfnRoute(this, 'Resource', routeProps); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts deleted file mode 100644 index fa54f2fe80a37..0000000000000 --- a/packages/@aws-cdk/aws-apigatewayv2/test/apigatewayv2.test.ts +++ /dev/null @@ -1,378 +0,0 @@ -// import { countResources, expect as expectCDK, haveOutput, haveResourceLike } from '@aws-cdk/assert'; -// import * as lambda from '@aws-cdk/aws-lambda'; -// import * as cdk from '@aws-cdk/core'; -// import * as apigatewayv2 from '../lib'; - -// let stack = new cdk.Stack(); -// let handler: lambda.Function; -// let rootHandler: lambda.Function; -// let checkIpUrl: string; -// let awsUrl: string; - -// beforeEach(() => { -// stack = new cdk.Stack(); -// handler = new lambda.Function(stack, 'MyFunc', { -// runtime: lambda.Runtime.PYTHON_3_7, -// handler: 'index.handler', -// code: new lambda.InlineCode(` -// import json -// def handler(event, context): -// return { -// 'statusCode': 200, -// 'body': json.dumps(event) -// }`), -// }); - -// rootHandler = new lambda.Function(stack, 'RootFunc', { -// runtime: lambda.Runtime.PYTHON_3_7, -// handler: 'index.handler', -// code: new lambda.InlineCode(` -// import json, os -// def handler(event, context): -// whoami = os.environ['WHOAMI'] -// http_path = os.environ['HTTP_PATH'] -// return { -// 'statusCode': 200, -// 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path }) -// }`), -// environment: { -// WHOAMI: 'root', -// HTTP_PATH: '/', -// }, -// }); -// checkIpUrl = 'https://checkip.amazonaws.com'; -// awsUrl = 'https://aws.amazon.com'; -// }); - -// test('create a HTTP API with no props correctly', () => { -// // WHEN -// const api = new apigatewayv2.HttpApi(stack, 'HttpApi'); -// new cdk.CfnOutput(stack, 'URL', { value: api.url }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(haveOutput({ -// outputName: 'URL', -// })); -// }); - -// test('create a HTTP API with empty props correctly', () => { -// // WHEN -// new apigatewayv2.HttpApi(stack, 'HttpApi', {}); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// }); - -// test('create a basic HTTP API correctly with targetUrl', () => { -// // WHEN -// new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetUrl: checkIpUrl, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// }); - -// test('create a basic HTTP API correctly with targetUrl and protocol', () => { -// // WHEN -// new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetUrl: checkIpUrl, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// }); - -// test('create a basic HTTP API correctly with target handler', () => { -// // WHEN -// new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// }); - -// test('import HTTP API from API ID correctly', () => { -// // WHEN -// // THEN -// expect(() => -// apigatewayv2.HttpApi.fromApiId(stack, 'HttpApi', 'foo'), -// ).not.toThrowError(); -// }); - -// test('create lambda proxy integration correctly', () => { -// // WHEN -// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { -// api: httpApi, -// targetHandler: rootHandler, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -// }); - -// test('create HTTP proxy integration correctly with targetUrl', () => { -// // WHEN -// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// new apigatewayv2.HttpProxyIntegration(stack, 'IntegRootHandler', { -// api: httpApi, -// targetUrl: 'https://aws.amazon.com', -// integrationMethod: apigatewayv2.HttpMethod.GET, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -// }); - -// test('create HTTP proxy integration correctly with Integration', () => { -// // WHEN -// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// new apigatewayv2.Integration(stack, 'IntegRootHandler', { -// apiId: httpApi.httpApiId, -// integrationMethod: apigatewayv2.HttpMethod.ANY, -// integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, -// integrationUri: awsUrl, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -// }); - -// test('import integration from integration ID correctly', () => { -// // WHEN -// // THEN -// expect(() => -// apigatewayv2.Integration.fromIntegrationId(stack, 'Integ', 'foo'), -// ).not.toThrowError(); -// }); - -// test('create the root route correctly', () => { -// // WHEN -// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { -// api: httpApi, -// targetHandler: rootHandler, -// }); -// httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { -// api: httpApi, -// httpPath: '/', -// integration: integRootHandler, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 1)); -// }); - -// test('import route from route ID correctly', () => { -// // WHEN -// // THEN -// expect(() => -// apigatewayv2.Route.fromRouteId(stack, 'Integ', 'foo'), -// ).not.toThrowError(); -// }); - -// test('addLambdaRoute correctly from a Route', () => { -// // WHEN -// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { -// api: httpApi, -// targetHandler: rootHandler, -// }); -// httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { -// api: httpApi, -// httpPath: '/', -// integration: integRootHandler, -// }); -// httpApi.root.addLambdaRoute('foo', 'Foo', { -// target: handler, -// method: apigatewayv2.HttpMethod.GET, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 2)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); -// }); - -// test('addHttpRoute correctly from a Route', () => { -// // WHEN -// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// const integRootHandler = new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { -// api: httpApi, -// targetHandler: rootHandler, -// }); -// httpApi.root = new apigatewayv2.Route(stack, 'RootRoute', { -// api: httpApi, -// httpPath: '/', -// integration: integRootHandler, -// }); -// httpApi.root.addHttpRoute('aws', 'AwsPage', { -// targetUrl: awsUrl, -// method: apigatewayv2.HttpMethod.ANY, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Integration', 2)); -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Route', 2)); -// }); - -// test('addLambdaRoute correctly from a HttpApi', () => { -// // WHEN -// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// httpApi.addLambdaRoute('/foo/bar', 'FooBar', { -// target: handler, -// method: apigatewayv2.HttpMethod.GET, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { -// ApiId: { -// Ref: 'HttpApiF5A9A8A7', -// }, -// RouteKey: 'GET /foo/bar', -// Target: { -// 'Fn::Join': [ -// '', -// [ -// 'integrations/', -// { -// Ref: 'HttpApiFooBarLambdaProxyIntegration2FFCA7FC', -// }, -// ], -// ], -// }, -// })); -// }); - -// test('addHttpRoute correctly from a HttpApi', () => { -// // WHEN -// const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// httpApi.addHttpRoute('/foo/bar', 'FooBar', { -// targetUrl: awsUrl, -// method: apigatewayv2.HttpMethod.ANY, -// }); -// // THEN -// expectCDK(stack).to(countResources('AWS::ApiGatewayV2::Api', 1)); -// expectCDK(stack).to(haveResourceLike('AWS::ApiGatewayV2::Route', { -// ApiId: { -// Ref: 'HttpApiF5A9A8A7', -// }, -// RouteKey: 'ANY /foo/bar', -// Target: { -// 'Fn::Join': [ -// '', -// [ -// 'integrations/', -// { -// Ref: 'HttpApiFooBarHttpProxyIntegration80C34C6B', -// }, -// ], -// ], -// }, -// })); -// }); - -// test('throws when both targetHandler and targetUrl are specified', () => { -// // WHEN -// // THEN -// expect(() => -// new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// targetUrl: awsUrl, -// }), -// ).toThrowError(/You must specify either a targetHandler or targetUrl, use at most one/); -// }); - -// test('throws when both targetHandler and targetUrl are specified for Route', () => { -// // WHEN -// const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// // THEN -// expect(() => -// new apigatewayv2.Route(stack, 'Route', { -// api, -// httpPath: '/', -// targetUrl: awsUrl, -// targetHandler: handler, -// }), -// ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); -// }); - -// test('throws when targetHandler, targetUrl and integration all specified for Route', () => { -// // WHEN -// const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// const integ = new apigatewayv2.Integration(stack, 'IntegRootHandler', { -// apiId: api.httpApiId, -// integrationMethod: apigatewayv2.HttpMethod.ANY, -// integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, -// integrationUri: awsUrl, -// }); -// // THEN -// expect(() => -// new apigatewayv2.Route(stack, 'Route', { -// api, -// httpPath: '/', -// targetUrl: awsUrl, -// targetHandler: handler, -// integration: integ, -// }), -// ).toThrowError(/You must specify targetHandler, targetUrl or integration, use at most one/); -// }); - -// test('throws when targetHandler, targetUrl and integration all unspecified for Route', () => { -// // WHEN -// const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { -// targetHandler: handler, -// }); -// // THEN -// expect(() => -// new apigatewayv2.Route(stack, 'Route', { -// api, -// httpPath: '/', -// }), -// ).toThrowError(/You must specify either a integration, targetHandler or targetUrl/); -// }); - -// test('create LambdaProxyIntegration correctly in aws china region', () => { -// // GIVEN -// const app = new cdk.App(); -// const stackcn = new cdk.Stack(app, 'stack', { env: { region: 'cn-north-1' } }); -// const handlercn = new lambda.Function(stackcn, 'MyFunc', { -// runtime: lambda.Runtime.PYTHON_3_7, -// handler: 'index.handler', -// code: new lambda.InlineCode(` -// import json -// def handler(event, context): -// return { -// 'statusCode': 200, -// 'body': json.dumps(event) -// }`), -// reservedConcurrentExecutions: 10, -// }); -// // WHEN -// const httpApi = new apigatewayv2.HttpApi(stackcn, 'HttpApi', { -// targetUrl: awsUrl, -// }); -// // THEN -// new apigatewayv2.LambdaProxyIntegration(stackcn, 'IntegRootHandler', { -// api: httpApi, -// targetHandler: handlercn, -// }); -// // THEN -// expectCDK(stackcn).to(countResources('AWS::ApiGatewayV2::Integration', 1)); -// }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts new file mode 100644 index 0000000000000..df8f843f7a887 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts @@ -0,0 +1,59 @@ +import '@aws-cdk/assert/jest'; +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import { Stack } from '@aws-cdk/core'; +import { HttpApi, LambdaProxyIntegration } from '../../lib'; + +describe('HttpApi', () => { + test('default', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'api'); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Api', { + Name: 'api', + ProtocolType: 'HTTP', + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Stage', { + ApiId: stack.resolve(api.httpApiId), + StageName: '$default', + AutoDeploy: true, + }); + + expect(stack).not.toHaveResource('AWS::ApiGatewayV2::Route'); + expect(stack).not.toHaveResource('AWS::ApiGatewayV2::Integration'); + + expect(api.url).toBeDefined(); + }); + + test('unsetting createDefaultStage', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'api', { + createDefaultStage: false, + }); + + expect(stack).not.toHaveResource('AWS::ApiGatewayV2::Stage'); + expect(api.url).toBeUndefined(); + }); + + test('default integration', () => { + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'api', { + defaultIntegration: new LambdaProxyIntegration({ + handler: new Function(stack, 'fn', { + code: Code.fromInline('foo'), + runtime: Runtime.NODEJS_12_X, + handler: 'index.handler', + }), + }), + }); + + expect(stack).toHaveResourceLike('AWS::ApiGatewayV2::Route', { + ApiId: stack.resolve(httpApi.httpApiId), + RouteKey: '$default', + }); + + expect(stack).toHaveResourceLike('AWS::ApiGatewayV2::Integration', { + ApiId: stack.resolve(httpApi.httpApiId), + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.http-proxy.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.http-proxy.expected.json new file mode 100644 index 0000000000000..475ef14f158ef --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.http-proxy.expected.json @@ -0,0 +1,236 @@ +{ + "Resources": { + "AlwaysSuccessServiceRole6DB8C2F6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AlwaysSuccess099EAB05": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function(event, context) { return { statusCode: 200, body: \"success\" }; };" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "AlwaysSuccessServiceRole6DB8C2F6", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + }, + "DependsOn": [ + "AlwaysSuccessServiceRole6DB8C2F6" + ] + }, + "AlwaysSuccessinteghttpproxyLambdaProxyApiDefaultRoute17D52FE1Permission3B39FF57": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AlwaysSuccess099EAB05", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "LambdaProxyApi67594471" + }, + "/*/*" + ] + ] + } + } + }, + "LambdaProxyApi67594471": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "LambdaProxyApi", + "ProtocolType": "HTTP" + } + }, + "LambdaProxyApiDefaultRouteDefaultRouteIntegration97D5250B": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationUri": { + "Fn::GetAtt": [ + "AlwaysSuccess099EAB05", + "Arn" + ] + }, + "PayloadFormatVersion": "2.0" + } + }, + "LambdaProxyApiDefaultRoute1EB30A46": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "RouteKey": "$default", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "LambdaProxyApiDefaultRouteDefaultRouteIntegration97D5250B" + } + ] + ] + } + } + }, + "LambdaProxyApiDefaultStage07C38681": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "StageName": "$default", + "AutoDeploy": true + } + }, + "HttpProxyApiD0217C67": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "HttpProxyApi", + "ProtocolType": "HTTP" + } + }, + "HttpProxyApiDefaultRouteDefaultRouteIntegrationF2E17850": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "HttpProxyApiD0217C67" + }, + "IntegrationType": "HTTP_PROXY", + "IntegrationMethod": "ANY", + "IntegrationUri": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "LambdaProxyApi67594471" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + }, + "PayloadFormatVersion": "1.0" + } + }, + "HttpProxyApiDefaultRoute8AF66B5C": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "HttpProxyApiD0217C67" + }, + "RouteKey": "$default", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "HttpProxyApiDefaultRouteDefaultRouteIntegrationF2E17850" + } + ] + ] + } + } + }, + "HttpProxyApiDefaultStageA88F9DE3": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "HttpProxyApiD0217C67" + }, + "StageName": "$default", + "AutoDeploy": true + } + } + }, + "Outputs": { + "Endpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "HttpProxyApiD0217C67" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.http-proxy.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.http-proxy.ts new file mode 100644 index 0000000000000..15db80661e3f1 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.http-proxy.ts @@ -0,0 +1,39 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { HttpApi, HttpProxyIntegration, LambdaProxyIntegration } from '../../../lib'; + +/* + * Stack verification steps: + * "curl " should return 'success' + */ + +const app = new App(); + +const stack = new Stack(app, 'integ-http-proxy'); + +// first create a lambda proxy endpoint that we can use as an HTTP proxy +const lambdaEndpoint = lambdaProxyEndpoint(stack); + +const httpEndpoint = new HttpApi(stack, 'HttpProxyApi', { + defaultIntegration: new HttpProxyIntegration({ + url: lambdaEndpoint.url!, + }), +}); + +new CfnOutput(stack, 'Endpoint', { + value: httpEndpoint.url!, +}); + +function lambdaProxyEndpoint(s: Stack): HttpApi { + const handler = new lambda.Function(s, 'AlwaysSuccess', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: new lambda.InlineCode('exports.handler = async function(event, context) { return { statusCode: 200, body: "success" }; };'), + }); + + return new HttpApi(s, 'LambdaProxyApi', { + defaultIntegration: new LambdaProxyIntegration({ + handler, + }), + }); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.lambda-proxy.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.lambda-proxy.expected.json new file mode 100644 index 0000000000000..ff35db0f3c0d5 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.lambda-proxy.expected.json @@ -0,0 +1,168 @@ +{ + "Resources": { + "AlwaysSuccessServiceRole6DB8C2F6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AlwaysSuccess099EAB05": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function(event, context) { return { statusCode: 200, body: \"success\" }; };" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "AlwaysSuccessServiceRole6DB8C2F6", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + }, + "DependsOn": [ + "AlwaysSuccessServiceRole6DB8C2F6" + ] + }, + "AlwaysSuccessinteglambdaproxyLambdaProxyApiDefaultRoute59CA2390Permission90573BC0": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AlwaysSuccess099EAB05", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "LambdaProxyApi67594471" + }, + "/*/*" + ] + ] + } + } + }, + "LambdaProxyApi67594471": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "LambdaProxyApi", + "ProtocolType": "HTTP" + } + }, + "LambdaProxyApiDefaultRouteDefaultRouteIntegration97D5250B": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationUri": { + "Fn::GetAtt": [ + "AlwaysSuccess099EAB05", + "Arn" + ] + }, + "PayloadFormatVersion": "2.0" + } + }, + "LambdaProxyApiDefaultRoute1EB30A46": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "RouteKey": "$default", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "LambdaProxyApiDefaultRouteDefaultRouteIntegration97D5250B" + } + ] + ] + } + } + }, + "LambdaProxyApiDefaultStage07C38681": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "LambdaProxyApi67594471" + }, + "StageName": "$default", + "AutoDeploy": true + } + } + }, + "Outputs": { + "Endpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "LambdaProxyApi67594471" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.lambda-proxy.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.lambda-proxy.ts new file mode 100644 index 0000000000000..2114f94729edf --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/integ.lambda-proxy.ts @@ -0,0 +1,28 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { HttpApi, LambdaProxyIntegration } from '../../../lib'; + +/* + * Stack verification steps: + * "curl " should return 'success' + */ + +const app = new App(); + +const stack = new Stack(app, 'integ-lambda-proxy'); + +const handler = new lambda.Function(stack, 'AlwaysSuccess', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: new lambda.InlineCode('exports.handler = async function(event, context) { return { statusCode: 200, body: "success" }; };'), +}); + +const endpoint = new HttpApi(stack, 'LambdaProxyApi', { + defaultIntegration: new LambdaProxyIntegration({ + handler, + }), +}); + +new CfnOutput(stack, 'Endpoint', { + value: endpoint.url!, +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts new file mode 100644 index 0000000000000..25df12528e63e --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts @@ -0,0 +1,66 @@ +import '@aws-cdk/assert/jest'; +import { Stack } from '@aws-cdk/core'; +import { HttpApi, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteIntegration, + PayloadFormatVersion } from '../../lib'; + +describe('HttpRoute', () => { + test('default', () => { + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'HttpApi'); + + new HttpRoute(stack, 'HttpRoute', { + httpApi, + integration: new DummyIntegration(), + routeKey: HttpRouteKey.with('/books', HttpMethod.GET), + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Route', { + ApiId: stack.resolve(httpApi.httpApiId), + RouteKey: 'GET /books', + Target: { + 'Fn::Join': [ + '', + [ + 'integrations/', + { + Ref: 'HttpRouteHttpRouteIntegration6EE0FE47', + }, + ], + ], + }, + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + ApiId: stack.resolve(httpApi.httpApiId), + }); + }); + + test('integration is configured correctly', () => { + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'HttpApi'); + + new HttpRoute(stack, 'HttpRoute', { + httpApi, + integration: new DummyIntegration(), + routeKey: HttpRouteKey.with('/books', HttpMethod.GET), + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + ApiId: stack.resolve(httpApi.httpApiId), + IntegrationType: 'HTTP_PROXY', + PayloadFormatVersion: '2.0', + IntegrationUri: 'some-uri', + }); + }); +}); + +class DummyIntegration implements IHttpRouteIntegration { + public bind(): HttpRouteIntegrationConfig { + return { + type: HttpIntegrationType.HTTP_PROXY, + payloadFormatVersion: PayloadFormatVersion.VERSION_2_0, + uri: 'some-uri', + method: HttpMethod.DELETE, + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts new file mode 100644 index 0000000000000..336d3a74852a4 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts @@ -0,0 +1,40 @@ +import '@aws-cdk/assert/jest'; +import { Stack } from '@aws-cdk/core'; +import { HttpApi, HttpStage } from '../../lib'; + +describe('HttpStage', () => { + test('default', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'Api', { + createDefaultStage: false, + }); + + new HttpStage(stack, 'Stage', { + httpApi: api, + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Stage', { + ApiId: stack.resolve(api.httpApiId), + StageName: '$default', + }); + }); + + test('url returns the correct path', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'Api', { + createDefaultStage: false, + }); + + const defaultStage = new HttpStage(stack, 'DefaultStage', { + httpApi: api, + }); + + const betaStage = new HttpStage(stack, 'BetaStage', { + httpApi: api, + stageName: 'beta', + }); + + expect(defaultStage.url.endsWith('/')).toBe(true); + expect(betaStage.url.endsWith('/')).toBe(false); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json deleted file mode 100644 index 251d6101958ec..0000000000000 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.expected.json +++ /dev/null @@ -1,505 +0,0 @@ -{ - "Resources": { - "MyFuncServiceRole54065130": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "MyFunc8A243A2C": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "\nimport json\ndef handler(event, context):\n return {\n 'statusCode': 200,\n 'body': json.dumps(event)\n }" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "MyFuncServiceRole54065130", - "Arn" - ] - }, - "Runtime": "python3.7" - }, - "DependsOn": [ - "MyFuncServiceRole54065130" - ] - }, - "RootFuncServiceRoleE4AA9E41": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "RootFuncF39FB174": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "\nimport json, os\ndef handler(event, context):\n whoami = os.environ['WHOAMI']\n http_path = os.environ['HTTP_PATH']\n return {\n 'statusCode': 200,\n 'body': json.dumps({ 'whoami': whoami, 'http_path': http_path })\n }" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "RootFuncServiceRoleE4AA9E41", - "Arn" - ] - }, - "Runtime": "python3.7", - "Environment": { - "Variables": { - "WHOAMI": "root", - "HTTP_PATH": "/" - } - } - }, - "DependsOn": [ - "RootFuncServiceRoleE4AA9E41" - ] - }, - "HttpApiF5A9A8A7": { - "Type": "AWS::ApiGatewayV2::Api", - "Properties": { - "Name": "HttpApi", - "ProtocolType": "HTTP", - "Target": "https://checkip.amazonaws.com" - } - }, - "HttpApiLambdaRoute41C718EF": { - "Type": "AWS::ApiGatewayV2::Route", - "Properties": { - "ApiId": { - "Ref": "HttpApiF5A9A8A7" - }, - "RouteKey": "ANY /fn", - "Target": { - "Fn::Join": [ - "", - [ - "integrations/", - { - "Ref": "HttpApiLambdaRouteLambdaProxyIntegration9CDB3CA1" - } - ] - ] - } - } - }, - "HttpApiLambdaRouteLambdaProxyIntegration9CDB3CA1": { - "Type": "AWS::ApiGatewayV2::Integration", - "Properties": { - "ApiId": { - "Ref": "HttpApiF5A9A8A7" - }, - "IntegrationType": "AWS_PROXY", - "IntegrationMethod": "POST", - "IntegrationUri": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":apigateway:", - { - "Ref": "AWS::Region" - }, - ":lambda:path/2015-03-31/functions/", - { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - }, - "/invocations" - ] - ] - }, - "PayloadFormatVersion": "1.0" - } - }, - "HttpApiIntegPermissionApiagtewayV2HttpApiLambdaRouteLambdaProxyIntegration13D61555AWSPROXYPOST97C13A63": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":execute-api:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "HttpApiF5A9A8A7" - }, - "/*/*" - ] - ] - } - } - }, - "HttpApi2C094A993": { - "Type": "AWS::ApiGatewayV2::Api", - "Properties": { - "Name": "HttpApi2", - "ProtocolType": "HTTP", - "Target": { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - } - } - }, - "ApiPermissionApiagtewayV2HttpApiHttpApi212E01E71ANY": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":execute-api:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "HttpApi2C094A993" - }, - "/*/*" - ] - ] - } - } - }, - "IntegRootHandler0E7ABE31": { - "Type": "AWS::ApiGatewayV2::Integration", - "Properties": { - "ApiId": { - "Ref": "HttpApi2C094A993" - }, - "IntegrationType": "AWS_PROXY", - "IntegrationMethod": "POST", - "IntegrationUri": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":apigateway:", - { - "Ref": "AWS::Region" - }, - ":lambda:path/2015-03-31/functions/", - { - "Fn::GetAtt": [ - "RootFuncF39FB174", - "Arn" - ] - }, - "/invocations" - ] - ] - }, - "PayloadFormatVersion": "1.0" - } - }, - "IntegPermissionApiagtewayV2HttpApiIntegRootHandler3D54DAE8AWSPROXYPOST": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "RootFuncF39FB174", - "Arn" - ] - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":execute-api:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "HttpApi2C094A993" - }, - "/*/*" - ] - ] - } - } - }, - "RootRoute357999D4": { - "Type": "AWS::ApiGatewayV2::Route", - "Properties": { - "ApiId": { - "Ref": "HttpApi2C094A993" - }, - "RouteKey": "ANY /", - "Target": { - "Fn::Join": [ - "", - [ - "integrations/", - { - "Ref": "IntegRootHandler0E7ABE31" - } - ] - ] - } - } - }, - "RootRouteFoo0BA98CEE": { - "Type": "AWS::ApiGatewayV2::Route", - "Properties": { - "ApiId": { - "Ref": "HttpApi2C094A993" - }, - "RouteKey": "GET /foo", - "Target": { - "Fn::Join": [ - "", - [ - "integrations/", - { - "Ref": "RootRouteFooLambdaProxyIntegration5B1AEF5D" - } - ] - ] - } - } - }, - "RootRouteFooAwsPageB70848AE": { - "Type": "AWS::ApiGatewayV2::Route", - "Properties": { - "ApiId": { - "Ref": "HttpApi2C094A993" - }, - "RouteKey": "ANY /foo/aws", - "Target": { - "Fn::Join": [ - "", - [ - "integrations/", - { - "Ref": "RootRouteFooAwsPageHttpProxyIntegration9D16D6B6" - } - ] - ] - } - } - }, - "RootRouteFooAwsPageCheckIp644AED0C": { - "Type": "AWS::ApiGatewayV2::Route", - "Properties": { - "ApiId": { - "Ref": "HttpApi2C094A993" - }, - "RouteKey": "ANY /foo/aws/checkip", - "Target": { - "Fn::Join": [ - "", - [ - "integrations/", - { - "Ref": "RootRouteFooAwsPageCheckIpHttpProxyIntegration4E30792F" - } - ] - ] - } - } - }, - "RootRouteFooAwsPageCheckIpHttpProxyIntegration4E30792F": { - "Type": "AWS::ApiGatewayV2::Integration", - "Properties": { - "ApiId": { - "Ref": "HttpApi2C094A993" - }, - "IntegrationType": "HTTP_PROXY", - "IntegrationMethod": "ANY", - "IntegrationUri": "https://checkip.amazonaws.com", - "PayloadFormatVersion": "1.0" - } - }, - "RootRouteFooAwsPageHttpProxyIntegration9D16D6B6": { - "Type": "AWS::ApiGatewayV2::Integration", - "Properties": { - "ApiId": { - "Ref": "HttpApi2C094A993" - }, - "IntegrationType": "HTTP_PROXY", - "IntegrationMethod": "ANY", - "IntegrationUri": "https://aws.amazon.com", - "PayloadFormatVersion": "1.0" - } - }, - "RootRouteFooLambdaProxyIntegration5B1AEF5D": { - "Type": "AWS::ApiGatewayV2::Integration", - "Properties": { - "ApiId": { - "Ref": "HttpApi2C094A993" - }, - "IntegrationType": "AWS_PROXY", - "IntegrationMethod": "POST", - "IntegrationUri": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":apigateway:", - { - "Ref": "AWS::Region" - }, - ":lambda:path/2015-03-31/functions/", - { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - }, - "/invocations" - ] - ] - }, - "PayloadFormatVersion": "1.0" - } - }, - "RootRouteIntegPermissionApiagtewayV2HttpApiRootRouteFooLambdaProxyIntegration5622B5E6AWSPROXYPOST4694F774": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":execute-api:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "HttpApi2C094A993" - }, - "/*/*" - ] - ] - } - } - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts b/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts deleted file mode 100644 index 6b529d2099ff8..0000000000000 --- a/packages/@aws-cdk/aws-apigatewayv2/test/integ.api.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as lambda from '@aws-cdk/aws-lambda'; -import { App, CfnOutput, Stack } from '@aws-cdk/core'; -import { HttpApi, HttpProxyIntegration, LambdaProxyIntegration } from '../lib'; - -const app = new App(); - -const stack = new Stack(app, 'ApiagtewayV2HttpApi'); - -const handler = new lambda.Function(stack, 'MyFunc', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code: new lambda.InlineCode(` -import json -def handler(event, context): - return { - 'statusCode': 200, - 'body': json.dumps(event) - }`), -}); - -// const rootUrl = 'https://checkip.amazonaws.com'; -// const defaultUrl = 'https://aws.amazon.com'; - -// create a basic HTTP API with http proxy integration as the $default route -const firstone = new HttpApi(stack, 'HttpApi', { - defaultIntegration: new LambdaProxyIntegration({ - handler, - }), -}); - -const secondone = new HttpApi(stack, 'AnotherHttpApi', { - defaultIntegration: new HttpProxyIntegration({ - url: firstone.url!, - }), -}); - -new CfnOutput(stack, 'firsturl', { - value: firstone.url!, -}); - -new CfnOutput(stack, 'secondurl', { - value: secondone.url!, -}); -// new Route(api, 'allroutes', { -// httpApi: api, -// path: '/', -// method: HttpMethod.ANY, -// integration: new LambdaProxyIntegration({ -// handler, -// }), -// }); - -// new Stage(api, 'mystage', { -// api, -// }); - -// api.addRoutes('/books/reviews', 'GetBookReviewRoute', { -// methods: [HttpMethod.GET], -// integration: new LambdaProxyIntegration(stack, 'getBookReviewInteg', { -// api, -// targetHandler: getbookReviewsHandler, -// }), -// }); - -// // pass the rootIntegration to addRootRoute() to initialize the root route -// // HTTP GET / -// api.addRootRoute(rootIntegration, HttpMethod.GET) -// // HTTP GET /foo -// .addLambdaRoute('foo', 'Foo', { -// target: handler, -// method: apigatewayv2.HttpMethod.GET -// }) -// // HTTP ANY /foo/aws -// .addHttpRoute('aws', 'AwsPage', { -// targetUrl: awsUrl, -// method: apigatewayv2.HttpMethod.ANY -// }) -// // HTTP ANY /foo/aws/checkip -// .addHttpRoute('checkip', 'CheckIp', { -// targetUrl: checkIpUrl, -// method: apigatewayv2.HttpMethod.ANY -// }); \ No newline at end of file From a5e2b205fa742a5e76389878bfbe40d1383953d0 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 30 Apr 2020 10:02:43 +0100 Subject: [PATCH 36/42] addRoutes() --- .../@aws-cdk/aws-apigatewayv2/lib/http/api.ts | 42 ++++++++++------ .../aws-apigatewayv2/lib/http/route.ts | 48 +++++------------- .../aws-apigatewayv2/test/http/api.test.ts | 50 ++++++++++++++++++- 3 files changed, 88 insertions(+), 52 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index 577a11d20c998..c0ca922a69347 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -1,7 +1,7 @@ import { Construct, IResource, Resource } from '@aws-cdk/core'; import { CfnApi, CfnApiProps } from '../apigatewayv2.generated'; import { IHttpRouteIntegration } from './integration'; -import { HttpRoute, HttpRouteKey } from './route'; +import { BatchHttpRouteOptions, HttpMethod, HttpRoute, HttpRouteKey } from './route'; import { HttpStage, HttpStageOptions } from './stage'; /** @@ -38,6 +38,22 @@ export interface HttpApiProps { readonly createDefaultStage?: boolean; } +/** + * Options for the Route with Integration resoruce + */ +export interface AddRoutesOptions extends BatchHttpRouteOptions { + /** + * The path at which all of these routes are configured. + */ + readonly path: string; + + /** + * The HTTP methods to be configured + * @default HttpMethod.ANY + */ + readonly methods?: HttpMethod[]; +} + /** * Create a new API Gateway HTTP API endpoint. * @resource AWS::ApiGatewayV2::Api @@ -103,19 +119,15 @@ export class HttpApi extends Resource implements IHttpApi { } /** - * add routes on this API + * Add multiple routes that uses the same configuration. The routes all go to the same path, but for different + * methods. */ - // public addRoutes(pathPart: string, id: string, options: AddRoutesOptions): Route[] { - // const routes: Route[] = []; - // const methods = options.methods ?? [ HttpMethod.ANY ]; - // for (const m of methods) { - // routes.push(new Route(this, `${id}${m}`, { - // api: this, - // integration: options.integration, - // httpMethod: m, - // httpPath: pathPart, - // })); - // } - // return routes; - // } + public addRoutes(options: AddRoutesOptions): HttpRoute[] { + const methods = options.methods ?? [ HttpMethod.ANY ]; + return methods.map((method) => new HttpRoute(this, `${method}${options.path}`, { + httpApi: this, + routeKey: HttpRouteKey.with(options.path, method), + integration: options.integration, + })); + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts index c5662824addc4..574b2c82275e8 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts @@ -78,10 +78,21 @@ export class HttpRouteKey { } } +/** + * Options used when configuring multiple routes, at once. + * The options here are the ones that would be configured for all being set up. + */ +export interface BatchHttpRouteOptions { + /** + * The integration to be configured on this route. + */ + readonly integration: IHttpRouteIntegration; +} + /** * Properties to initialize a new Route */ -export interface HttpRouteProps { +export interface HttpRouteProps extends BatchHttpRouteOptions { /** * the API the route is associated with */ @@ -91,29 +102,8 @@ export interface HttpRouteProps { * The key to this route. This is a combination of an HTTP method and an HTTP path. */ readonly routeKey: HttpRouteKey; - - /** - * The integration to be configured on this route. - */ - readonly integration: IHttpRouteIntegration; } -// /** -// * Options for the Route with Integration resoruce -// */ -// export interface AddRoutesOptions { -// /** -// * HTTP methods -// * @default HttpMethod.ANY -// */ -// readonly methods?: HttpMethod[]; - -// /** -// * The integration for this path -// */ -// readonly integration: Integration; -// } - /** * Route class that creates the Route for API Gateway HTTP API * @resource AWS::ApiGatewayV2::Route @@ -148,18 +138,4 @@ export class HttpRoute extends Resource implements IHttpRoute { const route = new CfnRoute(this, 'Resource', routeProps); this.routeId = route.ref; } - - // public addRoutes(pathPart: string, id: string, options: AddRoutesOptions): Route[] { - // const routes: Route[] = []; - // const methods = options.methods ?? [ HttpMethod.ANY ]; - // for (const m of methods) { - // routes.push(new Route(this, `${id}${m}`, { - // api: this.httpApi, - // integration: options.integration, - // httpMethod: m, - // httpPath: pathPart, - // })); - // } - // return routes; - // } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts index df8f843f7a887..3f89ff312b14f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts @@ -1,7 +1,7 @@ import '@aws-cdk/assert/jest'; import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; import { Stack } from '@aws-cdk/core'; -import { HttpApi, LambdaProxyIntegration } from '../../lib'; +import { HttpApi, HttpMethod, LambdaProxyIntegration } from '../../lib'; describe('HttpApi', () => { test('default', () => { @@ -56,4 +56,52 @@ describe('HttpApi', () => { ApiId: stack.resolve(httpApi.httpApiId), }); }); + + test('addRoutes() configures the correct routes', () => { + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'api'); + + httpApi.addRoutes({ + path: '/pets', + methods: [ HttpMethod.GET, HttpMethod.PATCH ], + integration: new LambdaProxyIntegration({ + handler: new Function(stack, 'fn', { + code: Code.fromInline('foo'), + runtime: Runtime.NODEJS_12_X, + handler: 'index.handler', + }), + }), + }); + + expect(stack).toHaveResourceLike('AWS::ApiGatewayV2::Route', { + ApiId: stack.resolve(httpApi.httpApiId), + RouteKey: 'GET /pets', + }); + + expect(stack).toHaveResourceLike('AWS::ApiGatewayV2::Route', { + ApiId: stack.resolve(httpApi.httpApiId), + RouteKey: 'PATCH /pets', + }); + }); + + test('addRoutes() creates the default method', () => { + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'api'); + + httpApi.addRoutes({ + path: '/pets', + integration: new LambdaProxyIntegration({ + handler: new Function(stack, 'fn', { + code: Code.fromInline('foo'), + runtime: Runtime.NODEJS_12_X, + handler: 'index.handler', + }), + }), + }); + + expect(stack).toHaveResourceLike('AWS::ApiGatewayV2::Route', { + ApiId: stack.resolve(httpApi.httpApiId), + RouteKey: 'ANY /pets', + }); + }); }); \ No newline at end of file From 48aa1af6ff1679e3593ade880c9c6f3e13ca6dc4 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 30 Apr 2020 11:00:57 +0100 Subject: [PATCH 37/42] tests for integrations --- .../test/http/integrations/http.test.ts | 41 +++++++++++++++ .../test/http/integrations/lambda.test.ts | 50 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/http.test.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/lambda.test.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/http.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/http.test.ts new file mode 100644 index 0000000000000..a8d69ac1009d9 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/http.test.ts @@ -0,0 +1,41 @@ +import '@aws-cdk/assert/jest'; +import { Stack } from '@aws-cdk/core'; +import { HttpApi, HttpMethod, HttpProxyIntegration, HttpRoute, HttpRouteKey } from '../../../lib'; + +describe('HttpProxyIntegration', () => { + test('default', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyRoute', { + httpApi: api, + integration: new HttpProxyIntegration({ + url: 'some-target-url', + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + IntegrationUri: 'some-target-url', + PayloadFormatVersion: '1.0', + IntegrationMethod: 'ANY', + }); + }); + + test('method option is correctly recognized', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyRoute', { + httpApi: api, + integration: new HttpProxyIntegration({ + url: 'some-target-url', + method: HttpMethod.PATCH, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationMethod: 'PATCH', + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/lambda.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/lambda.test.ts new file mode 100644 index 0000000000000..9984b8fdd9bff --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/lambda.test.ts @@ -0,0 +1,50 @@ +import '@aws-cdk/assert/jest'; +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import { Stack } from '@aws-cdk/core'; +import { HttpApi, HttpRoute, HttpRouteKey, LambdaProxyIntegration, PayloadFormatVersion } from '../../../lib'; + +describe('LambdaProxyIntegration', () => { + test('default', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + const fooFn = fooFunction(stack, 'Fn'); + new HttpRoute(stack, 'LambdaProxyRoute', { + httpApi: api, + integration: new LambdaProxyIntegration({ + handler: fooFn, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'AWS_PROXY', + IntegrationUri: stack.resolve(fooFn.functionArn), + PayloadFormatVersion: '2.0', + }); + }); + + test('payloadFormatVersion selection', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'LambdaProxyRoute', { + httpApi: api, + integration: new LambdaProxyIntegration({ + handler: fooFunction(stack, 'Fn'), + payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + PayloadFormatVersion: '1.0', + }); + }); +}); + +function fooFunction(stack: Stack, id: string) { + return new Function(stack, id, { + code: Code.fromInline('foo'), + runtime: Runtime.NODEJS_12_X, + handler: 'index.handler', + }); +} \ No newline at end of file From c18a7d0c55ba98baae3e058f7ccceddb4338d172 Mon Sep 17 00:00:00 2001 From: Pahud Date: Mon, 4 May 2020 12:50:59 +0800 Subject: [PATCH 38/42] update README --- packages/@aws-cdk/aws-apigatewayv2/README.md | 223 +++++++------------ 1 file changed, 75 insertions(+), 148 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 7cb44a6aa54da..070325ad43b6d 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -27,196 +27,123 @@ REST APIs can be created using the `@aws-cdk/aws-apigateway` module. HTTP APIs enable creation of RESTful APIs that integrate with AWS Lambda functions, known as Lambda proxy integration, or to any routable HTTP endpoint, known as HTTP proxy integration. -Read more about [Working with AWS Lambda Proxy Integrations for HTTP -APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) -and [Working with HTTP Proxy Integrations for HTTP -APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html). - -Use `HttpApi` to create HTTP APIs with Lambda or HTTP proxy integration. The $default route will be created as well that acts as a catch-all for requests that don’t match any other routes. +Use `HttpApi` to create HTTP APIs with HTTP proxy integration as the `defaultIntegration` ```ts -// Create a vanilla HTTP API with no integration targets -new apigatewayv2.HttpApi(stack, 'HttpApi1'); +new HttpApi(stack, 'HttpProxyApi', { + defaultIntegration: new HttpProxyIntegration({ + url:'http://example.com', + }), +}); +``` + +To create HTTP APIs with Lambda proxy integration as the `defaultIntegration` -// Create a HTTP API with Lambda Proxy Integration as its $default route -const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { - targetHandler: handler +```ts +new HttpApi(stack, 'LambdaProxyApi', { + defaultIntegration: new LambdaProxyIntegration({ + handler, + }), }); +``` + +To create HTTP APIs with no default integration -// Create a HTTP API with HTTP Proxy Integration as its $default route -const httpApi3 = new apigatewayv2.HttpApi(stack, 'HttpApi3', { - targetUrl: checkIpUrl +```ts +new HttpApi(stack, 'Api'); }); ``` + + +Read more about [Working with AWS Lambda Proxy Integrations for HTTP +APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) +and [Working with HTTP Proxy Integrations for HTTP +APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html). + + + ## Route Routes direct incoming API requests to backend resources. Routes consist of two parts: an HTTP method and a resource path—for example, -`GET /pets`. You can define specific HTTP methods for your route, or use the `ANY` method to match all methods that you haven't defined for a resource. +`GET /books`. You can define specific HTTP methods for your route, or use the `ANY` method to match all methods that you haven't defined for a resource. You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See [Working with Routes for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). -When you create HTTP APIs with either `Lambda Proxy Integration` or `HTTP Proxy Integration`, the `$default route` will be created as well. +When you create HTTP APIs with `defaultIntegration`, the `$default route` will be created as well. -## Defining APIs +Use `HttpRoute` to create a `Route` resource for HTTP APIs and `HttpRouteKey` to define your route key. -APIs are defined as a hierarchy of routes. `addLambdaRoute` and `addHttpRoute` can be used to build this hierarchy. The root resource is `api.root`. - -For example, the following code defines an API that includes the following HTTP endpoints: ANY /, GET /books, POST /books, GET /books/{book_id}, DELETE /books/{book_id}. ```ts -const checkIpUrl = 'https://checkip.amazonaws.com'; -const awsUrl = 'https://aws.amazon.com'; - -const api = new apigatewayv2.HttpApi(this, 'HttpApi'); -api.root - -api.root - // HTTP GET /foo - .addLambdaRoute('foo', 'Foo', { - target: handler, - method: apigatewayv2.HttpMethod.GET - }) - // HTTP ANY /foo/aws - .addHttpRoute('aws', 'AwsPage', { - targetUrl: awsUrl, - method: apigatewayv2.HttpMethod.ANY - }) - // HTTP ANY /foo/aws/checkip - .addHttpRoute('checkip', 'CheckIp', { - targetUrl: checkIpUrl, - method: apigatewayv2.HttpMethod.ANY - }); +new HttpRoute(stack, 'HttpRoute', { + httpApi, + integration, + routeKey: HttpRouteKey.with('/books', HttpMethod.GET), +}); ``` -To create a specific route directly rather than building it from the root, just create the `Route` resource with `targetHandler`, `targetUrl` or `integration`. -```ts -// create a specific 'GET /some/very/deep/route/path' route with Lambda proxy integration for an existing HTTP API -const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { - api: httpApi, - httpPath: '/some/very/deep/route/path', - targetHandler -}); +## Stage -// with HTTP proxy integration -const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { - api: httpApi, - httpPath: '/some/very/deep/route/path', - targetUrl -}); +A stage is a named reference to a deployment, which is a snapshot of the API. You use a `Stage` to manage and optimize a particular deployment. -// with existing integration resource -const someDeepRoute = new apigatewayv2.Route(stack, 'SomeDeepRoute', { - api: httpApi, - httpPath: '/some/very/deep/route/path', - integration -}); -``` - -You may also `addLambdaRoute` or `addHttpRoute` from the `HttpAppi` resource. +Use `HttpStage` to create a `Stage` resource for HTTP APIs ```ts -// addLambdaRoute or addHttpRoute from the HttpApi resource -httpApi.addLambdaRoute('/foo/bar', 'FooBar', { - target: handler, - method: apigatewayv2.HttpMethod.GET -}); - -httpApi.addHttpRoute('/foo/bar', 'FooBar', { - targetUrl: awsUrl, - method: apigatewayv2.HttpMethod.ANY +new HttpStage(stack, 'Stage', { + httpApi: api, + stageName: 'beta', }); ``` -## Integration - -Integrations connect a route to backend resources. HTTP APIs support Lambda proxy and HTTP proxy integrations. -For example, you can configure a POST request to the /signup route of your API to integrate with a Lambda function -that handles signing up customers. +If you omit the `stageName`, the `$default` stage will be created. -Use `Integration` to create the integration resources +Please note when you create `HttpApi` resource, the `$default` stage will be created as well unless you set `createDefaultStage` to `false`. ```ts -// create API -const api = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetHandler +// create HttpApi without $default stage +const api = new HttpApi(stack, 'Api', { + createDefaultStage: false, }); -// create Integration -const integ = new apigatewayv2.Integration(stack, 'IntegRootHandler', { - apiId: api.httpApiId, - integrationMethod: apigatewayv2.HttpMethod.ANY, - integrationType: apigatewayv2.IntegrationType.HTTP_PROXY, - integrationUri: awsUrl -}); -// create a specific route with the integration above for the API -new apigatewayv2.Route(stack, 'SomeDeepRoute', { - api: httpApi, - httpPath: '/some/very/deep/route/path', - integration + +// create the $default stage for it +new HttpStage(stack, 'Stage', { + httpApi: api, }); ``` -You may use `LambdaProxyIntegraion` or `HttpProxyIntegration` to easily create the integrations. +## Defining APIs + +APIs are defined as a hierarchy of routes. `addRoutes` can be used to build this hierarchy. + +For example, the following code defines an API that includes the following HTTP endpoints: `ANY /`, `GET /books`, `POST /books`, `GET /books/{book_id}` and `DELETE /books/{book_id}`. ```ts -// create a Lambda proxy integration -new apigatewayv2.LambdaProxyIntegration(stack, 'IntegRootHandler', { - api - targetHandler +const httpApi = new HttpApi(this, 'HttpApi', { + defaultIntegration: new LambdaProxyIntegration({ + rootHandler, + }), }); -// or create a Http proxy integration -new apigatewayv2.HttpProxyIntegration(stack, 'IntegRootHandler', { - api - targetUrl +// GET or POST /books +httpApi.addRoutes({ + path: '/books', + methods: [ HttpMethod.GET, HttpMethod.POST ], + integration: new LambdaProxyIntegration({ + handler, + }), }); -``` -## Samples -```ts -// create a HTTP API with HTTP proxy integration as the $default route -const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { - targetUrl: checkIpUrl +// GET or DELETE {book_id} +httpApi.addRoutes({ + path: '/books/{book_id}', + methods: [ HttpMethod.GET, HttpMethod.DELETE ], + integration: new LambdaProxyIntegration({ + handler, + }), }); -// print the API URL -new cdk.CfnOutput(stack, 'URL', { value: httpApi.url} ); -// create another HTTP API with Lambda proxy integration as the $default route -const httpApi2 = new apigatewayv2.HttpApi(stack, 'HttpApi2', { - targetHandler: handler -}); -// print the API URL -new cdk.CfnOutput(stack, 'URL2', { value: httpApi2.url }); - -// create a root route for the API with the integration we created above and assign the route resource -// as a 'root' property to the API -httpApi2.root = httpApi2.addLambdaRoute('/', 'RootRoute', { - target: rootHandler -}) - -// Now, extend the route tree from the root -httpApi2.root - // HTTP ANY /foo - .addLambdaRoute('foo', 'Foo', { - target: handler, - }) - // HTTP ANY /foo/aws - .addHttpRoute('aws', 'AwsPage', { - targetUrl: awsUrl, - method: apigatewayv2.HttpMethod.ANY - }) - // HTTP GET /foo/aws/checkip - .addHttpRoute('checkip', 'CheckIp', { - targetUrl: checkIpUrl, - method: apigatewayv2.HttpMethod.GET - }); - -// And create a specific route for it as well -// HTTP ANY /some/very/deep/route/path -httpApi2.addLambdaRoute('/some/very/deep/route/path', 'FooBar', { - target: handler -}); -``` +``` \ No newline at end of file From 8ded78b6c7d5d8f2c97a8bdd58651b3a7769810e Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Mon, 4 May 2020 13:44:49 +0800 Subject: [PATCH 39/42] Update README.md --- packages/@aws-cdk/aws-apigatewayv2/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 070325ad43b6d..27589881a2c10 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -6,11 +6,13 @@ > All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + --- -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. - ## Introduction Amazon API Gateway is an AWS service for creating, publishing, maintaining, monitoring, and securing REST, HTTP, and WebSocket @@ -146,4 +148,4 @@ httpApi.addRoutes({ }), }); -``` \ No newline at end of file +``` From df21f0fc49b9fd4882bc5f5ac80ebee584fdf57f Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Mon, 4 May 2020 13:58:42 +0800 Subject: [PATCH 40/42] Update package.json --- packages/@aws-cdk/aws-apigatewayv2/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 7f7ccfdcc0e44..b8300d6269048 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -110,7 +110,7 @@ ] }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false } From b808736718d1574d1d01574c81ad7ec3662b7a7f Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 4 May 2020 10:26:28 +0100 Subject: [PATCH 41/42] language and structural adjustments to the README --- packages/@aws-cdk/aws-apigatewayv2/README.md | 145 +++++++------------ 1 file changed, 52 insertions(+), 93 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 27589881a2c10..49e9530404a56 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -1,4 +1,5 @@ ## AWS::APIGatewayv2 Construct Library + --- @@ -13,6 +14,13 @@ --- +## Table of Contents + +- [Introduction](#introduction) +- [HTTP API](#http-api) + - [Defining HTTP APIs](#defining-http-apis) + - [Publishing HTTP APIs](#publishing-http-apis) + ## Introduction Amazon API Gateway is an AWS service for creating, publishing, maintaining, monitoring, and securing REST, HTTP, and WebSocket @@ -29,123 +37,74 @@ REST APIs can be created using the `@aws-cdk/aws-apigateway` module. HTTP APIs enable creation of RESTful APIs that integrate with AWS Lambda functions, known as Lambda proxy integration, or to any routable HTTP endpoint, known as HTTP proxy integration. -Use `HttpApi` to create HTTP APIs with HTTP proxy integration as the `defaultIntegration` +### Defining HTTP APIs -```ts -new HttpApi(stack, 'HttpProxyApi', { - defaultIntegration: new HttpProxyIntegration({ - url:'http://example.com', - }), -}); -``` +HTTP APIs have two fundamental concepts - Routes and Integrations. -To create HTTP APIs with Lambda proxy integration as the `defaultIntegration` +Routes direct incoming API requests to backend resources. Routes consist of two parts: an HTTP method and a resource +path, such as, `GET /books`. Learn more at [Working with +routes](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). Use the `ANY` method +to match any methods for a route that are not explicitly defined. -```ts -new HttpApi(stack, 'LambdaProxyApi', { - defaultIntegration: new LambdaProxyIntegration({ - handler, - }), -}); -``` +Integrations define how the HTTP API responds when a client reaches a specific Route. HTTP APIs support two types of +integrations - Lambda proxy integration and HTTP proxy integration. Learn more at [Configuring +integrations](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations.html). -To create HTTP APIs with no default integration +The code snippet below configures a route `GET /books` with an HTTP proxy integration and uses the `ANY` method to +proxy all other HTTP method calls to `/books` to a lambda proxy. ```ts -new HttpApi(stack, 'Api'); +const getBooksIntegration = new HttpProxyIntegration({ + url: 'https://get-books-proxy.myproxy.internal', }); -``` - +const booksDefaultFn = new lambda.Function(stack, 'BooksDefaultFn', { ... }); +const booksDefaultIntegration = new LambdaProxyIntegration({ + handler: booksDefaultFn, +}); -Read more about [Working with AWS Lambda Proxy Integrations for HTTP -APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) -and [Working with HTTP Proxy Integrations for HTTP -APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html). - - - -## Route - -Routes direct incoming API requests to backend resources. Routes consist of two parts: an HTTP method and a resource path—for example, -`GET /books`. You can define specific HTTP methods for your route, or use the `ANY` method to match all methods that you haven't defined for a resource. -You can create a `$default route` that acts as a catch-all for requests that don’t match any other routes. See -[Working with Routes for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html). - -When you create HTTP APIs with `defaultIntegration`, the `$default route` will be created as well. - -Use `HttpRoute` to create a `Route` resource for HTTP APIs and `HttpRouteKey` to define your route key. - +const httpApi = new HttpApi(stack, 'HttpApi'); -```ts -new HttpRoute(stack, 'HttpRoute', { - httpApi, - integration, - routeKey: HttpRouteKey.with('/books', HttpMethod.GET), +httpApi.addRoutes({ + path: '/books', + methods: [ HttpMethod.GET ], + integration: getBooksIntegration, +}); +httpApi.addRoutes({ + path: '/books', + methods: [ HttpMethod.ANY ], + integration: booksDefaultIntegration, }); ``` - -## Stage - -A stage is a named reference to a deployment, which is a snapshot of the API. You use a `Stage` to manage and optimize a particular deployment. - -Use `HttpStage` to create a `Stage` resource for HTTP APIs +The `defaultIntegration` option while defining HTTP APIs lets you create a default catch-all integration that is +matched when a client reaches a route that is not explicitly defined. ```ts -new HttpStage(stack, 'Stage', { - httpApi: api, - stageName: 'beta', +new HttpApi(stack, 'HttpProxyApi', { + defaultIntegration: new HttpProxyIntegration({ + url:'http://example.com', + }), }); ``` -If you omit the `stageName`, the `$default` stage will be created. +### Publishing HTTP APIs -Please note when you create `HttpApi` resource, the `$default` stage will be created as well unless you set `createDefaultStage` to `false`. +A Stage is a logical reference to a lifecycle state of your API (for example, `dev`, `prod`, `beta`, or `v2`). API +stages are identified by their stage name. Each stage is a named reference to a deployment of the API made available for +client applications to call. -```ts -// create HttpApi without $default stage -const api = new HttpApi(stack, 'Api', { - createDefaultStage: false, -}); +Use `HttpStage` to create a Stage resource for HTTP APIs. The following code sets up a Stage, whose URL is available at +`https://{api_id}.execute-api.{region}.amazonaws.com/beta`. -// create the $default stage for it +```ts new HttpStage(stack, 'Stage', { httpApi: api, + stageName: 'beta', }); - ``` -## Defining APIs - -APIs are defined as a hierarchy of routes. `addRoutes` can be used to build this hierarchy. +If you omit the `stageName` will create a `$default` stage. A `$default` stage is one that is served from the base of +the API's URL - `https://{api_id}.execute-api.{region}.amazonaws.com/`. -For example, the following code defines an API that includes the following HTTP endpoints: `ANY /`, `GET /books`, `POST /books`, `GET /books/{book_id}` and `DELETE /books/{book_id}`. - -```ts -const httpApi = new HttpApi(this, 'HttpApi', { - defaultIntegration: new LambdaProxyIntegration({ - rootHandler, - }), -}); - -// GET or POST /books -httpApi.addRoutes({ - path: '/books', - methods: [ HttpMethod.GET, HttpMethod.POST ], - integration: new LambdaProxyIntegration({ - handler, - }), -}); - - -// GET or DELETE {book_id} -httpApi.addRoutes({ - path: '/books/{book_id}', - methods: [ HttpMethod.GET, HttpMethod.DELETE ], - integration: new LambdaProxyIntegration({ - handler, - }), -}); - -``` +Note that, `HttpApi` will always creates a `$default` stage, unless the `createDefaultStage` property is unset. From 7cc71e09f000007805f3b4172bbd925491287fd4 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 4 May 2020 15:37:05 +0100 Subject: [PATCH 42/42] adjust http integration --- .../aws-apigatewayv2/lib/http/integration.ts | 24 +++++++++---------- .../@aws-cdk/aws-apigatewayv2/package.json | 1 + 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index 20f8b90416735..7e9672ed5a786 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -4,6 +4,14 @@ import { IIntegration } from '../common'; import { IHttpApi } from './api'; import { HttpMethod, IHttpRoute } from './route'; +/** + * Represents an Integration for an HTTP API. + */ +export interface IHttpIntegration extends IIntegration { + /** The HTTP API associated with this integration */ + readonly httpApi: IHttpApi; +} + /** * Supported integration types */ @@ -85,20 +93,11 @@ export interface HttpIntegrationProps { * The integration for an API route. * @resource AWS::ApiGatewayV2::Integration */ -export class HttpIntegration extends Resource implements IIntegration { - /** - * Import an existing integration using integration id - */ - public static fromIntegrationId(scope: Construct, id: string, integrationId: string): IIntegration { - class Import extends Resource implements IIntegration { - public readonly integrationId = integrationId; - } - - return new Import(scope, id); - } - +export class HttpIntegration extends Resource implements IHttpIntegration { public readonly integrationId: string; + public readonly httpApi: IHttpApi; + constructor(scope: Construct, id: string, props: HttpIntegrationProps) { super(scope, id); const integ = new CfnIntegration(this, 'Resource', { @@ -109,6 +108,7 @@ export class HttpIntegration extends Resource implements IIntegration { payloadFormatVersion: props.payloadFormatVersion?.version, }); this.integrationId = integ.ref; + this.httpApi = props.httpApi; } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index b8300d6269048..bfb1d13b0f29f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -103,6 +103,7 @@ }, "awslint": { "exclude": [ + "from-method:@aws-cdk/aws-apigatewayv2.HttpIntegration", "from-method:@aws-cdk/aws-apigatewayv2.HttpRoute", "props-physical-name-type:@aws-cdk/aws-apigatewayv2.HttpStageProps.stageName", "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpIntegrationProps",