您的前端应用程序是否可以通过实时访问更新的数据而获益?但您似乎无法找到一种 “无服务器” (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
为了使用 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 创建资源的规范化名称。)
这部分有点复杂。为了允许前端用户使用我们的消息代理,我们首先必须设置用户可以认证的身份池。
请注意,在我的应用程序中,我留下的权限比严格的生产应用程序中的权限要宽松一些。事实上,我在技术上让所有物联网用户都未经过身份验证。这是因为添加登录凭据有点复杂。
但是,我只允许他们订阅、接收和连接。所以没有人可以恶意地从他们的客户端发送一堆消息。
用于设置的 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 访问模式,就可以缩小它的范围。
假设用户发送 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/
观光\评论区