Kā veidot reāllaika lietojumprogrammas, izmantojot WebSockets ar AWS API Gateway un Lambda

Nesen AWS ir paziņojusi par plaši pieprasītas funkcijas palaišanu: WebSockets Amazon API Gateway. Izmantojot WebSockets, mēs varam izveidot divvirzienu sakaru līniju, kuru var izmantot daudzos gadījumos, piemēram, reāllaika lietojumprogrammās. Tas rada jautājumu: kas ir reāllaika lietojumprogrammas? Tāpēc vispirms atbildēsim uz šo jautājumu.

Lielākā daļa lietojumprogrammu, kas pašlaik darbojas, izmanto klienta-servera arhitektūru. Klienta-servera arhitektūrā klients nosūta pieprasījumus internetā, izmantojot tīkla sakarus, un pēc tam serveris apstrādā šos pieprasījumus un nosūta atbildi atpakaļ klientam.

Šeit jūs varat redzēt, ka tieši klients sāk sakarus ar serveri. Tātad vispirms klients uzsāk saziņu, un serveris atbild uz servera nosūtīto pieprasījumu. Tātad, ko darīt, ja serveris vēlas sākt saziņu un nosūtīt atbildes bez klienta pieprasījuma vispirms? Šeit spēlē reāllaika lietojumprogrammas.

Reāllaika lietojumprogrammas ir lietojumprogrammas, kurās serveris iegūst iespēju nosūtīt klientus bez klienta pieprasījuma vispirms. Pieņemsim, ka mums ir tērzēšanas programma, kurā divi tērzēšanas klienti var sazināties, izmantojot serveri. Šajā situācijā ir izšķērdība, ja visi tērzēšanas klienti pieprasa datus no servera kā katru sekundi. Efektīvāk ir tas, ja serveris nosūta datus klienta tērzēšanas lietojumprogrammām, kad tiek saņemta tērzēšana. Šo funkcionalitāti var veikt, izmantojot reāllaika lietojumprogrammas.

Amazon paziņoja, ka viņi atbalstīs WebSockets API Gateway vietnē AWS re: Invent 2018. Vēlāk decembrī viņi to palaida API Gateway. Tāpēc tagad, izmantojot AWS infrastruktūru, mēs varam izveidot reāllaika lietojumprogrammas, izmantojot API Gateway.

Šajā amatā mēs izveidosim vienkāršu tērzēšanas lietojumprogrammu, izmantojot API Gateway WebSockets. Pirms sākam tērzēšanas lietojumprogrammas ieviešanu, mums ir jāsaprot daži jēdzieni attiecībā uz reāllaika lietojumprogrammām un API vārteju.

WebSocket API koncepcijas

WebSocket API sastāv no viena vai vairākiem maršrutiem. Maršruta izvēle izteiksme ir tur, lai noteiktu, kura maršruts īpaši ienākošo pieprasījumu vajadzētu lietot, kas tiks sniegta ienākošo pieprasījumu. Izteiksme tiek vērtēta pēc ienākošā pieprasījuma radīt vērtību, kas atbilst vienai no jūsu maršruta routeKey vērtībām. Piemēram, ja mūsu JSON ziņojumos ir rekvizīta izsaukuma darbība un jūs vēlaties veikt dažādas darbības, pamatojoties uz šo rekvizītu, jūsu maršruta izvēles izteiksme varētu būt ${request.body.action}.

Piemēram: ja jūsu JSON ziņojums izskatās kā {“action”: “onMessage”, “message”: “Sveiki visiem”}, šim pieprasījumam tiks izvēlēts onMessage maršruts.

Pēc noklusējuma ir trīs maršruti, kas jau ir definēti WebSocket API. Papildus tālāk minētajiem maršrutiem mēs varam pievienot pielāgotus maršrutus savām vajadzībām.

  • $ default - tiek izmantots, ja maršruta izvēles izteiksme rada vērtību, kas neatbilst nevienai citai maršruta atslēgai jūsu API maršrutos. To var izmantot, piemēram, lai ieviestu vispārēju kļūdu apstrādes mehānismu.
  • $ connect - saistītais maršruts tiek izmantots, kad klients pirmo reizi izveido savienojumu ar jūsu WebSocket API.
  • $ disconnect - saistītais maršruts tiek izmantots, kad klients atvienojas no jūsu API.

Kad ierīce ir veiksmīgi savienota, izmantojot WebSocket API, ierīcei tiks piešķirts unikāls savienojuma ID. Ja savienojums tiks izveidots, šis savienojuma ID tiks saglabāts visu mūžu. Lai nosūtītu ziņojumus atpakaļ uz ierīci, mums jāizmanto šāds POST pieprasījums, izmantojot savienojuma ID.

POST //{api-id}.execute-api.us-east 1.amazonaws.com/{stage}/@connections/{connection_id}

Tērzēšanas lietojumprogrammas ieviešana

Pēc WebSocket API pamatjēdzienu apgūšanas apskatīsim, kā mēs varam izveidot reāllaika lietojumprogrammu, izmantojot WebSocket API. Šajā amatā mēs ieviesīsim vienkāršu tērzēšanas lietojumprogrammu, izmantojot WebSocket API, AWS LAmbda un DynamoDB. Šī diagramma parāda mūsu reāllaika lietojumprogrammas arhitektūru.

Mūsu lietojumprogrammā ierīces tiks savienotas ar API vārteju. Kad ierīce tiek savienota, lambda funkcija saglabās savienojuma ID DynamoDB tabulā. Gadījumā, ja mēs vēlamies nosūtīt ziņojumu atpakaļ uz ierīci, cita lambda funkcija atgūs savienojuma ID un POST datus atpakaļ uz ierīci, izmantojot atzvanīšanas URL.

WebSocket API izveide

Lai izveidotu WebSocket API, mums vispirms jādodas uz Amazon API Gateway pakalpojumu, izmantojot konsoli. Tur izvēlieties izveidot jaunu API. Noklikšķiniet uz WebSocket, lai izveidotu WebSocket API, norādiet API nosaukumu un mūsu maršruta izvēles izteiksmi. Mūsu gadījumā pievienojiet $ request.body.action kā mūsu izvēles izteiksmi un nospiediet Izveidot API.

Pēc API izveides mēs tiksim novirzīti uz maršrutu lapu. Šeit mēs varam redzēt jau iepriekš definētus trīs maršrutus: $ connect, $ disconnect un $ default. Mēs izveidosim arī pielāgotu maršrutu $ onMessage. Mūsu arhitektūrā maršruti $ connect un $ disconnect sasniedz šādus uzdevumus:

  • $ connect - izsaucot šo maršrutu, Lambda funkcija pievienos pievienotās ierīces savienojuma ID DynamoDB.
  • $ disconnect - izsaucot šo maršrutu, funkcija Lambda izdzēsīs atvienotās ierīces savienojuma ID no DynamoDB.
  • onMessage - izsaucot šo maršrutu, ziņojuma pamatteksts tiks nosūtīts uz visām ierīcēm, kuras tajā laikā ir pievienotas.

Pirms maršruta pievienošanas saskaņā ar iepriekš minēto, mums jāveic četri uzdevumi:

  • Izveidojiet DynamoDB tabulu
  • Izveidojiet savienojuma lambda funkciju
  • Izveidojiet atvienošanas lambda funkciju
  • Izveidojiet onMessage lambda funkciju

Vispirms izveidosim DynamoDB tabulu. Dodieties uz DynamoDB servisu un izveidojiet jaunu tabulu ar nosaukumu Chat. Pievienojiet primāro atslēgu kā “connectionid”.

Pēc tam izveidosim savienojuma Lambda funkciju. Lai izveidotu funkciju Lambda, dodieties uz Lambda pakalpojumiem un noklikšķiniet uz Izveidot funkciju. Atlasiet Autors no nulles un piešķiriet nosaukumu kā 'ChatRoomConnectFunction' un lomu ar nepieciešamajām atļaujām. (Lomai vajadzētu būt atļaujai iegūt, ievietot un izdzēst vienumus no DynamoDB, izsaukt API zvanus API vārtejā.)

Funkcijas lambda kodā pievienojiet šādu kodu. Šis kods pievienos pievienotās ierīces savienojuma ID mūsu izveidotajai DynamoDB tabulai.

exports.handler = (event, context, callback) => { const connectionId = event.requestContext.connectionId; addConnectionId(connectionId).then(() => { callback(null, { statusCode: 200, }) });}
function addConnectionId(connectionId) { return ddb.put({ TableName: 'Chat', Item: { connectionid : connectionId }, }).promise();}

Pēc tam izveidosim arī atvienošanas lambda funkciju. Izmantojot tās pašas darbības, izveidojiet jaunu lambda funkciju ar nosaukumu

'ChatRoomDonnectFunction'. Pievienojiet funkcijai šādu kodu. Šis kods noņems savienojuma ID no tabulas DynamoDB, kad ierīce tiks atvienota.

const AWS = require('aws-sdk');const ddb = new AWS.DynamoDB.DocumentClient();
exports.handler = (event, context, callback) => { const connectionId = event.requestContext.connectionId; addConnectionId(connectionId).then(() => { callback(null, { statusCode: 200, }) });}
function addConnectionId(connectionId) { return ddb.delete({ TableName: 'Chat', Key: { connectionid : connectionId, }, }).promise();}

Tagad mēs esam izveidojuši DynamoDB tabulu un divas lambda funkcijas. Pirms trešās lambda funkcijas izveidošanas atgriezīsimies atkal pie API vārtejas un konfigurēsim maršrutus, izmantojot mūsu izveidotās lambda funkcijas. Vispirms noklikšķiniet uz $ connect route. Kā integrācijas veidu atlasiet funkciju Lambda un atlasiet funkciju ChatRoomConnectionFunction.

Mēs varam darīt to pašu arī $ disconnect maršrutā, kur lambda funkcija būs ChatRoomDisconnectionFunction:

Now that we have configured our $connect and $disconnect routes, we can actually test whether out WebSocket API is working. To do that we must first to deploy the API. In the Actions button, click on Deploy API to deploy. Give a stage name such as Test since we are only deploying the API for testing.

After deploying, we will be presented with two URLs. The first URL is called WebSocket URL and the second is called Connection URL.

The WebSocket URL is the URL that is used to connect through WebSockets to our API by devices. And the second URL, which is Connection URL, is the URL which we will use to call back to the devices which are connected. Since we have not yet configured call back to devices, let’s first only test the $connect and $disconnect routes.

To call through WebSockets we can use the wscat tool. To install it, we need to just issue the npm install -g wscat command in the command line. After installing, we can use the tool using wscat command. To connect to our WebSocket API, issue the following command. Make sure to replace the WebSocket URL with the correct URL provided to you.

wscat -c wss://bh5a9s7j1e.execute-api.us-east-1.amazonaws.com/Test

When the connection is successful, a connected message will be displayed on the terminal. To check whether our lambda function is working, we can go to DynamoDB and look in the table for the connection id of the connected terminal.

As above, we can test the disconnect as well by pressing CTRL + C which will simulate a disconnection.

Now that we have tested our two routes, let us look into the custom route onMessage. What this custom route will do is it will get a message from the device and send the message to all the devices that are connected to the WebSocket API. To achieve this we are going to need another lambda function which will query our DynamoDB table, get all the connection ids, and send the message to them.

Let’s first create the lambda function in the same way we created other two lambda functions. Name the lambda function ChatRoomOnMessageFunction and copy the following code to the function code.

const AWS = require('aws-sdk');const ddb = new AWS.DynamoDB.DocumentClient();require('./patch.js');
let send = undefined;function init(event) { console.log(event) const apigwManagementApi = new AWS.ApiGatewayManagementApi({ apiVersion: '2018-11-29', endpoint: event.requestContext.domainName + '/' + event.requestContext.stage }); send = async (connectionId, data) => { await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: `Echo: ${data}` }).promise(); }}
exports.handler = (event, context, callback) => { init(event); let message = JSON.parse(event.body).message getConnections().then((data) => { console.log(data.Items); data.Items.forEach(function(connection) { console.log("Connection " +connection.connectionid) send(connection.connectionid, message); }); }); return {}};
function getConnections(){ return ddb.scan({ TableName: 'Chat', }).promise();}

The above code will scan the DynamoDB to get all the available records in the table. For each record, it will POST a message using the Connection URL provided to us in the API. In the code, we expect that the devices will send the message in the attribute named ‘message’ which the lambda function will parse and send to others.

Since WebSockets API is still new there are some things we need to do manually. Create a new file named patch.js and add the following code inside it.

require('aws-sdk/lib/node_loader');var AWS = require('aws-sdk/lib/core');var Service = AWS.Service;var apiLoader = AWS.apiLoader;
apiLoader.services['apigatewaymanagementapi'] = {};AWS.ApiGatewayManagementApi = Service.defineService('apigatewaymanagementapi', ['2018-11-29']);Object.defineProperty(apiLoader.services['apigatewaymanagementapi'], '2018-11-29', { get: function get() { var model = { "metadata": { "apiVersion": "2018-11-29", "endpointPrefix": "execute-api", "signingName": "execute-api", "serviceFullName": "AmazonApiGatewayManagementApi", "serviceId": "ApiGatewayManagementApi", "protocol": "rest-json", "jsonVersion": "1.1", "uid": "apigatewaymanagementapi-2018-11-29", "signatureVersion": "v4" }, "operations": { "PostToConnection": { "http": { "requestUri": "/@connections/{connectionId}", "responseCode": 200 }, "input": { "type": "structure", "members": { "Data": { "type": "blob" }, "ConnectionId": { "location": "uri", "locationName": "connectionId" } }, "required": [ "ConnectionId", "Data" ], "payload": "Data" } } }, "shapes": {} } model.paginators = { "pagination": {} } return model; }, enumerable: true, configurable: true});
module.exports = AWS.ApiGatewayManagementApi;

I took the above code from this article. The functionality of this code is to automatically create the Callback URL for our API and send the POST request.

Now that we have created the lambda function we can go ahead and create our custom route in API Gateway. In the New Route Key, add ‘OnMessage’ as a route and add the custom route. As configurations were done for other routes, add our lambda function to this custom route and deploy the API.

Now we have completed our WebSocket API and we can fully test the application. To test that sending messages works for multiple devices, we can open and connect using multiple terminals.

After connecting, issue the following JSON to send messages:

{"action" : "onMessage" , "message" : "Hello everyone"}

Here, the action is the custom route we defined and the message is the data that need to be sent to other devices.

Tas ir paredzēts mūsu vienkāršajai tērzēšanas lietojumprogrammai, izmantojot AWS WebSocket API. Mēs faktiski neesam konfigurējuši $ defalut maršrutu, kas tiek izsaukts ik reizi, kad maršruts nav atrasts. Es atstāšu šī maršruta ieviešanu jums. Paldies un tiekamies citā ierakstā. :)