无服务器物联网教程:使用 AWS IoT、AWS Lambda 与 Websockets 实现实时数据更新

您的前端应用程序是否可以通过实时访问更新的数据而获益?但您似乎无法找到一种 “无服务器” (serverless)的方式来实现它?那么,那是3个月前的我。

我的目标是制作无服务器(Serverless)聊天应用程序,但不知道如何在后端和前端客户端之间实现实时通信。如果你在 AWS 上搜索 'websockets serverless',你将不会发现任何东西(至少在写这篇文章的时候)。

那我是怎么做到的? 请继续阅读,我的朋友们!

(或者,只需查看我的 GitHub 代码 以查看我所做的工作。)

背景知识

在我们深入实时更新之前,我想解释一下如何构建我的应用程序。这是我创建的一个非常基本的图,用于展示整个应用程序如何组合在一起。

应用架构图

前端作为静态站点存储在 AWS S3 存储桶中。

我使用 VueJS 作为我的前端框架。该存储桶位于具有 Amazon Certificate Manager 的 SSL 证书的 CloudFront 分发之后,并且我的 Route53 域将 awschat.net(我的应用)指向该分发。

后端是您的标准 Serverless API。

API Gateway 将传入请求路由到特定的 Lambda 函数,然后访问 DynamoDB 和 IoT 消息代理。(我们稍后会详细介绍。)我还使用 Cognito User Pool 授权来确保我的所有请求都来自我的应用程序的用户。 我将在下一部分深入探讨这一点。

注意:如果您想在阅读之前查看我的应用程序的完成版本,请转至 https://awschat.net

设置 Cognito

为了使用 API 和前端进行授权,需要设置 Cognito User Pool,您需要一些 CloudFormation。

以下是我的 serverless.yml 资源部分中与 Cognito 相关的条目:

CognitoUserPoolMyUserPool:
  Type: "AWS::Cognito::UserPool"
  Properties:
    AdminCreateUserConfig:
      AllowAdminCreateUserOnly: False
    AliasAttributes:
      - email
    AutoVerifiedAttributes:
      - email
    Policies:
      PasswordPolicy:
        MinimumLength: 8
        RequireLowercase: false
        RequireNumbers: false
        RequireSymbols: false
        RequireUppercase: false
    UserPoolName: MY_USER_POOL_NAME
    Schema:
      - Name: email
        AttributeDataType: String
        DeveloperOnlyAttribute: false
        Mutable: true
        Required: true
WebAppUserPoolWebClient:
  Type: "AWS::Cognito::UserPoolClient"
  Properties:
    ClientName: Web
    GenerateSecret: false
    ReadAttributes:
      - "email"
    UserPoolId:
      Ref: CognitoUserPoolMyUserPool
    WriteAttributes:
      - "email"
ApiGatewayAuthorizer:
  Type: "AWS::ApiGateway::Authorizer"
  Properties:
    Name: UserPool
    ProviderARNs:
      - 'Fn::GetAtt': [ CognitoUserPoolMyUserPool, Arn ]
    RestApiId:
      Ref: ApiGatewayRestApi
    IdentitySource: "method.request.header.Authorization"
    Type: COGNITO_USER_POOLS   

需要注意的一件事是:当您要将 Cognito User Pool Authorizer 添加到端点时,Serverless Framework 不支持使用在同一堆栈中创建的用户池(User Pool)。

这意味着我们必须在 CloudFormation 中手动定义每个端点-授权附件(attachment),如下所示:

 ApiGatewayMethodUserGet:
  Type: "AWS::ApiGateway::Method"
  Properties:
    AuthorizationType: COGNITO_USER_POOLS
    AuthorizerId:
      Ref: ApiGatewayAuthorizer

我们还必须使用我们的 API Gateway 方法的规范化名称。(在这里了解 serverless 创建资源的规范化名称。)

设置 IoT 消息代理配置

步骤一:AWS

这部分有点复杂。为了允许前端用户使用我们的消息代理,我们首先必须设置用户可以认证的身份池。

请注意,在我的应用程序中,我留下的权限比严格的生产应用程序中的权限要宽松一些。事实上,我在技术上让所有物联网用户都未经过身份验证。这是因为添加登录凭据有点复杂。

但是,我只允许他们订阅、接收和连接。所以没有人可以恶意地从他们的客户端发送一堆消息。

用于设置的 CloudFormation 如下所示:

WebAppCognitoIdentityPool:
  Type: "AWS::Cognito::IdentityPool"
  Properties:
    IdentityPoolName: NAME_OF_IDENTITY_POOL
    AllowUnauthenticatedIdentities: true
    CognitoIdentityProviders:
      - ProviderName:
          'Fn::GetAtt': [ CognitoUserPoolMyUserPool, ProviderName ]
        ClientId:
          Ref: WebAppUserPoolWebClient
cognitoAuthRole:
  Type: 'AWS::IAM::Role'
  Properties:
    RoleName: NAME_OF_IDENTITY_POOL_Auth_Role
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: Allow
          Principal:
            Federated: cognito-identity.amazonaws.com
          Action: [ 'sts:AssumeRoleWithWebIdentity' ]
          Condition:
            StringEquals:
              'cognito-identity.amazonaws.com:aud':
                Ref: WebAppCognitoIdentityPool
            'ForAnyValue:StringLike':
                'cognito-identity.amazonaws.com:amr': authenticated
    Policies:
      - PolicyName: cognitoauth
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
              - 'iot:DescribeEndpoint'
              - 'iot:Receive'
              - 'iot:Subscribe',
              - 'iot:GetTopicRule'
              - 'iot:Connect'
              - 'mobileanalytics:PutEvents',
              - 'iot:DescribeCertificate',
              - 'cognito-sync:*',
              - 'iot:GetPolicyVersion'
              Resource:
              - "*"
