title description author publishedDate date
Api Gateway Socket
Just show a simple setup with api gateway socket
haimtran
08/14/2022
2022-08-14

Introduction

Setup and go through basic concepts of apigw socket

  • Routes: $connect, $disconnect, $default, $sendMessage
  • Role for apigw, and role for lambda
  • SendMessage lambda get the endpointUrl and send messages back to clients
  • Manage connectionId by a table
  • ApiGw lambda integration invoke uri

Api Socket

create an api socket, skip deploy options

const api = new aws_apigatewayv2.CfnApi(this, "ApiGwSocket", {
  name: "HelloApiGwSocket",
  protocolType: "WEBSOCKET",
  routeSelectionExpression: "$request.body.action",
});

role for apigw

const role = new aws_iam.Role(this, "RoleForApiGwInvokeLambda", {
  roleName: "InvokeLambdaRoleForApiGw",
  assumedBy: new aws_iam.ServicePrincipal("apigateway.amazonaws.com"),
});

role.addToPolicy(
  new aws_iam.PolicyStatement({
    effect: Effect.ALLOW,
    resources: [
      connectFunc.functionArn,
      disconnectFunc.functionArn,
      messageFunc.functionArn,
    ],
    actions: ["lambda:InvokeFunction"],
  })
);

create a table for connection ids

const table = new aws_dynamodb.Table(this, "ConnectionIdTable", {
  tableName: "ConnectionIdTable",
  partitionKey: {
    name: "ConnectionId",
    type: aws_dynamodb.AttributeType.STRING,
  },
  readCapacity: 5,
  writeCapacity: 5,
  removalPolicy: RemovalPolicy.DESTROY,
});

connect, disconnect handler

const connectFunc = new aws_lambda.Function(this, "ConnectFunc", {
  functionName: "ConnectFunc",
  code: aws_lambda.Code.fromAsset(path.join(__dirname, "./../lambdas")),
  handler: "connect.handler",
  runtime: aws_lambda.Runtime.PYTHON_3_8,
  timeout: Duration.seconds(300),
  memorySize: 256,
  environment: {
    TABLE_NAME: table.tableName,
  },
});
table.grantReadWriteData(messageFunc);

send message handler which send messages back to clients

const messageFunc = new aws_lambda.Function(this, "MessageFunc", {
  functionName: "MessageFunc",
  code: aws_lambda.Code.fromAsset(path.join(__dirname, "./../lambdas")),
  handler: "sendMessage.handler",
  runtime: aws_lambda.Runtime.PYTHON_3_8,
  timeout: Duration.seconds(300),
  memorySize: 256,
  environment: {
    TABLE_NAME: table.tableName,
    ENDPOINT_URL: `https://${api.ref}.execute-api.${this.region}.amazonaws.com/dev`,
  },
  initialPolicy: [
    new aws_iam.PolicyStatement({
      effect: Effect.ALLOW,
      actions: ["execute-api:ManageConnections"],
      resources: ["*"],
    }),
  ],
});

table.grantReadWriteData(messageFunc);

Lambda Integration

Use proxy integration between lambda and apigw

const connectIntegration = new aws_apigatewayv2.CfnIntegration(
  this,
  "ConnectLambdaIntegration",
  {
    apiId: api.ref,
    integrationType: "AWS_PROXY",
    integrationUri: `arn:aws:apigateway:${this.region}:lambda:path/2015-03-31/functions/${connectFunc.functionArn}/invocations`,
    credentialsArn: role.roleArn,
  }
);

const disconnectIntegration = new aws_apigatewayv2.CfnIntegration(
  this,
  "DisconnectLambdaIntegration",
  {
    apiId: api.ref,
    integrationType: "AWS_PROXY",
    integrationUri: `arn:aws:apigateway:${this.region}:lambda:path/2015-03-31/functions/${disconnectFunc.functionArn}/invocations`,
    credentialsArn: role.roleArn,
  }
);

const messageIntegration = new aws_apigatewayv2.CfnIntegration(
  this,
  "MessageLambdaIntegration",
  {
    apiId: api.ref,
    integrationType: "AWS_PROXY",
    integrationUri: `arn:aws:apigateway:${this.region}:lambda:path/2015-03-31/functions/${messageFunc.functionArn}/invocations`,
    credentialsArn: role.roleArn,
  }
);

then create routes

const connectRoute = new aws_apigatewayv2.CfnRoute(this, "ConnectRoute", {
  apiId: api.ref,
  routeKey: "$connect",
  authorizationType: "NONE",
  target: "integrations/" + connectIntegration.ref,
});

const disconnectRoute = new aws_apigatewayv2.CfnRoute(this, "DisconnectRoute", {
  apiId: api.ref,
  routeKey: "$disconnect",
  authorizationType: "NONE",
  target: "integrations/" + disconnectIntegration.ref,
});
const messageRoute = new aws_apigatewayv2.CfnRoute(this, "MessageRoute", {
  apiId: api.ref,
  routeKey: "sendmessage",
  authorizationType: "NONE",
  target: "integrations/" + messageIntegration.ref,
});

Deployment and Stage

const deployment = new aws_apigatewayv2.CfnDeployment(this, "deployment", {
  apiId: api.ref,
});

new aws_apigatewayv2.CfnStage(this, "DevStage", {
  stageName: "dev",
  deploymentId: deployment.ref,
  apiId: api.ref,
  autoDeploy: true,
});

dependency and export output

deployment.node.addDependency(connectRoute);
deployment.node.addDependency(disconnectRoute);
deployment.node.addDependency(messageRoute);

new CfnOutput(this, "endpointUrl", {
  exportName: "apiId",
  value: api.ref,
});

Test

Option 1. Just using online testing tool here message format sent from client

{ "action": "sendmessage", "message": "hello hai tran" }

Option 2. Build a react app

Screen Shot 2022-08-17 at 15 26 08

GitHub

View Github