Како се праве апликације у реалном времену помоћу ВебСоцкетс-а са АВС АПИ Гатеваи-ом и Ламбда-ом

Недавно је АВС најавио покретање широко тражене функције: ВебСоцкетс фор Амазон АПИ Гатеваи. Помоћу ВебСоцкетс-а можемо створити двосмерну комуникациону линију која се може користити у многим сценаријима попут апликација у стварном времену. Овде се поставља питање: шта су апликације у реалном времену? Па хајде да прво одговоримо на то питање.

Већина апликација које су тренутно оперативне користе архитектуру клијент-сервер. У архитектури клијент-сервер, клијент шаље захтеве путем Интернета користећи мрежну комуникацију, а затим сервер обрађује тај захтев и шаље одговор назад клијенту.

Овде можете видети да је клијент тај који започиње комуникацију са сервером. Дакле, прво клијент иницира комуникацију, а сервер одговара на захтев који је послао сервер. Па шта ако сервер жели да започне комуникацију и притисне одговоре, а да их клијент претходно не захтева? Ту у игру улазе апликације у стварном времену.

Апликације у реалном времену су апликације код којих сервер добија могућност да пристиже клијентима без да клијент претходно захтева податке. Претпоставимо да имамо апликацију за ћаскање где два клијента за ћаскање могу да комуницирају путем сервера. У овој ситуацији је расип ако сви клијенти ћаскања захтевају податке са сервера као сваке секунде. Ефикасније је ако сервер шаље податке клијентским апликацијама за ћаскање када прими ћаскање. Ова функционалност се може извршити путем апликација у реалном времену.

Амазон је најавио да ће подржати ВебСоцкетс у АПИ Гатеваи-у на АВС ре: Инвент 2018. Касније у децембру, покренули су га у АПИ Гатеваи-у. Дакле, сада користећи АВС инфраструктуру у могућности смо да креирамо апликације у реалном времену користећи АПИ Гатеваи.

У овом посту ћемо створити једноставну апликацију за ћаскање користећи АПИ Гатеваи ВебСоцкетс. Пре него што започнемо са применом наше апликације за ћаскање, постоје неки концепти које морамо разумети у вези са апликацијама у реалном времену и АПИ мрежним пролазом.

Концепти АПИ-ја ВебСоцкет

АПИ ВебСоцкет састоји се од једне или више рута. Израз избор руте је ту да одреди којим путем одређена долазни захтев треба користити, који ће бити обезбеђен у долазних захтева. Израз се процењује према улазном захтеву да би се произвела вредност која одговара једној од вредности роутеКеи ваше руте . На пример, ако наше ЈСОН поруке садрже радњу позива својства и желите да извршите различите радње на основу овог својства, ваш израз за одабир руте могао би бити ${request.body.action}.

На пример: ако ваша ЈСОН порука изгледа као „радња“: „онМессаге“, „мессаге“: „Поздрав свима“}, тада ће за овај захтев бити изабрана рута онМессаге.

Подразумевано постоје три руте које су већ дефинисане у ВебСоцкет АПИ. Поред доле наведених рута, можемо додати и прилагођене руте за наше потребе.

  • $ дефаулт - Користи се када израз за одабир руте даје вредност која се не подудара ни са једним другим кључем руте у вашим АПИ рутама. Ово се може користити, на пример, за примену генеричког механизма за руковање грешкама.
  • $ цоннецт - Повезана рута се користи када се клијент први пут повеже са вашим ВебСоцкет АПИ.
  • $ дисцоннецт - повезана рута се користи када се клијент прекине везе са вашим АПИ-јем.

Када се уређај успешно повеже путем ВебСоцкет АПИ-ја, уређају ће се доделити јединствени ИД везе. Овај ИД везе ће се задржати током целог живота ако је веза. Да бисмо на уређај послали поруке, морамо да користимо следећи ПОСТ захтев користећи ИД везе.

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

Имплементација апликације за ћаскање

