Skip to content

12. Функції HTTP на JavaScript

Image

12.1. Вибір бібліотеки HTTP

Тут ми обрали дві бібліотеки:

EcmaScript 6 має вбудовану функцію HTTP під назвою [fetch], яка не реалізована в [node.js] (вересень 2019 р.). Існує бібліотека під назвою [node-fetch], яка дозволяє використовувати функцію [fetch] у середовищі Node. Ця бібліотека використовує певні API, властиві [node.js]. Отже, код [node-fetch] може бути не на 100 % сумісним у середовищі, відмінному від [node], наприклад, у веб-браузері;

Крім того, існує бібліотека під назвою [axios], призначена для запитів HTTP, сумісна як з [node.js], так і з браузерами. Саме цю бібліотеку ми в підсумку й будемо використовувати.

Ми продемонструємо один і той самий скрипт, написаний з використанням обох бібліотек, щоб показати, що підхід до кодування з ними є аналогічним.

12.2. Налаштування робочого середовища

12.2.1. Встановлення сервера розрахунку податків

Зрештою, ми напишемо веб-додаток із такою архітектурою:

Image

JS: JavaScript

Код JavaScript є клієнтським:

  • сервісу статичних сторінок або фрагментів;
  • сервісу jSON;

Отже, код JavaScript є клієнтом jSON і, як такий, може бути організований у шари [UI, métier, dao] (UI: User Interface), як і наші клієнти jSON, написані на PHP.

Сервером буде той самий сервер для розрахунку податку, для якого ми вже написали 13 версій. Ми збираємося написати 14-ту. Отже, спочатку ми дублюємо в NetBeans папку версії 13 у папці версії 14:

Image

  • у [6], змінюємо файл [config.json] 14-ї версії наступним чином:

{
    "databaseFilename": "Config/database.json",
    "rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-14",
    "relativeDependencies": [

        "/Entities/BaseEntity.php",
        "/Entities/Simulation.php",
        ...
    "vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
    "vue-erreurs": "vue-erreurs.php"
}
  • у рядку 3 змінюємо кореневу папку додатка;

Щоб отримати доступ до цього сервера, потрібно запустити служби [Laragon].

Зробивши це, ми можемо протестувати за допомогою [Postman] (див. статтю за посиланням) цю нову версію сервера, яка наразі ідентична версії 13. Ми можемо використовувати набір запитів, що застосовувався для тестування версії 12 сервера розрахунку податків:

Image

  • у [1-4] — використати запит [init-session-700] для ініціалізації сесії jSON;
  • у [4-5] замість [version-12] вкажіть [version-14] для тестування версії 14 проекту;
  • під час виконання слід отримати відповідь jS0N [6] від сервера;

Версія 14 сервера тепер працює. Нам доведеться дещо її змінити. Нагадаємо код API цього сервера:

Дія
Роль
Контекст виконання
init-session
Служить для визначення типу (json, xml, html) бажаних відповідей
Запит GET main.php?action=init-session&type=x
може бути відправлений у будь-який час
authentifier-utilisateur
Дозволяє або забороняє користувачеві увійти в систему
Запит POST main.php?action=authentifier-utilisateur
Запит повинен містити два параметри, надіслані методом POST [user, password]
Може бути відправлений лише у тому випадку, якщо тип сесії (json, xml, html) відомий
calculer-impot
Виконує симуляцію розрахунку податку
Запит POST main.php?action=calculer-impot
Запит повинен містити три параметри, надіслані методом POST: [marié, enfants, salaire]
Може бути відправлений лише у разі, якщо тип сесії (json, xml, html) відомий, а користувач пройшов автентифікацію
lister-simulations
Запит на перегляд списку симуляцій, виконаних з початку сеансу
Запит GET main.php?action=lister-simulations
Запит не приймає жодних інших параметрів
Може бути виведено лише в тому випадку, якщо тип сесії (json, xml, html) відомий, а користувач пройшов автентифікацію
видалити-симуляцію
Видаляє симуляцію зі списку симуляцій
Запит GET main.php?action=lister-simulations&numéro=x
Запит не приймає жодних інших параметрів
Може бути відправлений лише за умови, що тип сесії (json, xml, html) відомий, а користувач пройшов автентифікацію
завершення-сесії
Завершує сесію симуляцій.
Технічно старий веб-сеанс видаляється, а створюється новий
Може бути відправлено лише в тому випадку, якщо тип сесії (json, xml, html) відомий, а користувач пройшов автентифікацію

12.2.2. Встановлення бібліотек HTTP для клієнтського JavaScript

На початковому етапі ми працюватимемо з такою архітектурою:

Image

  • у [1] консольний скрипт [node.js] надсилає запит HTTP до сервера jSON для розрахунку податку;
  • у [4] він отримує цю відповідь і виводить її на консоль;

У прикладі № 1 ми будемо використовувати бібліотеки [node-fetch] та [axios], а для наступних прикладів залишимо лише [axios]. Тепер ми встановимо ці дві бібліотеки JavaScript із терміналу [VSCode]:

Image

Ми також будемо використовувати бібліотеку [qs], яка дозволяє кодувати URL рядок символів. Нагадаємо, що це кодування використовується для кодування параметрів запиту HTTP, GET або POST.

Image

12.3. скрипт [fetch-01]

Скрипт [fetch-01] використовує бібліотеку [node-fetch] для ініціалізації сеансу jSON із сервером розрахунку податків. Його код такий:


'use strict';

// імпорт
import fetch from 'node-fetch';
import qs from 'qs';
import { sprintf } from 'sprintf-js';
import moment from 'moment';


// базовий сервер розрахунку податків URL
const baseUrl = 'http://localhost/php7/scripts-web/impots/version-14/main.php?';
// ініціалізація сеансу
async function initSession() {
  // параметри запиту HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // виконання запиту HTTP [get /main.php?action=init-session&type=json]
  let débutFetch;
  try {
    // асинхронний запит — [fetch] повертає обіцянку
    débutFetch = moment(Date.now());
    const response = await fetch(baseUrl + qs.stringify({
      action: 'init-session',
      type: 'json'
    }), options);
    // [response] — це повна відповідь HTTP від сервера (заголовки HTTP + сама відповідь)
    // ми відображаємо цю відповідь, щоб побачити її структуру
    console.log(sprintf("réponse fetch formatée en json,=%j, %s", response, heure(débutFetch)));
    console.log("réponse fetch en javascript=", response);
    // можна отримати заголовки HTTP
    console.log("entêtes de la réponse=", response.headers);
    // якщо відповідь типу application/json, то відповідь у форматі JSON від сервера отримується за допомогою асинхронної функції [response.json()]
    // у цьому випадку код, що викликає, отримує об’єкт [Promise]
    // [await] дозволяє отримати відповідь [json] від сервера замість його обіцянки
    const débutJson = moment(Date.now());
    const objet = await response.json();
    console.log(sprintf("réponse json=%j, type=%s, %s", objet, typeof (objet), heure(débutJson)));
    return objet;
    // якщо відповідь типу text/plain, текстову відповідь від сервера отримують за допомогою [response.text()]
    // у цьому випадку код, що викликає, отримує об’єкт [Promise]
    // [await] дозволяє отримати відповідь [texte] від сервера замість його обіцянки
    // const text = await response.text();
    // console.log("відповідь text=", text);
    // return text;
  } catch (error) {
    // ми опинилися тут, тому що сервер надіслав код помилки [404 Not Found, ...] разом із порожнім тілом — ми відображаємо помилку, щоб побачити її структуру
    // або тому, що клієнт [fetch] викликав виняток (мережа недоступна тощо)
    // відображається структура помилки
    console.log(sprintf("error fetch en json=%j, %s", error, heure(débutFetch)));
    console.log("error fetch en javascript=", typeof (error), error);
    // викликається отримане повідомлення про помилку
    throw error.message;
  }
}

// функція main виконує асинхронну функцію [initSession]
async function main() {
  try {
    console.log("requête HTTP vers le serveur en cours ---------------------------------------------");
    const response = await initSession();
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response, typeof (response))
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error, typeof (error));
  }
}

