Skip to content

16. Приклад [nuxt-13]: перевірка навігації [nuxt-12]

У цьому прикладі нас цікавить навігація [nuxt-12]. Ми не робили цього в [nuxt-12], оскільки перевірка навігації ускладнила б і без того складний приклад.

Мета: ми хочемо, щоб користувач міг виконувати лише дозволені дії:

  • якщо сесія jSON не запущена, то дозволено лише URL та [/];
  • якщо сесія jSON розпочалася, але користувач не пройшов автентифікацію, то дозволено лише URL та [/authentification];
  • якщо сесія jSON розпочалася і користувач пройшов автентифікацію, то дозволені лише URL та [/get-admindata, /fin-session];
  • коли поточна ціль маршрутизації не дозволена, то відбудеться перенаправлення на дозволену сесію URL;

Приклад [nuxt-13] спочатку отримується шляхом копіювання прикладу [nuxt-12]:

Image

Саме у папці маршрутизації [middleware] відбуватимуться зміни.

16.1. Маршрутизація додатка [nuxt]

Маршрутизація додатка налаштована наступним чином у файлі [nuxt.config]:


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

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


/* eslint-disable no-console */

// імпортуємо серверне та клієнтське проміжне програмне забезпечення
import serverRouting from './server/routing'
import clientRouting from './client/routing'

export default function(context) {
  // хто виконує цей код?
  console.log('[middleware], process.server', process.server, ', process.client=', process.client)
  if (process.server) {
    // маршрутизація на сервері
    serverRouting(context)
  } else {
    // маршрутизація на стороні клієнта
    clientRouting(context)
  }
}
  • рядки 10–16: маршрутизація клієнта та сервера [nuxt] обробляється по-різному. Це момент, у якому вони суттєво відрізняються;
  • рядок 4: маршрутизація сервера реалізована скриптом [middleware/server/routing];
  • рядок 5: маршрутизація клієнта реалізована за допомогою скрипта [middleware/client/routing];

16.2. Маршрутизація клієнта [nuxt]

Маршрутизація клієнта [nuxt] залишається такою ж, як і в [nuxt-12]:


/* 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
  }

  ...
}

Щоб запобігти переходу клієнта на недозволені маршрути, ми просто запропонуємо йому в меню навігації клієнта лише дозволені маршрути. Компонент [components/navigation] набуває такого вигляду:


<template>
  <!-- меню Bootstrap із трьома пунктами -->
  <b-nav vertical>
    <b-nav-item v-if="$store.state.jsonSessionStarted && !$store.state.userAuthenticated" to="/authentification" exact exact-active-class="active">
      Authentification
    </b-nav-item>
    <b-nav-item
      v-if="$store.state.jsonSessionStarted && $store.state.userAuthenticated && !$store.state.adminData"
      to="/get-admindata"
      exact
      exact-active-class="active"
    >
      Requête AdminData
    </b-nav-item>
    <b-nav-item v-if="$store.state.jsonSessionStarted && $store.state.userAuthenticated" to="/fin-session" exact exact-active-class="active">
      Fin session impôt
    </b-nav-item>
  </b-nav>
</template>
  • рядок 4: опція [Authentification] пропонується лише в тому випадку, якщо сесія jSON розпочалася, але користувач не пройшов аутентифікацію. Якщо сесія jSON не розпочалася або користувач уже пройшов автентифікацію, то ця опція не пропонується;
  • рядки 7–11: опція [Requête AdminData] доступна лише в тому випадку, якщо сесія jSON розпочалася, користувач пройшов автентифікацію, а дані [AdminData] ще не отримано. Якщо хоча б одна з цих трьох умов не виконана (сесія jSON не розпочата, користувач не пройшов автентифікацію або дані [AdminData] вже отримано), опція не пропонується;
  • рядок 15: опція [Fin session impôt] пропонується, щойно сесія jSON розпочалася і користувач пройшов автентифікацію, в іншому випадку вона не пропонується;

16.3. Маршрутизація сервера [nuxt]

Маршрутизація сервера, як правило, складніша, ніж маршрутизація клієнта, оскільки користувач може ввести будь-яку адресу URL у адресний рядок свого браузера. Можна залишити все як є (адже користувач не повинен цього робити) або спробувати контролювати ситуацію. Саме це ми й зробимо тут, для прикладу, оскільки у випадку додатка [nuxt-12] можна цілком обійтися без цього, оскільки сервер розрахунку податку добре захищений від таких URL, введених вручну, і вміє надсилати відповідні повідомлення про помилки. Ми бачили це в [next-12], де не було жодного контролю маршрутизації.

Маршрутизація сервера [nuxt] суттєво відрізняється від маршрутизації клієнта [nuxt] щодо поняття перенаправлення:

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

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

Скрипт маршрутизації сервера [middleware/server/routing] матиме такий вигляд:


/* eslint-disable no-console */
export default function(context) {
  // хто виконує цей код?
  console.log('[middleware server], process.server', process.server, ', process.client=', process.client)

  // отримуємо деяку інформацію зі сховища [nuxt]
  const store = context.store
  // звідки ми прийшли?
  const from = store.state.from || 'nowhere'
  ...
}
  • у маршрутизації клієнта функція маршрутизації отримує контекст [context] із властивістю [context.from], яка є маршрутом сторінки, з якої ми перейшли. Маршрут, куди ми переходимо, отримується за допомогою [context.route];
  • під час маршрутизації на сервері функція маршрутизації отримує контекст [context] без властивості [context.from]. Маршрутизація сервера відбувається лише тоді, коли URL запитується вручну у сервера [nuxt]. Відомо, що в цьому випадку весь додаток [nuxt] перезапускається. Це нібито початок з нуля, тому поняття «попередньої сторінки» відсутнє;
  • завдяки сесії [nuxt] ми знаємо, що сервер може відновити цю сесію і, отже, не починати з нуля. Отже, саме в цій сесії [nuxt], а точніше — у сховищі цієї сесії, ми будемо зберігати назву останньої сторінки, яку відобразив браузер клієнта перед тим, як до сервера [nuxt] надійшов запит URL;
  • рядки 7–9: отримуємо назву останньої сторінки, яку відобразив браузер клієнта. Під час запуску додатка ця інформація [from] у сховищі відсутня. Тоді змінній [from] присвоюється ім’я [nowhere];

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


