简体   繁体   English

如何以编程方式连接到 AWS websocket API 网关

[英]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.我正在尝试利用 AWS 的 websocket api 网关在我的网站上的用户之间实现消息传递。 Every guide/documentation that I look at says to use wscat to test the connection to the gateway.我查看的每个指南/文档都说使用 wscat 来测试与网关的连接。 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.我现在可以连接到 api 网关并使用 wscat 在客户端之间发送消息,但我正在努力从我的 ts 代码中以编程方式使其工作。

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.我想要做的是在用户登录后对 websocket api 网关进行 api 调用,以便他们可以随时发送消息。 I am using serverless for my backend and Angular 6 for the front end.我的后端使用无服务器,前端使用 Angular 6。 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.我读到我需要向https://{api-id}.execute-api.us-east-1.amazonaws.com/{stage}/@connections/{connection_id}发出POST请求以通过websocket 连接,但我在我创建的用于连接/获取连接 ID 的服务中使用打字稿时遇到问题。

I am making a second API call after the user successfully logs in to open a connection to the websocket api gateway.在用户成功登录以打开与 websocket api 网关的连接后,我正在进行第二次 API 调用。 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.我尝试调用一个函数,该函数发出一个没有正文的 post 请求(不确定我会在请求正文中发送什么,因为我只使用 wscat 工具连接到它)到部署我的无服务器代码后得到的 URL。 I also tried making a POST request to the https:// URL I see in the AWS console after manually deploying the API gateway.在手动部署 API 网关后,我还尝试向在 AWS 控制台中看到的 https:// URL 发出 POST 请求。

base.service.ts基础.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身份验证.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 authentication.service.ts 扩展了 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无服务器.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) 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.预期:触发 POST api 调用并打开与 Websocket API 网关的持久连接。

Actual: unable to connect via API call above.实际:无法通过上述 API 调用进行连接。 I get a 403 in the console with the message:我在控制台中收到一个 403 消息:

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. CORS 策略已阻止在“ https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/@connections ”处访问 XMLHttpRequest,来自源“ http://localhost:4200 ”:响应预检请求未通过访问控制检查:请求的资源上不存在“Access-Control-Allow-Origin”标头。

Not sure why im getting a CORS error when I have CORS enabled in my serverless file.当我在无服务器文件中启用 CORS 时,不知道为什么会出现 CORS 错误。

I had a similar problem.我有一个类似的问题。 You can use all the socket.io-client goodness.您可以使用 socket.io-client 的所有优点。 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.因此,您的应用程序将开始轮询并导致 CORS 错误。 It's not that clear in the docs, but here is a useful link .文档中不是很清楚,但这里有一个有用的链接

Look under "Available options for the underlying Engine.IO client:".查看“底层 Engine.IO 客户端的可用选项:”。

I had the same problem and finally figured out, that normally there should not be such a CORS error message with websockets:我遇到了同样的问题,最后发现,通常情况下,websockets 不应该有这样的 CORS 错误消息:

Why is there no same-origin policy for WebSockets? 为什么 WebSockets 没有同源策略? Why can I connect to ws://localhost? 为什么我可以连接到 ws://localhost?

Skipping the client library "socket.io" and using "vanilla websockets" helped me out.跳过客户端库“socket.io”并使用“vanilla websockets”帮助了我。

In your case I would check the libraries behind "connectToWebSocket".在你的情况下,我会检查“connectToWebSocket”背后的库。

  • I used python lambda handler, so it might be helpful to many.我使用了 python lambda 处理程序,所以它可能对很多人有帮助。
  • I have implemented a websocket which sends user some message 5 times with a gap我已经实现了一个 websocket,它向用户发送了 5 次有间隙的消息
  • serverless.yml无服务器.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


声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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