// тест
main();

// утиліта для відображення часу та тривалості
function heure(début) {
  // поточний час
  const now = moment(Date.now());
  // форматування часу
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // чи потрібно обчислювати тривалість?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // форматування часу + тривалості
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // результат
  return result;
}

Коментарі

  • функції HTTP у JavaScript є асинхронними. Тут ми використовуємо те, що вивчили в попередньому розділі (див. посилання);
  • рядок 24: щоб дочекатися, поки відповідь асинхронної функції [fetch] з’явиться у циклі подій [node.js], ми використовуємо ключове слово [await]. Ми знаємо, що ця інструкція має бути в коді, якому передує ключове слово [async] (рядок 13);
  • рядки 13–56: ми інкапсулюємо код HTTP у асинхронну функцію [initSession];
  • рядки 59–69: друга асинхронна функція [main] використовується для блокуючого виклику (async / await) асинхронної функції [initSession];
  • рядок 72: викликається асинхронна функція [main];
  • хоча весь код виглядає як синхронний, насправді виконуються саме асинхронні функції, але у блокуючому режимі;
  • рядок 19: щоб ініціалізувати сесію jSON із сервером розрахунку податків, потрібно надіслати їй команду HTTP [get /main.php?action=init-session&type=json]. Саме це робить код у рядках 24–27. Синтаксис команди [fetch] є таким: [fetch(URL, options)] із:
    • [URL] — запитуваний URL;
    • [options] — об’єкт, що визначає параметри запиту. Саме тут, зокрема, визначаються заголовки HTTP, які потрібно надіслати цільовій машині;
  • рядки 15–18: визначаються параметри запиту, який потрібно виконати:
    • [method]: потрібно виконати GET;
    • [timeout]: потрібно, щоб клієнт [fetch] чекав на відповідь сервера не більше 2 секунд. Якщо цей час перевищено, [fetch] викличе виняток;
  • рядок 24: щоб отримати URL [/main.php?action=init-session&type=json], використовуємо бібліотеку [qs], щоб отримати кодування URL для параметрів [action,type] з GET. Отриманий рядок — [init-session&type=json] — ми могли б побудувати й самостійно. Ми просто хотіли показати, як отримати закодований рядок URL;
  • рядок 24: ключове слово [await] вказує, що тут запускається асинхронне завдання і що очікується, що воно опублікує свою відповідь у циклі подій [node.js];
  • рядок 24: у [response] отримуємо складний об’єкт, який описує всю отриману відповідь HTTP (заголовки та документ);
  • рядки 30–31: виводимо об’єкт [response], щоб побачити його структуру, спочатку як рядок, а потім як об’єкт JavaScript;
  • рядок 33: виводимо заголовки HTTP, надіслані сервером;
  • рядок 38: відомо, що сервер розрахунку податків надішле рядок jSON. Він інкапсульований в об’єкті [response]. Його можна отримати за допомогою методу [response.json()]. Однак цей метод є асинхронним. Тому ми записуємо [await response.json()], щоб отримати рядок jSON, який буде опубліковано у циклі подій [node.js]. Насправді ми отримуємо не сам рядок jSON, а об’єкт JavaScript, який він представляє;
  • рядок 39: виведення отриманого рядка jSON;
  • рядок 40: повертається отриманий об’єкт JavaScript;
  • рядок 47: перехоплюється можлива помилка оператора [fetch]. Цей оператор генерує виняток лише в тому випадку, якщо операція HTTP не була виконана успішно і не було отримано жодної відповіді від сервера. Якщо відповідь отримано, навіть із кодом HTTP, відмінним від [200 OK], [fetch] не генерує винятку, і відповідь сервера буде доступна у рядку 38;
  • рядки 51–52: виводиться об’єкт [error], отриманий за допомогою оператора [catch], спочатку як рядок jSON, а потім як об’єкт JavaScript;
  • рядок 54: повідомлення про помилку [fetch] міститься в [error.message];
  • рядки 59–69: асинхронна функція [main] запускає асинхронну функцію [initSession] у блокуючому режимі (await, рядок 62);
  • рядок 72: запускається асинхронна функція [main], після чого виконання основного коду скрипта завершується. Глобальний скрипт завершиться, коли запущені асинхронні завдання опублікують свої результати у циклі подій;

