简体   繁体   中英

How to programmatically connect to AWS websocket API Gateway

I am trying to implement messaging between users on my website by leveraging AWS's websocket api gateway. Every guide/documentation that I look at says to use wscat to test the connection to the gateway. I am at the point where I can connect to the api gateway and send messages between clients using wscat but am struggling to get it working programmatically from my ts code.

What I want to do is make an api call to the websocket api gateway once the user logs in so they can send messages at any point. I am using serverless for my backend and Angular 6 for the front end. I read that I need to make a POST request to https://{api-id}.execute-api.us-east-1.amazonaws.com/{stage}/@connections/{connection_id} to send messages through a websocket connection but i'm having trouble using typescript in a service I created to connect/get a connection id.

I am making a second API call after the user successfully logs in to open a connection to the websocket api gateway. I tried calling a function that makes a post request with no body (not sure what I would send in the body of the request since I've only connected to it using the wscat tool) to the URL I get after deploying my serverless code. I also tried making a POST request to the https:// URL I see in the AWS console after manually deploying the API gateway.

base.service.ts

protected getBaseSocketEndpoint(): string {
        // 'wss://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev' <-- tried this too
        return 'https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/@connections';
    }

authentication.service.ts

this.authService.login(username, password).pipe(first()).subscribe(
            (response) => {
                console.log(response);
                this.authService.setCookie('userId', response.idToken.payload.sub);
                this.authService.setCookie('jwtToken', response.idToken.jwtToken);
                this.authService.setCookie('userEmail', response.idToken.payload.email);
                this.authService.setCookie('refreshToken', response.refreshToken.token);

                this.toastr.success('Login successful. Redirecting to your dashboard.', 'Success!', {
                    timeOut: 1500
                });

                this.authService.connectToWebSocket(response.idToken.payload.sub).pipe(first()).subscribe(
                    response => {
                        console.log(response);
                    }
                );

                this.routerService.routeToUserDashboard();
            },
            (error) => {
                // const errorMessage = JSON.parse(error._body).message;
                this.toastr.error("Incorrect username and password combination.", 'Error!', {
                    timeOut: 1500
                });
            }
        );

authentication.service.ts extends BaseService

public connectToWebSocket(userId: string): Observable<any> {
        const body = {
            userId: userId
        };

        console.log('calling connectToWebSocket()..');
        return this.http.post(this.getBaseSocketEndpoint(), body).pipe(map(response => {
            console.log(response);
        }));
    }

serverless.yaml

functions:
  connectionHandler:
    handler: connectionHandler.connectionHandler
    events:
      - websocket:
          route: $connect
          cors: true
      - websocket:
          route: $disconnect
          cors: true
  defaultHandler:
    handler: connectionHandler.defaultHandler
    events:
      - websocket:
          route: $default
          cors: true
  sendMessageHandler:
    handler: messageHandler.sendMessageHandler
    events:
      - websocket:
          route: sendMessage
          cors: true

connectionHandler.js (lambda)

const success = {
  statusCode: 200,
  headers: { "Access-Control-Allow-Origin": "*" },
  body: "everything is alright"
};

module.exports.connectionHandler = (event, context, callback) => {
  var connectionId = event.requestContext.connectionId;
  if (event.requestContext.eventType === "CONNECT") {
    addConnection(
      connectionId,
      "b72656eb-db8e-4f32-a6b5-bde4943109ef",
      callback
    )
      .then(() => {
        console.log("Connected!");
        callback(null, success);
      })
      .catch(err => {
        callback(null, JSON.stringify(err));
      });
  } else if (event.requestContext.eventType === "DISCONNECT") {
    deleteConnection(
      connectionId,
      "b72656eb-db8e-4f32-a6b5-bde4943109ef",
      callback
    )
      .then(() => {
        console.log("Disconnected!");
        callback(null, success);
      })
      .catch(err => {
        callback(null, {
          statusCode: 500,
          body: "Failed to connect: " + JSON.stringify(err)
        });
      });
  }
};

// THIS ONE DOESNT DO ANYHTING
module.exports.defaultHandler = (event, context, callback) => {
  callback(null, {
    statusCode: 200,
    body: "default handler was called."
  });
};

const addConnection = (connectionId, userId, callback) => {
  const params = {
    TableName: CHATCONNECTION_TABLE,
    Item: {
      connectionId: connectionId,
      userId: userId
    }
  };

  var response;
  return dynamo
    .put(params, function(err, data) {
      if (err) {
        errorHandler.respond(err, callback);
        return;
      } else {
        response = {
          statusCode: 200,
          headers: { "Access-Control-Allow-Origin": "*" },
          body: JSON.stringify(data)
        };
        callback(null, response);
      }
    })
    .promise();
};

const deleteConnection = (connectionId, userId, callback) => {
  const params = {
    TableName: CHATCONNECTION_TABLE,
    Key: {
      connectionId: connectionId,
      userId: userId
    }
  };

  var response;
  return dynamo
    .delete(params, function(err, data) {
      if (err) {
        errorHandler.respond(err, callback);
        return;
      } else {
        response = {
          statusCode: 200,
          headers: { "Access-Control-Allow-Origin": "*" },
          body: JSON.stringify(data)
        };
        callback(null, response);
      }
    })
    .promise();
};

Expected: trigger POST api call and open a persistent connection with the Websocket API Gateway.

Actual: unable to connect via API call above. I get a 403 in the console with the message:

Access to XMLHttpRequest at ' https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/@connections ' from origin ' http://localhost:4200 ' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Not sure why im getting a CORS error when I have CORS enabled in my serverless file.

I had a similar problem. You can use all the socket.io-client goodness. But you have to set the option transports to :

 let socket = io(url, {
        reconnectionDelayMax: 1000,
        transports: ["websocket"],
      });

The default is

transports: ["polling", "websocket"],

So your app will kick off polling with the resulting in a CORS error. It's not that clear in the docs, but here is a useful link .

Look under "Available options for the underlying Engine.IO client:".

I had the same problem and finally figured out, that normally there should not be such a CORS error message with websockets:

Why is there no same-origin policy for WebSockets? Why can I connect to ws://localhost?

Skipping the client library "socket.io" and using "vanilla websockets" helped me out.

In your case I would check the libraries behind "connectToWebSocket".

  • I used python lambda handler, so it might be helpful to many.
  • I have implemented a websocket which sends user some message 5 times with a gap
  • serverless.yml
service: realtime-websocket-test

provider:
  name: aws
  stage: ${opt:stage, 'dev'}
  runtime: python3.8
  region: ${opt:region, 'ap-south-1'}
  memorySize: 128
  timeout: 300

functions:
  connect:
    handler: handler.lambda_handler
    events:
      - websocket:
          route: $connect
      - websocket:
          route: $disconnect
      - websocket:
          route: $default    
  • handler.py
import time
import json
import boto3


def lambda_handler(event, context):
    print(event)
    event_type = event["requestContext"]["eventType"]

    if event_type == "CONNECT" or event_type == "DISCONNECT":
        response = {'statusCode': 200}
        return response     
    
    elif event_type == "MESSAGE":   
        connection_id = event["requestContext"]["connectionId"]
        domain_name = event["requestContext"]["domainName"]
        stage = event["requestContext"]["stage"]

        message = f'{domain_name}: {connection_id}'.encode('utf-8')
        api_client = boto3.client('apigatewaymanagementapi', endpoint_url = f"https://{domain_name}/{stage}")

        for _ in range(5):
            api_client.post_to_connection(Data=message,
                                                ConnectionId=connection_id)
            time.sleep(5)

    
        response = {'statusCode': 200}
        return response


The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM