cmputernetwork

Комп'ютерні мережі та розподілені системи

Лабораторні

ЛАБОРАТОРНА РОБОТА № 4. Створення розподіленого застосунку

Тривалість: 4 акад. години (2 пари).

Мета: Навчитися створювати розподілені застосунки на базі REST API з використанням різноманітних пристроїв

Лабораторна установка

Необхідне апаратне забезпечення.

Для проведення лабораторних робіт необхідно мати комп’ютер з наступною мінімальною апаратною конфігурацією:

Необхідне програмне забезпечення.

Загальна постановка задачі.

Цілі роботи:

1) навчитися створювати розподілені застосунки на базі REST API 2) навчитися використовувати Google Apps Scripts для створення WEB-застосунків 3) навчитися використовувати утиліти тестування HTTP API

Лабораторна робота розроблена з урахуванням самостійного виконання без наявності реального обладнання окрім ПК та смартфону.

У даній лабораторній роботі необхідно розробити розподілений застосунок, який повинен виконувати кілька функцій:

Послідовність виконання роботи

1. Створення документу Google Sheet з розширенням Google Apps Scripts (GAS)

Google Sheet - це хмарний застосунок від Google для роботи з електронними таблицями. За функціональністю і принципами роботи він схожий на Microsoft Excel. Усі створені таблиці зберігаються на Гугл Диску (Google Drive). У даному рішенні електронна таблиця буде виконувати роль простої бази даних куди буде накопичуватися різноманітна інформація з різних пристроїв. Взаємодія з базою даних відбуватиметься через розгорнутий Web Applications на базі сервісів Google Apps Scripts, надалі вживатиметься термін GAS. У цьому пункті необхідно:

рис.4.1. Створення таблиці Google Sheet

Альтернативно можна відразу перейти на сторінку

image-20221206172807635

рис.4.2. Створення розширення на Apps Script

2. Створення функції Google Apps Scripts для запису полів у вказаний лист

//тестовий запис
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')
}

image-20221206182104694

рис.4.3. Дозвіл на доступ застосунку до Google Sheet

image-20221206182744389

рис.4.4. Відображення запису

3. Створення та перше розгортання Web Application на базі GAS

У даному пункті необхідно реалізувати HTTP API інтерфейс для доступу до даних для читання. Читання даних передбачатиме доступ до записів в конкретних листів, відповідного device з використанням методу GET за шаблоном:

GET baseurl/device

Де baseurl - базова частина URL-шляху, яка вказується Google при публікації або тестуванні інетрфейсу Якщо device не вказується, тобто:

GET baseurl

то виклик методу повинен повинен повертати перелік листів. В обидвох випадках дані повинні повертатися у форматі JSON.

У даній частині лабораторної роботи не проводиметься захист даних і не вимагатиметься автентифікація при доступі до інтерфейсу, а доступ до даних в Google Sheet відбуватиметься через автентифікацію від імені власника застосунку.

// доступ до даних
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);
}

image-20221206223810030

рис.4.5. Публікація скрипту як Веб-застосунку

Після публікації застосунку його базовий URL матиме вигляд:

https://script.google.com/macros/s/AKfyVOWxGcR9A/exec

Зауважте що кожна зміна коду буде приводити до необхідності публікації нового застосунку. Стара адреса буде використовуватися зі старим кодом. Так забезпечується версійність, коли старі застосунки залишаються робочими при зміні реалізації.

Також варто відзначити, що Google Apps Scripts пропонує два інтерфейси:

https://script.google.com/macros/s/AKfycbzES1LxLzsgWPDm/dev

Таким чином налагоджувати веб-застосунок простіше з URL, що завершується на /dev

4. Тестування Web Application з використанням тестової утиліти

Для тестування API можна скористатися різними безкоштовними утилітами. У даній лабораторній роботі пропонується скористатися доповненням до браузерів FireFox або Chrome з назвою RESTED.

image-20221206225524279

рис.4.6. Отримання посилання на інтерфейс режиму розроблення

image-20221206232645522

рис.4.7. Перевірка роботи запиту GET для доступу до переліку листів.

У відповіді повинен прийти список, або повідомлення про помилку.

Увага! Лист APIGET призначений в даному розроблювальному застосунку тільки для тестування, варто закоментувати частину коду з викликом запису в даний лист, якщо налагодження не потребується, щоб не збільшувати даремно об’єм файлу.

image-20221206233321195

рис.4.8. Перегляд журналу виконання

.../dev/testdevice

5. Реалізація та перевірка методу POST для запису

У даному пункті необхідно реалізувати HTTP API інтерфейс для доступу до даних для запису. Запис даних передбачатиме відправку методу POST на базовий URL тобто за шаблоном:

POST baseurl

у якому в якості корисного навантаження передаються дані в форматі JSON в форматі:

{"device": "devicename", "field1": "fieldvalue1", ... "fieldn": "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);
}

image-20221207125150736

рис.4.9. Перегляд методу POST за допомогою RESTED

6. Перевірка роботи через опублікований API

У попередніх пунктах використовувався API режиму розробника, URL якого не змінюється під час зміни проекту та новій публікації. У цьому пункті необхідно протестувати API режиму виконання, URL якого змінюється при кожній публікації нового серверу.

image-20221207130922173

рис.4.10. Публікація нової версії застосунку

Цю адресу можна також буде знайти у будь який момент часу у пункті “Керування введенням в дію”

7. Перевірка роботи клієнта Web Application через curl

cURL — назва проєкту і крос-платформового програмного засобу, що служить для передачі даних через HTTP. Його можна використовувати як для тестування так і для реалізації обміну по HTTP через командний рядок.

У даному пункті cURL буде використовуватися як тестова утиліта. Але так само її можна запускати на різноманітних пристроях з ОС, наприклад для запису якихось значень з планувальника.

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\"}"

У наведеному прикладі використовувалися опції:

Крім того, враховуючи щ в JSON форматі використовуються дужки, необхідно їх дзеркалювати зворотною косою \, щоб вони не були прийняті як частина синтаксису команди curl

8. Відправки даних з Mikrotik

У даному пункті необхідно реалізувати HTTP клієнта на маршрутизаторі Mikrotik, вірніше на віртуальній машині з ОС Mikrotik, який буде періодично відправляти повідомлення з вказівкою часу з моменту включення.

Для цього використовуються вбудовані в маршрутизатор скрипти на мові RouterOS (повний опис), які викликатимуться по певним подіям планувальника (повний опис).У межах цієї лабораторної роботи не будемо заглиблюватися в деталі реалізації, зупинимося лише на основних моментах.

Для обміну по HTTP в RouterOS використовується схожа за функціоналом до cURL вбудована утиліта Fetch . Це один із інструментів консолі в Mikrotik RouterOS, який як правило використовується для копіювання файлів на/з мережевого пристрою через HTTP/HTTPs, FTP або SFTP, але його також можна використовувати для надсилання запитів POST/GET і надсилання будь-яких даних на віддалений сервер

# 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

image-20221207151140977

рис.4.11. Створення скрипта для запису часу роботи на WEB застосунок

Прокоментуємо скрипт написаний вище

:local і :global - це інструкції для створення глобальних та локальних змінних. Так створюються наступні змінні.

image-20221207153148624

рис.4.12. Налаштування планувальника.

Таким чином можна віддалено контролювати працездатність маршрутизатора, його доступ до інтернету та оцінювати час його перезавантаження.

9. Відправки даних з Android

У даному пункті у якості клієнта використовується мобільний застосунок, який за потребою користувача відправляє географічні координати та текстову примітку, яку він вводить. Таким чином, користувач може фіксувати певну інформацію вдповідно до локації, де він знаходиться.

Даний пункт передбачає використання смартфона під ОС Android з включеною геолокацією. Якщо у здобувача немає такої можливості, пункт можна не виконувати.

image-20221207165856214

рис.4.13. Створення змінних

image-20221207171108517

рис.4.14. Створення Shortcut

10. Реєстрація Telegram

img

рис.4.15. Доступні команди створення телеграм-бота

img

рис.4.16. Команда створення телеграм-бота

img

рис.4.17. Надання імені телеграм-боту

img

рис.4.18. Надання імені користувача телеграм-боту

За допомогою RPI_IvanenkoIvana_Bot ви зможете завжди відредагувати своїх ботів. Перелік своїх ботів можна подивитися через команду /mybots де також можна отримати по ньому всю необхідну інформацію

11. Перевірка роботи з ботом через RESTED

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"
          }
        ]
      }
    }
  ]
}

image-20221208104533416

рис.4.19. Приклад відправки повідомлення sendMessage

Деталі по sendmsg можна почитати в оригінальному описі за посиланням

12. Перевірка Telegram бота в GAS

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

13. Створення коду для генерування повідомлення про стан маршрутизатору

У цьому пункті необхідно реалізувати фрагмент програми, яка буде перевіряти активність маршрутизатору і повідомляти по Телеграму власника про те, що маршрутизатор з’явився в мережі або зник з неї. Це буде відбуватися за наступним принципом. Оскільки маршрутизатор періодично (раз в 5 хв) відправляє повідомлення про себе, які пишуться в Google Sheet, то відсутнє повідомлення більше ніж за 5 хвилин говорить про його зникнення. При цьому запам’ятовується статус пристрою як відключений "isConnected" : "false". Коли пристрій з’являється статус знову стає "isConnected" : "true", а користувачу відправляється відповідне повідомлення.

Перевірка статусу проводиться раз/хвилину, для цього використовується вбудовані в Google Scripts таймерні тригери. Для збереження стану пристрою використовується властивості скрипта, які можна змінювати під час роботи.

{"device" : "router", "isConnected" : "false", "onConnect": "false", "onDisconnect": "false",  "lastupdate" : "", "uptime": ""}

image-20221208145959795

рис.4.20. Добавлення властивості скрипта

Це дасть можливість зберігати дані між викликами.

// зчитуємо стан маршрутизатору зі властивості скрипта
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}); // відправляємо в Телеграм   
  }
}

image-20221208151349365

рис.4.21. Добавлення тригеру

Перевірка виконання роботи та питання до захисту.

Викладачем перевіряється виконання всіх пунктів роботи відповідно до занотованих у звіті результатів. Оцінюється повнота результатів. Кінцева оцінка коригується по усному опитуванню при очному спілкуванню. Кожен результат студент повинен пояснити. У випадку виникнення помилок або запитань щодо проведення певного пункту, його необхідно буде повторити.

1) Які цілі були поставлені в лабораторній роботі? Як вони досягалися? 2) Які хмарні сервіси та застосунки були використані в даній лабораторній роботі? 3) Розкажіть про принципи функціонування протоколу HTTP. Для чого він використовувався в даній лабораторній роботі? 4) Які тригери і для чого були використані в програмах GAS в даній лабораторній роботі? 5) Поясніть що таке JSON? Як був використаний JSON в даній лабораторній роботі? Які функції перетворення були застосовані? 6) Які способи та засоби автентифікації а також захисту були в лабораторній роботі? 7) Які об’єкти і методи Google Sheet API і для чого були використані в даній лабораторній роботі? 8) Які методи HTTP підтримуються в GAS при публікації програми як веб-застосунку? Як вони були використані в лабораторній роботі? 9) Розкажіть що таке REST? Як це стосується даної лабораторної роботи? 10) Які тестові утиліти і як були використані в даній лабораторній роботі для тестування опублікованого Веб-застосунку? 11) Поясніть роботу скрипту в Мікротіку, яка використовувалася в даній лабораторній роботі. 12) Розкажіть як в лабораторній роботі проводиться інтегрування з сервісами Телеграм.