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): add missing WebSocketIntegration props #29566

Merged
merged 9 commits into from
Mar 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import {
WebSocketRouteIntegrationConfig,
WebSocketRouteIntegrationBindOptions,
PassthroughBehavior,
ContentHandling,
} from '../../../aws-apigatewayv2';
import { IRole } from '../../../aws-iam';
import { Duration } from '../../../core';

/**
* Props for AWS type integration for an HTTP Api.
* Props for AWS type integration for a WebSocket Api.
*/
export interface WebSocketAwsIntegrationProps {
/**
Expand All @@ -21,6 +23,14 @@ export interface WebSocketAwsIntegrationProps {
*/
readonly integrationMethod: string;

/**
* Specifies how to handle response payload content type conversions.
*
* @default - The response payload will be passed through from the integration response to
* the route response or method response without modification.
*/
readonly contentHandling?: ContentHandling;

/**
* Specifies the credentials role required for the integration.
*
Expand Down Expand Up @@ -56,6 +66,14 @@ export interface WebSocketAwsIntegrationProps {
*/
readonly templateSelectionExpression?: string;

/**
* The maximum amount of time an integration will run before it returns without a response.
* Must be between 50 milliseconds and 29 seconds.
*
* @default Duration.seconds(29)
*/
readonly timeout?: Duration;

/**
* Specifies the pass-through behavior for incoming requests based on the
* Content-Type header in the request, and the available mapping templates
Expand Down Expand Up @@ -84,11 +102,13 @@ export class WebSocketAwsIntegration extends WebSocketRouteIntegration {
type: WebSocketIntegrationType.AWS,
uri: this.props.integrationUri,
method: this.props.integrationMethod,
contentHandling: this.props.contentHandling,
credentialsRole: this.props.credentialsRole,
requestParameters: this.props.requestParameters,
requestTemplates: this.props.requestTemplates,
passthroughBehavior: this.props.passthroughBehavior,
templateSelectionExpression: this.props.templateSelectionExpression,
timeout: this.props.timeout,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@ import {
} from '../../../aws-apigatewayv2';
import { ServicePrincipal } from '../../../aws-iam';
import { IFunction } from '../../../aws-lambda';
import { Stack } from '../../../core';
import { Duration, Stack } from '../../../core';

/**
* Props for Lambda type integration for a WebSocket Api.
*/
export interface WebSocketLambdaIntegrationProps {
/**
* The maximum amount of time an integration will run before it returns without a response.
* Must be between 50 milliseconds and 29 seconds.
*
* @default Duration.seconds(29)
*/
readonly timeout?: Duration;
}

/**
* Lambda WebSocket Integration
Expand All @@ -20,7 +33,11 @@ export class WebSocketLambdaIntegration extends WebSocketRouteIntegration {
* @param handler the Lambda function handler
* @param props properties to configure the integration
*/
constructor(id: string, private readonly handler: IFunction) {
constructor(
id: string,
private readonly handler: IFunction,
private readonly props: WebSocketLambdaIntegrationProps = {},
) {
super(id);
this._id = id;
}
Expand All @@ -47,6 +64,7 @@ export class WebSocketLambdaIntegration extends WebSocketRouteIntegration {
return {
type: WebSocketIntegrationType.AWS_PROXY,
uri: integrationUri,
timeout: this.props.timeout,
};
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { WebSocketAwsIntegration } from './../../lib/websocket/aws';
import { Template } from '../../../assertions';
import { WebSocketApi } from '../../../aws-apigatewayv2';
import { Stack } from '../../../core';
import { ContentHandling, PassthroughBehavior, WebSocketApi } from '../../../aws-apigatewayv2';
import * as iam from '../../../aws-iam';
import { Duration, Stack } from '../../../core';

describe('MockWebSocketIntegration', () => {
describe('AwsWebSocketIntegration', () => {
test('default', () => {
// GIVEN
const stack = new Stack();
Expand All @@ -25,4 +26,41 @@ describe('MockWebSocketIntegration', () => {
IntegrationMethod: 'POST',
});
});

test('can set custom properties', () => {
// GIVEN
const stack = new Stack();
const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') });

// WHEN
new WebSocketApi(stack, 'Api', {
defaultRouteOptions: {
integration: new WebSocketAwsIntegration('AwsIntegration', {
integrationUri: 'arn:aws:apigateway:us-west-2:dynamodb:action/PutItem',
integrationMethod: 'POST',
credentialsRole: role,
requestParameters: { foo: 'bar' },
requestTemplates: { 'application/json': '{ "statusCode": 200 }' },
templateSelectionExpression: '$request.body',
passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES,
contentHandling: ContentHandling.CONVERT_TO_BINARY,
timeout: Duration.seconds(10),
}),
},
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', {
IntegrationType: 'AWS',
IntegrationUri: 'arn:aws:apigateway:us-west-2:dynamodb:action/PutItem',
IntegrationMethod: 'POST',
CredentialsArn: { 'Fn::GetAtt': ['MyRoleF48FFE04', 'Arn'] },
RequestParameters: { foo: 'bar' },
RequestTemplates: { 'application/json': '{ "statusCode": 200 }' },
TemplateSelectionExpression: '$request.body',
PassthroughBehavior: 'WHEN_NO_TEMPLATES',
ContentHandlingStrategy: 'CONVERT_TO_BINARY',
TimeoutInMillis: 10000,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,33 @@ import { Template } from '../../../assertions';
import { WebSocketApi } from '../../../aws-apigatewayv2';
import { Code, Function } from '../../../aws-lambda';
import * as lambda from '../../../aws-lambda';
import { Stack } from '../../../core';
import { Duration, Stack } from '../../../core';

describe('LambdaWebSocketIntegration', () => {
const IntegrationUri = {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':apigateway:',
{
Ref: 'AWS::Region',
},
':lambda:path/2015-03-31/functions/',
{
'Fn::GetAtt': [
'Fn9270CBC0',
'Arn',
],
},
'/invocations',
],
],
};

test('default', () => {
// GIVEN
const stack = new Stack();
Expand All @@ -21,30 +45,32 @@ describe('LambdaWebSocketIntegration', () => {
// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', {
IntegrationType: 'AWS_PROXY',
IntegrationUri: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':apigateway:',
{
Ref: 'AWS::Region',
},
':lambda:path/2015-03-31/functions/',
{
'Fn::GetAtt': [
'Fn9270CBC0',
'Arn',
],
},
'/invocations',
],
],
IntegrationUri,
});
});

test('can set a custom timeout', () => {
// GIVEN
const stack = new Stack();
const fooFn = fooFunction(stack, 'Fn');

// WHEN
new WebSocketApi(stack, 'Api', {
connectRouteOptions: {
integration: new WebSocketLambdaIntegration(
'Integration',
fooFn,
{ timeout: Duration.seconds(10) },
),
},
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', {
IntegrationType: 'AWS_PROXY',
IntegrationUri,
TimeoutInMillis: 10000,
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { IWebSocketApi } from './api';
import { IWebSocketRoute } from './route';
import { CfnIntegration } from '.././index';
import { IRole } from '../../../aws-iam';
import { Resource } from '../../../core';
import { Duration, Resource } from '../../../core';
import { IIntegration } from '../common';

/**
Expand Down Expand Up @@ -32,6 +32,21 @@ export enum WebSocketIntegrationType {
AWS = 'AWS',
}

/**
* Integration content handling
*/
export enum ContentHandling {
/**
* Converts a request payload from a base64-encoded string to a binary blob.
*/
CONVERT_TO_BINARY = 'CONVERT_TO_BINARY',

/**
* Converts a request payload from a binary blob to a base64-encoded string.
*/
CONVERT_TO_TEXT = 'CONVERT_TO_TEXT',
}

/**
* Integration Passthrough Behavior
*/
Expand Down Expand Up @@ -82,6 +97,14 @@ export interface WebSocketIntegrationProps {
*/
readonly integrationMethod?: string;

/**
* Specifies how to handle response payload content type conversions.
*
* @default - The response payload will be passed through from the integration response to
* the route response or method response without modification.
*/
readonly contentHandling?: ContentHandling;

/**
* Specifies the IAM role required for the integration.
*
Expand Down Expand Up @@ -117,6 +140,14 @@ export interface WebSocketIntegrationProps {
*/
readonly templateSelectionExpression?: string;

/**
* The maximum amount of time an integration will run before it returns without a response.
* Must be between 50 milliseconds and 29 seconds.
*
* @default Duration.seconds(29)
*/
readonly timeout?: Duration;

/**
* Specifies the pass-through behavior for incoming requests based on the
* Content-Type header in the request, and the available mapping templates
Expand Down Expand Up @@ -144,11 +175,13 @@ export class WebSocketIntegration extends Resource implements IWebSocketIntegrat
integrationType: props.integrationType,
integrationUri: props.integrationUri,
integrationMethod: props.integrationMethod,
contentHandlingStrategy: props.contentHandling,
credentialsArn: props.credentialsRole?.roleArn,
requestParameters: props.requestParameters,
requestTemplates: props.requestTemplates,
passthroughBehavior: props.passthroughBehavior,
templateSelectionExpression: props.templateSelectionExpression,
timeoutInMillis: props.timeout?.toMilliseconds(),
});
this.integrationId = integ.ref;
this.webSocketApi = props.webSocketApi;
Expand Down Expand Up @@ -201,9 +234,11 @@ export abstract class WebSocketRouteIntegration {
integrationType: config.type,
integrationUri: config.uri,
integrationMethod: config.method,
contentHandling: config.contentHandling,
credentialsRole: config.credentialsRole,
requestTemplates: config.requestTemplates,
requestParameters: config.requestParameters,
timeout: config.timeout,
passthroughBehavior: config.passthroughBehavior,
templateSelectionExpression: config.templateSelectionExpression,
});
Expand Down Expand Up @@ -239,6 +274,14 @@ export interface WebSocketRouteIntegrationConfig {
*/
readonly method?: string;

/**
* Specifies how to handle response payload content type conversions.
*
* @default - The response payload will be passed through from the integration response to
* the route response or method response without modification.
*/
readonly contentHandling?: ContentHandling;

/**
* Credentials role
*
Expand Down Expand Up @@ -267,6 +310,14 @@ export interface WebSocketRouteIntegrationConfig {
*/
readonly templateSelectionExpression?: string;

/**
* The maximum amount of time an integration will run before it returns without a response.
* Must be between 50 milliseconds and 29 seconds.
*
* @default Duration.seconds(29)
nmussy marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly timeout?: Duration;

/**
* Integration passthrough behaviors.
*
Expand Down
Loading