Након што смо научили основне концепте ВебСоцкет АПИ-ја, погледајмо како можемо створити апликацију у реалном времену помоћу ВебСоцкет АПИ-ја. У овом посту ћемо имплементирати једноставну апликацију за ћаскање користећи ВебСоцкет АПИ, АВС ЛАмбда и ДинамоДБ. Следећи дијаграм приказује архитектуру наше апликације у реалном времену.

У нашој апликацији уређаји ће бити повезани на АПИ мрежни пролаз. Када се уређај повеже, ламбда функција ће сачувати ИД везе у ДинамоДБ табели. У случају да желимо да вратимо поруку на уређај, друга ламбда функција ће вратити ИД везе и ПОСТ податке натраг на уређај помоћу УРЛ-а за повратни позив.

Креирање АПИ-ја ВебСоцкет

Да бисмо креирали ВебСоцкет АПИ, прво морамо да приступимо услузи Амазон АПИ Гатеваи помоћу конзоле. Тамо одаберите да креирате нови АПИ. Кликните на ВебСоцкет да бисте креирали ВебСоцкет АПИ, дајте име АПИ-ја и наш израз за одабир руте. У нашем случају додајте $ рекуест.боди.ацтион као наш израз за одабир и притисните Цреате АПИ.

Након креирања АПИ-ја биће преусмерени на страницу рута. Овде можемо видети већ унапред дефинисане три руте: $ цоннецт, $ дисцоннецт и $ дефаулт. Такође ћемо створити прилагођену руту $ онМессаге. У нашој архитектури, руте $ цоннецт и $ дисцоннецт постижу следеће задатке:

  • $ цоннецт - када се позове ова рута, функција Ламбда ће додати ИД везе повезаног уређаја на ДинамоДБ.
  • $ дисцоннецт - када се позове ова рута, Ламбда функција ће избрисати ИД везе одспојеног уређаја са ДинамоДБ.
  • онМессаге - када се позове ова рута, тело поруке ће бити послато на све уређаје који су у то време повезани.

Пре додавања руте у складу са горенаведеним, потребно је да урадимо четири задатка:

  • Направите ДинамоДБ табелу
  • Креирајте ламбда функцију цоннецт
  • Креирајте ламбда функцију за одспајање
  • Креирајте онМессаге ламбда функцију

Прво, креирајмо ДинамоДБ табелу. Идите на услугу ДинамоДБ и креирајте нову табелу под називом Цхат. Додајте примарни кључ као 'цоннецтионид'.

Даље, креирајмо функцију повезивања Ламбда. Да бисте креирали Ламбда функцију, идите на Ламбда услуге и кликните на цреате фунцтион. Изаберите Аутор од нуле и дајте му име као 'ЦхатРоомЦоннецтФунцтион' и улогу са потребним дозволама. (Улога треба да има дозволу за преузимање, стављање и брисање ставки из ДинамоДБ-а, позивање АПИ позива у АПИ мрежном пролазу.)

У код ламбда функције додајте следећи код. Овај код ће додати ИД везе повезаног уређаја у ДинамоДБ табелу коју смо креирали.

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

Даље, креирајмо и ламбда функцију одспајања. Користећи исте кораке креирајте нову ламбда функцију са именом

'ЦхатРоомДоннецтФунцтион'. У функцију додајте следећи код. Овај код ће уклонити ИД везе из табеле ДинамоДБ када се уређај прекине.

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

Сада смо креирали ДинамоДБ табелу и две ламбда функције. Пре креирања треће ламбда функције, вратимо се поново на АПИ Гатеваи и конфигуришемо руте користећи наше креиране ламбда функције. Прво кликните на $ цоннецт роуте. Као врсту интеграције изаберите Ламбда функцију и изаберите ЦхатРоомЦоннецтионФунцтион.

То можемо учинити и на $ дисцоннецт рути, где ће ламбда функција бити ЦхатРоомДисцоннецтионФунцтион:

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.

То је то за нашу једноставну апликацију за ћаскање помоћу АВС ВебСоцкет АПИ. Заправо нисмо конфигурисали $ дефалут руту која се позива у свакој прилици када није пронађена рута. Спровођење те руте препустићу вама. Хвала и видимо се у другом посту. :)