Dans ce guide, nous allons voir comment utiliser WebSockets à l’aide d’un framework sans serveur AWS avec NodeJs. À la fin de ce guide, nous aurons une application où nous pourrons créer une salle de discussion et d’autres utilisateurs pourront rejoindre notre salle pour discuter entre eux dans une salle personnalisée. J’ai rendu la procédure très simple à suivre, et à la fin de cet article, vous obtiendrez également un lien vers le référentiel Github pour le code.
Configuration du projet
La première chose à faire est de configurer le dossier du projet et d’installer les dépendances du projet requises en créant un nouveau dossier et en exécutant les commandes ci-dessous à la racine du dossier du projet.
npm init
npm i aws-sdk --save
Créer un dossier nommé src à la racine du projet et à l’intérieure src dossier, créez trois autres dossiers avec index.js
fichiers dans chaque dossier :
- gestionnaire de connexion: ce dossier contiendra le fichier avec le code pour gérer les événements de connexion et de déconnexion de WebSockets.
- gérerChambre: Ce dossier contiendra le fichier avec le code pour créer/rejoindre la salle de discussion.
- envoyer le message: Ce dossier contiendra le fichier avec le code pour émettre le message à tous les utilisateurs connectés dans une salle particulière si un utilisateur de la salle envoie un message.
À présent, la structure de notre projet devrait ressembler à ceci :

Nous avons maintenant terminé la configuration de base du projet et nous sommes prêts à passer à l’étape suivante, qui consiste à créer le sans serveur.yml déposer.
Qu’est-ce qu’un fichier serverless.yml ?
Dans un langage très simple, un sans serveur.yml est utilisé pour coder le modèle en fonction des ressources que nous souhaitons créer dans notre compte AWS. Nous pouvons définir différents types de ressources dans le sans serveur.yml fichier, et nous pouvons également définir les différentes autorisations pour différentes ressources.
Dans ce projet, l’utilisation principale de sans serveur.yml sera de créer les fonctions Lambda et de configurer la table DynamoDB avec différentes autorisations.
Définition du bloc de configuration et d’autorisations dans le fichier serverless.yml
service: serverless-chat
provider:
name: aws
runtime: nodejs12.x
websocketsApiName: custom-websockets-api-name
websocketsApiRouteSelectionExpression: $request.body.action
environment:
DYNAMO_TABLE_NAME: connections
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:DeleteItem
- dynamodb:UpdateItem
- lambda:InvokeFunction
Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:
table/${self:provider.environment.DYNAMO_TABLE_NAME}"
C’est la première partie de notre sans serveur.yml déposer. Décomposons-le en parties.
- service: C’est le nom du modèle CloudFormation qui sera créé dans le compte AWS.
- fournisseur: Nous définissons la configuration, les variables d’environnement, les différentes autorisations et les rôles dans ce bloc. Ici, dans ce code, nous définissons des éléments tels que la version de NodeJs que nous souhaitons utiliser dans notre environnement AWS.
- WebsocketsApiRouteSelectionExpression: il s’agit de l’expression de sélection de route personnalisée, ce qui signifie que si nous voulons émettre des événements personnalisés à partir de notre client WebSocket, nous transmettrons le nom de l’événement dans la propriété action de la charge utile.
- action: Ce bloc a toutes les autorisations que nous souhaitons donner à notre fonction Lambda pour effectuer différentes opérations sur la table DynamoDB.
Définition du bloc de fonctions dans le fichier serverless.yml
functions:
connectionHandler:
handler: src/connectionHandler/index.connectionHandler
events:
- websocket:
route: $connect
- websocket:
route: $disconnect
sendMessage:
handler: src/sendMessage/index.sendMessage
events:
- websocket:
route: sendmessage
manageRoom:
handler: src/manageRoom/index.manageRoom
events:
- websocket:
route: manageroom
C’est ici que nous définirons toutes nos fonctions Lambda à créer. Décomposons-le un peu pour une meilleure compréhension :
- gestionnaire de connexion: Il s’agit de la fonction Lambda qui sera appelée lorsqu’un utilisateur se connectera ou se déconnectera de notre serveur WebSocket. Il existe trois événements ou itinéraires prédéfinis définis par Passerelle API :
$connect
,$disconnect
et$default
. - $connecter/$déconnecter: Lorsque l’utilisateur se connecte à notre serveur WebSocket,
$connect
est l’événement par défaut qui est appelé, et lorsque l’utilisateur se déconnecte, le$disconnect
l’événement est appelé. - envoyer le message: Cette fonction sera appelée si l’utilisateur envoie envoyer le message comme valeur de la propriété action dans la charge utile de la demande. Il gère l’envoi de messages à tous les utilisateurs connectés dans une pièce particulière.
- gérerChambre: Cette fonction permet de créer/rejoindre une salle en fonction de l’identifiant de la salle.
Définition du bloc de ressources dans le fichier serverless.yml
resources:
Resources:
UsersDynamoDbTable:
Type: AWS::DynamoDB::Table
DeletionPolicy: Retain
Properties:
AttributeDefinitions:
- AttributeName: connectionId
AttributeType: S
KeySchema:
- AttributeName: connectionId
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:provider.environment.DYNAMO_TABLE_NAME}
C’est notre bloc de ressources dans le sans serveur.yml déposer. Nous définissons toutes les ressources que nous souhaitons créer automatiquement dans le compte AWS dans ce fichier. Ici, nous créons une nouvelle table DynamoDB avec un Touche dièse, ou dans une autre langue, Clé primaire, si vous venez d’un background SQL.
Connexion et déconnexion des utilisateurs
Commençons à travailler sur la fonction Lambda pour connecter ou déconnecter les clients WebSocket. Nous utilisons le gestionnaire de connexion fonction pour gérer cette fonctionnalité. Cela ressemblera à quelque chose comme ceci :
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION });
exports.connectionHandler = async event => {
const connectionId = event.requestContext.connectionId;
const eventType = event.requestContext.eventType
if (eventType === 'DISCONNECT') {
try {
await ddb.delete({ TableName: process.env.DYNAMO_TABLE_NAME, Key: { connectionId } }).promise();
return { statusCode: 200, body: 'Disconnected' };
}
catch (e) {
return { statusCode: 500, body: 'Could not clear the connection.' };
}
}
else if (eventType === "CONNECT") {
const putParams = {
TableName: process.env.DYNAMO_TABLE_NAME,
Item: {
connectionId
}
};
try {
await ddb.put(putParams).promise();
} catch (err) {
return { statusCode: 500, body: 'Failed to connect: ' + JSON.stringify(err) };
}
return { statusCode: 200, body: 'Connected.' };
}
};
Nous allons détailler chaque partie de la fonction, commençons donc par la première partie : la gestion des utilisateurs connectés.
Connexion des utilisateurs
else if (eventType === "CONNECT") {
const putParams = {
TableName: process.env.DYNAMO_TABLE_NAME,
Item: {
connectionId
}
};
try {
await ddb.put(putParams).promise();
} catch (err) {
return { statusCode: 500, body: 'Failed to connect: ' + JSON.stringify(err) };
}
return { statusCode: 200, body: 'Connected.' };
}
Ce que nous faisons ici, c’est vérifier si l’utilisateur était connecté à l’aide de l’URL WebSocket API Gateway. Si l’utilisateur était connecté, nous obtenons le connectionId
du event.requestContext
objet et créer une nouvelle entrée dans la table DynamoDB avec le connectionId
valeur. Ceci est juste une simple opération d’insertion sur la table DynamoDB avec connectionId
.
Qu’est-ce que .Promise() ?
Si vous vous demandez pourquoi nous utilisons .promise()
ici, il est utilisé parce que nous voulons écrire du code propre au mieux de nos capacités, nous voulons donc utiliser async/await au lieu de callbacks. Pour utiliser async/await, l’appel de fonction doit retourner une promesse Javascript, c’est pourquoi nous utilisons le .promise()
appel. La plupart des fonctions d’AWS-SDK ont une option à utiliser .promise()
, ce qui permet à la fonction de renvoyer le résultat dans une promesse au lieu d’un rappel.
Déconnexion des utilisateurs
if (eventType === 'DISCONNECT') {
try {
await ddb.delete({ TableName: process.env.DYNAMO_TABLE_NAME, Key: { connectionId } }).promise();
return { statusCode: 200, body: 'Disconnected' };
}
catch (e) {
return { statusCode: 500, body: 'Could not clear the connection.' };
}
}
Ici, nous vérifions si l’utilisateur a été déconnecté du serveur WebSocket. Si l’utilisateur a été déconnecté, alors connectionId
est utilisé pour supprimer cette entrée utilisateur de la table DynamoDB.
Créer et rejoindre des salons de discussion
L’étape suivante consiste à configurer une fonction Lambda pour permettre aux utilisateurs de créer ou de rejoindre une salle. Le code de la fonction ressemblera à ceci :
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION });
exports.manageRoom = async event => {
const body = JSON.parse(event.body)
if (!body.roomid) return { statusCode: 200, body: 'Room id is required.' };
const params = {
TableName: process.env.DYNAMO_TABLE_NAME,
Key: {
connectionId: event.requestContext.connectionId,
},
ExpressionAttributeValues: {
":roomid": body.roomid,
},
UpdateExpression: "SET roomid = :roomid",
ReturnValues: "ALL_NEW"
};
const data = await ddb.update(params).promise();
if (data.Attributes) {
return { statusCode: 200, body: 'Room joined.' };
} else {
return { statusCode: 400, body: 'Some error has occured.' };
}
};
Découpons le code en différentes parties pour une meilleure compréhension du code.
Obtenir et vérifier l’ID de la salle
const body = JSON.parse(event.body)
if (!body.roomid) return { statusCode: 200, body: 'Room id is required.' };
Ici, nous obtenons le corps de la demande et l’analysons en tant que données JSON, et nous vérifions également si roomid
est présent dans l’objet corps, car roomid
est requis si l’utilisateur essaie de créer/rejoindre une salle de discussion.
Créer/rejoindre la salle de discussion
const params = {
TableName: process.env.DYNAMO_TABLE_NAME,
Key: {
connectionId: event.requestContext.connectionId,
},
ExpressionAttributeValues: {
":roomid": body.roomid,
},
UpdateExpression: "SET roomid = :roomid",
ReturnValues: "ALL_NEW"
};
const data = await ddb.update(params).promise();
if (data.Attributes) {
return { statusCode: 200, body: 'Room joined.' };
} else {
return { statusCode: 400, body: 'Some error has occured.' };
}
Ici, nous mettons à jour une entrée dans la table DynamoDB selon le connectionId
et régler la colonne roomid
avec la valeur qui est passée par l’utilisateur dans le corps de la requête. Par exemple, si connectionId
est #f!41fg
et le roomid
passé par l’utilisateur est test-chat-room, alors ce code mettra à jour le roomid
colonne avec la valeur test-chat-room dans la rangée où connectionId
est #f!41fg
.
Envoi d’un message à tous les utilisateurs connectés dans la salle de conversation
La dernière partie de notre projet consiste à créer une fonction Lambda pour envoyer un message à…