1

I try to use lambdas with my AWS websocket api gateway.

I have made the following template.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "On connect and on disconnect handler"
Parameters:
  DB_HOSTName:
    Type: String
    Default: '0.0.0.0:3306'
    Description: Database host
  
  DB_USERName:
    Type: String
    default:  myuser
    Description: Database User

  DB_PASSWORDName:
    Type: String
    default: mypasswd
    Description: Database Password

  DB_NAME:
    Type: String
    default: mydb
    Description: Database

Resources:
  # Websocket API
  OrderWebsocket:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: OrderWebsocket
      ProtocolType: WEBSOCKET
      RouteSelectionExpression: "$request.body.action"
  # On Connect
  ConnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref OrderWebsocket
      RouteKey: $connect
      AuthorizationType: NONE
      OperationName: ConnectRoute
      Target: !Join
        - '/'
        - - 'integrations'
          - !Ref ConnectInteg

  # Integrating on connect lambda into aws
  ConnectInteg:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref OrderWebsocket
      Description: Connect Integration
      IntegrationType: AWS_PROXY
      IntegrationUri: 
        Fn::Sub:
            arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunction.Arn}/invocations
  
  # On Disconnect lambda
  DisconnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref OrderWebsocket
      RouteKey: $disconnect
      AuthorizationType: NONE
      OperationName: DisconnectRoute
      Target: !Join
        - '/'
        - - 'integrations'
          - !Ref DisconnectInteg
  
  # Integrating OnDisconnect Lambda into websocket
  DisconnectInteg:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref OrderWebsocket
      Description: Disconnect Integration
      IntegrationType: AWS_PROXY
      IntegrationUri: 
        Fn::Sub:
            arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunction.Arn}/invocations

  Deployment:
    Type: AWS::ApiGatewayV2::Deployment
    DependsOn:
    - ConnectRoute
    - DisconnectRoute
    Properties:
      ApiId: !Ref OrderWebsocket
  Stage:
    Type: AWS::ApiGatewayV2::Stage
    Properties:
      StageName: Prod
      Description: Prod Stage
      DeploymentId: !Ref Deployment
      ApiId: !Ref OrderWebsocket

  # Where onconnect function is located into our source code
  OnConnectFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: onconnect/
      Handler: app.handler
      MemorySize: 256
      Runtime: nodejs12.x
      Environment:
        Variables:
          TABLE_NAME: !Ref TableName
      Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref TableName
  
  OnConnectPermission:
    Type: AWS::Lambda::Permission
    DependsOn:
      - OrderWebsocket
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref OnConnectFunction
      Principal: apigateway.amazonaws.com

  # Where ondisconnect function is located into our source code
  OnDisconnectFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ondisconnect/
      Handler: app.handler
      MemorySize: 256
      Runtime: nodejs12.x
      Environment:
        Variables:
          TABLE_NAME: !Ref TableName
      Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref TableName
          
  OnDisconnectPermission:
    Type: AWS::Lambda::Permission
    DependsOn:
      - OrderWebsocket
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref OnDisconnectFunction
      Principal: apigateway.amazonaws.com

Outputs:
  OnConnectFunctionArn:
    Description: "OnConnect function ARN"
    Value: !GetAtt OnConnectFunction.Arn

  OnDisconnectFunctionArn:
    Description: "OnDisconnect function ARN"
    Value: !GetAtt OnDisconnectFunction.Arn

  WebSocketURI:
    Description: "The WSS Protocol URI to connect to"
    Value: !Join [ '', [ 'wss://', !Ref OrderWebsocket, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/',!Ref 'Stage'] ]

And I try to test it locally without deploying it via the command:

sam local invoke OnConnectFunction

But I get the error:

Invoking app.handler (nodejs12.x)
Error: Could not find lambci/lambda:nodejs12.x image locally and failed to pull it from docker.

I also tried to launch it as a whole according to documentation but it failed as well:

$ sam local start-api
Error: Template does not have any APIs connected to Lambda functions

DO you have any idea why? And how I can invoke it locally, so I can test it in my computer first without need to deploy it?

The onconnect function is the following:

const mysql = require('mysql');

const dbconnection = mysql.createConnection({
    host: process.env.DB_HOST,
    user: prossess.env.DB_USER,
    password: prossess.env.DB_PASSWORD,
    database: prossess.env.DB_NAME,
});

exports.handler = async event => {

    // Api Gateway connection identifier
    let connection_id = event.requestContext.connectionId;

    let sql = "INSERT INTO websocket_connections (connection_id) VALUES (?)";

    connection.connect();
    try{
       await connection.query(sql, [connection_id]);
    } catch (err) {
        console.log('Failed to store into database', err.toString());
        return {statusCode: 500, body: 'Failed To Connect';}
    }

    connection.end();

    return { statusCode: 200, body: 'Connected.' };
}

The ondisconnect function is the following:

const mysql = require('mysql');

const dbconnection = mysql.createConnection({
    host: process.env.DB_HOST,
    user: prossess.env.DB_USER,
    password: prossess.env.DB_PASSWORD,
    database: prossess.env.DB_NAME,
});

exports.handler = async event => {
    let connection_id = event.requestContext.connectionId;
    let sql = "DELETE FROM websocket_connections WHERE connection_id = ?";

    connection.connect();

    try {
        await connection.query(sql, [connection_id]);
    } catch(err) {
        // console.log output is written into cloudwatch logs.
        console.log('Failed to store into database', err.toString());
        return {statusCode: 500, body: 'Failed To Connect';}
    }

    connection.end();
    return { statusCode: 200, body: 'Connected.' };
}

I just use a mysql database to store the client ids and once disconnected I remove them from my database.

Dimitrios Desyllas
  • 9,082
  • 15
  • 74
  • 164

2 Answers2

0

It's been a while but I was able to find a project called aws-lambda-ws-server as a possible workaround for your use case which will wrap your routes to a local Websockets server and also deploy to AWS Lambda with the same code.

Below you can watch a quick demo on how it works:

ASCIInema screenshot

I hope it can become helpful to you and others in the future!

fagiani
  • 2,293
  • 2
  • 24
  • 31
0

The error

Error: Could not find lambci/lambda:nodejs12.x image locally and failed to pull it from docker.

Is because there is some issue with your local docker user.

If you have one try logging out or logging in.

I.e. docker login

Make sure this works.

If you don't need a docker login, try deleting the file at ~/.docker/config.json -- or backing it up probably would be safer.