Skip to content

Instantly share code, notes, and snippets.

@maatthc
Last active April 30, 2024 18:13
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save maatthc/9d2dfe0448733f0ee1624d658fbac80f to your computer and use it in GitHub Desktop.
Save maatthc/9d2dfe0448733f0ee1624d658fbac80f to your computer and use it in GitHub Desktop.
Basic WebSocket mock in AWS ApiGateway using CloudFormation in AWS
AWSTemplateFormatVersion: '2010-09-09'
Description: |
AWS CloudFormation template for Mock WebSocket API Gateway. When deploying this stack please remember to check the option:
- I acknowledge that AWS CloudFormation might create IAM resources.
This template can help you to solve issues like:
- CloudWatch Logs role ARN must be set in account settings to enable logging
- Execution failed due to configuration error: statusCode should be an integer which defined in request template
- This custom domain name cannot map to WEBSOCKET protocol APIs
- Error during WebSocket handshake: Unexpected response code: 500
Resources:
ApiGwAccountConfig:
Type: "AWS::ApiGateway::Account"
Properties:
CloudWatchRoleArn: !GetAtt "ApiGatewayLoggingRole.Arn"
ApiGatewayLoggingRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- "apigateway.amazonaws.com"
Action: "sts:AssumeRole"
Path: "/"
ManagedPolicyArns:
- !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
WebSocketMock:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: WebSocketMock
ProtocolType: WEBSOCKET
RouteSelectionExpression: "$request.body.message"
Description: "Mock WebSocket API Gateway."
ConnectRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref WebSocketMock
RouteKey: $connect
RouteResponseSelectionExpression: '$default'
AuthorizationType: NONE
ApiKeyRequired: false
OperationName: ConnectRoute
Target: !Join
- '/'
- - 'integrations'
- !Ref ConnectInteg
ConnectInteg:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref WebSocketMock
Description: Connect Integration
IntegrationType: MOCK
RequestTemplates:
"200" : '{"statusCode" : 200}'
TemplateSelectionExpression: '200'
PassthroughBehavior: 'WHEN_NO_MATCH'
ConnectIntegResp:
Type: AWS::ApiGatewayV2::IntegrationResponse
Properties:
ApiId: !Ref WebSocketMock
IntegrationId: !Ref ConnectInteg
IntegrationResponseKey: '$default'
ResponseTemplates:
"200" : '{"statusCode" : 200}'
ConnectRouteResponse:
Type: AWS::ApiGatewayV2::RouteResponse
Properties:
RouteId: !Ref ConnectRoute
ApiId: !Ref WebSocketMock
RouteResponseKey: $default
DisconnectRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref WebSocketMock
RouteKey: $disconnect
RouteResponseSelectionExpression: '$default'
AuthorizationType: NONE
OperationName: DisconnectRoute
Target: !Join
- '/'
- - 'integrations'
- !Ref DisconnectInteg
DisconnectInteg:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref WebSocketMock
Description: Disconnect Integration
IntegrationType: MOCK
RequestTemplates:
"200" : '{"statusCode" : 200}'
TemplateSelectionExpression: '200'
PassthroughBehavior: 'WHEN_NO_MATCH'
DisconnectIntegResp:
Type: AWS::ApiGatewayV2::IntegrationResponse
Properties:
ApiId: !Ref WebSocketMock
IntegrationId: !Ref DisconnectInteg
IntegrationResponseKey: '$default'
ResponseTemplates:
"200" : '{"statusCode" : 200}'
DisconnectRouteResponse:
Type: AWS::ApiGatewayV2::RouteResponse
Properties:
RouteId: !Ref DisconnectRoute
ApiId: !Ref WebSocketMock
RouteResponseKey: $default
DefaultRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref WebSocketMock
RouteKey: $default
RouteResponseSelectionExpression: '$default'
AuthorizationType: NONE
OperationName: DefaultRoute
Target: !Join
- '/'
- - 'integrations'
- !Ref DefaultInteg
DefaultInteg:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref WebSocketMock
Description: Default Integration
IntegrationType: MOCK
RequestTemplates:
"200" : '{"statusCode" : 200}'
TemplateSelectionExpression: '200'
DefaultIntegResp:
Type: AWS::ApiGatewayV2::IntegrationResponse
Properties:
ApiId: !Ref WebSocketMock
IntegrationId: !Ref DefaultInteg
IntegrationResponseKey: $default
ResponseTemplates:
"200" : '{"statusCode" : 200, "connectionId" : "$context.connectionId"}'
TemplateSelectionExpression: '200'
DefaultRouteResponse:
Type: AWS::ApiGatewayV2::RouteResponse
Properties:
RouteId: !Ref DefaultRoute
ApiId: !Ref WebSocketMock
RouteResponseKey: $default
Deployment:
Type: AWS::ApiGatewayV2::Deployment
DependsOn:
- ConnectRoute
- DisconnectRoute
- DefaultRoute
Properties:
ApiId: !Ref WebSocketMock
Stage:
Type: AWS::ApiGatewayV2::Stage
Properties:
StageName: LATEST
Description: One and only Stage
DeploymentId: !Ref Deployment
ApiId: !Ref WebSocketMock
DefaultRouteSettings:
DetailedMetricsEnabled: true
LoggingLevel: INFO
@adamwatkins
Copy link

I'm not sure if I'm allowed to comment on this post, but man, yours is the only article or bit of information that I could find in days of searching to figure out how to implement these mock responses to routes. I can't tell you how much I appreciate it.

@maatthc
Copy link
Author

maatthc commented Oct 4, 2020

I'm happy that helped!
There is also a thread here about it with more context.

@adamwatkins
Copy link

Dude, that is perfect - I did have just syntax issues in those templates that caused my mock requests to fail, so I ran your script above and just verified how those templates should work. Thanks again!

@bjorg
Copy link

bjorg commented Jul 1, 2021

That is great! Thank you!

@BenPoling
Copy link

I 2nd @adamwatkins sentiment. This saved me a lot of time, thanks @maatthc!

@koblas
Copy link

koblas commented Feb 18, 2022

For anybody who stumbles across this and needs the CDK version for the mock.

///
//    Usage:  `new MockPing(this, "mockPing", { routeKey: "ping", sockapi: YOUR_WEB_SOCKET});`
//
export class MockPing extends Construct {
  constructor(scope: Construct, id: string, { sockapi, routeKey }: { routeKey: string; sockapi: WebSocketApi }) {
    super(scope, id);

    const intgration = new cdk.aws_apigatewayv2.CfnIntegration(this, "integration", {
      apiId: sockapi.apiId,
      integrationType: "MOCK",
      requestTemplates: {
        "200": '{"statusCode":200}',
      },
      templateSelectionExpression: "200",
      passthroughBehavior: "WHEN_NO_MATCH",
    });
    const route = new cdk.aws_apigatewayv2.CfnRoute(this, "route", {
      apiId: sockapi.apiId,
      routeKey,
      routeResponseSelectionExpression: "$default",
      operationName: "pingRoute",
      target: new cdk.StringConcat().join("integrations/", intgration.ref),
    });
    new cdk.aws_apigatewayv2.CfnIntegrationResponse(this, "response", {
      apiId: sockapi.apiId,
      integrationId: intgration.ref,
      integrationResponseKey: "/200/",
      responseTemplates: {
        "200": '{"statusCode": 200 }',
      },
    });
    new cdk.aws_apigatewayv2.CfnRouteResponse(this, "routeResponse", {
      apiId: sockapi.apiId,
      routeId: route.ref,
      routeResponseKey: "$default",
    });
   }
}

@stevenbdf
Copy link

Thanks @maatthc! This Gist helped answering a lot of questions I had when trying to add Response Integration and Route

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment