Комп'ютерні мережі та розподілені системи
Тривалість: 4 акад. години (2 пари).
Мета: Навчитися створювати розподілені застосунки на базі REST API з використанням різноманітних пристроїв
Необхідне апаратне забезпечення.
Для проведення лабораторних робіт необхідно мати комп’ютер з наступною мінімальною апаратною конфігурацією:
Необхідне програмне забезпечення.
Загальна постановка задачі.
Цілі роботи:
1) навчитися створювати розподілені застосунки на базі REST API 2) навчитися використовувати Google Apps Scripts для створення WEB-застосунків 3) навчитися використовувати утиліти тестування HTTP API
Лабораторна робота розроблена з урахуванням самостійного виконання без наявності реального обладнання окрім ПК та смартфону.
У даній лабораторній роботі необхідно розробити розподілений застосунок, який повинен виконувати кілька функцій:
Google Sheet
- це хмарний застосунок від Google для роботи з електронними таблицями. За функціональністю і принципами роботи він схожий на Microsoft Excel
. Усі створені таблиці зберігаються на Гугл Диску (Google Drive
). У даному рішенні електронна таблиця буде виконувати роль простої бази даних куди буде накопичуватися різноманітна інформація з різних пристроїв. Взаємодія з базою даних відбуватиметься через розгорнутий Web Applications на базі сервісів Google Apps Scripts, надалі вживатиметься термін GAS. У цьому пункті необхідно:
реалізувати функцію в GAS, яка буде записувати значення у вказані комірки вказаного листа з фіксацією часу
Якщо у Вас немає облікового запису Google - створіть його на сайті. Це безкоштовно, потребується тільки поштова скринька і номер телефону.
Google Sheet
(Таблиці
)рис.4.1. Створення таблиці Google Sheet
Альтернативно можна відразу перейти на сторінку
У новому вікні натисніть кнопку “+” (створити) щоб створити нову електронну таблицю
Змініть назву документу на якусь більш прийнятну, наприклад WebAppLaba4
Створіть розширення на Apps Script
рис.4.2. Створення розширення на Apps Script
WebAppYourName
, де YourName
ваше ім’я та прізвище//тестовий запис
let smplrecord = {
device: 'testdevice',
field1: Math.random(),
field2: 'this is a text',
field3: {text: 'this is JSON', n: Math.random()*100.0}
}
//тестування виклику
let result = LogToSheet (smplrecord);
console.log (result);
function LogToSheet (record) {
if (!record) return
let devicename = record.device;
// якщо імя пристрою не вказане, завершити виконання функції і
// і повернути відповідний текст
if (typeof devicename !== 'string') {
return 'There is no device field';
}
// доступ до активного документу Google Sheet
let ss = SpreadsheetApp.getActive();
// доступ до листа - журналу пристрою
let sheet = ss.getSheetByName(devicename);
// якщо такого листа немає - створити
if (!sheet) {
sheet = ss.insertSheet(devicename);
}
// спочатку вставляємо новий запис в 2-гу позицію
// усі старі записи зміщуються вниз
sheet.insertRowBefore(2);
// доступаємося до перших 2-х клітин в 1-му рядку
let r = sheet.getRange (1,1,1,2);
// пишемо назви колонок
r.setValues ([['TS','DT']]);
// доступаємося до перших 2-х клітин в 2-му рядку
r = sheet.getRange (2,1,1,2);
// пишемо TS - відмітку часу, DT - дату та часу
let date = new Date();
r.setValues ([[date.valueOf() , date.toLocaleDateString('uk-UA') + ' ' + date.toLocaleTimeString('uk-UA')]])
i=3; // починаємо з 3-го стовпчика
// перебираємо усі поля
for (let propname in record){
// device використовується для назив листа
if (propname !='device') {
// виділяємо область i-ї ствопця, 1 та 2 рядки
r = sheet.getRange(1,i,2,1);
// у 1-му рядку - назва, у 2-му - значення
r.setValues ([[propname],[record[propname]]])
i++;
}
}
return (i-3 + ' parameters are recorded')
}
рис.4.3. Дозвіл на доступ застосунку до Google Sheet
Після успішного виконання у вікні консолі повинно з’явитися повідомлення:
`Інформація 3 parameters are recorded`
Перевірте в документі Google Sheet створилася вкладка testdevice
з відповідним записом.
рис.4.4. Відображення запису
У даному пункті необхідно реалізувати HTTP API інтерфейс для доступу до даних для читання. Читання даних передбачатиме доступ до записів в конкретних листів, відповідного device
з використанням методу GET за шаблоном:
GET baseurl/device
Де baseurl
- базова частина URL-шляху, яка вказується Google при публікації або тестуванні інетрфейсу Якщо device
не вказується, тобто:
GET baseurl
то виклик методу повинен повинен повертати перелік листів. В обидвох випадках дані повинні повертатися у форматі JSON.
У даній частині лабораторної роботи не проводиметься захист даних і не вимагатиметься автентифікація при доступі до інтерфейсу, а доступ до даних в Google Sheet відбуватиметься через автентифікацію від імені власника застосунку.
doGet(e)
яка повертає зміст вказаного листа або перелік листів, якщо лист не заданий.// доступ до даних
function doGet(e) {
// все що йде після базової частини URL,
// тобто праворуч від /dev або /exec
let path = e.pathInfo;
// запис для журналу для налагодження
let record = {device : "APIGET", path: path};
LogToSheet (record); // закоментувати за відсутності налагодження
let ss = SpreadsheetApp.getActive();
let sheets = ss.getSheets();
let retob = ['Not Found']; // якщо щось пішло не так
if (typeof path == 'string') { // якщо параметр є
try { // ловимо помилку
// доступ до вказаного листа
let sheet = ss.getSheetByName(path);
// отримати всі дані
retob = sheet.getDataRange().getValues();
} catch (error) { // якщо зловили помилку
retob = [error + ' ' + path];
}
} else { // якщо без параметрів, тільки базоий URL
try {
retob = []; // масив з переліком назв листів
for (let sheet of sheets){ //перебираємо листи
retob.push (sheet.getName()) //добавляємо назви в масив
}
} catch (error) {
retob = [error + ' ' + path];
}
}
// формуємо вихід як JSON
return ContentService.createTextOutput(JSON.stringify(retob)).setMimeType(ContentService.MimeType.JSON);
}
рис.4.5. Публікація скрипту як Веб-застосунку
Після публікації застосунку його базовий URL матиме вигляд:
https://script.google.com/macros/s/AKfyVOWxGcR9A/exec
Зауважте що кожна зміна коду буде приводити до необхідності публікації нового застосунку. Стара адреса буде використовуватися зі старим кодом. Так забезпечується версійність, коли старі застосунки залишаються робочими при зміні реалізації.
Також варто відзначити, що Google Apps Scripts пропонує два інтерфейси:
/exec
і постійно змінюється при публікації/dev
і не змінюється при публікації, є постійною що спрощує налагодження але доступний тільки розробникам. Приклад такого URL:https://script.google.com/macros/s/AKfycbzES1LxLzsgWPDm/dev
Таким чином налагоджувати веб-застосунок простіше з URL, що завершується на /dev
Для тестування API можна скористатися різними безкоштовними утилітами. У даній лабораторній роботі пропонується скористатися доповненням до браузерів FireFox
або Chrome
з назвою RESTED.
Встановіть розширення для браузера RESTED, або аналогічне, яке можна знайти за посиланнями:
https://chrome.google.com/webstore/detail/rested/eelcnbccaccipfolokglfhhmapdchbfg
\dev
). Вставте в якийсь текстовий файл.рис.4.6. Отримання посилання на інтерфейс режиму розроблення
Запустіть розширення RESTED, як правило іконка на панелі інструментів браузера
У полі URL впишіть скопійовану адресу і натисніть Send Request
рис.4.7. Перевірка роботи запиту GET для доступу до переліку листів.
У відповіді повинен прийти список, або повідомлення про помилку.
APIGET
з даними по запитам.Увага! Лист APIGET
призначений в даному розроблювальному застосунку тільки для тестування, варто закоментувати частину коду з викликом запису в даний лист, якщо налагодження не потребується, щоб не збільшувати даремно об’єм файлу.
рис.4.8. Перегляд журналу виконання
/testdevice
, щоб URL закінчувався на:.../dev/testdevice
+
)У даному пункті необхідно реалізувати HTTP API інтерфейс для доступу до даних для запису. Запис даних передбачатиме відправку методу POST на базовий URL тобто за шаблоном:
POST baseurl
у якому в якості корисного навантаження передаються дані в форматі JSON в форматі:
{"device": "devicename", "field1": "fieldvalue1", ... "fieldn": "fieldvaluen"}
де:
devicename
- ідентифікатор пристрою, для якого буде створена окрема вкладкаfield1
.. fieldn
- назва параметрів, для яких відбуватиметься записfieldvalue1
.. fieldvaluen
- значення параметрів, для яких відбуватиметься запис
// запис даних
function doPost(e) {
// корисне навантаження
let contents = e.postData.contents;
let retob = {contents: contents};
try {
// перетворюємо на об'єкт
let payload = JSON.parse(contents);
// якщо є властивість device
if (payload.device) {
LogToSheet (payload);
retob.msg = 'logged succefull'
} else {
retob.error = 'device field not found'
}
} catch (error) {
retob.error = error;
}
return ContentService.createTextOutput(JSON.stringify(retob)).setMimeType(ContentService.MimeType.JSON);
}
/dev
device
зі значенням testdevice
та кілька інших полів зі значеннямиtestdevice
у Google Sheetрис.4.9. Перегляд методу POST за допомогою RESTED
У попередніх пунктах використовувався API режиму розробника, URL якого не змінюється під час зміни проекту та новій публікації. У цьому пункті необхідно протестувати API режиму виконання, URL якого змінюється при кожній публікації нового серверу.
рис.4.10. Публікація нової версії застосунку
Цю адресу можна також буде знайти у будь який момент часу у пункті “Керування введенням в дію”
cURL — назва проєкту і крос-платформового програмного засобу, що служить для передачі даних через HTTP. Його можна використовувати як для тестування так і для реалізації обміну по HTTP через командний рядок.
У даному пункті cURL буде використовуватися як тестова утиліта. Але так само її можна запускати на різноманітних пристроях з ОС, наприклад для запису якихось значень з планувальника.
C:/temp
cd C:\temp\curl-7.86.0_2-win64-mingw\bin
curl -h
curl -L https://script.google.com/macros/s/AKfycbyPDQrLEi20cmHw/exec
Опція -L
потрібна для того, щоб у випадку коли сервер повідомляє, що запитану сторінку переміщено в інше місце (позначене заголовком Location
: і кодом відповіді 3XX
), ця опція змусить curl повторити запит у новому місці (деталі). Google Apps Script працює саме через перенаправлення заради безпеки, тому ця опція потрібна.
curl -L https://script.google.com/macros/s/AKfycbyPDQrLEiQjV8YCLplk2FENdnEWssuQK0AVuhLkP8VckZXmJyyml5EtDBHynACJ20cmHw/exec -H "Content-Type:application/json" -d "{\"device\":\"testdevice\", \"field1\":\"1\", \"field2\":\"2\", \"field3\":\"3\"}"
У наведеному прикладі використовувалися опції:
-H
для вказівки заголовку-d
для вказівки даних що передаються, що автоматично переключає утиліту в режим використання методу POSTКрім того, враховуючи щ в JSON форматі використовуються дужки, необхідно їх дзеркалювати зворотною косою \
, щоб вони не були прийняті як частина синтаксису команди curl
У даному пункті необхідно реалізувати HTTP клієнта на маршрутизаторі Mikrotik, вірніше на віртуальній машині з ОС Mikrotik, який буде періодично відправляти повідомлення з вказівкою часу з моменту включення.
Для цього використовуються вбудовані в маршрутизатор скрипти на мові RouterOS
(повний опис), які викликатимуться по певним подіям планувальника (повний опис).У межах цієї лабораторної роботи не будемо заглиблюватися в деталі реалізації, зупинимося лише на основних моментах.
Для обміну по HTTP в RouterOS
використовується схожа за функціоналом до cURL вбудована утиліта Fetch
. Це один із інструментів консолі в Mikrotik RouterOS, який як правило використовується для копіювання файлів на/з мережевого пристрою через HTTP/HTTPs, FTP або SFTP, але його також можна використовувати для надсилання запитів POST/GET і надсилання будь-яких даних на віддалений сервер
Mikrotik RouterOS
ip address print
щоб дізнатися IP адресу віртуальної машини маршрутизаторуWebFig
далі в розділ System->Scripts
periodic
, наведений нижче, замінивши URL, на відповідний з вашого API# send periodic message
:local upTime1 [/system resource get uptime];
:global URL "https://script.google.com/macros/s/0AVuhLkP8VckZXmJyyml5EtDBHynACJ20cmHw/exec"
:global httpdata1 ("{\"device\":\"router\", \"uptime\":\"" . $upTime1 . "\"}");
/tool fetch \
http-method=post \
http-header-field="Content-Type: application/json" \
http-data=$httpdata1 \
url=$URL
Apply
після чого Run Script
. Зачекайте кілька секунд і у Gogle Sheet повинен з’явитися лист з відповідним записом.Ok
рис.4.11. Створення скрипта для запису часу роботи на WEB застосунок
Прокоментуємо скрипт написаний вище
:local
і :global
- це інструкції для створення глобальних та локальних змінних. Так створюються наступні змінні.
upTime1
якій при створенні назначається значення з системної властивості /system resource get uptime
(її значення можна подивитися на відповідній вкладці веб-консолі). За необхідності можна записувати аналогічним чином будь яку доступну властивість на маршрутизаторі.URL
назначається значення рядку що відповідає за URL.httpdata1
назначається значення корисного навантаження. Зверніть увагу, що тут також використовується екранування/tool fetch
викликає утиліту fetch
до якої передаються параметри:
http-method
- методhttp-header-field
- заголовкиhttp-data
- тіло (корисне навантаження)url
System->Scheduler
. Натисніть Add New
.periodic
кожні 5 хвилин. І натисніть Ok
рис.4.12. Налаштування планувальника.
Таким чином можна віддалено контролювати працездатність маршрутизатора, його доступ до інтернету та оцінювати час його перезавантаження.
У даному пункті у якості клієнта використовується мобільний застосунок, який за потребою користувача відправляє географічні координати та текстову примітку, яку він вводить. Таким чином, користувач може фіксувати певну інформацію вдповідно до локації, де він знаходиться.
Даний пункт передбачає використання смартфона під ОС Android з включеною геолокацією. Якщо у здобувача немає такої можливості, пункт можна не виконувати.
Static Variable
, в яких вкажіть тільки назву:geolacation
note
рис.4.13. Створення змінних
Створіть новий Shortcut: натисніть +
для добавлення, для якого (рис.4.14):
Regular Shorcuts
.надайте ім’я та виберіть піктограму.
у Basic request settiings
вкажіть метод POST, а в рядок заголовку потрібний URL, який можна передати на смартфон наприклад через Telegram (пункт “Збережене”)
request body
виберіть тип application/json
та означте структуру тіла, в якому в якості імені пристрою передається myphone
, у властивості location
передається значення geolocation
, а у властивості note
значення змінної note
(зверніть увагу на лапки!); для вставлення змінних використовується кнопка {}
scripting
для Run Before Execution
(виконується перед запуском HTTP запиту) записати скрипт який буде записувати у значення змінних:
geolocation
значення функції getGeolocation
, яка повертає плинну позицію телефонуnote
результат виклику діалогової функції, яка попросить оператора ввести нотаткурис.4.14. Створення Shortcut
myphone
.@BotFather
- це бот, який створює ботів/start
з’явиться вікно з доступними командамирис.4.15. Доступні команди створення телеграм-бота
/newbot
для створення нового ботарис.4.16. Команда створення телеграм-бота
RPI Ivanenko Ivana
рис.4.17. Надання імені телеграм-боту
, наприклад
RPI_IvanenkoIvana_Bot` . За допомогою цього нікнейма можна знайти й додати вашого бота до своїх контактів.рис.4.18. Надання імені користувача телеграм-боту
t.me/<username>
./start
За допомогою RPI_IvanenkoIvana_Bot
ви зможете завжди відредагувати своїх ботів. Перелік своїх ботів можна подивитися через команду /mybots
де також можна отримати по ньому всю необхідну інформацію
https://api.telegram.org/botXXX:YYYY/getUpdates
де замість XXX:YYYY
підставте маркер (токен) отриманий від бота.
У результаті бот відповість повідомленням з історією останніх повідомлень, наприклад:
{
"ok": true,
"result": [
{
"update_id": 60872760,
"message": {
"message_id": 514,
"from": {
"id": 7654321,
"is_bot": false,
"first_name": "Oleksandr",
"last_name": "Pupena",
"username": "pupena_san",
"language_code": "uk"
},
"chat": {
"id": 1234567,
"first_name": "Oleksandr",
"last_name": "Pupena",
"username": "pupena_san",
"type": "private"
},
"date": 1670484704,
"text": "/start",
"entities": [
{
"offset": 0,
"length": 6,
"type": "bot_command"
}
]
}
}
]
}
id
у структурі chat
у прикладі це 1234567
і зробіть наступний запит POST за URL https://api.telegram.org/botXXX:YYYY/sendMessage
(вставте туди токен) та наступним змістом body та заголовків:рис.4.19. Приклад відправки повідомлення sendMessage
Деталі по sendmsg можна почитати в оригінальному описі за посиланням
+
“Додати файл”, дайте йому назву telegram
const token = 'XXX:YYYY';
const tgBotUrl = 'https://api.telegram.org/bot' + token;
const chat_id = 1234567
let payload = {
chat_id: chat_id,
text: 'Привіт чатботу від Google Scripts!',
}
sendToTelegram(payload);
function sendToTelegram(payload){
let options = {
method : 'post',
contentType: 'application/json',
headers: {"Content-Type":"application/json"},
payload: JSON.stringify(payload),
muteHttpExceptions: true
}
// відправляє http запит з вказним URL та опціями
return UrlFetchApp.fetch(tgBotUrl + '/sendMessage', options);
}
У цьому пункті необхідно реалізувати фрагмент програми, яка буде перевіряти активність маршрутизатору і повідомляти по Телеграму власника про те, що маршрутизатор з’явився в мережі або зник з неї. Це буде відбуватися за наступним принципом. Оскільки маршрутизатор періодично (раз в 5 хв) відправляє повідомлення про себе, які пишуться в Google Sheet, то відсутнє повідомлення більше ніж за 5 хвилин говорить про його зникнення. При цьому запам’ятовується статус пристрою як відключений "isConnected" : "false"
. Коли пристрій з’являється статус знову стає "isConnected" : "true"
, а користувачу відправляється відповідне повідомлення.
Перевірка статусу проводиться раз/хвилину, для цього використовується вбудовані в Google Scripts таймерні тригери. Для збереження стану пристрою використовується властивості скрипта, які можна змінювати під час роботи.
sendToTelegram
{"device" : "router", "isConnected" : "false", "onConnect": "false", "onDisconnect": "false", "lastupdate" : "", "uptime": ""}
рис.4.20. Добавлення властивості скрипта
Це дасть можливість зберігати дані між викликами.
telegram.gs
допишіть наступний фрагмент.// зчитуємо стан маршрутизатору зі властивості скрипта
let RouterStatus = JSON.parse (PropertiesService.getScriptProperties().getProperty('RouterStatus'));
// обробляємо плинний статус
GetActivity (RouterStatus);
// зберігаємо плинний статус у властивості скрипта
PropertiesService.getScriptProperties().setProperty('RouterStatus', JSON.stringify(RouterStatus));
// функція обробки плинного статусу
function GetActivity (status){
let now = new Date();
let ss = SpreadsheetApp.getActive();
let sheets = ss.getSheets();
let sheet = ss.getSheetByName(status.device);
if (sheet) {
// у другому рядку знаходиться останній запис від маршрутизатору
// у другій комірці дата та час запису
let dt = sheet.getRange (2,2,1,1).getValue();
if (typeof dt == 'object') {
let deviceupdate = new Date(dt);
// різниця між плинною датою і датою фіксації в секундах
let delta = (now - deviceupdate)/1000;
// стан "підключео", якщо різниця менше 6 хвилин
let isConnected = delta < 360;
// стан тількино підключився
status.onConnect = isConnected === true && status.isConnected === false;
// стан тількино віддключився
status.onDisconnect = isConnected === false && status.isConnected === true;
status.isConnected = isConnected;
status.lastupdate = deviceupdate;
// також користувачу може бути цікавим час роботи маршрутизатору
status.uptime = sheet.getRange (2,3,1,1).getDisplayValue();
}
}
let text = '';
// значення в локальному форматі дати та часу
let localenow = now.toLocaleDateString('uk-UA') + ' ' + now.toLocaleTimeString('uk-UA');
let localedt = status.lastupdate.toLocaleDateString('uk-UA') + ' ' + status.lastupdate.toLocaleTimeString('uk-UA');
if (status.onConnect === true) { // тільки но включився
text = `${status.device} з'явився в мережі о ${localedt} час роботи ${status.uptime}`
}
if (status.onDisconnect === true) { // тільки но відключився
text = `${status.device} зник з мережі десь між ${localedt} і ${localenow}, час роботи ${status.uptime}`
}
if (status.onDisconnect==true || status.onConnect == true){
sendToTelegram({chat_id: chat_id,text}); // відправляємо в Телеграм
}
}
рис.4.21. Добавлення тригеру
Викладачем перевіряється виконання всіх пунктів роботи відповідно до занотованих у звіті результатів. Оцінюється повнота результатів. Кінцева оцінка коригується по усному опитуванню при очному спілкуванню. Кожен результат студент повинен пояснити. У випадку виникнення помилок або запитань щодо проведення певного пункту, його необхідно буде повторити.
1) Які цілі були поставлені в лабораторній роботі? Як вони досягалися? 2) Які хмарні сервіси та застосунки були використані в даній лабораторній роботі? 3) Розкажіть про принципи функціонування протоколу HTTP. Для чого він використовувався в даній лабораторній роботі? 4) Які тригери і для чого були використані в програмах GAS в даній лабораторній роботі? 5) Поясніть що таке JSON? Як був використаний JSON в даній лабораторній роботі? Які функції перетворення були застосовані? 6) Які способи та засоби автентифікації а також захисту були в лабораторній роботі? 7) Які об’єкти і методи Google Sheet API і для чого були використані в даній лабораторній роботі? 8) Які методи HTTP підтримуються в GAS при публікації програми як веб-застосунку? Як вони були використані в лабораторній роботі? 9) Розкажіть що таке REST? Як це стосується даної лабораторної роботи? 10) Які тестові утиліти і як були використані в даній лабораторній роботі для тестування опублікованого Веб-застосунку? 11) Поясніть роботу скрипту в Мікротіку, яка використовувалася в даній лабораторній роботі. 12) Розкажіть як в лабораторній роботі проводиться інтегрування з сервісами Телеграм.