/* 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
  }

  // у сесію записується назва сторінки, на яку відбувається перехід — без серверного перенаправлення
  context.store.commit('replace', { serverRedirection: false, from: context.route.name })
  // зберігаємо store в сесії [nuxt]
  const session = context.app.$session()
  session.value.store = context.store.state
  session.save(context)
}
  • додано рядки 19–24;
  • рядок 20: у store записується назва сторінки [context.route.name], яка буде відображена і яка, отже, під час наступної маршрутизації стане сторінкою, з якої ми прийшли. Крім того, ми побачимо, що під час маршрутизації сервера [nuxt] йому потрібно знати, чи поточна маршрутизація є наслідком попереднього перенаправлення з сервера [nuxt]. У даному випадку це не так, тому ми встановлюємо властивість [serverRedirection] на [false];
  • рядки 22–24: стан сховища записується в сесію [nuxt] (рядок 23), а потім сесія [nuxt] зберігається у файлі cookie (рядок 24), який, у свою чергу, буде збережено в браузері клієнта [nuxt];

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


/* eslint-disable no-console */
export default function(context) {
  // хто виконує цей код?
  console.log('[middleware server], process.server', process.server, ', process.client=', process.client)

  // отримуємо деяку інформацію зі сховища [nuxt]
  const store = context.store
  // звідки ми прийшли?
  const from = store.state.from || 'nowhere'
  // куди ми йдемо?
  const to = context.route.name
  // можливе перенаправлення
  let redirection = ''
  // управління маршрутизацією завершено
  let done = false

  // чи вже відбувається перенаправлення з сервера [nuxt]?
  if (store.state.serverRedirection) {
    // нічого робити не потрібно
    done = true
  }

  // чи це перезавантаження сторінки?
  if (to === from) {
    // нічого не робити
    done = true
  }
  
  // контроль навігації сервера [nuxt]
  // використовується навігація клієнта в компоненті [navigation]

  // випадок, коли сесія PHP не запущена
  if (!done && !store.state.jsonSessionStarted && to !== 'index') {
    // перенаправлення
    redirection = 'index'
    // робота завершена
    done = true
  }

  // у разі, якщо користувач не пройшов автентифікацію
  if (!done && store.state.jsonSessionStarted && !store.state.userAuthenticated && to !== 'authentification') {
    // перенаправлення
    redirection = from
    // робота завершена
    done = true
  }

  // випадок, коли користувач пройшов автентифікацію
  if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && to !== 'get-admindata' && to !== 'fin-session') {
    // залишаємося на тій самій сторінці
    redirection = from
    // робота завершена
    done = true
  }

  // випадок, коли отримано [adminData]
  if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && store.state.adminData && to !== 'fin-session') {
    // залишаємося на тій самій сторінці
    redirection = from
    // робота завершена
    done = true
  }

  // всі перевірки виконано ---------------------
  // перенаправлення?
  if (redirection) {
    // перенаправлення зафіксовано в магазині
    store.commit('replace', { serverRedirection: true })
  } else {
    // перенаправлення відсутнє
    store.commit('replace', { serverRedirection: false, from: to })
  }
  // зберігаємо стан у сесії [nuxt]
  const session = context.app.$session()
  session.value.store = store.state
  session.save(context)
  // виконується можливе перенаправлення
  if (redirection) {
    context.redirect({ name: redirection })
  }
}
  • рядки 6–9: отримуємо значення [from] із сховища сервера [nuxt];
  • рядок 11: фіксується ціль поточної маршрутизації;
  • рядок 13: маршрутизація може призвести до перенаправлення браузера клієнта. [redirection] буде ціллю цього перенаправлення;
  • рядок 15: [done] до [true] вказує, що маршрутизація завершена;
  • рядки 17–21: спочатку перевіряється, чи поточна маршрутизація є наслідком запиту на перенаправлення, надісланого до браузера клієнта. Ця інформація зберігається у властивості [serverRedirection] сховища. Якщо ця властивість має значення «true», це означає, що сервер [nuxt] надіслав перенаправлення до браузера клієнта під час попереднього запиту до сервера [nuxt]. У цьому випадку маршрутизація не потрібна. Під час попереднього запиту маршрутизатор сервера [nuxt] вирішив, що клієнтський браузер має бути перенаправлений. Це рішення не має бути переглянуте в результаті нової маршрутизації;
  • рядки 23–27: перевіряється, чи поточна маршрутизація є перезавантаженням сторінки. Якщо так, то процес продовжується;
  • починаючи з рядка 29, застосовуються ті самі правила, що й у компоненті [navigation] клієнта [nuxt] (див. попередній абзац);
  • рядки 32–38: обробляється випадок, коли сесія jSON не запущена, а ціллю маршрутизації не є сторінка [index]. У цьому випадку браузер клієнта перенаправляється на сторінку [index];
  • рядки 40–46: розглядається випадок, коли сесія jSON розпочалася, користувач не пройшов автентифікацію, а ціллю поточного маршрутизації не є сторінка [authentification]. У цьому випадку маршрутизація відхиляється, і ми залишаємося там, де були;
  • рядки 48–54: розглядається випадок, коли сесія jSON розпочалася, користувач пройшов автентифікацію, а ціллю поточного маршрутизації є ані сторінка [get-admindata], ані сторінка [fin-session], які на цей момент є єдиними можливими пунктами призначення. У цьому випадку запит на маршрутизацію відхиляється, і виконання повертається до попереднього стану;
  • рядки 56–62: обробляється випадок, коли отримано [adminData]. У цьому випадку існує лише одна можлива ціль для маршрутизації: сторінка [fin-session]. Якщо запит стосувався не її, маршрутизацію відхиляють і повертаються до попереднього стану;
  • рядки 64–72: якщо відбулося перенаправлення, це фіксується в сховищі сервера [nuxt]: [serverRedirection: true]. Зазначимо, що властивості [from] у сховищі значення не присвоюється. Причина полягає в тому, що відбудеться перенаправлення з браузера клієнта, і ми бачили, що в цьому випадку маршрутизації не відбувається (рядки 17–20), а властивість [from] у сховищі не використовується;
  • рядки 66–69: якщо перенаправлення відсутнє, то це також фіксується у сховищі сервера [nuxt]: [serverRedirection: false]. Крім того, поточна маршрутизація відобразить сторінку [to], яка для наступного запиту (клієнт або сервер [nuxt]) стане попередньою сторінкою. Тому записується [from: to];
  • рядки 73–76: зберігаємо store у сесії [nuxt], яка сама зберігається у файлі cookie;
  • рядки 77–80: якщо [redirection] не порожній, то браузеру надсилається запит на перенаправлення. В іншому випадку (що тут не показано) життєвий цикл сервера [nuxt] продовжиться: сторінка [to] буде оброблена сервером [nuxt] і надіслана до браузера клієнта [nuxt] разом із сесійним файлом cookie [nuxt];

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

