Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(apigatewayv2-integrations): http api - support for request parameter mapping #15630

Merged
merged 23 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1886a8b
Add parameter mapping support, readme update
Jul 18, 2021
3f2078f
Update packages/@aws-cdk/aws-apigatewayv2-integrations/README.md
dan-lind Jul 29, 2021
6703f2c
Update packages/@aws-cdk/aws-apigatewayv2-integrations/README.md
dan-lind Jul 29, 2021
1e2c799
Update packages/@aws-cdk/aws-apigatewayv2-integrations/README.md
dan-lind Jul 29, 2021
6d3aa5f
Merge branch 'master' into add-requestparams-support-apigatewayv2
dan-lind Jul 29, 2021
8b36ecc
Update packages/@aws-cdk/aws-apigatewayv2-integrations/README.md
dan-lind Jul 30, 2021
0c81a2a
Fix ParameterMapping example
Jul 30, 2021
b1f6d15
Add first unit test
Aug 5, 2021
e5d5374
Remove logging
Aug 5, 2021
fbc06b8
Simplyfy API
Aug 24, 2021
4ce0dc6
Merge branch 'master' into add-requestparams-support-apigatewayv2
dan-lind Aug 24, 2021
97df41e
Merge branch 'master' into add-requestparams-support-apigatewayv2
dan-lind Oct 3, 2021
8856e00
Fix README, move parameter-mapping to separate class
Oct 3, 2021
0dce9a1
Merge branch 'add-requestparams-support-apigatewayv2' of https://gith…
Oct 3, 2021
ed2c1f3
Fix README, move parameter-mapping to separate class
Oct 3, 2021
8da3978
Implementation for higher level constructs, docs update, unit tests
Oct 4, 2021
b3d1963
Fix imports
Oct 4, 2021
8d3abb6
Merge branch 'master' into add-requestparams-support-apigatewayv2
dan-lind Oct 4, 2021
42ef243
Apply suggestions from code review
Oct 13, 2021
74a4ce1
Apply suggestions from code review
Oct 13, 2021
3ddef7d
Merge branch 'master' into add-requestparams-support-apigatewayv2
dan-lind Oct 13, 2021
bc284d5
Merge branch 'master' into add-requestparams-support-apigatewayv2
dan-lind Oct 13, 2021
69cef43
Merge branch 'master' into add-requestparams-support-apigatewayv2
mergify[bot] Oct 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-integrations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [Lambda Integration](#lambda)
- [HTTP Proxy Integration](#http-proxy)
- [Private Integration](#private-integration)
- [Request Parameters](#request-parameters)
- [WebSocket APIs](#websocket-apis)
- [Lambda WebSocket Integration](#lambda-websocket-integration)

Expand Down Expand Up @@ -149,6 +150,48 @@ const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', {
});
```

### Request Parameters

dan-lind marked this conversation as resolved.
Show resolved Hide resolved
Request parameter mapping allows API requests from clients to be modified before they reach backend integrations.
To use parameter mapping, you specify API request parameters to modify, and specify how to modify the parameters.

Request parameter mapping is supported through a set of helper methods.
The following example renames a request header from header1 to header2 by generating mappings
`append:header.header2: $request.header.header1` and `remove:header.header1: ''`:
nija-at marked this conversation as resolved.
Show resolved Hide resolved

```ts
const vpc = new ec2.Vpc(stack, 'VPC');
const lb = new elbv2.ALB(stack, 'lb', { vpc });
const listener = lb.addListener('listener', { port: 80 });
listener.addTargets('target', {
port: 80,
});

const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', {
defaultIntegration: new HttpAlbIntegration({
listener,
requestParameters: new ParameterMapping()
.appendHeader('header2', MappingValue.header('header1'))
.removeHeader('header1');
}),
}),
});
nija-at marked this conversation as resolved.
Show resolved Hide resolved
```

To use keys and values which does not yet have any helper functions, you can use the custom method:
nija-at marked this conversation as resolved.
Show resolved Hide resolved

```ts
const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', {
defaultIntegration: new HttpAlbIntegration({
listener,
requestParameters: new ParameterMapping()
.custom('myKey', 'myValue'),
}),
}),
});
```


## WebSocket APIs

WebSocket integrations connect a route to backend resources. The following integrations are supported in the CDK.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class HttpAlbIntegration extends HttpPrivateIntegration {
connectionId: vpcLink.vpcLinkId,
uri: this.props.listener.listenerArn,
secureServerName: this.props.secureServerName,
parameterMapping: this.props.parameterMapping,
};
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpMethod, IVpcLink } from '@aws-cdk/aws-apigatewayv2';
import { HttpMethod, IVpcLink, ParameterMapping } from '@aws-cdk/aws-apigatewayv2';

/**
* Base options for private integration
Expand All @@ -24,4 +24,11 @@ export interface HttpPrivateIntegrationOptions {
*/

readonly secureServerName?: string;

/**
* Specifies how to transform HTTP requests before sending them to the backend
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html
* @default undefined requests are sent to the backend unmodified
*/
readonly parameterMapping?: ParameterMapping;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
HttpRouteIntegrationConfig,
HttpMethod,
IHttpRouteIntegration,
ParameterMapping,
PayloadFormatVersion,
} from '@aws-cdk/aws-apigatewayv2';

Expand All @@ -21,6 +22,13 @@ export interface HttpProxyIntegrationProps {
* @default HttpMethod.ANY
*/
readonly method?: HttpMethod;

/**
* Specifies how to transform HTTP requests before sending them to the backend
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html
* @default undefined requests are sent to the backend unmodified
*/
readonly parameterMapping?: ParameterMapping;
}

/**
Expand All @@ -36,6 +44,7 @@ export class HttpProxyIntegration implements IHttpRouteIntegration {
payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, // 1.0 is required and is the only supported format
type: HttpIntegrationType.HTTP_PROXY,
uri: this.props.url,
parameterMapping: this.props.parameterMapping,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
HttpRouteIntegrationConfig,
IHttpRouteIntegration,
PayloadFormatVersion,
ParameterMapping,
} from '@aws-cdk/aws-apigatewayv2';
import { ServicePrincipal } from '@aws-cdk/aws-iam';
import { IFunction } from '@aws-cdk/aws-lambda';
Expand All @@ -24,6 +25,13 @@ export interface LambdaProxyIntegrationProps {
* @default PayloadFormatVersion.VERSION_2_0
*/
readonly payloadFormatVersion?: PayloadFormatVersion;

/**
* Specifies how to transform HTTP requests before sending them to the backend
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html
* @default undefined requests are sent to the backend unmodified
*/
readonly parameterMapping?: ParameterMapping;
}

/**
Expand All @@ -50,6 +58,7 @@ export class LambdaProxyIntegration implements IHttpRouteIntegration {
type: HttpIntegrationType.LAMBDA_PROXY,
uri: this.props.handler.functionArn,
payloadFormatVersion: this.props.payloadFormatVersion ?? PayloadFormatVersion.VERSION_2_0,
parameterMapping: this.props.parameterMapping,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class HttpNlbIntegration extends HttpPrivateIntegration {
connectionId: vpcLink.vpcLinkId,
uri: this.props.listener.listenerArn,
secureServerName: this.props.secureServerName,
parameterMapping: this.props.parameterMapping,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class HttpServiceDiscoveryIntegration extends HttpPrivateIntegration {
connectionId: this.props.vpcLink.vpcLinkId,
uri: this.props.service.serviceArn,
secureServerName: this.props.secureServerName,
parameterMapping: this.props.parameterMapping,
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Template } from '@aws-cdk/assertions';
import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2';
import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink, ParameterMapping, MappingValue } from '@aws-cdk/aws-apigatewayv2';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
import { Stack } from '@aws-cdk/core';
Expand Down Expand Up @@ -143,4 +143,34 @@ describe('HttpAlbIntegration', () => {
},
});
});

test('parameterMapping option is correctly recognized', () => {
// GIVEN
const stack = new Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const lb = new elbv2.ApplicationLoadBalancer(stack, 'lb', { vpc });
const listener = lb.addListener('listener', { port: 80 });
listener.addTargets('target', { port: 80 });

// WHEN
const api = new HttpApi(stack, 'HttpApi');
new HttpRoute(stack, 'HttpProxyPrivateRoute', {
httpApi: api,
integration: new HttpAlbIntegration({
listener,
parameterMapping: new ParameterMapping()
.appendHeader('header2', MappingValue.requestHeader('header1'))
.removeHeader('header1'),
}),
routeKey: HttpRouteKey.with('/pets'),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', {
RequestParameters: {
'append:header.header2': '$request.header.header1',
'remove:header.header1': '',
},
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Template } from '@aws-cdk/assertions';
import { HttpApi, HttpIntegration, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteKey, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
import { HttpApi, HttpIntegration, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
import { Stack } from '@aws-cdk/core';
import { HttpProxyIntegration } from '../../lib';

Expand Down Expand Up @@ -71,4 +71,26 @@ describe('HttpProxyIntegration', () => {
IntegrationUri: 'some-target-url',
});
});

test('parameterMapping is correctly recognized', () => {
const stack = new Stack();
const api = new HttpApi(stack, 'HttpApi');
new HttpIntegration(stack, 'HttpInteg', {
httpApi: api,
integrationType: HttpIntegrationType.HTTP_PROXY,
integrationUri: 'some-target-url',
parameterMapping: new ParameterMapping()
.appendHeader('header2', MappingValue.requestHeader('header1'))
.removeHeader('header1'),
});

Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', {
IntegrationType: 'HTTP_PROXY',
IntegrationUri: 'some-target-url',
RequestParameters: {
'append:header.header2': '$request.header.header1',
'remove:header.header1': '',
},
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Template } from '@aws-cdk/assertions';
import { HttpApi, HttpRoute, HttpRouteKey, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
import { HttpApi, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
import { Code, Function, Runtime } from '@aws-cdk/aws-lambda';
import { App, Stack } from '@aws-cdk/core';
import { LambdaProxyIntegration } from '../../lib';
Expand Down Expand Up @@ -41,6 +41,28 @@ describe('LambdaProxyIntegration', () => {
});
});

test('parameterMapping selection', () => {
const stack = new Stack();
const api = new HttpApi(stack, 'HttpApi');
new HttpRoute(stack, 'LambdaProxyRoute', {
httpApi: api,
integration: new LambdaProxyIntegration({
handler: fooFunction(stack, 'Fn'),
parameterMapping: new ParameterMapping()
.appendHeader('header2', MappingValue.requestHeader('header1'))
.removeHeader('header1'),
}),
routeKey: HttpRouteKey.with('/pets'),
});

Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', {
RequestParameters: {
'append:header.header2': '$request.header.header1',
'remove:header.header1': '',
},
});
});

test('no dependency cycles', () => {
const app = new App();
const lambdaStack = new Stack(app, 'lambdaStack');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Template } from '@aws-cdk/assertions';
import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2';
import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, VpcLink } from '@aws-cdk/aws-apigatewayv2';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
import { Stack } from '@aws-cdk/core';
Expand Down Expand Up @@ -140,4 +140,34 @@ describe('HttpNlbIntegration', () => {
},
});
});

test('paramaterMapping option is correctly recognized', () => {
// GIVEN
const stack = new Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const lb = new elbv2.NetworkLoadBalancer(stack, 'lb', { vpc });
const listener = lb.addListener('listener', { port: 80 });
listener.addTargets('target', { port: 80 });

// WHEN
const api = new HttpApi(stack, 'HttpApi');
new HttpRoute(stack, 'HttpProxyPrivateRoute', {
httpApi: api,
integration: new HttpNlbIntegration({
listener,
parameterMapping: new ParameterMapping()
.appendHeader('header2', MappingValue.requestHeader('header1'))
.removeHeader('header1'),
}),
routeKey: HttpRouteKey.with('/pets'),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', {
RequestParameters: {
'append:header.header2': '$request.header.header1',
'remove:header.header1': '',
},
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Template } from '@aws-cdk/assertions';
import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2';
import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, VpcLink } from '@aws-cdk/aws-apigatewayv2';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as servicediscovery from '@aws-cdk/aws-servicediscovery';
import { Stack } from '@aws-cdk/core';
Expand Down Expand Up @@ -125,4 +125,38 @@ describe('HttpServiceDiscoveryIntegration', () => {
},
});
});

test('parameterMapping option is correctly recognized', () => {
// GIVEN
const stack = new Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const vpcLink = new VpcLink(stack, 'VpcLink', { vpc });
const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', {
name: 'foobar.com',
vpc,
});
const service = namespace.createService('Service');

// WHEN
const api = new HttpApi(stack, 'HttpApi');
new HttpRoute(stack, 'HttpProxyPrivateRoute', {
httpApi: api,
integration: new HttpServiceDiscoveryIntegration({
vpcLink,
service,
parameterMapping: new ParameterMapping()
.appendHeader('header2', MappingValue.requestHeader('header1'))
.removeHeader('header1'),
}),
routeKey: HttpRouteKey.with('/pets'),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', {
RequestParameters: {
'append:header.header2': '$request.header.header1',
'remove:header.header1': '',
},
});
});
});
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ abstract class HttpApiBase extends ApiBase implements IHttpApi { // note that th
connectionType: config.connectionType,
payloadFormatVersion: config.payloadFormatVersion,
secureServerName: config.secureServerName,
parameterMapping: config.parameterMapping,
});
this._integrationCache.saveIntegration(scope, config, integration);

Expand Down
Loading