Skip to content

15. Приклад [nuxt-12]: запити HTTP за допомогою axios

15.1. Présentation

У цьому новому прикладі ми дізнаємося, як у функціях [asyncData] можна виконувати запити HTTP за допомогою бібліотеки [axios]. Крім того, ми будемо використовувати вже засвоєні поняття:

  • використання плагінів із прикладу [nuxt-06]:
  • збереження даних у сесійному файлі cookie з прикладу [nuxt-06];
  • управління навігацією за допомогою проміжного програмного забезпечення з прикладу [nuxt-09];
  • обробка помилок з прикладу [nuxt-11];

Архітектура прикладу буде такою:

Image

  • додаток [nuxt] буде розміщено на сервері [node.js] [3], завантажено браузером [1], який потім його виконає;
  • як клієнт [nuxt] [1], так і сервер [nuxt] [3] надсилатимуть запити HTTP до сервера даних [2]. Цей сервер буде сервером розрахунку податку, розробленим у розділі PHP 7. Ми будемо використовувати його останню версію, версію 14, з дозволеними запитами CORS;

Архітектуру прикладу можна спростити наступним чином:

Image

  • у [1] сервер [node.js] видає сторінки [nuxt] браузеру [2]. Саме рівень [web] [8] сервера доставляє ці сторінки. Для доставки сторінки сервер міг запитати зовнішні дані у сервера даних [3]. Саме рівень [DAO] [9] виконує необхідні запити HTTP;
  • при кожному виклику сторінки на сервер [node.js][1], браузер [2] отримує весь додаток [nuxt], який потім виконується в режимі SPA. Блок [UI] (User Interface) [4] відображає сторінки [vue.js] для користувача. Дії користувача або природний життєвий цикл сторінок можуть спричинити запити зовнішніх даних до сервера даних [3]. Саме рівень [DAO] [5] потім виконує необхідні запити HTTP;

15.2. Структура проекту

Image

15.3. Файл конфігурації [nuxt.config.js]

Проект контролюватиметься за допомогою наступного файлу [nuxt.config.js]:


export default {
  mode: 'universal',
  /*
   ** Headers of the page
   */
  head: {
    title: 'Introduction à [nuxt.js]',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: 'ssr routing loading asyncdata middleware plugins store'
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },
  /*
   ** Customize the progress-bar color
   */
  loading: false,

  /*
   ** Global CSS
   */
  css: [],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [
    { src: '@/plugins/client/plgSession', mode: 'client' },
    { src: '@/plugins/server/plgSession', mode: 'server' },
    { src: '@/plugins/client/plgDao', mode: 'client' },
    { src: '@/plugins/server/plgDao', mode: 'server' },
    { src: '@/plugins/client/plgEventBus', mode: 'client' }
  ],
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // Документація: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module'
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // Документація: https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // Документація: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    // https://www.npmjs.com/package/cookie-universal-nuxt
    'cookie-universal-nuxt'
  ],
  /*
   ** Axios module configuration
   ** See https://axios.nuxtjs.org/options
   */
  axios: {},
  /*
   ** Build configuration
   */
  build: {
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) { }
  },
  // каталог з вихідним кодом
  srcDir: 'nuxt-12',
  // маршрутизатор
  router: {
    // кореневий каталог додатка URL
    base: '/nuxt-12/',
    // проміжне програмне забезпечення для маршрутизації
    middleware: ['routing']
  },
  // сервер
  server: {
    // порт служби, за замовчуванням 3000
    port: 81,
    // мережеві адреси, на яких відбувається прослуховування; за замовчуванням localhost: 127.0.0.1
    // 0.0.0.0 = усі мережеві адреси комп’ютера
    host: 'localhost'
  },
  // середовище
  env: {
    // конфігурація axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // конфігурація сесійного файлу cookie [nuxt]
    maxAge: 60 * 5
  }
}
  • рядок 22: ми самостійно обробляємо сповіщення про очікування завершення асинхронної дії;
  • рядок 31: ми будемо використовувати різні плагіни, які будуть спеціалізовані або для клієнта, або для сервера, але не для обох одночасно;
  • рядок 52: модуль [axios] інтегровано в [nuxt]. Це призведе до того, що об’єкт [axios], який буде надсилати запити HTTP здодатку [nuxt] до сервера PHP для розрахунку податку, буде доступний у [context.$axios];
  • рядок 54: модуль [cookie-universal-nuxt] дозволить нам зберегти сесію [nuxt] у файлі cookie;
  • рядок 60: властивість [axios] дозволяє нам налаштувати модуль [@nuxtjs/axios] з рядка 52. Ми не будемо використовувати цю можливість, віддавши перевагу властивості [env] з рядка 88;
  • рядок 90: максимальний час очікування відповіді від сервера розрахунку податку;
  • рядок 91: необхідний для клієнта [nuxt] — дозволяє використовувати файли cookie під час обміну даними з сервером розрахунку податку;
  • рядок 92: базовий URL сервера розрахунку податку;
  • рядок 94: тривалість сеансу nuxt (5 хв);
  • рядок 77: навігація клієнта та сервера [nuxt] контролюватиметься маршрутизаційним проміжним програмним забезпеченням;

15.4. Рівень [UI] додатка

Image

Ми надамо додатку [nuxt] доступ до API сервера розрахунку податку через наступний вигляд:

Image

  • у [2] — меню, яке надає доступ до API сервера розрахунку податків:
    • [Authentification]: відповідає сторінці [authentification]. Ця сторінка надсилає запит на автентифікацію до сервера розрахунку податку з використанням ідентифікаційних даних [admin, admin], які наразі є єдиними дозволеними. Результат, що відображається, аналогічний [3];
    • [Requête AdminData]: відповідає сторінці [get-admindata]. Ця сторінка запитує у сервера розрахунку податку дані, що тут називаються [adminData], які дозволяють розрахувати податок. Результат, що відображається, аналогічний [3];
    • [Fin session impôt]: відповідає сторінці [fin-session]. Ця сторінка надсилає запит на завершення сеансу PHP до сервера розрахунку податку. Сервер скасовує поточну сесію PHP і ініціалізує нову порожню сесію;

15.5. Рівні [dao] додатка [nuxt]

Як зазначено вище, архітектура додатка [nuxt] буде такою:

Image

  • у [1] сервер [node.js] передає сторінки [nuxt] браузеру [2]. Саме рівень [web] [8] сервера доставляє ці сторінки. Для доставки сторінки сервер міг запитати зовнішні дані у сервера даних [3]. Саме рівень [DAO] [9] виконує необхідні запити HTTP;
  • при кожному виклику сторінки на сервер [node.js][1], браузер [2] отримує весь додаток [nuxt], який потім виконується в режимі SPA. Блок [UI] (User Interface) [4] відображає сторінки [vue.js] для користувача. Дії користувача або життєвий цикл сторінок можуть спричинити запити до зовнішніх даних на сервері даних [3]. Саме рівень [DAO] [5] здійснює необхідні запити HTTP;

Ми будемо використовувати версію 14 сервера розрахунку податків, розробленого в документі |Вступ до мови PHP7 на прикладі|. Ми будемо використовувати лише частину його API (інтерфейсу прикладного програмування) jSON:

Запит
Відповідь
1
2
3
4
5
6
[initialisation d’une session jSON avec le
serveur de calcul de l’impôt]
[une session PHP est créée avec le serveur de
calcul de l’impôt]

GET main.php?action=init-session&type=json

{
    "action": "init-session",
    "état"700,
    "réponse": "session démarrée avec type [json]"
}

[authentification de l’utilisateur]
[l’authentification est stockée dans la
session PHP]

POST main.php?action=authentifier-utilisateur
paramètres postés : user, admin

{
    "action": "authentifier-utilisateur",
    "état"200,
    "réponse": "Authentification réussie [admin, admin]"
}

[demande des données de l’administration
fiscale]
[les données reçues sont stockées dans la
session PHP]

GET main.php?action=get-admindata

{
    "action": "get-admindata",
    "état"1000,
    "réponse": {
        "limites": [
            9964,
            27519,
            73779,
            156244,
            0
        ],
        "coeffR": [
            0,
            0.14,
            0.3,
            0.41,
            0.45
        ],
        "coeffN": [
            0,
            1394.96,
            5798,
            13913.69,
            20163.45
        ],
        "plafondQfDemiPart": 1551,
        "plafondRevenusCelibatairePourReduction": 21037,
        "plafondRevenusCouplePourReduction": 42074,
        "valeurReducDemiPart": 3797,
        "plafondDecoteCelibataire": 1196,
        "plafondDecoteCouple": 1970,
        "plafondImpotCouplePourDecote": 2627,
        "plafondImpotCelibatairePourDecote": 1595,
        "abattementDixPourcentMax": 12502,
        "abattementDixPourcentMin": 437
    }
}

[fin de la session PHP avec le serveur de
calcul de l’impôt]
[supprime la session PHP courante et crée une
nouvelle session PHP. Dans celle-ci, la
session jSON reste activée, mais l’utilisateur
n’est plus authentifié]

GET main.php?action=fin-session

{
    "action": "fin-session",
    "état"400,
    "réponse": "session supprimée"
}

15.5.1. Шар [dao] сервера [nuxt]

Image

Сервер [node.js] [1] використовуватиме шар [dao], описаний у документі |Вступ до фреймворку VUE.JS на прикладі|. Нагадаємо його код:


'use strict';

// імпорт
import qs from 'qs'

class Dao {

  // конструктор
  constructor(axios) {
    this.axios = axios;
    // сесійний файл cookie
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
  }

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

  async  authentifierUtilisateur(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 this.getRemoteData(options);
  }

  async getAdminData() {
    // параметри запиту HHTP [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // параметри запиту URL
      params: {
        action: 'get-admindata'
      }
    };
    // виконання запиту HTTP
    const data = await this.getRemoteData(options);
    // результат
    return data;
  }

  async  getRemoteData(options) {
    // для сесійного файлу cookie
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.Cookie = this.sessionCookie;
    // виконання запиту HTTP
    let response;
    try {
      // асинхронний запит
      response = await this.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('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // зберігаємо сесійний файл cookie
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // знайдено
          trouvé = true;
        } else {
          // наступний елемент
          i++;
        }
      }
    }
    // відповідь сервера міститься в [response.data]
    return response.data;
  }
}

// експорт класу
export default Dao;
  • усі методи шару [dao] повертають об’єкт, надісланий сервером даних [{action : ‘xx’, état : nn, réponse : {...}], із:
    • [action]: назва дії, виконаної сервером даних;
    • [état]: числовий показник:
      • [initSession]: стан=700 для відповіді без помилок;
      • [authentifierUtilisateur]: статус=200 для відповіді без помилок;
      • [getAdminData]: стан=1000 для відповіді без помилок;
      • [fin-session]: статус=400 для відповіді без помилок;
    • [réponse]: відповідь, пов’язана з цифровим показником [état]. Може змінюватися залежно від цього цифрового показника;

Розглянемо конструктор класу [Dao]:


// конструктор
  constructor(axios) {
    this.axios = axios;
    // сесійний файл cookie
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
}
  • рядок 2: об’єкт [axios], переданий як аргумент конструктору, надається кодом, що викликає. Саме він буде виконувати запити HTTP;
  • рядок 5: ім’я сесійного файлу cookie, надісланого сервером даних, записане як PHP;
  • рядок 6: сесійний файл cookie, який обмінюється між рівнем [dao] та сервером даних. Він ініціалізується функцією [getRemoteData] у рядках 67–113;

Щодо сесійного файлу cookie, нам слід розглянути два окремі рівні [dao]:

  • рівень браузера;
  • на рівні сервера;

Нам доведеться керувати трьома сесійними файлами cookie:

  1. той, що обмінюється між клієнтом [nuxt] та сервером PHP 7;
  2. той, що обмінюється між сервером [nuxt] та сервером PHP 7;
  3. файл, що обмінюється між клієнтом [nuxt] та сервером [nuxt];

Ми забезпечимо, щоб файл cookie сеансу з сервером PHP був однаковим для клієнта та сервера [nuxt]. Ми назвемо цей файл cookie «файлом cookie сеансу PHP». Цей файл cookie відповідає випадкам 1 і 2. Файл cookie з випадку 3 ми назвемо «файлом cookie сеансу [nuxt]». Отже, у нас буде два сеанси:

  • сесія PHP із сесійним файлом cookie PHP;
  • сесія [nuxt] із сесійним файлом cookie [nuxt];

Чому для сесій PHP клієнта та браузера [nuxt] використовується один і той самий файл cookie? Ми хочемо, щоб додаток міг взаємодіяти з сервером PHP незалежно від того, чи це клієнт, чи сервер [nuxt]:

  • якщо дія A сервера [nuxt] переводить сервер PHP у стан E, цей стан відображається у сесії PHP, яку підтримує сервер PHP;
  • використовуючи той самий сесійний файл cookie PHP, що й сервер, дія B клієнта [nuxt], яка слідуватиме за дією A сервера [nuxt], знайде сервер PHP устані E, залишеному сервером [nuxt], і, отже, міг би спиратися на роботу, вже виконану сервером [nuxt];
  • якщо після дії B клієнта [nuxt] слідує дія C сервера [nuxt], то з тієї ж причини, що й раніше, ця дія зможе спиратися на роботу, виконану дією B клієнта [nuxt];

Щоб браузер клієнта [nuxt] міг взаємодіяти з сервером PHP для розрахунку податку, ми будемо використовувати версію 14 цього сервера, яка дозволяє міждоменні виклики, тобто виклики з браузера на сервер PHP. Натомість виклики з сервера [nuxt] на сервер PHP не є міждоменними. Це поняття існує лише для викликів, що здійснюються з браузера.

Повернемося до коду конструктора класу [Dao], розглянутого раніше:


// конструктор
  constructor(axios) {
    this.axios = axios;
    // сесійний файл cookie
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
}
  • рядки 5 і 6 відповідають сесійному файлу cookie PHP із сервером розрахунку податку;

Управління сесійним файлом cookie PHP, про який йшлося вище, не підходить для сервера [nuxt]: його шар [dao] інстанціюється при кожному новому запиті до сервера [nuxt]. Слід пам’ятати, що запит на сторінку до сервера [nuxt] прирівнюється до перезапуску додатка [nuxt]. Отже, коли після першого запиту, зробленого сервером [nuxt] до сервера даних, сесійний файл cookie PHP шару [dao] ініціалізується, це значення втрачається під час наступного запиту HTTP від того самого сервера [nuxt], оскільки тим часом його шар [dao] було відтворено, конструктор — перезапущено, а сесійний файл cookie PHP — скинуто до порожнього рядка (рядок 6);

Одним із рішень є використання іншого конструктора для шару [dao] сервера:


// конструктор
  constructor(axios, phpSessionCookie) {
    // бібліотека axios
    this.axios = axios
    // значення сесійного файлу cookie
    this.phpSessionCookie = phpSessionCookie
    // назва сесійного файлу cookie сервера PHP
    this.phpSessionCookieName = 'PHPSESSID'
  }
  • рядок 2: цього разу сесійний файл cookie PHP буде передано конструктору шару [dao] сервера даних;

Як сервер [nuxt] зможе передати цей сесійний файл cookie PHP конструктору свого шару [dao]? Ми збережемо сесійний файл cookie PHP у сесійному файлі cookie [nuxt], який обмінюється між браузером та сервером [nuxt]. Процес відбувається наступним чином:

  1. запускається додаток [nuxt];
  2. коли сервер [nuxt] надсилає свій перший запит HTTP до сервера PHP, він зберігає сесійний файл cookie PHP, який отримав, у сесійному файлі cookie [nuxt], який обмінюється з клієнтом [nuxt];
  3. браузер, у якому працює клієнт [nuxt], отримує цей сесійний файл cookie [nuxt] і, отже, систематично надсилає його разом із кожним новим запитом до сервера [nuxt];
  4. коли сервер [nuxt] повинен буде надіслати новий запит на сервер PHP, він знайде сесійний файл cookie PHP у сесійному файлі cookie [nuxt], який браузер надіслав йому. Потім він надішле його на сервер PHP;

Існує два сесійні файли cookie, і їх не слід плутати:

  • сесійний файл [nuxt], що обмінюється між сервером [nuxt] та браузером клієнта [nuxt];
  • сесійний файл PHP, що обмінюється між сервером [nuxt] та сервером PHP або між клієнтом [nuxt] та сервером PHP;

Тепер повернемося до коду методу класу [Dao]. Він не містить функції для закриття сесії PHP із сервером розрахунку податку. Додамо її:


// завершення сеансу розрахунку податку
  async finSession() {
    // параметри запиту HHTP  [get /main.php?action=fin-session]
    const options = {
      method: 'GET',
      // параметри запиту URL
      params: {
        action: 'fin-session'
      }
    }
    // виконання запиту HTTP
    const data = await this.getRemoteData(options)
    // результат
    return data
  }

Під час тестування виявляється, що функція [getRemoteData], яка викликається у рядку 12, не підходить для методу [finSession]:


async  getRemoteData(options) {
    // для сесійного файлу cookie
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.Cookie = this.sessionCookie;
    // виконання запиту HTTP
    let response;
    try {
      // асинхронний запит
      response = await this.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('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // зберігаємо сесійний файл cookie
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // знайдено
          trouvé = true;
        } else {
          // наступний елемент
          i++;
        }
      }
    }
    // відповідь сервера міститься в [response.data]
    return response.data;
  }
  • рядки 30–43: виконується пошук файлу cookie [PHPSESSID=xxx]. Якщо його знайдено, він зберігається у класі (рядок 36);

Цей код не підходить для нового методу [finSession], оскільки під час дії [fin-session] сервер PHP надсилає два файли cookie з іменем [PHPSESSID]. Ось приклад, отриманий за допомогою клієнта [Postman]:

Image

  • у [1] — запит клієнта [Postman];
  • у [3] — відповідь сервера PHP;
  • у [4] — заголовки HTTP відповіді сервера PHP;

Image

  • у [5] сервер PHP спочатку повідомляє, що він видалив поточну сесію PHP;
  • у [6] сервер PHP надсилає файл cookie нової сесії PHP;

Зі поточним кодом функція [getRemoteData] отримує файл cookie [5], тоді як потрібно зберегти файл cookie [6].

Тому потрібно змінити код функції [getRemoteData]:


async getRemoteData(options) {
    // чи існує сесійний файл cookie PHP?
    if (this.phpSessionCookie) {
      // чи є заголовки?
      if (!options.headers) {
        // створюється порожній об’єкт
        options.headers = {}
      }
      // заголовок сесійного файлу cookie PHP
      options.headers.Cookie = this.phpSessionCookie
    }
    // виконання запиту HTTP
    let response
    try {
      // асинхронний запит
      response = await this.axios.request('main.php', options)
    } catch (error) {
      // параметр [error] є екземпляром винятку — він може мати різні форми
      if (error.response) {
        // відповідь сервера міститься в [error.response]
        response = error.response
      } else {
        // помилка повторюється
        throw error
      }
    }
    // response — це повна відповідь HTTP від сервера (заголовки HTTP + сама відповідь)
    // шукаємо сесійний файл cookie PHP серед отриманих файлів cookie
    // усі отримані файли cookie
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // файли cookie — це масив
      // шукаємо сесійний файл cookie PHP у цьому масиві
      let trouvé = false
      let i = 0
      while (!trouvé && i < cookies.length) {
        // шукаємо сесійний файл cookie PHP
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // зберігається сесійний файл cookie PHP
          const phpSessionCookie = results[1]
          // чи є в ньому слово [deleted]?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // сесійний файл cookie PHP правильний
            this.phpSessionCookie = phpSessionCookie
            // знайдено
            trouvé = true
          } else {
            // наступний елемент
            i++
          }
        } else {
          // наступний елемент
          i++
        }
      }
    }
    // відповідь сервера міститься в [response.data]
    return response.data
  }
  • рядок 41: знайдено файл cookie з іменем [PHPSESSID]. Його зберігаємо локально;
  • рядок 43: перевіряємо, чи є у збереженому файлі cookie рядок [PHPSESSID=deleted];
  • рядок 46: якщо відповідь «ні», то це означає, що знайдено правильний файл cookie [PHPSESSID]. Його зберігаємо у класі;

Після функції [getRemoteData] сесійний файл cookie PHP зберігається у класі, у полі [this.phpSessionCookie]. Ми вже зазначали, що клас інстанціюється при кожному новому запиті HTTP від сервера [nuxt]. Отже, сесійний файл cookie PHP потрібно вилучити з класу. Для цього до класу додаємо новий метод:


// доступ до сесійного файлу cookie PHP
  getPhpSessionCookie() {
    return this.phpSessionCookie
}
  • сервер [nuxt] запитує дію у свого рівня [dao], передаючи сесійний файл cookie PHP у його конструктор, якщо такий є;
  • після виконання дії сервер [nuxt] отримує сесійний файл cookie PHP, збережений шаром [dao] за допомогою попереднього методу [getPhpSessionCookie]. Цей файл cookie може бути таким самим, як попередній, або іншим. Останній випадок трапляється у двох випадках:
    • під час виконання методу [initSession] (раніше сесійного файлу cookie PHP не було);
    • під час виконання методу [finSession] (сервер PHP змінює сесійний файл cookie PHP);

Звернемо увагу на особливість сесійного файлу cookie PHP. Сервер [nuxt] не завжди отримує цей файл cookie від сервера PHP. Справа в тому, що останній надсилає його лише один раз. Потім він більше його не надсилає. Якщо розглянути код [getRemoteData] та код [getPhpSessionCookie], то можна побачити, що коли сервер PHP не надсилає сесійний файл cookie, функція [getPhpSessionCookie] повертає сесійний файл cookie PHP, переданий у конструктор. Таким чином, сервер завжди надсилає серверу PHP останній сесійний файл cookie PHP, який той йому надіслав.

15.5.2. Рівень [dao] клієнта [nuxt]

Image

Для клієнта [nuxt], що виконується у браузері, використовуємо код класу [Dao] із документа |Вступ до фреймворку VUE.JS на прикладі|:


"use strict";

// імпорт
import qs from "qs";

class Dao {
  // конструктор
  constructor(axios) {
    this.axios = axios;
  }

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

  async authentifierUtilisateur(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 this.getRemoteData(options);
  }

  async getAdminData() {
    // параметри запиту HHTP  [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // параметри запиту URL
      params: {
        action: "get-admindata"
      }
    };
    // виконання запиту HTTP
    const data = await this.getRemoteData(options);
    // результат
    return data;
  }

  async getRemoteData(options) {
    // виконання запиту HTTP
    let response;
    try {
      // асинхронний запит
      response = await this.axios.request("main.php", options);
    } catch (error) {
      // параметр [error] є екземпляром винятку — він може мати різні форми
      if (error.response) {
        // відповідь сервера міститься в [error.response]
        response = error.response;
      } else {
        // помилка повторюється
        throw error;
      }
    }
    // response — це повна відповідь сервера у форматі HTTP (заголовки HTTP + сама відповідь)
    // відповідь сервера міститься в [response.data]
    return response.data;
  }
}

// експорт класу
export default Dao;

Цей код відрізняється від шару [dao] сервера [nuxt] тим, що він не обробляє сесійний файл cookie PHP із сервером розрахунку податку: це робить браузер.

Ми, як і у випадку з шаром [dao] сервера [nuxt], додамо метод [finSession]:


// завершення сеансу розрахунку податку
  async finSession() {
    // параметри запиту HHTP  [get /main.php?action=fin-session]
    const options = {
      method: 'GET',
      // параметри запиту URL
      params: {
        action: 'fin-session'
      }
    }
    // виконання запиту HTTP
    const data = await this.getRemoteData(options)
    // результат
    return data
  }

Коли клієнт [nuxt] виконує цей метод, він, як і сервер [nuxt], отримує два сесійні файли cookie PHP. Насправді їх отримує браузер, і він правильно обробляє цю ситуацію: він зберігає лише файл cookie нової сесії PHP, яку ініціював сервер розрахунку податку. Отже, під час наступної дії клієнта [nuxt] щодо сервера PHP сесійний файл cookie PHP буде правильним, оскільки саме браузер надсилає його. Однак є одна проблема: сервер [nuxt] не знає про те, що сесійний файл cookie PHP змінився. Під час обміну даними з сервером PHP він надішле сесійний файл cookie PHP, якого вже не існує, і це спричинить проблеми. Клієнт [nuxt] мав би повідомити сервер [nuxt] про те, що сесійний файл cookie PHP змінився, і передати йому новий. Ми знаємо, як він може це зробити: за допомогою сесійного файлу cookie [nuxt] — файлу cookie, що обмінюється між клієнтом і сервером [nuxt]. Клієнт [nuxt] має щонайменше два способи отримати новий сесійний файл cookie PHP:

  1. запитавши його у браузера;
  2. використавши метод [getRemoteData] сервера, який знає, як отримати новий сесійний файл cookie PHP;

Ми скористаємося другим варіантом, оскільки він уже готовий до використання. Метод [getRemoteData] клієнта [nuxt] тоді виглядає так:


async getRemoteData(options) {
    // виконання запиту HTTP
    let response
    try {
      // асинхронний запит
      response = await this.axios.request('main.php', options)
    } catch (error) {
      // параметр [error] є екземпляром винятку — він може мати різні форми
      if (error.response) {
        // відповідь сервера міститься в [error.response]
        response = error.response
      } else {
        // помилка повторюється
        throw error
      }
    }
    // response — це повна відповідь HTTP від сервера (заголовки HTTP + сама відповідь)
    // шукаємо сесійний файл cookie PHP серед отриманих файлів cookie
    // усі отримані файли cookie
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // файли cookie — це масив
      // шукаємо сесійний файл cookie PHP у цьому масиві
      let trouvé = false
      let i = 0
      while (!trouvé && i < cookies.length) {
        // шукаємо сесійний файл cookie PHP
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // зберігається сесійний файл cookie PHP
          const phpSessionCookie = results[1]
          // чи є в ньому слово [deleted]?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // сесійний файл cookie PHP правильний
            this.phpSessionCookie = phpSessionCookie
            // знайдено
            trouvé = true
          } else {
            // наступний елемент
            i++
          }
        } else {
          // наступний елемент
          i++
        }
      }
    }
    // відповідь сервера міститься в [response.data]
    return response.data
  }

У [getRemoteData] ми залишили лише код, який обробляє відповідь сервера PHP на запит щодо сесійного файлу cookie PHP. Не було збережено код, який включав сесійний файл cookie PHP у запит до сервера PHP, оскільки цим займається браузер, у якому працює клієнт [nuxt].

Після того як сесійний файл cookie PHP отримано клієнтом [nuxt], його потрібно додати до сесії [nuxt], щоб сервер [nuxt] міг ним скористатися. Цим не займається рівень [dao], але він надає доступ за допомогою методу до сесійного файлу cookie PHP, який він запам’ятав:


// доступ до сесійного файлу cookie PHP
  getPhpSessionCookie() {
    return this.phpSessionCookie
}

Функція [getPhpSessionCookie] не завжди повертає дійсний сесійний файл cookie:

  • тут слід пам’ятати, що рівень [dao] клієнта [nuxt] є стійким. Він ініціалізується один раз і потім залишається в пам’яті;
  • доки сервер PHP не надішле сесійний файл cookie PHP клієнту [nuxt], функція [getPhpSessionCookie] клієнта [nuxt] повертає значення [undefined];
  • коли сервер PHP надсилає сесійний файл cookie PHP клієнту [nuxt], воно зберігається в [this.phpSessionCookie] і залишатиметься там, доки не буде замінено новим сесійним файлом cookie PHP, надісланим сервером PHP. Функція [getPhpSessionCookie] клієнта [nuxt] потім повертає останній отриманий сесійний файл cookie PHP;

Рівень [dao] клієнта [nuxt] відрізняється від рівня сервера [nuxt] лише одним моментом: вона сама не надсилає сесійний файл cookie PHP, оскільки це робить браузер. Проте ми вирішили залишити два окремі шари [dao], оскільки міркування, що призводять до їхнього відповідного написання, є різними.

15.6. Сесія [nuxt]

Image

Сесія [nuxt] (між клієнтом і сервером Nuxt) буде інкапсульована в наступний об’єкт [session]:


/* eslint-disable no-console */
// визначення сеансу
const session = {
  // вміст сесії
  value: {
    // неініціалізований store
    initStoreDone: false,
    // значення сховища Vuex
    store: ''
  },
  // збереження сесії у файлі cookie
  save(context) {
    // збереження сховища в сесії
    this.value.store = context.store.state
    console.log('nuxt-session save=', this.value)
    // збереження значення сесії
    context.app.$cookies.set('nuxt-session', this.value, { path: context.base, maxAge: context.env.maxAge })
  },
  // скидання сесії
  reset(context) {
    console.log('nuxt-session reset')
    // скидання сховища
    context.store.commit('reset')
    // збереження нового сховища в сесії та збереження сесії
    this.save(context)
  }
}
// експорт сесії
export default session
  • рядки 5–10: сесія має лише одну властивість [value] із двома підвластивостями:
    • [initStoreDone], що вказує, чи було ініціалізовано сховище;
    • [store]: значення [store.state] сховища Vuex додатка;
  • рядки 12–18: метод [save] слугує для збереження сесії [nuxt] у файлі cookie. Тут для управління файлом cookie використовується бібліотека [cookie-universal-nuxt]. Зверніть увагу на ім’я файлу cookie сесії [nuxt]: [nuxt-session] (рядок 17);
  • рядки 20–26: метод [reset] скидає сесію [nuxt];
    • рядок 23: store Vuex скидається, а потім зберігається в сесії, рядок 25;

15.7. Плагіни управління сесією [nuxt]

Image

15.7.1. Плагін управління сесією [nuxt] для serveur та [nuxt]

Під час запуску програми першим запускається сервер [nuxt]. Отже, саме він ініціалізує сесію [nuxt]. Скрипт [server/plgSession] має такий вигляд:


/* eslint-disable no-console */

// імпорт сесії
import session from '@/entities/session'

export default (context, inject) => {
  // управління сесією на сервері
  console.log('[plugin server plgSession]')

  // чи існує сесія?
  const value = context.app.$cookies.get('nuxt-session')
  if (!value) {
    // нова сесія
    console.log("[plugin server plgSession], démarrage d'une nouvelle session")
  } else {
    // існуюча сесія
    console.log("[plugin server plgSession], reprise d'une session existante")
    session.value = value
  }

  // в [context, Vue] вставляється функція, яка зробить сесію поточною
  inject('session', () => session)
}
  • рядок 4: імпортується код сесії [nuxt];
  • рядок 11: отримується значення файлу cookie сесії [nuxt];
  • рядки 12–15: якщо файл cookie сесії [nuxt] не існував, то сесії [nuxt], імпортованої в рядку 4, достатньо. Більше нічого робити не потрібно;
  • рядки 15–19: якщо файл cookie сеансу [nuxt] існував, то в рядку 18 його значення зберігається в сеансі, імпортованому в рядку 4;
  • рядок 22: сесія була або ініціалізована, або відновлена. Її роблять доступною за допомогою функції [$session];

15.7.2. Плагін управління сесією [nuxt] клієнта [nuxt]

Скрипт [client/plgSession] має такий вигляд:


/* eslint-disable no-console */

// імпорт сесії
import session from '@/entities/session'

export default (context, inject) => {
  // управління сесією клієнта
  console.log('[plugin client plgSession], reprise de la session [nuxt] du serveur')
  // отримання існуючої сесії з сервера Nuxt
  session.value = context.app.$cookies.get('nuxt-session')

  // вставляємо функцію в [context, Vue], яка зробить сесію поточною
  inject('session', () => session)
}
  • рядок 4: імпортується сесія [nuxt];
  • рядок 10: отримується поточна сесія [nuxt] із файлу cookie [nuxt-session];
  • рядок 13: сесія [nuxt], імпортована в рядку 4, повертається через вставлену функцію [$session];

15.8. Плагіни шарів [dao]

Image

15.8.1. Плагін шару [dao] клієнта [nuxt]

Скрипт [client/plgDao] має такий вигляд:


/* eslint-disable no-console */
// створюємо точку доступу до шару [Dao]
import Dao from '@/api/client/Dao'
export default (context, inject) => {
  // конфігурація axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  context.$axios.defaults.withCredentials = context.env.withCredentials
  // інстанціювання шару [dao]
  const dao = new Dao(context.$axios)
  // введення функції [$dao] у контекст
  inject('dao', () => dao)
  // журнал
  console.log('[fonction client $dao créée]')
}
  • рядок 3: імпортується шар [dao] клієнта [nuxt];
  • рядки 6–8: налаштовуєтьсяоб’єкт [context.$axios], який буде виконувати запити HTTP з шару [dao] клієнта [nuxt], використовуючи інформацію з файлу [nuxt.config]:

// середовище
  env: {
    // конфігурація axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // конфігурація сесійного файлу cookie [nuxt]
    maxAge: 60 * 5
  }
  • рядок 10: створюється екземпляр шару [dao] клієнта [nuxt];
  • рядок 12: функція [$dao] вводиться в контекст і сторінки клієнта. Ця функція надає доступ до шару [dao] з рядка 10;

Отже, слід запам’ятати, що для отримання доступу до шару [dao] клієнта [nuxt] під час його виконання слід написати:

  • [context.app.$dao()], якщо контекст відомий;
  • [this.$dao()] на сторінці [Vue.js];

15.8.2. Плагін шару [dao] для serveur [nuxt]

Скрипт [server/plgDao] має такий вигляд:


/* eslint-disable no-console */
// створюється точка доступу до шару [Dao]
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // налаштування axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // отримуємо сесійний файл cookie
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // інстанціювання шару [dao]
  const dao = new Dao(context.$axios, phpSessionCookie)
  // введення функції [$dao] у контекст
  inject('dao', () => dao)
  // запис у журнал
  console.log('[fonction server $dao créée]')
}
  • рядок 3: імпортується шар [dao] із сервера [nuxt];
  • рядки 6–7: налаштовуєтьсяоб’єкт [context.$axios], який буде надсилати запити HTTP до шару [dao] сервера [nuxt] з використанням інформації з файлу [nuxt.config]:

// середовище
  env: {
    // конфігурація axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // конфігурація сесійного файлу cookie [nuxt]
    maxAge: 60 * 5
  }
  • рядок 9: отримуємо store додатка [nuxt];
  • рядок 10: якщо магазин існує, отримуємо файл cookie сеансу PHP, оскільки він потрібен для створення екземпляра шару [dao] сервера [nuxt];
  • рядок 13: створюється екземпляр шару [dao] сервера [nuxt];
  • рядок 15: функція [$dao] вводиться в контекст і сторінки сервера [nuxt]. Ця функція надає доступ до шару [dao] з рядка 13;

Отже, слід запам’ятати, що для отримання доступу до шару [dao] сервера [nuxt] під час його виконання слід написати:

  • [context.app.$dao()], якщо контекст відомий;
  • [this.$dao()] на сторінці [Vue.js];

15.9. Магазин Vuex

Image

Стор [Vuex] зберігатиме всі дані, які мають бути спільними для різних компонентів додатка [pages, client, serveur], однак ці дані не будуть реактивними.


/* eslint-disable no-console */

// стан магазину
export const state = () => ({
  // сесія jSON розпочата
  jsonSessionStarted: false,
  // користувач автентифікований
  userAuthenticated: false,
  // сесійний файл cookie PHP
  phpSessionCookie: '',
  // adminData
  adminData: ''
})

// зміни в сховищі
export const mutations = {
  // заміна стану
  replace(state, newState) {
    for (const attr in newState) {
      state[attr] = newState[attr]
    }
  },
  // скидання сховища
  reset() {
    this.commit('replace', { jsonSessionStarted: false, userAuthenticated: false, phpSessionCookie: '', adminData: '' })
  }
}

// дії з магазином
export const actions = {
  nuxtServerInit(store, context) {
    // хто виконує цей код?
    console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
    // ініціалізація сеансу
    initStore(store, context)
  }
}

function initStore(store, context) {
  // store — це сесія, яку потрібно ініціалізувати
  // отримуємо сесію
  const session = context.app.$session()
  // чи сесія вже була ініціалізована?
  if (!session.value.initStoreDone) {
    // запускаємо новий магазин
    console.log("nuxtServerInit, initialisation d'une nouvelle session")
    // зберігаємо store у сесії
    session.value.store = store.state
    // сховище тепер ініціалізовано
    session.value.initStoreDone = true
  } else {
    console.log("nuxtServerInit, reprise d'un store existant")
    // зберігання оновлюється за допомогою зберігання сесії
    store.commit('replace', session.value.store)
  }
  // зберігаємо сесію
  session.save(context)
  // журнал
  console.log('initStore terminé, store=', store.state)
}

У сховищі зберігаються такі дані:

  • рядок 6: [jsonSessionStarted] буде встановлено в значення «true», щойно буде успішно завершено ініціалізацію сеансу jSON із сервером PHP, незалежно від того, чи була вона здійснена клієнтом, чи сервером [nuxt]. Після завершення цієї ініціалізації сесійний файл cookie для сервера PHP буде отримано та розміщено у властивості [phpSessionCookie], рядок 10;
  • рядок 8: [userAuthenticated] буде встановлено в значення «true», щойно відбудеться успішна автентифікація на сервері PHP, незалежно від того, чи була вона здійснена клієнтом, чи сервером [nuxt];
  • рядок 12: [adminData] буде значенням [adminData], отриманим від сервера PHP після успішної автентифікації;
  • рядки 18–22: присвоєння [replace] дозволяє ініціалізувати попередні властивості за допомогою властивостей об’єкта, переданого як параметр;
  • рядки 24–26: операція [reset] повертає властивостям сховища їхні початкові значення;
  • рядки 31–37: функція [nuxtServerInit] делегує свою роботу функції [initStore];
  • рядки 39–60: функція [initStore] виконує дві ролі:
    • якщо store не було ініціалізовано, його ініціалізують і додають до сесії;
    • якщо store вже ініціалізовано, його значення отримується із сесії [nuxt];
  • рядок 42: отримується сесія nuxt;
  • рядок 44: перевіряється, чи було ініціалізовано store:
    • якщо ні, то в сесію додається початковий стор (рядок 48);
    • потім у рядку 50 вказується, що store було ініціалізовано;
  • рядки 51–55: якщо store було ініціалізовано, то його використовують (рядок 54) для ініціалізації store значенням, що міститься в сесії;
  • рядок 57: у будь-якому випадку сесія зберігається у файлі cookie [nuxt-session] разом із масивом, що міститься в ній;

15.10. Плагін [plgEventBus]

Image

Цей плагін призначений для надання клієнту [nuxt] доступу до шини подій за допомогою функції [$eventBus], вбудованої в контекст клієнта [nuxt]. Вводити його в контекст сервера [nuxt] немає сенсу, оскільки сервер не вміє обробляти події. Проте ми вже бачили, що введення його на стороні сервера та подальше використання не викликає помилок.


/* eslint-disable no-console */
// створюється шина подій між поданнями
import Vue from 'vue'
export default (context, inject) => {
  // канал подій
  const eventBus = new Vue()
  // введення функції [$eventBus] у контекст
  inject('eventBus', () => eventBus)
  // журнал
  console.log('[fonction $eventBus créée]')
}

Ми вже зустрічали цей плагін у розділі «Посилання». Функція [$eventBus] буде доступна на стороні клієнта за допомогою таких позначень:

  • [context.app.$eventBus()] там, де доступний контекст;
  • [this.$eventBus()] на сторінках [Vue.js] клієнта;

15.11. Компоненти додатка [nuxt]

Image

Компонент [layout] — це той самий, що й у попередніх прикладах:


<!-- розміщення переглядів -->
<template>
  <!-- рядок -->
  <div>
    <b-row>
      <!-- область із трьома стовпцями -->
      <b-col v-if="left" cols="3">
        <slot name="left" />
      </b-col>
      <!-- область з дев'ятьма стовпцями -->
      <b-col v-if="right" cols="9">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>

<script>
export default {
  // параметри
  props: {
    left: {
      type: Boolean
    },
    right: {
      type: Boolean
    }
  }
}
</script>

Компонент [navigation] має такий вигляд:


<template>
  <!-- меню Bootstrap із трьома пунктами -->
  <b-nav vertical>
    <b-nav-item to="/authentification" exact exact-active-class="active">
      Authentification
    </b-nav-item>
    <b-nav-item to="/get-admindata" exact exact-active-class="active">
      Requête AdminData
    </b-nav-item>
    <b-nav-item to="/fin-session" exact exact-active-class="active">
      Fin session impôt
    </b-nav-item>
  </b-nav>
</template>

15.12. Макетi додатка [nuxt]

Image

15.12.1. [default]

Макет [default] використовується для прикладу [nuxt-11] у розділі «Посилання»:


<template>
  <div class="container">
    <b-card>
      <!-- повідомлення -->
      <b-alert show variant="success" align="center">
        <h4>[nuxt-12] : requêtes HTTP avec axios</h4>
      </b-alert>
      <!-- поточний вигляд маршрутизації -->
      <nuxt />
      <!-- повідомлення про очікування -->
      <b-alert v-if="showLoading" show variant="light">
        <strong>Requête au serveur de données en cours...</strong>
        <div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
      </b-alert>
      <!-- помилка асинхронної операції -->
      <b-alert v-if="showErrorLoading" show variant="danger">
        <strong>La requête au serveur de données a échoué : {{ errorLoadingMessage }}</strong>
      </b-alert>
    </b-card>
  </div>
</template>

<script>
/* eslint-disable no-console */
export default {
  name: 'App',
  data() {
    return {
      showLoading: false,
      showErrorLoading: false
    }
  },
  // життєвий цикл
  beforeCreate() {
    console.log('[default beforeCreate]')
  },
  created() {
    console.log('[default created]')
    if (process.client) {
      // обслуговується подія [loading]
      this.$eventBus().$on('loading', this.mShowLoading)
      // а також подію [errorLoadingMessage]
      this.$eventBus().$on('errorLoading', this.mShowErrorLoading)
    }
  },
  beforeMount() {
    console.log('[default beforeMount]')
  },
  mounted() {
    console.log('[default mounted]')
  },
  methods: {
    // обробка повідомлення в режимі очікування
    mShowLoading(value) {
      console.log('[default mShowLoading], showLoading=', value)
      this.showLoading = value
    },
    // помилка асинхронної операції
    mShowErrorLoading(value, errorLoadingMessage) {
      console.log('[default mShowErrorLoading], showErrorLoading=', value, 'errorLoadingMessage=', errorLoadingMessage)
      this.showErrorLoading = value
      this.errorLoadingMessage = errorLoadingMessage
    }
  }
}
</script>
  • рядки 10–14: відображають повідомлення про очікування завершення асинхронної операції клієнта [nuxt];
  • рядки 15–18: відображають можливе повідомлення про помилку асинхронної операції;
  • рядок 37: функція [created] сторінки [default] виконується перед функцією [mounted] сторінок;
  • рядок 39: якщо виконавцем є клієнт [nuxt], то сторінка [default] переходить у режим очікування подій:
    • [loading], що сигналізує про початок або кінець очікування. Після цього виконується функція [mShowLoading];
    • [errorLoading], яка сигналізує про необхідність відображення повідомлення про помилку. Тоді виконується функція [mShowErrorLoading];
  • сторінки [nuxt]:
    • виводять повідомлення про очікування, надсилаючи подію [‘loading’, true] на шину подій;
    • приховують повідомлення про очікування, надсилаючи подію [‘loading’, false] на шину подій;
    • виводять повідомлення про помилку, надсилаючи подію [‘errorLoading’, true] на шину подій;
    • приховують повідомлення про помилку, надсилаючи подію [‘errorLoading’, false] на шину подій;

15.12.2. [error]

Макет [error] відображає системне повідомлення про помилку (не обробляється розробником):


<!-- визначення HTML подання -->
<template>
  <!-- форматування -->
  <Layout :left="true" :right="true">
    <!-- сповіщення у правій колонці -->
    <template slot="right">
      <!-- повідомлення на рожевому тлі -->
      <b-alert show variant="danger" align="center">
        <h4>L'erreur suivante s'est produite : {{ JSON.stringify(error) }}</h4>
      </b-alert>
    </template>
    <!-- меню навігації у лівій колонці -->
    <Navigation slot="left" />
  </Layout>
</template>

<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */

import Layout from '@/components/layout'
import Navigation from '@/components/navigation'

export default {
  name: 'Error',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  // властивість [props]
  props: { error: { type: Object, default: () => 'waiting ...' } },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[error beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[error created, error=]', this.error)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[error beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[error mounted]')
  }
}
</script>

15.13. Сторінка [index], що виконується сервером [nuxt]

Image

Сторінка [index.vue] має особливість: доступ до неї можливий лише через сервер [nuxt]. Користувачеві не надається жодного посилання для доступу до неї через клієнт [nuxt]. Її код такий:


<!-- головна сторінка -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення-->
    <b-alert slot="right" show variant="warning">Initialisation de la session avec le serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'InitSession',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  // асинхронні дані
  async asyncData(context) {
    // журнал
    console.log('[index asyncData started]')
    try {
      // запуск сеансу jSON
      const dao = context.app.$dao()
      const response = await dao.initSession()
      // журнал
      console.log('[index asyncData response=]', response)
      // отримано сесійний файл cookie PHP для наступних запитів
      const phpSessionCookie = dao.getPhpSessionCookie()
      // зберігаємо сесійний файл cookie PHP у сесії [nuxt]
      context.store.commit('replace', { phpSessionCookie })
      // Чи сталася помилка?
      if (response.état !== 700) {
        // помилка міститься в response.réponse
        throw new Error(response.réponse)
      }
      // відзначаємо, що сесія jSON розпочалася
      context.store.commit('replace', { jsonSessionStarted: true })
      // повертається результат
      return { result: '[succès]' }
    } catch (e) {
      // журнал
      console.log('[index asyncData error=]', e)
      // фіксується факт, що сесія jSON не розпочалася
      context.store.commit('replace', { jsonSessionStarted: false })
      // повідомляється про помилку
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // зберігаємо дані в сховищі
      const session = context.app.$session()
      session.save(context)
      // журнал
      console.log('[index asyncData finished]')
    }
  },
  // життєвий цикл
  beforeCreate() {
    console.log('[index beforeCreate]')
  },
  created() {
    console.log('[index created]')
  },
  beforeMount() {
    console.log('[index beforeMount]')
  },
  mounted() {
    console.log('[index mounted]')
    // тільки для клієнта
    if (this.showErrorLoading) {
      console.log('[index mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>
  • рядок 7: сторінка відображає результат [result] асинхронного запиту (рядки 46 та 51);
  • рядок 31: асинхронна операція — це відкриття сеансу jSON із сервером розрахунку податку;
  • рядок 25: відомо, що коли сторінка запитується безпосередньо на сервері [nuxt], функція [asyncData] виконується лише сервером, а не клієнтом [nuxt], який запускається після того, як браузер отримав відповідь від сервера [nuxt];
  • рядок 30: отримується рівень [dao] у контексті сервера [nuxt];
  • рядок 35: якщо сервер ще не надсилав запит до сервера розрахунку податку, він отримує свій перший сесійний файл cookie PHP, інакше — останній сесійний файл cookie PHP, який він отримав (див. код шару [dao] сервера [nuxt] у розділі за посиланням);
  • рядок 37: цей сесійний файл cookie PHP зберігається у сховищі;
  • рядки 39–42: перевіряється, чи операція пройшла успішно. Якщо ні, генерується виняток, який буде перехоплений [catch] у рядку 47;
  • рядок 44: у сховищі фіксується, що сесія jSON із сервером PHP розпочалася;
  • рядок 46: повертається результат [result], який відображається в рядку 7;
  • рядки 47–54: обробляється можливе виключення. Воно може бути двох типів:
    • операція HTTP у рядку 31 завершилася невдало через помилку зв’язку між сервером [nuxt] та сервером PHP;
    • операція HTTP у рядку 31 завершилася успішно, але в отриманому результаті було виявлено помилку (рядки 39–42);
  • рядок 51: слід зазначити, що сесія jSON із сервером PHP не розпочалася;
  • рядок 53: повертається результат [result], який відображається у рядку 7. Крім того, встановлюються властивості [showErrorLoading] та [errorLoadingMessage], які клієнт [nuxt] використовуватиме для відображення повідомлення про помилку, коли отримає сторінку, надіслану сервером [nuxt] (рядки 72–79);
  • рядки 54–60: код, що виконується в будь-якому випадку (у разі успіху чи невдачі);
  • рядок 56: отримуємо сесію [nuxt] у контексті сервера [nuxt];
  • рядок 57: її зберігають;
  • рядки 63–68: після завершення функції [asyncData] сервер [nuxt] виконує функції [beforeCreate] та [create];

Примітка: виконання сторінки [index] сервером [nuxt] може завершитися невдачею, наприклад, якщо сервер розрахунку податку не запущений на момент запуску додатка [nuxt]:

Image

У цьому випадку єдиним рішенням є запуск сервера розрахунку податку, а потім самої програми [nuxt], оскільки меню навігації не пропонує опції для ініціювання сеансу jSON із сервером розрахунку податку;

15.14. Сторінка [index], що виконується клієнтом [nuxt]

Сторінка [index] виконується клієнтом [nuxt] лише після того, як сервер [nuxt] надіслав її йому. Сервер надіслав йому інформацію [result] та, можливо, [showErrorLoading] і [errorLoadingMessage].

Відомо, що функція [asyncData] не буде виконана. Отже, залишаються функції життєвого циклу, зокрема функція [mounted]:


mounted() {
    console.log('[index mounted]')
    // тільки для клієнта
    if (this.showErrorLoading) {
      console.log('[index mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
}
  • клієнт [nuxt] автоматично вбудовує у властивості сторінки елементи [result] та, можливо, [showErrorLoading, errorLoadingMessage], які йому надіслав сервер [nuxt]:
  • властивість [result] відображається у рядку 7;
  • властивості [showErrorLoading, errorLoadingMessage] використовуються методом [mounted]: у рядку 4 перевіряється властивість [showErrorLoading]. Якщо вона має значення «true», у рядку 6 використовується шина подій клієнта [nuxt] для повідомлення про те, що потрібно відобразити повідомлення про помилку;
  • подія [errorLoading], запущена у рядку 6, перехоплюється сторінкою [layouts/default], описаною у розділі «Посилання»;

15.15. Сторінка [authentification], що виконується сервером [nuxt]

Сторінка [authentification] відповідає за ідентифікацію користувача на сервері розрахунку податку. Її код такий:


<!-- сторінка автентифікації -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення-->
    <b-alert slot="right" show variant="warning">Authentification auprès du serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Authentification',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  // асинхронні дані
  async asyncData(context) {
    // журнал
    console.log('[authentification asyncData started]')
    if (process.client) {
      // початок очікування клієнта [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // помилок немає
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // аутентифікація на сервері
      const dao = context.app.$dao()
      const response = await dao.authentifierUtilisateur('admin', 'admin')
      // журнал
      console.log('[authentification asyncData response=]', response)
      // результат
      const userAuthenticated = response.état === 200
      // фіксується, чи пройшов користувач аутентифікацію
      context.store.commit('replace', { userAuthenticated })
      // зберігаємо дані в сесії [nuxt]
      const session = context.app.$session()
      session.save(context)
      // помилка автентифікації?
      if (!userAuthenticated) {
        // помилка міститься в response.réponse
        throw new Error(response.réponse)
      }
      // отримано результат
      return { result: '[succès]' }
    } catch (e) {
      // повідомляємо про помилку
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // журнал
      console.log('[authentification asyncData finished]')
      if (process.client) {
        // завершення очікування клієнта [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // життєвий цикл
  beforeCreate() {
    console.log('[authentification beforeCreate]')
  },
  created() {
    console.log('[authentification created]')
  },
  beforeMount() {
    console.log('[authentification beforeMount]')
  },
  mounted() {
    console.log('[authentification mounted]')
    // тільки клієнт
    if (this.showErrorLoading) {
      console.log('[authentification mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>
  • рядок 7: сторінка відображає результат [result] асинхронного запиту [asyncData] з рядків 25–65;
  • рядки 28–33: сервер не виконує ці рядки, призначені для клієнта [nuxt];
  • рядок 36: отримується шар [dao] із сервера [nuxt];
  • рядок 37: відбувається аутентифікація на сервері розрахунку податку за допомогою тестових ідентифікаторів [admin, admin], які є єдиними, що приймаються сервером розрахунку податку;
  • рядок 41: операція автентифікації завершилася успішно, лише якщо відповідь має статус 200;
  • рядок 43: у сховище записується властивість [userAuthenticated];
  • рядки 44–46: дані зберігаються у сесії [nuxt];
  • рядки 48–51: якщо автентифікація не вдалася, генерується виняток із повідомленням про помилку, надісланим сервером розрахунку податку;
  • в іншому випадку (рядок 53) повертається результат успішного виконання, який буде відображено в рядку 7;
  • рядки 54–57: у разі помилки встановлюються три властивості сторінки [result, showErrorLoading, errorLoadingMessage]. Властивість [result] буде відображена в рядку 7. Ці три властивості будуть надіслані клієнту [nuxt];
  • рядки 60–63: не виконуються сервером [nuxt];
  • як тільки [asyncData] повернув результат, він відображається у рядку 7. Потім виконуються методи [beforeCreate] (рядки 67–69) та [created] (рядки 70–72);
  • все закінчено;

Примітка: виконання сторінки [authentification] сервером [nuxt] може завершитися невдачею, наприклад, якщо сесія jSON із сервером розрахунку податку не була ініціалізована. Це можна зробити наступним чином:

  • видаліть сесійний файл cookie PHP зі свого браузера (щоб почати з нуля):

Image

  • запустіть додаток [nuxt], поки сервер розрахунку не запущено: ви отримаєте повідомлення про помилку;
  • запустіть сервер розрахунку податку;
  • введіть URL [/authentification] безпосередньо в адресний рядок браузера:

Image

У цьому випадку єдиним рішенням знову є перезавантаження сторінки [index].

15.16. Сторінка [authentification], що виконується клієнтом [nuxt]

Повернемося до коду сторінки:


<!-- сторінка автентифікації -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення-->
    <b-alert slot="right" show variant="warning">Authentification auprès du serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Authentification',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  // асинхронні дані
  async asyncData(context) {
    // журнал
    console.log('[authentification asyncData started]')
    if (process.client) {
      // початок очікування клієнта [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // помилок немає
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // аутентифікація на сервері
      const dao = context.app.$dao()
      const response = await dao.authentifierUtilisateur('admin', 'admin')
      // журнал
      console.log('[authentification asyncData response=]', response)
      // результат
      const userAuthenticated = response.état === 200
      // фіксується, чи пройшов користувач аутентифікацію
      context.store.commit('replace', { userAuthenticated })
      // зберігаємо значення в сесії [nuxt]
      const session = context.app.$session()
      session.save(context)
      // помилка автентифікації?
      if (!userAuthenticated) {
        // помилка міститься в response.réponse
        throw new Error(response.réponse)
      }
      // повертається результат
      return { result: '[succès]' }
    } catch (e) {
      // повідомляється про помилку
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // журнал
      console.log('[authentification asyncData finished]')
      if (process.client) {
        // завершення очікування клієнта [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // життєвий цикл
  beforeCreate() {
    console.log('[authentification beforeCreate]')
  },
  created() {
    console.log('[authentification created]')
  },
  beforeMount() {
    console.log('[authentification beforeMount]')
  },
  mounted() {
    console.log('[authentification mounted]')
    // тільки клієнт
    if (this.showErrorLoading) {
      console.log('[authentification mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

Існує два випадки виконання сторінки [authentification] клієнтом [nuxt]:

  1. клієнт [nuxt] запускається після того, як сервер [nuxt] надіслав браузеру клієнта [nuxt] сторінку [authentification];
  2. клієнт [nuxt], оскільки користувач натиснув на посилання [Authentification] у меню навігації:

Image

Спочатку розглянемо перший випадок. У цьому випадку клієнт [nuxt] не виконує функцію [asyncData]. Він включає у властивості сторінки елементи [result] та, можливо, [showErrorLoading, errorLoadingMessage], які йому надіслав сервер [nuxt]:

  • властивість [result] відображається у рядку 7;
  • властивості [showErrorLoading, errorLoadingMessage] використовуються методом [mounted]: у рядку 79 перевіряється властивість [showErrorLoading]. Якщо вона має значення «true», у рядку 81 використовується шина подій клієнта [nuxt] для сигналізації про наявність повідомлення про помилку, яке потрібно відобразити;

Механізм відображення повідомлення про помилку було пояснено для сторінки [index] у розділі «Посилання».

Випадок 2 стосується клієнта [nuxt], який запускається, коли користувач натискає на посилання [Authentification]. У цьому випадку клієнт [nuxt] запускається автономно, а не після сервера [nuxt]. Після цього виконується функція [asyncData]. Ми наводимо лише ті деталі, які відрізняються від пояснень, наведених для сторінки, що виконується сервером [nuxt]:

  • рядки 28–33: клієнт [nuxt] запитує відображення повідомлення про очікування та приховування можливого повідомлення про помилку, яке могло бути раніше відображене;
  • рядок 36: тепер тут отримується рівень [dao] клієнта [nuxt];
  • рядки 60–63: клієнт [nuxt] вимагає припинення відображення повідомлення про очікування;
  • після завершення роботи [asyncData] розпочнеться життєвий цикл сторінки. Буде виконано функцію [mounted] у рядках 76–83. Якщо сталася помилка, буде відображено повідомлення про помилку;

Примітка: щоб спровокувати помилку, дотримуйтесь процедури, описаної для сервера [nuxt] наприкінці абзацу з посиланням, але замість того, щоб завантажити сторінку [authentification], ввівши її адресу URL у адресний рядок, скористайтеся посиланням [Authentification] у меню навігації. У цьому випадку запускається клієнт [nuxt].

15.17. Сторінка [get-admindata]

Код сторінки [get-admindata] такий:


<!-- перегляд get-admindata -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення -->
    <b-alert slot="right" show variant="secondary"> Demande de [adminData] au serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'GetAdmindata',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  // асинхронні дані
  async asyncData(context) {
    // журнал
    console.log('[get-admindata asyncData started]')
    if (process.client) {
      // початок очікування
      context.app.$eventBus().$emit('loading', true)
      // помилок немає
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // запит даних [admindata]
      const response = await context.app.$dao().getAdminData()
      // журнал
      console.log('[get-admindata asyncData response=]', response)
      // результат
      const adminData = response.état === 1000 ? response.réponse : ''
      // дані записуються в сховище
      context.store.commit('replace', { adminData })
      // зберігаємо сховище в сесії [nuxt]
      const session = context.app.$session()
      session.save(context)
      // чи сталася помилка?
      if (!adminData) {
        // помилка міститься в response.réponse
        throw new Error(response.réponse)
      }
      // повертається отримане значення
      return { result: adminData }
    } catch (e) {
      // повідомляємо про помилку
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // журнал
      console.log('[get-admindata asyncData finished]')
      if (process.client) {
        // завершення очікування
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // життєвий цикл
  beforeCreate() {
    console.log('[get-admindata beforeCreate]')
  },
  created() {
    console.log('[get-admindata created]')
  },
  beforeMount() {
    console.log('[get-admindata beforeMount]')
  },
  mounted() {
    console.log('[get-admindata mounted]')
    // клієнт
    if (this.showErrorLoading) {
      console.log('[get-admindata mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

Ця сторінка дуже схожа на сторінку [authentification]. Пояснення є аналогічними як для її виконання сервером [nuxt], так і для її виконання клієнтом [nuxt]. Однак слід зауважити, що у рядку 7 відображається не «успіх»/«невдача», як раніше, а значення даних, отриманих від сервера розрахунку податку (рядок 52):

Image

Наведений вище результат отримується як на сервері, так і в клієнті [nuxt]. Щоб викликати помилку, спробуйте завантажити сторінку [get-admindata] через сервер або клієнт [nuxt] без проходження автентифікації:

Image

15.18. Сторінка [fin-session]

Код сторінки такий:


<!-- головна сторінка -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення-->
    <b-alert slot="right" show variant="warning">Fin de la session avec le serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'FinSession',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  // асинхронні дані
  async asyncData(context) {
    // журнал
    console.log('[fin-session asyncData started]')
    // випадок клієнта [nuxt]
    if (process.client) {
      // початок очікування
      context.app.$eventBus().$emit('loading', true)
      // помилок немає
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // запит на нову сесію PHP до сервера розрахунку податку
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // журнал
      console.log('[fin-session asyncData response=]', response)
      // чи сталася помилка?
      if (response.état !== 400) {
        // помилка міститься в response.réponse
        throw new Error(response.réponse)
      }
      // сервер надіслав новий сесійний файл cookie PHP
      // його отримують як сервер, так і клієнт Nuxt
      // якщо цей код виконується клієнтом [nuxt], сесійний файл cookie PHP має бути доданий до сесії Nuxt
      // щоб плагін [plgDao] сервера [nuxt] міг його отримати та ініціалізувати шар [dao] за допомогою
      //; якщо цей код виконується сервером [nuxt], сесійний файл cookie PHP повинен бути доданий до сесії Nuxt
      //, щоб маршрутизатор клієнта [nuxt] міг його отримати та передати браузеру
      const phpSessionCookie = dao.getPhpSessionCookie()
      // у сховищі фіксується факт запуску сесії jSON і зберігається сесійний файл cookie PHP
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // зберігаємо дані сховища в сесії [nuxt]
      const session = context.app.$session()
      session.save(context)
      // повертається результат
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    } catch (e) {
      // журнал
      console.log('[fin-session asyncData error=]', e)
      // повідомляємо про помилку
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // журнал
      console.log('[fin-session asyncData finished]')
      if (process.client) {
        // завершення очікування
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // життєвий цикл
  beforeCreate() {
    console.log('[fin-session beforeCreate]')
  },
  created() {
    console.log('[fin-session created]')
  },
  beforeMount() {
    console.log('[fin-session beforeMount]')
  },
  mounted() {
    console.log('[fin-session mounted]')
    // тільки клієнт
    if (this.showErrorLoading) {
      console.log('[fin-session mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

Код дуже схожий на код попередніх сторінок, і пояснення є такими самими. Слід лише звернути увагу на один момент: асинхронна операція у рядку 38 призводить до того, що сервер розрахунку податку надішле новий сесійний файл cookie PHP. Пояснення щодо роботи з цим файлом cookie відрізняються залежно від того, хто виконує цей код — сервер чи клієнт [nuxt].

Почнемо з сервера [nuxt]:

  • рядок 37: інстанціюється шар [dao] сервера [nuxt]. Нагадаємо код його конструктора:

// конструктор
  constructor(axios, phpSessionCookie) {
    // бібліотека axios
    this.axios = axios
    // значення сесійного файлу cookie
    this.phpSessionCookie = phpSessionCookie
    // назва сесійного файлу cookie сервера PHP
    this.phpSessionCookieName = 'PHPSESSID'
}

У рядку 1 бачимо, що конструктору потрібен поточний сесійний файл cookie PHP — останній отриманий, незалежно від того, чи це сервер, чи клієнт [nuxt];

  • у рядку 52: сервер [nuxt] отримує файл cookie нової сесії PHP або старий файл cookie, якщо операція завершення сесії завершилася невдало;
  • рядок 54: сесійний файл cookie PHP поміщається в сховище, а потім зберігається в сесії [nuxt] у рядках 56–57;
  • після сервера саме клієнт [nuxt] виконує сторінку [fin-session] з даними, надісланими сервером. Відомо, що він не виконуватиме функцію [asyncData];
  • у підсумку, після того як сервер і клієнт [nuxt] завершили свою роботу, відомо, що файл cookie PHP, необхідний для обміну даними з сервером розрахунку податку, знаходиться в сесії [nuxt];

Для сервера достатньо того, що файл cookie PHP знаходиться в сесії [nuxt], оскільки саме звідти його отримає його рівень [dao]. У плагіні [server/plgDao], який ініціалізує рівень [dao] сервера, написано:


/* eslint-disable no-console */
// створюється точка доступу до шару [Dao]
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // конфігурація axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // отримуємо сесійний файл cookie
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // інстанціювання шару [dao]
  const dao = new Dao(context.$axios, phpSessionCookie)
  // введення функції [$dao] у контекст
  inject('dao', () => dao)
  // запис у журнал
  console.log('[fonction server $dao créée]')
}
  • у рядку 13 шар [dao] сервера [nuxt] ініціалізується з сесійним файлом cookie PHP, взятим із сесії [nuxt], рядки 9–10;

Що стосується клієнта [nuxt], то це зовсім інша історія. Адже не він надсилає файл cookie, а браузер, який його виконує. Однак цей браузер не знає файлу cookie нової сесії PHP, отриманого сервером [nuxt]. Якщо скористатися посиланнями з меню навігації [3]:

Image

Сервер розрахунку податку отримає від браузера застарілий сесійний файл cookie PHP і відповість, що з цим файлом cookie не пов’язано жодної сесії jSON. Нам потрібно знайти спосіб передати браузеру новий сесійний файл cookie PHP.

Для цього можна використати проміжне програмне забезпечення для маршрутизації:

Image

Скрипт [client/routing] — це проміжне програмне забезпечення для маршрутизації, яке оголошено у файлі [nuxt.config]:


// маршрутизатор
  router: {
    // кореневий каталог URL додатка
    base: '/nuxt-12/',
    // проміжне програмне забезпечення маршрутизації
    middleware: ['routing']
},

Скрипт [middleware/routing] має такий вигляд:


/* eslint-disable no-console */

// імпортуємо клієнтський проміжний модуль
import clientRouting from './client/routing'

export default function(context) {
  // хто виконує цей код?
  console.log('[middleware], process.server', process.server, ', process.client=', process.client)
  if (process.client) {
    // маршрутизація клієнта
    clientRouting(context)
  }
}
  • рядки 9–12: маршрутизується лише клієнт за допомогою функції, імпортованої в рядку 4;

Скрипт [middleware/client/routing] має такий вигляд:


/* eslint-disable no-console */
export default function(context) {
  // хто виконує цей код?
  console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
  // управління сесійним файлом cookie PHP у браузері
  // сесійний файл cookie PHP у браузері має збігатися з тим, що знайдено в сесії Nuxt
  // акція [fin-session] отримує новий файл cookie PHP (як сервер, так і клієнт Nuxt)
  // якщо його отримує сервер, клієнт повинен передати його браузеру
  // для власного обміну даними з сервером PHP
  //— тут відбувається маршрутизація на стороні клієнта

  // отримуємо сесійний файл cookie PHP
  const phpSessionCookie = context.store.state.phpSessionCookie
  if (phpSessionCookie) {
    // якщо він існує, сесійний файл cookie PHP присвоюється браузеру
    document.cookie = phpSessionCookie
  }
}

Повернемося до ситуації одразу після виконання сторінки [fin-session] сервером [nuxt]:

Image

Якщо натиснути на одне з посилань у меню [3], клієнт [nuxt] перебере управління. Оскільки відбудеться зміна сторінки, виконається скрипт маршрутизації клієнта:

  • рядок 13: сесійний файл cookie PHP знайдено у сховищі додатка [nuxt];
  • рядок 14: якщо він не порожній, його передають браузеру (рядок 16). Відтепер браузер клієнта [nuxt] має правильний сесійний файл cookie PHP;

Скрипт [client/routing] виконується при кожній зміні сторінки клієнта [nuxt]. Код скрипта працює незалежно від цільової сторінки: просто в більшості випадків він надає браузеру сесійний файл cookie PHP, який у нього вже є, за винятком двох випадків:

  • відразу після запуску додатка, сервер [nuxt] виконує сторінку [index] і отримує перший сесійний файл cookie PHP, якого немає у браузері клієнта [nuxt];
  • коли сервер [nuxt] виконує сторінку [fin-session], як це щойно було пояснено;

Тепер розглянемо випадок, коли сторінка [fin-session] виконується виключно клієнтом [nuxt], оскільки було натиснуто на її посилання в меню навігації. Тепер саме клієнт [nuxt] виконує функцію [asyncData]:


try {
      // запитуємо нову сесію PHP у сервера розрахунку податку
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // журнал
      console.log('[fin-session asyncData response=]', response)
      // чи сталася помилка?
      if (response.état !== 400) {
        // помилка міститься в response.réponse
        throw new Error(response.réponse)
      }
      // сервер надіслав новий сесійний файл cookie PHP
      // його отримують як сервер, так і клієнт Nuxt
      // якщо цей код виконується клієнтом [nuxt], сесійний файл cookie PHP має бути доданий до сесії Nuxt
      // щоб плагін [plgDao] сервера [nuxt] міг його отримати та ініціалізувати шар [dao] за допомогою
      //; якщо цей код виконується сервером [nuxt], сесійний файл cookie PHP повинен бути доданий до сесії Nuxt
      //, щоб маршрутизатор клієнта [nuxt] міг його отримати та передати браузеру
      const phpSessionCookie = dao.getPhpSessionCookie()
      // у сховищі фіксується факт запуску сесії jSON і зберігається сесійний файл cookie PHP
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // зберігаємо дані сховища в сесії [nuxt]
      const session = context.app.$session()
      session.save(context)
      // повертається результат
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    } catch (e) {
      // журнал
      console.log('[fin-session asyncData error=]', e)
      // повідомляємо про помилку
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // журнал
      console.log('[fin-session asyncData finished]')
      if (process.client) {
        // завершення очікування
        context.app.$eventBus().$emit('loading', false)
      }
    }
  • рядок 3: тут отримується шар [dao] клієнта [nuxt];
  • рядок 18: сесійний файл cookie PHP, отриманий шаром [dao] клієнта [nuxt], запам'ятовується, поміщається в сховище (рядок 20), а потім збережено в сесії [nuxt] (рядки 22–23);
  • далі все відбувається правильно, оскільки відомо, що рівень [dao] сервера [nuxt] отримає сесійний файл cookie PHP із сесії [nuxt];

15.19. Exécution

Щоб виконати цей приклад, перед запуском потрібно видалити сесійний файл cookie [nuxt] та файл cookie PHP з браузера, на якому працює клієнт [nuxt], щоб почати з «чистого аркуша». Нижче наведено приклад для браузера Chrome:

Image

15.20. Conclusion

Цей приклад був особливо складним. Він поєднував знання, отримані в попередніх прикладах: збереження сховища в сесії [nuxt], плагіни для введення функцій, проміжне програмне забезпечення для маршрутизації, обробка помилок асинхронних операцій. Складність була підвищена тим, що ми хотіли, щоб користувач міг як користуватися посиланнями в меню навігації, так і вводити URL вручну, не порушуючи при цьому роботу додатка. Для цього нам довелося проаналізувати, як поводиться кожна сторінка залежно від того, чи виконується вона на стороні клієнта, чи на стороні сервера [nuxt].

Така узгодженість поведінки клієнта та сервера [nuxt] не є обов’язковою. Можна розглянути типовий випадок, коли:

  • перша сторінка видається сервером [nuxt];
  • усі наступні сторінки надаються клієнтом [nuxt], який у цей час працює в режимі [SPA];

Проте навіть у цьому випадку слід перевірити, що дасть виконання всіх сторінок сервером [nuxt], оскільки саме це отримають пошукові системи, які їх запитують.