【问题标题】:How to programmatically connect to AWS websocket API Gateway如何以编程方式连接到 AWS websocket API Gateway
【发布时间】:2019-09-11 03:15:07
【问题描述】:

我正在尝试通过利用 AWS 的 websocket api 网关在我的网站上实现用户之间的消息传递。我看到的每个指南/文档都说使用 wscat 来测试与网关的连接。我现在可以连接到 api 网关并使用 wscat 在客户端之间发送消息,但我正在努力让它从我的 ts 代码以编程方式工作。

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

在用户成功登录以打开与 websocket api 网关的连接后,我正在进行第二次 API 调用。我尝试调用一个函数,该函数发出没有正文的发布请求(不确定我会在请求正文中发送什么,因为我只使用 wscat 工具连接到它)到部署无服务器代码后获得的 URL。在手动部署 API 网关后,我还尝试向我在 AWS 控制台中看到的 https:// URL 发出 POST 请求。

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 扩展 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();
};

预期:触发 POST api 调用并打开与 Websocket API 网关的持久连接。

实际:无法通过上述 API 调用进行连接。我在控制台中收到 403 消息:

从源“http://localhost:4200”访问“https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/@connections”处的 XMLHttpRequest 已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:没有“Access-Control-Allow-Origin”标头出现在请求的资源上。

当我在无服务器文件中启用 CORS 时,我不确定为什么会出现 CORS 错误。

【问题讨论】:

  • 你发现了吗?我只是为此使用vanillajs。基本上你不能调用连接 URL,因为你不会拥有所有需要的数据。您需要使用从 wscat 使用的相同 JSON 对象调用套接字,这将向 lamda 函数返回一个事件对象,其中包含所有必需的详细信息以回发到打开的连接。如何构造从 lamda 到 wss:// 的调用是我的问题。

标签: javascript amazon-web-services websocket aws-api-gateway


【解决方案1】:

我遇到了类似的问题。您可以使用所有的 socket.io-client 优点。但是您必须将选项传输设置为:

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

默认是

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

因此,您的应用将启动轮询并导致 CORS 错误。文档中不是很清楚,但这里有一个useful link

查看“底层 Engine.IO 客户端的可用选项:”。

【讨论】:

    【解决方案2】:

    我遇到了同样的问题,最后发现,通常 websockets 不应该出现这样的 CORS 错误消息:

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

    跳过客户端库“socket.io”并使用“vanilla websockets”帮助了我。

    在你的情况下,我会检查“connectToWebSocket”后面的库。

    【讨论】:

    • 很高兴知道我不是世界上唯一一个遇到这个问题的人。你知道他们什么时候会支持它吗?它是否在他们的文档中说它不受支持?我一定错过了。
    【解决方案3】:
    • 我使用了 python lambda 处理程序,所以它可能对许多人有帮助。
    • 我已经实现了一个 websocket,它会向用户发送 5 次有间隙的消息
    • 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
    
    
    

    【讨论】:

    • OP 正在使用打字稿,我认为您提出的解决方案没有以正确的方式回答问题。
    猜你喜欢
    • 2021-12-14
    • 2021-04-18
    • 1970-01-01
    • 1970-01-01
    • 2021-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-27
    相关资源
    最近更新 更多