Результати виконання такі:

Випадок 1: сервер Laragon не запущено


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\fetch-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
error fetch en json={"message":"network timeout at: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json","type":"request-timeout"}, heure=10:08:48:180, durée= 2 seconde(s) et 62 millisecondes
error fetch en javascript= object { FetchError: network timeout at: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json
    at Timeout.<anonymous> (c:\Data\st-2019\dev\es6\javascript\node_modules\node-fetch\lib\index.js:1448:13)
    at ontimeout (timers.js:436:11)
    at tryOnTimeout (timers.js:300:5)
    at listOnTimeout (timers.js:263:5)
    at Timer.processTimers (timers.js:223:10)
  message:
   'час очікування мережі на: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
  type: 'request-timeout' }
erreur ---------------------------------------------
erreur= network timeout at: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json рядок

[Done] exited with code=0 in 2.804 seconds

Коментарі

  • рядок 3: запит HTTP завершується з помилкою через 2 секунди та 62 мілісекунди через тайм-аут у 2 секунди, який було встановлено для запиту HTTP;
  • рядки 4–9: об’єкт JavaScript [error], перехоплений за допомогою умови [catch(error)]. Цей об’єкт має дві властивості:
    • [FetchError]: рядок 4;
    • [message]: рядки 10–12;
  • рядок 14: повідомлення про помилку, отримане асинхронною функцією [main];

Випадок 2: сервер Laragon запущено


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\fetch-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
réponse fetch formatée en json,={"size":0,"timeout":2000}, heure=10:13:50:814, durée= 0 seconde(s) et 375 millisecondes
réponse fetch en javascript= Response {
  size: 0,
  timeout: 2000,
  [Symbol(Body internals)]:
   { body:
      PassThrough {
        _readableState: [ReadableState],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        _writableState: [WritableState],
        writable: false,
        allowHalfOpen: true,
        _transformState: [Object] },
     disturbed: false,
     error: null },
  [Symbol(Response internals)]:
   { url:
      'http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
     status: 200,
     statusText: 'OK',
     headers: Headers { [Symbol(map)][Object] },
     counter: 0 } }
entêtes de la réponse= Headers {
  [Symbol(map)]:
   [Object: null prototype] {
     date: [ 'Sat, 14 Sep 2019 08:13:50 GMT' ],
     server: [ 'Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11' ],
     'x-powered-by': [ 'PHP/7.2.11' ],
     'cache-control': [ 'max-age=0, private, must-revalidate, no-cache, private' ],
     'set-cookie': [ 'PHPSESSID=99q2iinusmhl55fa600aie2mmu; path=/' ],
     'content-length': [ '86' ],
     connection: [ 'close' ],
     'content-type': [ 'application/json' ] } }
réponse json={"action":"init-session","état":700,"réponse":"session démarrée avec type [json]"}, type=object, heure=10:13:50:825, durée= 0 seconde(s) et 1 millisecondes
succès ---------------------------------------------
réponse= { action: 'init-session',
  'статус': 700,
  'відповідь': 'сесія розпочата з типом [json]' } об’єкт

[Done] exited with code=0 in 1.022 seconds

Коментарі

  • рядок 3: [fetch] отримує відповідь від сервера через 375 мс;
  • рядки 4–39: структура об’єкта JavaScript [response], що інкапсулює відповідь сервера. Серед його властивостей нас можуть зацікавити такі:
    • [status] (рядок 25): код HTTP відповіді сервера;
    • [statusText] (рядок 26): текст, пов’язаний із цим кодом;
    • [headers] (рядок 27): заголовки HTTP відповіді сервера;
    • [body] (рядок 8): представляє документ, надісланий сервером. Команда [fetch] надає методи для його обробки;
  • рядки 29–39: заголовки HTTP у відповіді сервера;
  • рядок 40: асинхронна функція [response.json()] опублікувала свою відповідь через 1 мілісекунду;
  • рядки 42–44: об’єкт JavaScript, отриманий асинхронною функцією [main];

Випадок 3: сервер Laragon запущено, але йому надсилається помилкова команда:

Image

  • вище, у рядку 26, серверу передається неправильний тип сесії;

Результати виконання такі:


requête HTTP vers le serveur en cours ---------------------------------------------
réponse fetch formatée en json,={"size":0,"timeout":2000}, heure=10:27:54:114, durée= 0 seconde(s) et 136 millisecondes
réponse fetch en javascript= Response {
  size: 0,
  timeout: 2000,
  [Symbol(Body internals)]:
   { body:
      PassThrough {
        _readableState: [ReadableState],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        _writableState: [WritableState],
        writable: false,
        allowHalfOpen: true,
        _transformState: [Object] },
     disturbed: false,
     error: null },
  [Symbol(Response internals)]:
   { url:
      '«http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=x»,
     status: 400,
     statusText: 'Bad Request',
     headers: Headers { [Symbol(map)][Object] },
     counter: 0 } }
entêtes de la réponse= Headers {
  [Symbol(map)]:
   [Object: null prototype] {
     date: [ 'Sat, 14 Sep 2019 08:27:54 GMT' ],
     server: [ 'Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11' ],
     'x-powered-by': [ 'PHP/7.2.11' ],
     'cache-control': [ 'max-age=0, private, must-revalidate, no-cache, private' ],
     'set-cookie': [ 'PHPSESSID=5ku9gfok81ikj98hia0meeum57; path=/' ],
     'content-length': [ '79' ],
     connection: [ 'close' ],
     'content-type': [ 'application/json' ] } }
réponse json={"action":"init-session","état":703,"réponse":"paramètre type=[x] invalide"}, type=object, heure=10:27:54:127, durée= 0 seconde(s) et 2 millisecondes
succès ---------------------------------------------
réponse= { action: 'init-session',
  'стан: 703,
  'відповідь': 'неправильний параметр type=[x]' } об'єкт

[Done] exited with code=0 in 0.712 seconds
  • відповідь сервера отримано у рядку 2;
  • у рядку 24: можна побачити, що код HTTP у відповіді сервера дорівнює 400, що є кодом помилки. Проте [fetch] не викликав винятку. Поки [fetch] отримує відповідь від сервера, він її обробляє і не генерує винятку;
  • рядки 41–43: відповідь, отримана асинхронною функцією [main];

12.4. скрипт [fetch-02]

Наступний скрипт є версією скрипта [fetch-01], з якого вилучено всі зайві деталі:


'use strict';

// імпортує
import fetch from 'node-fetch';
import qs from 'qs';

// URL базовий сервер розрахунку податків
const baseUrl = 'http://localhost/php7/scripts-web/impots/version-14/main.php?';
// ініціалізація сеансу
async function initSession() {
  // параметри запиту HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // виконання запиту HTTP [get /main.php?action=init-session&type=json]
  const response = await fetch(baseUrl + qs.stringify({
    action: 'init-session',
    type: 'json'
  }), options);
  // результат отримано у jSON
  return await response.json();
}

// функція main виконує асинхронну функцію [initSession]
async function main() {
  try {
    console.log("requête HTTP vers le serveur en cours ---------------------------------------------");
    const response = await initSession();
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response)
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error.message);
  }
}

// тест
main();

Результати нормального виконання:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\fetch-02.js"
requête HTTP vers le serveur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'init-session',
  'стан: 700,
  'відповідь: 'сесія запущена з типом [json]' }

[Done] exited with code=0 in 0.56 seconds

Результати виконання з винятком (зупинка сервера Laragon):


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\fetch-02.js"
requête HTTP vers le serveur en cours ---------------------------------------------
erreur ---------------------------------------------
erreur= network timeout at: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json

[Done] exited with code=0 in 2.701 seconds

12.5. скрипт [axios-01]

Тут ми повертаємося до скрипта [fetch-01], який переписуємо з використанням бібліотеки [axios]. Нагадаємо, що ця бібліотека цікава нам тим, що її можна переносити між середовищем [node.js] та середовищами звичайних браузерів. Це дозволяє:

  • на етапі 1 — тестувати наші скрипти в середовищі [node.js];
  • на етапі 2 — перенести їх у браузер;

Скрипт [axios-01] повторює структуру скрипта [fetch-01]:


'use strict';
import axios from 'axios';

// конфігурація axios за замовчуванням
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// ініціалізація сесії
async function initSession(axios) {
  // параметри запиту HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // параметри запиту URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  // виконання запиту HTTP [get /main.php?action=init-session&type=json]
  try {
    // асинхронний запит
    const response = await axios.request('main.php', options);
    // відповідь — це повний текст відповіді HTTP від сервера (заголовки HTTP + сама відповідь)
    // цю відповідь відображають, щоб побачити її структуру
    console.log("réponse axios=", response);
    // відповідь сервера міститься в [response.data]
    return response.data;
  } catch (error) {
    // ми опинилися тут, оскільки сервер надіслав код помилки [404 Not Found, 500 Internal Server Error, ...]
    // параметр [error] є екземпляром винятку — він може мати різні форми
    // його відображають, щоб побачити його структуру
    console.log("axios error=", typeof (error), error);
    if (error.response) {
      // сервер повідомив про помилку у статусі HTTP, але також надіслав відповідь
      // тож її можна знайти в [error.response.data]
      // відомо, що сервер надсилає відповіді jSON зі структурою {дія, стан, відповідь}
      // і що в разі помилки повідомлення про помилку міститься в [réponse]
      return error.response.data;
    } else {
      // видається повідомлення про помилку
      throw error;
    }
  }
}

// функція main виконує асинхронну функцію [initSession]
async function main() {
  try {
    console.log("requête HTTP vers le serveur en cours ---------------------------------------------");
    const response = await initSession(axios);
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response, typeof (response))
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error.message);
  }
}

// тест
main();

Коментарі

  • рядок 2: імпортуємо бібліотеку [axios];
  • рядки 5–6: конфігурація за замовчуванням для запитів HTTP. Параметри [axios.defaults] діють для всіх запитів HTTP, що надсилаються об’єктом [axios], без необхідності повторного їхнього зазначення для кожного нового запиту;
  • рядок 5: тайм-аут у 2 секунди для всіх запитів;
  • рядок 6: усі URL будуть виражатися відносно базового URL;
  • рядок 9: асинхронна функція [initSession];
  • рядки 11–18: параметри запиту HTTP, який буде відправлено (на додаток до параметрів за замовчуванням, вже визначених у рядках 5–6);
  • рядки 14–17: параметри URL [action=init-session&type=json]. Об’єкт [params] автоматично перетвориться на закодований рядок URL;
  • рядок 22: блокуючий виклик асинхронної функції [axios.request]. Першим параметром є цільовий об’єкт URL, побудований шляхом додавання [main.php] до базового об’єкта URL, визначеного у рядку 6. Другим параметром є об’єкт [options] із рядків 11–18;
  • рядок 25: [response] — це об’єкт JavaScript, що інкапсулює всю відповідь HTTP від сервера (заголовки HTTP + документ-відповідь). Його відображають, щоб побачити його структуру в JavaScript;
  • рядок 27: якщо сервер надіслав документ, то його можна знайти в [response.data]. У цьому випадку ми знаємо, що сервер надсилає відповідь jSON разом із заголовком HTTP [Content-type : application/json]. Наявність цього заголовка призводить до того, що [axios] автоматично десеріалізує [response.data] в об’єкт JavaScript;
  • рядок 28: функція [axios] може генерувати виняток. Саме в цьому [axios] відрізняється від [fetch]. Виняток генерується в таких випадках:
    • запит HTTP не вдалося відправити (помилка на стороні клієнта);
    • сервер надіслав код помилки HTTP (400, 404, 500, …) (цей параметр, власне, можна налаштувати). Нагадаємо, що якщо цей код HTTP супроводжується відповіддю, то [fetch] не викликає винятку, тоді як [axios] викликає його. Проте, якщо код помилки HTTP супроводжується документом, цей документ поміщається в [error.response];
  • рядок 32: виводиться структура об’єкта [error] у форматі JavaScript;
  • рядки 33–38: якщо об’єкт [error] містить об’єкт [response], то саме цю відповідь повертають коду, що викликає;
  • рядки 39–42: в інших випадках об’єкт [error] передається коду, що викликає;
  • рядки 47–57: асинхронна функція [main];
  • рядок 50: блокуючий виклик асинхронної функції [initSession]. Від сервера отримується відповідь jSON у вигляді об’єкта JavaScript;
  • рядки 53–56: перехоплення можливої помилки. Повідомлення про помилку міститься в [error.message];

