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 11 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
47 changes: 47 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,52 @@ 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 values which does not yet have any helper functions, you can use the custom methods:
nija-at marked this conversation as resolved.
Show resolved Hide resolved

```ts
const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', {
defaultIntegration: new HttpAlbIntegration({
listener,
requestParameters: new RequestParameters()
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be ParameterMapping?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, fixed

Copy link
Contributor

Choose a reason for hiding this comment

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

same here

.addParameter({
mappingKey: MappingKey.custom('new.header'),
mappingValue: MappingValue.custom('new.value'),
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just

new ParameterMapping().custom('new.header', MappingValue.custom('new.value'))?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Copy link
Contributor

@nija-at nija-at Sep 14, 2021

Choose a reason for hiding this comment

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

But it's not fixed though? It's still the same in the README.

EDIT: Ahh, I see you fixed the API but not the README,

}),
});
```


## WebSocket APIs

WebSocket integrations connect a route to backend resources. The following integrations are supported in the CDK.
Expand Down
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
67 changes: 67 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,68 @@ export class PayloadFormatVersion {
}
}

export interface IMappingValue {
readonly value: string;
};

export class MappingValue implements IMappingValue {
public static readonly NONE = new MappingValue('');
public static requestHeader(name: string) { return new MappingValue(`$request.header.${name}`); }
public static requestQueryString(name: string) { return new MappingValue(`$request.querystring.${name}`); }
public static requestBody(name: string) { return new MappingValue(`$request.body.${name}`); }
public static requestPathFull() {return new MappingValue('$request.path'); }
public static requestPath(name: string) { return new MappingValue(`$request.path.${name}`); }
public static contextVariable(variableName: string) { return new MappingValue(`$context.${variableName}`); }
public static stageVariablesVariable(variableName: string) { return new MappingValue(`$stageVariables.${variableName}`); }
public static custom(value: string) {return new MappingValue(value); }
protected constructor(public readonly value: string) {}
}

export class ParameterMapping {
Copy link
Contributor

Choose a reason for hiding this comment

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

Move all of these classes and interfaces into their own file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if I still should? Only one class left

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I think you still should.

Copy link
Contributor

Choose a reason for hiding this comment

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

These should also move into the base apigateway module.

public readonly mappings: {[key: string]: string}
constructor() {
this.mappings = {};
}

public appendHeader(name: string, value: MappingValue): ParameterMapping {
this.mappings[`append:header.${name}`] = value.value;
return this;
}

public overwriteHeader(name: string, value: MappingValue): ParameterMapping {
this.mappings[`overwrite:header.${name}`] = value.value;
return this;
}

public removeHeader(name: string): ParameterMapping {
this.mappings[`remove:header.${name}`] = '';
return this;
}

public appendQueryString(name: string, value: MappingValue): ParameterMapping {
this.mappings[`append:querystring.${name}`] = value.value;
return this;
}
public overwriteQueryString(name: string, value: MappingValue): ParameterMapping {
this.mappings[`overwrite:querystring.${name}`] = value.value;
return this;
}
public removeQueryString(name: string): ParameterMapping {
this.mappings[`remove:querystring.${name}`] = '';
return this;
}

public overwritePath(value: MappingValue): ParameterMapping {
this.mappings['overwrite:path'] = value.value;
return this;
}

public custom(key: string, value:string): ParameterMapping {
this.mappings[key] = value;
return this;
}
}

/**
* The integration properties
*/
Expand Down Expand Up @@ -128,6 +190,8 @@ export interface HttpIntegrationProps {
* @default undefined private integration traffic will use HTTP protocol
*/
readonly secureServerName?: string;

readonly parameterMapping?: ParameterMapping;
}

/**
Expand All @@ -149,6 +213,7 @@ export class HttpIntegration extends Resource implements IHttpIntegration {
connectionId: props.connectionId,
connectionType: props.connectionType,
payloadFormatVersion: props.payloadFormatVersion?.version,
requestParameters: props.parameterMapping?.mappings,
});

if (props.secureServerName) {
Expand Down Expand Up @@ -237,4 +302,6 @@ export interface HttpRouteIntegrationConfig {
* @default undefined private integration traffic will use HTTP protocol
*/
readonly secureServerName?: string;

readonly parameterMapping?: ParameterMapping;
}
49 changes: 47 additions & 2 deletions packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Template } from '@aws-cdk/assertions';
import { Stack, App } from '@aws-cdk/core';
import {
HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteAuthorizerBindOptions,
HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, IHttpRouteIntegration, PayloadFormatVersion,
HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute,
HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, IHttpRouteIntegration,
MappingValue, ParameterMapping, PayloadFormatVersion,
} from '../../lib';

describe('HttpRoute', () => {
Expand Down Expand Up @@ -174,6 +175,9 @@ describe('HttpRoute', () => {
connectionType: HttpConnectionType.VPC_LINK,
uri: 'some-target-arn',
secureServerName: 'some-server-name',
parameterMapping: new ParameterMapping()
.appendHeader('header2', MappingValue.requestHeader('header1'))
.removeHeader('header1'),
};
}
}
Expand Down Expand Up @@ -201,6 +205,47 @@ describe('HttpRoute', () => {
Template.fromStack(stack).resourceCountIs('AWS::ApiGatewayV2::VpcLink', 0);
});

test('configures private integration correctly when parameter mappings are passed', () => {
// GIVEN
const stack = new Stack();
const httpApi = new HttpApi(stack, 'HttpApi');

class PrivateIntegration implements IHttpRouteIntegration {
public bind(): HttpRouteIntegrationConfig {
return {
method: HttpMethod.ANY,
payloadFormatVersion: PayloadFormatVersion.VERSION_1_0,
type: HttpIntegrationType.HTTP_PROXY,
uri: 'some-target-arn',
parameterMapping: new ParameterMapping()
.appendHeader('header2', MappingValue.requestHeader('header1'))
.removeHeader('header1'),
};
}
}

// WHEN
new HttpRoute(stack, 'HttpRoute', {
httpApi,
integration: new PrivateIntegration(),
routeKey: HttpRouteKey.with('/books', HttpMethod.GET),
});

// THEN
expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', {
IntegrationType: 'HTTP_PROXY',
IntegrationMethod: 'ANY',
IntegrationUri: 'some-target-arn',
PayloadFormatVersion: '1.0',
RequestParameters: {
'append:header.header2': '$request.header.header1',
'remove:header.header1': '',
},
});

expect(stack).not.toHaveResource('AWS::ApiGatewayV2::VpcLink');
});

test('can create route with an authorizer attached', () => {
const stack = new Stack();
const httpApi = new HttpApi(stack, 'HttpApi');
Expand Down