Introduction#

GitHub 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
Api Gateway Socket

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