Результати виконання такі:

Випадок 1: сервер Laragon не запущено


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
axios error= object { Error: timeout of 2000ms exceeded
    at createError (c:\Data\st-2019\dev\es6\javascript\node_modules\axios\lib\core\createError.js:16:15)
    at Timeout.handleRequestTimeout (c:\Data\st-2019\dev\es6\javascript\node_modules\axios\lib\adapters\http.js:252:16)
    at ontimeout (timers.js:436:11)
    at tryOnTimeout (timers.js:300:5)
    at listOnTimeout (timers.js:263:5)
    at Timer.processTimers (timers.js:223:10)
  config:
   { url:
      'http://localhost/php7/scripts-web/impots/version-14/main.php',
     method: 'get',
     params: { action: 'init-session', type: 'json' },
     headers:
      { Accept: 'application/json, text/plain, */*',
        'User-Agent': 'axios/0.19.0' },
     baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
     transformRequest: [ [Function: transformRequest] ],
     transformResponse: [ [Function: transformResponse] ],
     timeout: 2000,
     adapter: [Function: httpAdapter],
     xsrfCookieName: 'XSRF-TOKEN',
     xsrfHeaderName: 'X-XSRF-TOKEN',
     maxContentLength: -1,
     validateStatus: [Function: validateStatus],
     data: undefined },
  code: 'ECONNABORTED',
  request:
   Writable {
     _writableState:
      WritableState {
        objectMode: false,
        highWaterMark: 16384,
        finalCalled: false,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        destroyed: false,
        decodeStrings: true,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: true,
        bufferProcessing: false,
        onwrite: [Function: bound onwrite],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false,
        emitClose: true,
        bufferedRequestCount: 0,
        corkedRequestsFree: [Object] },
     writable: true,
     domain: null,
     _events:
      [Object: null prototype] {
        response: [Function: handleResponse],
        error: [Function: handleRequestError] },
     _eventsCount: 2,
     _maxListeners: undefined,
     _options:
      { protocol: 'http:',
        maxRedirects: 21,
        maxBodyLength: 10485760,
        path:
         '/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
        method: 'GET',
        headers: [Object],
        agent: undefined,
        auth: undefined,
        hostname: 'localhost',
        port: null,
        nativeProtocols: [Object],
        pathname: '/php7/scripts-web/impots/version-14/main.php',
        search: '?action=init-session&type=json' },
     _redirectCount: 0,
     _redirects: [],
     _requestBodyLength: 0,
     _requestBodyBuffers: [],
     _onNativeResponse: [Function],
     _currentRequest:
      ClientRequest {
        domain: null,
        _events: [Object],
        _eventsCount: 6,
        _maxListeners: undefined,
        output: [],
        outputEncodings: [],
        outputCallbacks: [],
        outputSize: 0,
        writable: true,
        _last: true,
        chunkedEncoding: false,
        shouldKeepAlive: false,
        useChunkedEncodingByDefault: false,
        sendDate: false,
        _removedConnection: false,
        _removedContLen: false,
        _removedTE: false,
        _contentLength: 0,
        _hasBody: true,
        _trailer: '',
        finished: true,
        _headerSent: true,
        socket: [Socket],
        connection: [Socket],
        _header:
         'GET /php7/scripts-web/impots/version-14/main.php?action=init-session&type=json HTTP/1.1\r\nAccept: application/json, text/plain, */*\r\nUser-Agent: axios/0.19.0\r\nHost: localhost\r\nConnection: close\r\n\r\n',
        _onPendingData: [Function: noopPendingOutput],
        agent: [Agent],
        socketPath: undefined,
        timeout: undefined,
        method: 'GET',
        path:
         '/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
        _ended: false,
        res: null,
        aborted: 1568528450762,
        timeoutCb: null,
        upgradeOrConnect: false,
        parser: [HTTPParser],
        maxHeadersCount: null,
        _redirectable: [Circular],
        [Symbol(isCorked)]: false,
        [Symbol(outHeadersKey)][Object] },
     _currentUrl:
      'http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json' },
  response: undefined,
  isAxiosError: true,
  toJSON: [Function] }
erreur ---------------------------------------------
erreur= timeout of 2000ms exceeded

[Done] exited with code=0 in 2.784 seconds

Коментарі

  • рядки 33–136: об’єкт [error] містить багато інформації;
    • [Error], рядки 3–9: опис помилки, що сталася;
    • [config], рядки 10–27: конфігурація запиту HTTP, що призвела до цієї помилки;
    • [config.url], рядки 11–12: ціль URL;
    • [config.method], рядок 13: метод запиту;
    • [config.params], рядок 14: параметри URL;
    • [config.headers], рядки 16–17: заголовки запиту HTTP;
    • [config.baseURL], рядок 18: базовий URL цільового URL;
    • [config.timeout], рядок 21: час очікування запиту;
    • [code], рядок 28: код помилки;
    • [request], рядки 29–133: детальний опис запиту HTTP. Слід зауважити, що більшість властивостей мають префікс у вигляді підкреслення _, що вказує на те, що це внутрішні властивості об’єкта [request], які не призначені для безпосереднього використання розробником;
    • [response], рядок 134: відповідь сервера, яка в даному випадку відсутня;
  • рядок 138: повідомлення про помилку, яке виводить функція [main];

Випадок 2: сервер Laragon запущено


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
réponse axios= { status: 200,
  statusText: 'OK',
  headers:
   { date: 'Sun, 15 Sep 2019 07:09:26 GMT',
     server: 'Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11',
     'x-powered-by': 'PHP/7.2.11',
     'cache-control': 'max-age=0, private, must-revalidate, no-cache, private',
     'set-cookie': [ 'PHPSESSID=uas6lugtblstktcifpd8e5irm6; path=/' ],
     'content-length': '86',
     connection: 'close',
     'content-type': 'application/json' },
  config:
   { url:
      '«http://localhost/php7/scripts-web/impots/version-14/main.php»,
     method: 'get',
     params: { action: 'init-session', type: 'json' },
     headers:
      { Accept: 'application/json, text/plain, */*',
        'User-Agent': 'axios/0.19.0' },
     baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
     transformRequest: [ [Function: transformRequest] ],
     transformResponse: [ [Function: transformResponse] ],
     timeout: 2000,
     adapter: [Function: httpAdapter],
     xsrfCookieName: 'XSRF-TOKEN',
     xsrfHeaderName: 'X-XSRF-TOKEN',
     maxContentLength: -1,
     validateStatus: [Function: validateStatus],
     data: undefined },
  request:
   ClientRequest {
     domain: null,
     _events:
      [Object: null prototype] {
        socket: [Function],
        abort: [Function],
        aborted: [Function],
        error: [Function],
        timeout: [Function],
        prefinish: [Function: requestOnPrefinish] },
     _eventsCount: 6,
     _maxListeners: undefined,
     output: [],
     outputEncodings: [],
     outputCallbacks: [],
     outputSize: 0,
     writable: true,
     _last: true,
     chunkedEncoding: false,
     shouldKeepAlive: false,
     useChunkedEncodingByDefault: false,
     sendDate: false,
     _removedConnection: false,
     _removedContLen: false,
     _removedTE: false,
     _contentLength: 0,
     _hasBody: true,
     _trailer: '',
     finished: true,
     _headerSent: true,
     socket:
      Socket {
        connecting: false,
        _hadError: false,
        _handle: [TCP],
        _parent: null,
        _host: 'localhost',
        _readableState: [ReadableState],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 7,
        _maxListeners: undefined,
        _writableState: [WritableState],
        writable: false,
        allowHalfOpen: false,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        parser: null,
        _httpMessage: [Circular],
        [Symbol(asyncId)]: 6,
        [Symbol(lastWriteQueueSize)]: 0,
        [Symbol(timeout)]: null,
        [Symbol(kBytesRead)]: 0,
        [Symbol(kBytesWritten)]: 0 },
     connection:
      Socket {
        connecting: false,
        _hadError: false,
        _handle: [TCP],
        _parent: null,
        _host: 'localhost',
        _readableState: [ReadableState],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 7,
        _maxListeners: undefined,
        _writableState: [WritableState],
        writable: false,
        allowHalfOpen: false,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        parser: null,
        _httpMessage: [Circular],
        [Symbol(asyncId)]: 6,
        [Symbol(lastWriteQueueSize)]: 0,
        [Symbol(timeout)]: null,
        [Symbol(kBytesRead)]: 0,
        [Symbol(kBytesWritten)]: 0 },
     _header:
      'GET /php7/scripts-web/impots/version-14/main.php?action=init-session&type=json HTTP/1.1\r\nAccept: application/json, text/plain, */*\r\nUser-Agent: axios/0.19.0\r\nHost: localhost\r\nConnection: close\r\n\r\n',
     _onPendingData: [Function: noopPendingOutput],
     agent:
      Agent {
        domain: null,
        _events: [Object],
        _eventsCount: 1,
        _maxListeners: undefined,
        defaultPort: 80,
        protocol: 'http:',
        options: [Object],
        requests: {},
        sockets: [Object],
        freeSockets: {},
        keepAliveMsecs: 1000,
        keepAlive: false,
        maxSockets: Infinity,
        maxFreeSockets: 256 },
     socketPath: undefined,
     timeout: undefined,
     method: 'GET',
     path:
      '/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
     _ended: true,
     res:
      IncomingMessage {
        _readableState: [ReadableState],
        readable: false,
        domain: null,
        _events: [Object],
        _eventsCount: 3,
        _maxListeners: undefined,
        socket: [Socket],
        connection: [Socket],
        httpVersionMajor: 1,
        httpVersionMinor: 0,
        httpVersion: '1.0',
        complete: true,
        headers: [Object],
        rawHeaders: [Array],
        trailers: {},
        rawTrailers: [],
        aborted: false,
        upgrade: false,
        url: '',
        method: null,
        statusCode: 200,
        statusMessage: 'OK',
        client: [Socket],
        _consuming: false,
        _dumped: false,
        req: [Circular],
        responseUrl:
         'http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
        redirects: [] },
     aborted: undefined,
     timeoutCb: null,
     upgradeOrConnect: false,
     parser: null,
     maxHeadersCount: null,
     _redirectable:
      Writable {
        _writableState: [WritableState],
        writable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        _options: [Object],
        _redirectCount: 0,
        _redirects: [],
        _requestBodyLength: 0,
        _requestBodyBuffers: [],
        _onNativeResponse: [Function],
        _currentRequest: [Circular],
        _currentUrl:
         'http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json' },
     [Symbol(isCorked)]: false,
     [Symbol(outHeadersKey)]:
      [Object: null prototype] { accept: [Array], 'user-agent': [Array], host: [Array] } },
  data:
   { action: 'init-session',
     'стан': 700,
     'відповідь': 'сесія запущена з типом [json]' } }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'стан': 700,
  'відповідь': 'сесія розпочата з типом [json]' } об’єкт

[Done] exited with code=0 in 1.115 seconds

Коментарі

  • рядки 3–203: об’єкт JavaScript [response], що інкапсулює відповідь HTTP від сервера;
  • рядок 3, [status]: код HTTP відповіді;
  • рядок 4, [statusText]: текст, пов’язаний із попереднім кодом HTTP;
  • рядки 5–13, [headers]: заголовки HTTP у відповіді:
    • рядок 10, [Set-Cookie]: сесійний файл cookie;
    • рядок 13, [Content-Type]: тип документа, надісланого сервером;
  • рядки 14–31, [config]: конфігурація відправленого запиту HTTP;
  • рядки 32–199, [request]: об’єкт JavaScript, що містить детальну інформацію про відправлений запит HTTP;
  • рядки 200–203, [request.data]: об’єкт JavaScript, що інкапсулює відповідь jSON від сервера;
  • рядки 205–207: відповідь, отримана асинхронною функцією [main];

Випадок 3: надсилається помилковий запит [init-session] до сервера Laragon;

Image

Результати виконання такі:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
axios error= object { Error: Request failed with status code 400
   ...
  config:
   { url:
      '«http://localhost/php7/scripts-web/impots/version-14/main.php»,
     ...
     data: undefined },
  request:
   ...
     [Symbol(outHeadersKey)]:
      [Object: null prototype] { accept: [Array], 'user-agent': [Array], host: [Array] } },
  response:
   { status: 400,
     statusText: 'Bad Request',
     headers:
      { date: 'Sun, 15 Sep 2019 07:25:58 GMT',
        server: 'Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11',
        'x-powered-by': 'PHP/7.2.11',
        'cache-control': 'max-age=0, private, must-revalidate, no-cache, private',
        'set-cookie': [Array],
        'content-length': '79',
        connection: 'close',
        'content-type': 'application/json' },
     config:
      { url:
         '«http://localhost/php7/scripts-web/impots/version-14/main.php»,
        ...
        data: undefined },
     request:
      ...
        [Symbol(outHeadersKey)][Object] },
     data:
      { action: 'init-session',
        '«état»: 703,
        'відповідь: 'неправильний параметр type=[x]' } },
  isAxiosError: true,
  toJSON: [Function] }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'стан': 703,
  'відповідь': 'неправильний параметр type=[x]' } об'єкт

[Done] exited with code=0 in 0.69 seconds

Оскільки сервер відповів кодом HTTP 400 (рядок 15), [axios] видав виняток (ця поведінка, знову ж таки, налаштовується). Хоча [axios] видав виняток, у [error.response] (рядок 14) ми все ж отримуємо відповідь HTTP від сервера, а документ jSON надіслано [error.response.data] (рядок 34). Рядки 41–43: функція [main] коректно отримує відповідь jSON від сервера.

12.6. скрипт [axios-02]

Тепер, коли ми детально розглянули об’єкти, з якими працює бібліотека [axios] під час запиту HTTP, можна переписати скрипт [axios-01] наступним чином:


'use strict';
import axios from 'axios';

// конфігурація axios за замовчуванням
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// ініціалізація сесії
async function initSession(axios) {
  // параметри запиту HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // параметри запиту URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    // виконання запиту HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    // відповідь сервера міститься в [response.data]
    return response.data;
  } catch (error) {
    // відповідь сервера
    if (error.response) {
      // відповідь jSON міститься в [error.response.data]
      return error.response.data;
    } else {
      // помилка повторюється
      throw error;
    }
  }
}

// функція main виконує асинхронну функцію [initSession]
async function main() {
  try {
    console.log("requête HTTP vers le serveur en cours ---------------------------------------------");
    const response = await initSession(axios);
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response, typeof (response))
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error.message);
  }
}

// тест
main();

12.7. скрипт [axios-03]

Скрипт [axios-03] використовує ту саму методологію, що й скрипт [axios-02]. Цього разу додаємо запит HTTP [authentifier-utilisateur] до сервера, що здійснюється за допомогою POST:


'use strict';
import axios from 'axios';
import qs from 'qs'

// конфігурація axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';


// ініціалізація сесії
async function initSession(axios) {
  // параметри запиту HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // параметри запиту URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    // виконання запиту HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    // відповідь сервера міститься в [response.data]
    return response.data;
  } catch (error) {
    // відповідь сервера
    if (error.response) {
      // відповідь jSON міститься в [error.response.data]
      return error.response.data;
    } else {
      // повторюється помилка
      throw error;
    }
  }
}

async function authentifierUtilisateur(axios, user, password) {
  // параметри запиту HHTP [POST /main.php?action=authentifier-utilisateur]
  const options = {
    method: "POST",
    headers: {
      '«Content-type»: «application/x-www-form-urlencoded»,
    },
    // тіло POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    // параметри URL
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  try {
    // виконання запиту HTTP [post /main.php?action=authentifier-utilisateur]
    const response = await axios.request('main.php', options);
    // відповідь сервера міститься в [response.data]
    return response.data;
  } catch (error) {
    // відповідь сервера
    if (error.response) {
      // відповідь jSON міститься в [error.response.data]
      return error.response.data;
    } else {
      // помилка повторюється
      throw error;
    }
  }
}

// функція main виконує асинхронні функції по черзі
async function main() {
  try {
    // init-session
    console.log("action init-session en cours ---------------------------------------------");
    const response1 = await initSession(axios);
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response1);
    // аутентифікація користувача
    console.log("action authentifier-utilisateur en cours ---------------------------------------------");
    const response2 = await authentifierUtilisateur(axios, 'admin', 'admin');
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response2)
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error);
  }
}

// тест
main();

Коментарі

  • рядки 38–70: асинхронна функція [authentifierUtilisateur];
  • рядок 39: потрібно виконати запит [POST /main.php?action=authentifier-utilisateur];
  • рядки 40–54: параметри запиту HTTP;
  • рядок 41: це POST;
  • рядки 42–44: параметри POST будуть закодовані як URL у документі, який клієнт надсилає разом зі своїм запитом;
  • рядки 46–49: властивість [data] має містити закодований рядок POST URL. Для цього тут використовується бібліотека [qs], імпортована у рядку 3;
  • рядки 55–69: для виконання запиту використовується той самий код, що й у методі [initSession];
  • рядки 73–89: метод [asynchrone] послідовно, у блокуючому режимі, викликає методи [initSession, authentifierUtilisateur], рядки 77 та 82;
  • рядок 82: як ідентифікатори для входу використовується пара (admin, admin). Відомо, що сервер їх розпізнає;

Результати виконання такі:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-03.js"
action init-session en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'init-session',
  'стан': 700,
  'відповідь: 'сесія запущена з типом [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'стан': 103,
  'відповідь':
   [ 'pas de session en cours. Commencer par action [init-session]' ] }

[Done] exited with code=0 in 0.834 seconds
  • рядки 9–12: автентифікація користувача завершилася невдало: сервер не запам’ятав, що було ініційовано сесію jSON. Це пов’язано з тим, що не було надіслано сесійний файл cookie, отриманий у відповідь на перший запит [init-session];

12.8. скрипт [axios-04]

Скрипт [axios-04] вносить два вдосконалення до скрипта [axios-03]:

  • він обробляє сесійний файл cookie;
  • виокремлює у функцію [getRemoteData] те, що є спільним для функцій [initSession] та [authentifierUtilisateur];

'use strict';
import axios from 'axios';
import qs from 'qs'

// конфігурація Axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// сесійний файл cookie
const sessionCookieName = "PHPSESSID";
let sessionCookie = '';

// ініціалізація сесії
async function initSession(axios) {
  // параметри запиту HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // параметри URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  // виконання запиту HTTP
  return await getRemoteData(axios, options);
}

async function authentifierUtilisateur(axios, user, password) {
  // параметри запиту HHTP [post /main.php?action=authentifier-utilisateur]
  const options = {
    method: "POST",
    headers: {
      '«Content-type»: «application/x-www-form-urlencoded»,
    },
    // тіло POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    // параметри запиту URL
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  // виконання запиту HTTP
  return await getRemoteData(axios, options);
}

async function getRemoteData(axios, options) {
  // для сесійного файлу cookie
  if (!options.headers) {
    options.headers = {};
  }
  options.headers.Cookie = sessionCookie;
  // виконання запиту HTTP
  let response;
  try {
    // асинхронний запит
    response = await axios.request('main.php', options);
  } catch (error) {
    // параметр [error] є екземпляром винятку — він може мати різні форми
    if (error.response) {
      // відповідь сервера міститься в [error.response]
      response = error.response;
    } else {
      // помилка повторюється
      throw error;
    }
  }
  // response — це повна відповідь HTTP від сервера (заголовки HTTP + сама відповідь)
  // отримуємо сесійний файл cookie, якщо він існує
  const setCookie = response.headers['set-cookie'];
  if (setCookie) {
    // setCookie — це масив
    // шукаємо сесійний файл cookie в цьому масиві
    let trouvé = false;
    let i = 0;
    while (!trouvé && i < setCookie.length) {
      // шукаємо сесійний файл cookie
      const results = RegExp('^(' + sessionCookieName + '.+?);').exec(setCookie[i]);
      if (results) {
        // зберігаємо сесійний файл cookie
        // eslint-disable-next-line require-atomic-updates
        sessionCookie = results[1];
        // знайдено
        trouvé = true;
      } else {
        // наступний елемент
        i++;
      }
    }
  }
  // відповідь сервера знаходиться в [response.data]
  return response.data;
}

// функція main виконує асинхронні функції по черзі
async function main() {
  try {
    // init-session
    console.log("action init-session en cours ---------------------------------------------");
    const response1 = await initSession(axios);
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response1);
    // аутентифікація користувача
    console.log("action authentifier-utilisateur en cours ---------------------------------------------");
    const response2 = await authentifierUtilisateur(axios, 'admin', 'admin');
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response2)
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error.message);
  }
}

// тест
main();

Коментарі

  • рядки 14–26: функція [initSession]. Відтепер вона лише готує запит HTTP для відправки на сервер, але не виконує його. Цю роль вона доручає методу [getRemoteDate] у рядках 49–95;
  • рядки 28–47: функція [authentifierUtilisateur] діє за тим самим принципом;
  • рядок 49: функція [getRemoteData] отримує дві інформації, які дозволяють їй виконати запит HTTP:
    • [axios] — об’єкт, який відповідатиме за відправлення запиту та отримання відповіді;
    • [options] — параметри конфігурації запиту, що надсилається на сервер;
  • рядок 59: виконання запиту та блокуюче очікування відповіді jSON;
  • рядки 60–68: обробка можливого винятку;
  • рядок 64: отримуємо відповідь, яка може бути інкапсульована в об’єкті помилки;
  • рядок 67: якщо сервер викликав виняток, не включивши до нього відповідь сервера, то отримана помилка передається до коду, що викликає;
  • функція [getRemoteData] обробляє сесійний файл cookie:
    • він зберігає його у змінній [sessionCookie] (рядок 11) під час першого отримання;
    • потім він повертає його у відповідь на кожен новий запит HTTP;
  • рядки 72–92: [getRemoteData] аналізує кожну відповідь сервера, щоб з’ясувати, чи надіслав він заголовок HTTP [Set-Cookie]. Відомо, що сервер надсилає сесійний файл cookie з іменем [PHPSESSID] (рядок 10). Отже, саме цей файл cookie ми шукаємо (рядок 10);
  • рядок 72: витягуються заголовки HTTP та [Set-Cookie], якщо вони існують (регістр не має значення). Дійсно, може бути кілька заголовків [Set-Cookie], тому ми отримуємо масив;
  • рядок 73: якщо отримано масив файлів cookie;
  • рядки 78–90: шукаємо сесійний файл cookie серед усіх файлів cookie у масиві;
  • рядок 80: реляційний вираз, що дозволяє шукати сесійний файл cookie у файлі cookie № i;
  • рядок 81: якщо порівняння дало результати;
  • рядок 84: у results[1] міститься перша дужка шаблону реляційного виразу, тобто (PHPSESSID=xxxx) аж до ; (не включено), що завершує сесійний файл cookie;
  • рядки 50–54: у кожному запиті сесійний файл cookie включається в заголовки запиту HTTP. Уперше цей файл cookie порожній, тому сервер його проігнорує;

Результати виконання такі:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-04.js"
action init-session en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'init-session',
  'стан': 700,
  'відповідь: 'сесія запущена з типом [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'стан': 200,
  'відповідь': 'Аутентифікація успішна [admin, admin]' }

[Done] exited with code=0 in 0.982 seconds