Можна вдосконалити один момент, коли сторінка, що завантажується в кінцевому підсумку, є оригінальною сторінкою. Існують два випадки:

  • користувач ініціював перезавантаження сторінки (to===from);
  • відбувається перенаправлення на вихідну сторінку (redirection===from);

В обох випадках вихідна сторінка буде знову виконана з її асинхронним викликом до сервера розрахунку податку. Розглянемо приклад. Якщо після авторизації користувач перезавантажує сторінку (F5). У цьому випадку у наведеному вище маршруті маємо: [to]=[from]=[authentification]. Перенаправлення не відбувається. Сторінка [to=authentification] буде виконана сервером [nuxt]. Якщо нічого не робити, функція [asyncData] виконається знову. Це марно, оскільки автентифікація вже відбулася.

Ситуацію можна покращити, трохи змінивши сторінку [authentification]:


// асинхронні дані
  async asyncData(context) {
    // журнал
    console.log('[authentification asyncData started]')
    // не виконуємо дії двічі, якщо сторінка вже була запитана
    if (process.server && context.store.state.userAuthenticated) {
      console.log('[authentification asyncData canceled]')
      return { result: '[succès]' }
    }
     // клієнт [nuxt]
    if (process.client) {
      // початок очікування
      context.app.$eventBus().$emit('loading', true)
      // помилок немає
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // проводиться аутентифікація на сервері
...
  • рядки 6–9: якщо сторінка виконується сервером [nuxt] і в сховищі виявлено, що автентифікація вже відбулася, то безпосередньо повертається бажаний результат (рядок 8);

Те саме робимо для всіх сторінок:

Сторінка [index]:


// асинхронні дані
  async asyncData(context) {
    // журнал
    console.log('[index asyncData started]')
    // не виконуємо дії двічі, якщо сторінка вже була запитана
    if (process.server && context.store.state.jsonSessionStarted) {
      console.log('[index asyncData canceled]')
      return { result: '[succès]' }
    }
    try {
...

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


// асинхронні дані
  async asyncData(context) {
    // журнал
    console.log('[get-admindata asyncData started]')
    // не виконувати операцію двічі, якщо сторінка вже була запитана
    if (process.server && context.store.state.adminData) {
      console.log('[get-admindata asyncData canceled]')
      return { result: context.store.state.adminData }
    }
    // клієнт
    if (process.client) {
      // початок очікування
      context.app.$eventBus().$emit('loading', true)
      // помилки немає
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
   ...  

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


// асинхронні дані
  async asyncData(context) {
    // журнал
    console.log('[fin-session asyncData started]')
    // не виконуємо операцію двічі, якщо сторінка вже була запитана
    if (process.server && context.store.state.jsonSessionStarted && !context.store.state.userAuthenticated) {
      console.log('[fin-session asyncData canceled]')
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    }
    // випадок клієнта [nuxt]
    if (process.client) {
      // початок очікування
      context.app.$eventBus().$emit('loading', true)
      // помилки немає
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
   

16.4. Exécution

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

Image

16.5. Conclusion

Маршрутизація сервера [nuxt] є складною, оскільки потрібно передбачити всі варіанти URL, які користувач може ввести вручну. Це класичний приклад. Додаток [nuxt] не призначений для використання таким чином. Після того, як сторінка [index] буде надана маршрутизатором сервера [nuxt], наступні запити до сервера можна перенаправляти на сторінку помилки.

У конкретному випадку нашого прикладу [nuxt-13] маршрутизація сервера [nuxt] була зайвою. Маршрутизація за замовчуванням (фактично відсутність маршрутизації) у прикладі [nuxt-12] цілком підходила.