最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to programmatically connect to AWS websocket API Gateway - Stack Overflow

programmeradmin3浏览0评论

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/{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/dev' <-- tried this too
        return '/@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 bination.", '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 '/@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 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./{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./dev' <-- tried this too
        return 'https://xxxxxxxx.execute-api.us-east-1.amazonaws./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 bination.", '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./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.

Share Improve this question asked Apr 21, 2019 at 18:52 Scott Scott 7058 silver badges19 bronze badges 1
  • Did you figure this out? Im just using vanillajs for this. Basically you cant call the connection URL because you wont have all the required data for this. You need to invoke the socket using the same JSON object you use from wscat, this will return an event object to the lamda function with all the required details to post back to the open connections. How to construct the call from lamda to wss:// is the question for me. – Matt Muller Commented May 29, 2019 at 22:39
Add a ment  | 

3 Answers 3

Reset to default 2

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


发布评论

评论列表(0)

  1. 暂无评论