Skip to content

Ack connect route #2780

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
73 changes: 73 additions & 0 deletions ack-connect-route/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
## Websocket API Gateway acknowledgement for $connect route.

The Serverless Application Model (SAM) template deploys an Amazon WebSocket API Gateway and two AWS Lambda functions. When a client connects, the API Gateway creates a $connect route with Lambda proxy integration. The first Lambda function processes the initial connection, capturing both the Connection ID and API Gateway stage URL, then asynchronously triggers the second Lambda function. This second function validates the Connection ID and, if valid, uses SDK API calls to send a greeting message back to the client.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the AWS Pricing page for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.


## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed

## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```
git clone https://github.com/aws-samples/serverless-patterns
```
2. Change directory to the pattern directory:
```
cd ack-connect-route
```
3. From the command line, use AWS SAM to build and deploy the AWS resources for the pattern as specified in the template.yml file:

```
sam deploy --guided
```
4. During the prompts:
* Enter a stack name
* Enter the desired AWS Region
* Allow SAM CLI to create IAM roles with the required permissions.

Once you have run guided mode once, you can use `sam deploy` in future to use these defaults.

1. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing.

## Testing

Once the application is deployed, retrieve the WebSocketURL value from CloudFormation Outputs. To test the WebSocket API, you can use [wscat](https://github.com/websockets/wscat) which is an open-source command line tool.

1. [Install NPM](https://www.npmjs.com/get-npm).

2. Install wscat:
```
$ npm install -g wscat
```

3. Connect to your WebSocketURL by executing the following command:
$ wscat -c <YOUR WEBSOCKET URL>
```

4. To test the custom route and its associated function, send a JSON-formatted request. The Lambda function sends back the value of the "data" key using the callback URL:
```
$ wscat -c <YOUR WEBSOCKET URL>
connected (press CTRL+C to quit)
```
## Cleanup

1. Delete the stack
```
aws cloudformation delete-stack --stack-name <YOUR STACK NAME>
```

2. Confirm the stack has been deleted
```
aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'<YOUR STACK NAME>')].StackStatus"
```


44 changes: 44 additions & 0 deletions ack-connect-route/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"title": "Websocket API Gateway acknowledgement for $connect route.",
"description": "The SAM template deploys a WebSocket API Gateway that creates a $connect route, triggering two Lambda functions in sequence - the first captures connection details and triggers the second asynchronously, which then validates the connection and sends a greeting via SDK API calls.",
"language": "Python",
"level": "200",
"framework": "SAM",
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/ack-connect-route",
"templateURL": "serverless-patterns/ack-connect-route",
"projectFolder": "ack-connect-route",
"templateFile": "template.yaml"
}
},
"deploy": {
"text": [
"sam deploy"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>sam delete</code>."
]
},
"authors": [
{
"name": "Manasvi Jain",
"image": "https://avatars.githubusercontent.com/u/56217984?v=4",
"bio": "Associate Partner Solutions Architect at AWS",
"linkedin": "https://www.linkedin.com/in/manasvi-jain-36b9941a3/"
},
{
"name": "Umang Aggarwal",
"image": "https://avatars.githubusercontent.com/Umang071",
"bio": "Technical Account Manager @ AWS",
"linkedin": "https://www.linkedin.com/in/umangaggarwal"
}
]
}
12 changes: 12 additions & 0 deletions ack-connect-route/src/lambda_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import json
import boto3

lambdaClient = boto3.client('lambda')
def lambda_handler(event, context):
print("Got an event from some route ", event)
if event['requestContext']['routeKey'] == "$connect":
url = "https://" + event["requestContext"]["domainName"] + "/" + event["requestContext"]["stage"]

response = lambdaClient.invoke(FunctionName="WebsocketPostToConnectionId",InvocationType="Event",Payload=json.dumps({"url": url, "connectionId":event['requestContext']['connectionId']}))
payload = "This won't be received by the client"
return {'statusCode': 200,'body': payload}
13 changes: 13 additions & 0 deletions ack-connect-route/src/lambda_function_postToConnection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import json
import boto3

def lambda_handler(event, context):

print("Got an event from parent lambda ", event)
gatewayapi = boto3.client("apigatewaymanagementapi",endpoint_url = event["url"])
response = gatewayapi.get_connection(ConnectionId=event['connectionId'])
print("Response is ", response)

if response["ResponseMetadata"]["HTTPStatusCode"] == 200:
gatewayapi.post_to_connection(ConnectionId=event['connectionId'], Data="Hello XXX, we are now connected!! ")
return {}
9 changes: 9 additions & 0 deletions ack-connect-route/src/ondisconnect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import json

def lambda_handler(event, context):
# TODO implement
print("Connection has been disconnected")
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
142 changes: 142 additions & 0 deletions ack-connect-route/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Description: An Amazon API Gateway WebSocket API and an AWS Lambda function.

# Global values that are applied to all applicable resources in this template
Globals:
Function:
CodeUri: ./src
Runtime: python3.12
MemorySize: 128
Timeout: 15

Resources:
# API Gateway WebSocket API
WebSocketApi:
Type: 'AWS::ApiGatewayV2::Api'
Properties:
Name: !Ref AWS::StackName
Description: An Amazon API Gateway WebSocket API and an AWS Lambda function.
ProtocolType: WEBSOCKET
RouteSelectionExpression: "$request.body.action"
# Lambda Function - uses Globals to define additional configuration values
OnConnectLambdaFunction:
Type: 'AWS::Serverless::Function'
Properties:
FunctionName: !Sub '${AWS::StackName}-onconnect-function'
Handler: lambda_function.lambda_handler
MemorySize: 256
Policies:
- Statement:
- Effect: Allow
Action:
- 'lambda:InvokeFunction'
Resource:
- !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:WebsocketPostToConnectionId'
# Function permissions grant an AWS service or another account permission to use a function
OnConnectFunctionResourcePermission:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
FunctionName: !Ref OnConnectLambdaFunction
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*'
OnConnectIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref WebSocketApi
Description: OnConnect Integration
IntegrationType: AWS_PROXY
IntegrationUri:
Fn::Sub:
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectLambdaFunction.Arn}/invocations
OnConnectRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref WebSocketApi
RouteKey: $connect
AuthorizationType: NONE
OperationName: OnConnectRoute
Target: !Join
- '/'
- - 'integrations'
- !Ref OnConnectIntegration
# Lambda Function - uses Globals to define additional configuration values
PostLambdaFunction:
Type: 'AWS::Serverless::Function'
Properties:
FunctionName: WebsocketPostToConnectionId
Handler: lambda_function_postToConnection.lambda_handler
MemorySize: 256
Policies:
- Statement:
- Effect: Allow
Action:
- 'execute-api:ManageConnections'
Resource:
- !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*'
# Function permissions grant an AWS service or another account permission to use a function

OnDisconnectLambdaFunction:
Type: 'AWS::Serverless::Function'
Properties:
FunctionName: !Sub '${AWS::StackName}-ondisconnect-function'
Handler: ondisconnect.lambda_handler
MemorySize: 256
# Function permissions grant an AWS service or another account permission to use a function
OnDisconnectFunctionResourcePermission:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
FunctionName: !Ref OnDisconnectLambdaFunction
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*'
OnDisconnectIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref WebSocketApi
Description: OnDisconnect Integration
IntegrationType: AWS_PROXY
IntegrationUri:
Fn::Sub:
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectLambdaFunction.Arn}/invocations
OnDisconnectRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref WebSocketApi
RouteKey: $disconnect
AuthorizationType: NONE
OperationName: OnDisconnectRoute
Target: !Join
- '/'
- - 'integrations'
- !Ref OnDisconnectIntegration
Deployment:
Type: AWS::ApiGatewayV2::Deployment
DependsOn:
- OnConnectRoute
- OnDisconnectRoute
Properties:
ApiId: !Ref WebSocketApi
Stage:
Type: AWS::ApiGatewayV2::Stage
Properties:
StageName: prod
Description: Prod Stage
DeploymentId: !Ref Deployment
ApiId: !Ref WebSocketApi

Outputs:
OnConnectLambdaFunctionArn:
Description: "OnConnect function ARN"
Value: !GetAtt OnConnectLambdaFunction.Arn
OnDisconnectLambdaFunctionArn:
Description: "OnDisconnect function ARN"
Value: !GetAtt OnDisconnectLambdaFunction.Arn
PostLambdaFunctionArn:
Description: "Post function ARN"
Value: !GetAtt PostLambdaFunction.Arn
WebSocketURL:
Description: "The WSS Protocol URL to connect to"
Value: !Join [ '', [ 'wss://', !Ref WebSocketApi, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/',!Ref 'Stage'] ]