cognitoUnauthRole:
  Type: 'AWS::IAM::Role'
  Properties:
    RoleName: NAME_OF_IDENTITY_POOL_Unauth_Role
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: Allow
          Principal:
            Federated: cognito-identity.amazonaws.com
          Action: [ 'sts:AssumeRoleWithWebIdentity' ]
          Condition:
            StringEquals:
              'cognito-identity.amazonaws.com:aud':
                Ref: WebAppCognitoIdentityPool
            'ForAnyValue:StringLike':
                'cognito-identity.amazonaws.com:amr': unauthenticated
    Policies:
      - PolicyName: cognitounauth
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
              - 'iot:DescribeEndpoint'
              - 'iot:Receive'
              - 'iot:Subscribe',
              - 'iot:GetTopicRule'
              - 'iot:Connect'
              - 'mobileanalytics:PutEvents',
              - 'iot:DescribeCertificate',
              - 'iot:Publish,
              - 'cognito-sync:*',
              - 'iot:GetPolicyVersion'
              Resource:
              - "*"
myApiIdentityPoolRoleAttachment:
  DependsOn: [ WebAppCognitoIdentityPool, cognitoUnauthRole, cognitoAuthRole ]
  Type: "AWS::Cognito::IdentityPoolRoleAttachment"
  Properties:
    IdentityPoolId:
      Ref: WebAppCognitoIdentityPool
    Roles:
      unauthenticated:
        'Fn::GetAtt': [ cognitoUnauthRole, Arn ]
      authenticated:
        'Fn::GetAtt': [ cognitoAuthRole, Arn]

我们还需要向物联网添加一项允许所有操作执行的策略。

要更改物联网政策,您必须转到 IoT 控制台并点击 “Get started”。 从那里,点击左侧菜单中的 “Secure”,然后点击 “Policies”。

您需要创建一个如下所示的新策略:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

注意:同样的,这可能对于生产应用程序的策略来说过于宽泛。一旦建立了应用程序的 IoT 访问模式,就可以缩小它的范围。

步骤二:从 Lambda 访问 AWS IoT

假设用户发送 API 请求以向聊天添加消息。我们更新数据库,然后将消息返回给发送它的用户,但我们如何通知其他用户已添加消息?

这就是 AWS SDK 的用武之地。我的例子使用 JavaScript SDK。

您需要获得您的 IoT 端点,如果您拥有 AWS CLI,则可以通过在控制台中运行 aws iot describe-endpoint来实现。或者您可以通过转到 IoT 控制台,单击 “getting started”,然后单击左下角的 “getting started” 来找到它。

const iotData = new AWS.IotData({endpoint: 'YOUR_IOT_ENDPOINT HERE'});

exports.handler = (event, context, callback) => {
  const iotParams = {
    payload: JSON.stringify({ message: 'Hello!'})
    topic: `/my-app/${event.receiverId}`
  }

  iotData.publish(iotParams, (err, data) => {
    if (err) {
      // handle error here
    }
    callback(null, { success: true })
  })
}

上面的示例中 AWS Lambda采用用户标识(event.receiverId)并在我们的应用程序中广播到该用户的频道。在我们的主题 my-app/中有一个前缀,它允许我们为多个应用程序使用相同的 IoT 端点。

我们需要做的最后一件事是设置我们的前端应用程序,以使用 IoT 消息代理,以便我们可以实际接收发布事件。

步骤三:连接你的前端

幸运的是,有人已经想出了一个库,让我们通过 Websockets 来监听物联网 MQTT 代理! 你可以在这里找到

下面是一个如何在 webpack 构建中使用它的例子(摘自上面提到的库):

import AWS from 'aws-sdk/global'
import AWSMqtt from 'aws-mqtt'
AWS.config.region = 'us-east-1' // your region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: '...' // your identity pool id
})

const client = AWSMqtt.connect({
  WebSocket: window.WebSocket, 
  region: AWS.config.region,
  credentials: AWS.config.credentials,
  endpoint: '...iot.us-east-1.amazonaws.com', // NOTE: get this value with `aws iot describe-endpoint`
  clientId: 'mqtt-client-' + (Math.floor((Math.random() * 100000) + 1)), // clientId to register with MQTT broker. Need to be unique per client
})

client.on('connect', () => {
  client.subscribe('/myTopic')
})
client.on('message', (topic, message) => {
  // this is where you will handle the messages you send from Lambda
  console.log(topic, message)
})
client.on('close', () => {
  // ...
})
client.on('offline', () => {
  // ...
})

结论

就是这么简单! 现在你知道如何在你的前端应用程序,和 Serverless 后端之间实现实时通信。

该项目相关的代码如下:

英语原文链接:https://serverless.com/blog/realtime-updates-using-lambda-websockets-iot/

尚未评分
您的评分将帮助我们做出更好的玩法

观光\评论区

Copyright © 2017 玩点什么. All Rights Reserved.