Skip to content

15. Przykład [nuxt-12]: żądania HTTP za pomocą axios

15.1. Présentation

W tym nowym przykładzie dowiemy się, jak w funkcjach [asyncData] można wysyłać żądania HTTP za pomocą biblioteki [axios]. Ponadto wykorzystamy już poznane pojęcia:

  • korzystanie z wtyczek z przykładu [nuxt-06]:
  • zapisanie stanu w pliku cookie sesji z przykładu [nuxt-06];
  • kontrola nawigacji za pomocą middleware'ów z przykładu [nuxt-09];
  • obsługa błędów z przykładu [nuxt-11];

Architektura przykładu będzie wyglądać następująco:

Image

  • aplikacja [nuxt] będzie przechowywana na serwerze [node.js] [3], pobrana przez przeglądarkę [1], która następnie ją uruchomi;
  • zarówno klient [nuxt] [1], jak i serwer [nuxt] [3] będą wysyłać zapytania HTTP do serwera danych [2]. Serwer ten będzie serwerem obliczającym podatek, opracowanym w części PHP 7. Wykorzystamy jego najnowszą wersję, wersję 14, z autoryzowanymi zapytaniami CORS;

Architekturę przykładu można uprościć w następujący sposób:

Image

  • w [1] serwer [node.js] dostarcza strony [nuxt] do przeglądarki [2]. To właśnie warstwa serwera o numerach [web] i [8] dostarcza te strony. Aby dostarczyć stronę, serwer mógł zwrócić się o dane zewnętrzne do serwera danych o nazwie [3]. To warstwa [DAO] [9] wysyła niezbędne żądania HTTP;
  • przy każdym wywołaniu strony na serwerze [node.js][1], przeglądarka [2] otrzymuje całą aplikację [nuxt], która następnie uruchamia się w trybie SPA. Blok [UI] (interfejs użytkownika) [4] wyświetla użytkownikowi strony [vue.js]. Działania użytkownika lub naturalny cykl życia stron mogą powodować wywołania danych zewnętrznych z serwera danych [3]. To właśnie warstwa [DAO] [5] wysyła wówczas niezbędne zapytania HTTP;

15.2. Struktura drzewa projektu

Image

15.3. Plik konfiguracyjny [nuxt.config.js]

Projekt będzie sterowany przez następujący plik [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: [
    // Dokumentacja: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module'
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // Dokumentacja: https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // Dokumentacja: 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) { }
  },
  // katalog kodu źródłowego
  srcDir: 'nuxt-12',
  // router
  router: {
    // katalog główny aplikacji URL
    base: '/nuxt-12/',
    // oprogramowanie pośredniczące routingu
    middleware: ['routing']
  },
  // serwer
  server: {
    // port usługi, domyślnie 3000
    port: 81,
    // adresy sieciowe, na których nasłuchuje aplikacja, domyślnie localhost: 127.0.0.1
    // 0.0.0.0 = wszystkie adresy sieciowe komputera
    host: 'localhost'
  },
  // środowisko
  env: {
    // konfiguracja axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://„localhost/php7/scripts-web/impots/version-14”,
    // konfiguracja pliku cookie sesji [nuxt]
    maxAge: 60 * 5
  }
}
  • wiersz 22: samodzielnie zarządzamy alertem oczekiwania na zakończenie operacji asynchronicznej;
  • wiersz 31: będziemy korzystać z różnych wtyczek, które będą przeznaczone albo dla klienta, albo dla serwera, ale nie dla obu jednocześnie;
  • wiersz 52: moduł [axios] jest zintegrowany z modułem [nuxt]. Spowoduje to, że obiekt [axios], który będzie wysyłał żądania HTTP zaplikacji [nuxt] do serwera obliczającego podatek PHP, będzie dostępny w [context.$axios];
  • wiersz 54: moduł [cookie-universal-nuxt] pozwoli nam zapisać sesję [nuxt] w pliku cookie;
  • wiersz 60: właściwość [axios] pozwala nam skonfigurować moduł [@nuxtjs/axios] z wiersza 52. Nie skorzystamy z tej możliwości, preferując zamiast tego właściwość [env] z wiersza 88;
  • wiersz 90: maksymalny czas oczekiwania na odpowiedź serwera obliczającego podatek;
  • wiersz 91: wymagane dla klienta [nuxt] – zezwala na użycie plików cookie podczas komunikacji z serwerem obliczającym podatek;
  • wiersz 92: podstawowy identyfikator URL serwera obliczającego podatek;
  • wiersz 94: czas trwania sesji Nuxt (5 min);
  • wiersz 77: nawigacja między klientem a serwerem [nuxt] będzie kontrolowana przez oprogramowanie pośredniczące do routingu;

15.4. Warstwa aplikacji [UI]

Image

Zapewnimy aplikacji [nuxt] dostęp do serwera obliczającego podatek API za pośrednictwem następującego widoku:

Image

  • w [2] menu zapewniające dostęp do serwera obliczającego podatek API:
    • [Authentification]: odpowiada stronie [authentification]. Strona ta wysyła żądanie uwierzytelnienia do serwera obliczającego podatek, używając identyfikatorów [admin, admin], które są obecnie jedynymi dozwolonymi. Wyświetlany wynik jest analogiczny do strony [3];
    • [Requête AdminData]: odpowiada stronie [get-admindata]. Strona ta zwraca się do serwera obliczającego podatek o dane, określone tutaj jako [adminData], które umożliwiają obliczenie podatku. Wyświetlany wynik jest analogiczny do strony [3];
    • [Fin session impôt]: odpowiada stronie [fin-session]. Strona ta wysyła do serwera obliczającego podatek żądanie zakończenia sesji PHP. Serwer anuluje wówczas bieżącą sesję PHP i inicjuje nową, pustą sesję;

15.5. Warstwy [dao] aplikacji [nuxt]

Jak wskazano powyżej, architektura aplikacji [nuxt] będzie wyglądać następująco:

Image

  • w [1] serwer [node.js] dostarcza strony [nuxt] do przeglądarki [2]. To warstwa serwera o numerach [web] i [8] dostarcza te strony. Aby dostarczyć stronę, serwer mógł zwrócić się o dane zewnętrzne do serwera danych o nazwie [3]. To warstwa [DAO] [9] wysyła niezbędne żądania HTTP;
  • przy każdym wywołaniu strony na serwerze [node.js][1], przeglądarka [2] otrzymuje całą aplikację [nuxt], która następnie uruchamia się w trybie SPA. Blok [UI] (interfejs użytkownika) [4] wyświetla użytkownikowi strony [vue.js]. Działania użytkownika lub cykl życia stron mogą powodować wywołania danych zewnętrznych z serwera danych [3]. To właśnie warstwa [DAO] [5] wysyła wówczas niezbędne zapytania HTTP;

Będziemy korzystać z wersji 14 serwera obliczającego podatek opracowanego w dokumencie |Introduction au langage PHP7 par l’exemple|. Wykorzystamy jedynie część jego interfejsu programowania aplikacji (API) API:

Zapytanie
Odpowiedź
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. Warstwa [dao] serwera [nuxt]

Image

Serwer [node.js] [1] będzie korzystał z warstwy [dao] opisanej w dokumencie |Introduction au framework VUE.JS par l’exemple|. Przytaczamy tutaj jej kod:


'use strict';

// importy
import qs from 'qs'

class Dao {

  // konstruktor
  constructor(axios) {
    this.axios = axios;
    // plik cookie sesji
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
  }

  // inicjowanie sesji
  async  initSession() {
    // opcje żądania HHTP [get /main.php?action=init-session&type=json]
    const options = {
      method: "GET",
      // parametry URL
      params: {
        action: 'init-session',
        type: 'json'
      }
    };
    // wykonanie zapytania HTTP
    return await this.getRemoteData(options);
  }

  async  authentifierUtilisateur(user, password) {
    // opcje zapytania HHTP [post /main.php?action=authentifier-utilisateur]
    const options = {
      method: "POST",
      headers: {
        'Content-type': 'application/x-www-form-urlencoded',
      },
      // treść POST
      data: qs.stringify({
        user: user,
        password: password
      }),
      // parametry URL
      params: {
        action: 'authentifier-utilisateur'
      }
    };
    // wykonanie zapytania HTTP
    return await this.getRemoteData(options);
  }

  async getAdminData() {
    // opcje zapytania HHTP [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // parametry zapytania URL
      params: {
        action: 'get-admindata'
      }
    };
    // wykonanie zapytania HTTP
    const data = await this.getRemoteData(options);
    // wynik
    return data;
  }

  async  getRemoteData(options) {
    // dla pliku cookie sesji
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.Cookie = this.sessionCookie;
    // wykonanie zapytania HTTP
    let response;
    try {
      // żądanie asynchroniczne
      response = await this.axios.request('main.php', options);
    } catch (error) {
      // parametr [error] jest instancją wyjątku – może przybierać różne formy
      if (error.response) {
        // odpowiedź serwera znajduje się w [error.response]
        response = error.response;
      } else {
        // błąd jest ponownie generowany
        throw error;
      }
    }
    // odpowiedź stanowi całość odpowiedzi serwera w formacie HTTP (nagłówki HTTP + sama odpowiedź)
    // pobieramy plik cookie sesji, jeśli istnieje
    const setCookie = response.headers['set-cookie'];
    if (setCookie) {
      // setCookie to tablica
      // w tej tablicy szuka się pliku cookie sesji
      let trouvé = false;
      let i = 0;
      while (!trouvé && i < setCookie.length) {
        // szukamy pliku cookie sesji
        const results = RegExp('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // zapisujemy plik cookie sesji
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // znaleziono
          trouvé = true;
        } else {
          // następny element
          i++;
        }
      }
    }
    // odpowiedź serwera znajduje się w [response.data]
    return response.data;
  }
}

// eksport klasy
export default Dao;
  • wszystkie metody warstwy [dao] zwracają obiekt wysłany przez serwer danych [{action : ‘xx’, état : nn, réponse : {...}] wraz z:
    • [action]: nazwa akcji wykonywanej przez serwer danych;
    • [état]: wskaźnik numeryczny:
      • [initSession]: status=700 dla odpowiedzi bez błędu;
      • [authentifierUtilisateur]: status=200 dla odpowiedzi bez błędu;
      • [getAdminData]: stan=1000 dla odpowiedzi bez błędu;
      • [fin-session]: status=400 dla odpowiedzi bez błędu;
    • [réponse]: odpowiedź powiązana z wskaźnikiem numerycznym [état]. Może się różnić w zależności od tego wskaźnika numerycznego;

Przyjrzyjmy się konstruktorowi klasy [Dao]:


// konstruktor
  constructor(axios) {
    this.axios = axios;
    // plik cookie sesji
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
}
  • wiersz 2: obiekt [axios] przekazany jako argument do konstruktora jest dostarczany przez kod wywołujący. To właśnie on będzie wysyłał żądania HTTP;
  • wiersz 5: nazwa pliku cookie sesji wysłanego przez serwer danych zapisana jako PHP;
  • wiersz 6: plik cookie sesji, który jest wymieniany między warstwą [dao] a serwerem danych. Jest on inicjowany przez funkcję [getRemoteData] w wierszach 67–113;

W przypadku pliku cookie sesji należy uwzględnić dwie odrębne warstwy [dao]:

  • warstwę przeglądarki;
  • warstwę serwera;

Będziemy musieli zarządzać trzema plikami cookie sesji:

  1. ten wymieniany między klientem [nuxt] a serwerem PHP 7;
  2. ten wymieniany między serwerem [nuxt] a serwerem PHP 7;
  3. plik wymieniany między klientem [nuxt] a serwerem [nuxt];

Zadbamy o to, aby plik cookie sesji z serwerem PHP był taki sam dla klienta i serwera [nuxt]. Plik ten nazwiemy plikiem sesyjnym PHP. Jest to plik z przypadków 1 i 2. Plik z przypadku 3 nazwiemy plikiem sesyjnym [nuxt]. Będziemy więc mieli dwie sesje:

  • sesja PHP z plikiem sesyjnym PHP;
  • sesja [nuxt] z plikiem sesyjnym [nuxt];

Dlaczego używamy tego samego pliku cookie dla sesji klienta PHP i przeglądarki [nuxt]? Chcemy, aby aplikacja mogła komunikować się z serwerem PHP niezależnie od tego, czy jest to klient, czy serwer [nuxt]:

  • jeśli akcja A serwera [nuxt] powoduje przejście serwera PHP do stanu E, stan ten jest odzwierciedlany w sesji PHP obsługiwanej przez serwer PHP;
  • Korzystając z tego samego pliku cookie sesji PHP co serwer, akcja B klienta [nuxt], następująca po akcji A serwera [nuxt], odnalazłaby serwer PHP wstanie E pozostawionym przez serwer [nuxt] i mógłby w ten sposób wykorzystać pracę już wykonaną przez serwer [nuxt];
  • jeśli po akcji B klienta [nuxt] nastąpi akcja C serwera [nuxt], to z tego samego powodu, co poprzednio, akcja ta będzie mogła opierać się na pracy wykonanej przez akcję B klienta [nuxt];

Aby przeglądarka klienta [nuxt] mogła komunikować się z serwerem PHP służącym do obliczania podatku, wykorzystamy wersję 14 tego serwera, która zezwala na wywołania międzydomenowe, tj. wywołania z przeglądarki do serwera PHP. Wywołania z serwera [nuxt] do serwera PHP nie są natomiast wywołaniami międzydomenowymi. Pojęcie to odnosi się wyłącznie do wywołań wykonywanych z przeglądarki.

Wróćmy do kodu konstruktora poprzedniej klasy [Dao]:


// konstruktor
  constructor(axios) {
    this.axios = axios;
    // plik cookie sesji
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
}
  • wiersze 5 i 6 odpowiadają plikowi cookie sesji PHP z serwerem obliczającym podatek;

Obsługa powyższego pliku cookie sesji PHP nie jest odpowiednia dla serwera [nuxt]: jego warstwa [dao] jest instancjonowana przy każdym nowym żądaniu kierowanym do serwera [nuxt]. Należy bowiem pamiętać, że wysłanie żądania o stronę do serwera [nuxt] oznacza zresetowanie aplikacji [nuxt]. Tak więc gdy po zakończeniu pierwszego żądania skierowanego do serwera danych przez serwer [nuxt] zainicjowany zostanie plik cookie sesji PHP warstwy [dao], wartość ta zostanie utracona podczas kolejnego żądania HTTP wysłanego przez ten sam serwer [nuxt], ponieważ w międzyczasie jego warstwa [dao] została odtworzona, konstruktor uruchomiono ponownie, a plik cookie sesji PHP zresetowano do pustego ciągu znaków (wiersz 6);

Rozwiązaniem jest użycie innego konstruktora dla warstwy [dao] serwera:


// konstruktor
  constructor(axios, phpSessionCookie) {
    // biblioteka axios
    this.axios = axios
    // wartość pliku cookie sesji
    this.phpSessionCookie = phpSessionCookie
    // nazwa pliku cookie sesji serwera PHP
    this.phpSessionCookieName = 'PHPSESSID'
  }
  • wiersz 2: tym razem plik cookie sesji PHP zostanie przekazany do konstruktora warstwy [dao] serwera danych;

W jaki sposób serwer [nuxt] będzie mógł przekazać ten plik cookie sesji PHP konstruktorowi swojej warstwy [dao]? Zapiszemy plik sesyjny PHP w pliku sesyjnym [nuxt] wymienianym między przeglądarką a serwerem [nuxt]. Proces przebiega następująco:

  1. uruchamiana jest aplikacja [nuxt];
  2. gdy serwer [nuxt] wysyła swoje pierwsze żądanie HTTP do serwera PHP, zapisuje on plik cookie sesji PHP, który otrzymał, w pliku cookie sesji [nuxt], który wymienia z klientem [nuxt];
  3. przeglądarka obsługująca klienta [nuxt] odbiera ten plik sesyjny [nuxt] i w związku z tym systematycznie odsyła go przy każdym nowym żądaniu kierowanym do serwera [nuxt];
  4. gdy serwer [nuxt] będzie musiał wysłać nowe żądanie do serwera PHP, odnajdzie plik cookie sesji PHP w pliku cookie sesji [nuxt], który przeglądarka mu przesłała. Następnie prześle je do serwera PHP;

Istnieją dwa pliki cookie sesji i nie należy ich mylić:

  • plik sesyjny [nuxt] wymieniany między serwerem [nuxt] a przeglądarką klienta [nuxt];
  • plik sesyjny PHP wymieniany między serwerem [nuxt] a serwerem PHP lub między klientem [nuxt] a serwerem PHP;

Wróćmy teraz do kodu metody klasy [Dao]. Nie zawiera ona funkcji służącej do zamknięcia sesji PHP z serwerem obliczającym podatek. Dodajemy ją:


// koniec sesji obliczania podatku
  async finSession() {
    // opcje żądania HHTP  [get /main.php?action=fin-session]
    const options = {
      method: 'GET',
      // parametry zapytania URL
      params: {
        action: 'fin-session'
      }
    }
    // wykonanie zapytania HTTP
    const data = await this.getRemoteData(options)
    // wynik
    return data
  }

Podczas testów okazuje się, że funkcja [getRemoteData] wywoływana w wierszu 12 nie jest odpowiednia dla metody [finSession]:


async  getRemoteData(options) {
    // dla pliku cookie sesji
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.Cookie = this.sessionCookie;
    // wykonanie zapytania HTTP
    let response;
    try {
      // żądanie asynchroniczne
      response = await this.axios.request('main.php', options);
    } catch (error) {
      // parametr [error] jest instancją wyjątku – może przybierać różne formy
      if (error.response) {
        // odpowiedź serwera znajduje się w [error.response]
        response = error.response;
      } else {
        // błąd jest ponownie generowany
        throw error;
      }
    }
    // odpowiedź stanowi całość odpowiedzi serwera w formacie HTTP (nagłówki HTTP + sama odpowiedź)
    // pobieramy plik cookie sesji, jeśli istnieje
    const setCookie = response.headers['set-cookie'];
    if (setCookie) {
      // setCookie to tablica
      // szukamy pliku cookie sesji w tej tabeli
      let trouvé = false;
      let i = 0;
      while (!trouvé && i < setCookie.length) {
        // szukamy pliku cookie sesji
        const results = RegExp('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // zapisujemy plik cookie sesji
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // znaleziono
          trouvé = true;
        } else {
          // kolejny element
          i++;
        }
      }
    }
    // odpowiedź serwera znajduje się w [response.data]
    return response.data;
  }
  • wiersze 30–43: szukamy pliku cookie o nazwie [PHPSESSID=xxx]. Jeśli go znajdziemy, jest on zapisywany w klasie (wiersz 36);

Ten kod nie pasuje do nowej metody [finSession], ponieważ podczas akcji [fin-session] serwer PHP wysyła dwa pliki cookie o nazwie [PHPSESSID]. Oto przykład uzyskany przy użyciu klienta [Postman]:

Image

  • w [1] – żądanie klienta [Postman];
  • w [3] – odpowiedź serwera PHP;
  • w [4] – nagłówki HTTP z odpowiedzi serwera PHP;

Image

  • w [5] serwer PHP najpierw informuje, że usunął bieżącą sesję PHP;
  • w [6] serwer PHP wysyła plik cookie nowej sesji PHP;

W obecnym kodzie funkcja [getRemoteData] pobiera plik cookie [5], podczas gdy należy zapamiętać plik cookie [6].

Należy zatem zmodyfikować kod funkcji [getRemoteData]:


async getRemoteData(options) {
    // czy istnieje plik cookie sesji o nazwie PHP?
    if (this.phpSessionCookie) {
      // czy są nagłówki?
      if (!options.headers) {
        // tworzymy pusty obiekt
        options.headers = {}
      }
      // nagłówek pliku cookie sesji PHP
      options.headers.Cookie = this.phpSessionCookie
    }
    // wykonanie żądania HTTP
    let response
    try {
      // żądanie asynchroniczne
      response = await this.axios.request('main.php', options)
    } catch (error) {
      // parametr [error] jest instancją wyjątku – może przybierać różne formy
      if (error.response) {
        // odpowiedź serwera znajduje się w [error.response]
        response = error.response
      } else {
        // błąd jest ponownie generowany
        throw error
      }
    }
    // odpowiedź stanowi całość odpowiedzi serwera HTTP (nagłówki HTTP + sama odpowiedź)
    // szukamy pliku cookie sesji PHP wśród otrzymanych plików cookie
    // wszystkie otrzymane pliki cookie
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // pliki cookie to tablica
      // szukamy pliku cookie sesji PHP w tej tablicy
      let trouvé = false
      let i = 0
      while (!trouvé && i < cookies.length) {
        // szukamy pliku cookie sesji o nazwie PHP
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // zapisujemy plik cookie sesji PHP
          const phpSessionCookie = results[1]
          // czy występuje w nim słowo [deleted]?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // mamy prawidłowy plik cookie sesji PHP
            this.phpSessionCookie = phpSessionCookie
            // znaleziono
            trouvé = true
          } else {
            // następujący element
            i++
          }
        } else {
          // kolejny element
          i++
        }
      }
    }
    // odpowiedź serwera znajduje się w [response.data]
    return response.data
  }
  • wiersz 41: znaleziono plik cookie o nazwie [PHPSESSID]. Zapisujemy go lokalnie;
  • wiersz 43: sprawdzamy, czy w zapisanym pliku cookie znajduje się ciąg znaków [PHPSESSID=deleted];
  • wiersz 46: jeśli odpowiedź brzmi „nie”, oznacza to, że znaleziono właściwy plik cookie o nazwie [PHPSESSID]. Zapisujemy go w klasie;

Po wykonaniu funkcji [getRemoteData] plik cookie sesji PHP jest zapisywany w klasie, w zmiennej [this.phpSessionCookie]. Stwierdzono, że klasa jest instancjonowana przy każdym nowym żądaniu HTTP serwera [nuxt]. Plik cookie sesji PHP musi zatem zostać wyeksfiltrowany z klasy. W tym celu dodajemy do niej nową metodę:


// dostęp do pliku cookie sesji PHP
  getPhpSessionCookie() {
    return this.phpSessionCookie
}
  • serwer [nuxt] żąda wykonania akcji od swojej warstwy [dao], przekazując plik cookie sesji PHP do jej konstruktora, o ile taki istnieje;
  • po wykonaniu akcji serwer [nuxt] pobiera plik cookie sesji PHP zapisany przez warstwę [dao] za pomocą poprzedniej metody [getPhpSessionCookie]. Ten plik cookie może być taki sam jak poprzedni lub inny. Ten ostatni przypadek ma miejsce w dwóch sytuacjach:
    • podczas wykonywania metody [initSession] (wcześniej nie istniał plik cookie sesji PHP);
    • podczas wykonania metody [finSession] (serwer PHP zmienia plik cookie sesji PHP);

Warto zwrócić uwagę na pewną cechę charakterystyczną pliku cookie sesji PHP. Serwer [nuxt] nie zawsze otrzymuje ten plik cookie od serwera PHP. Serwer ten wysyła go bowiem tylko raz. Następnie już go nie wysyła. Analizując kod plików [getRemoteData] i [getPhpSessionCookie], można zauważyć, że gdy serwer PHP nie wysyła pliku cookie sesji, funkcja [getPhpSessionCookie] zwraca plik cookie sesji PHP przekazany do konstruktora. W ten sposób serwer zawsze wysyła do serwera PHP ostatni plik cookie sesji PHP, który ten mu przesłał.

15.5.2. Warstwa [dao] klienta [nuxt]

Image

W przypadku klienta [nuxt], działającego w przeglądarce, wykorzystujemy kod klasy [Dao] z dokumentu |Introduction au framework VUE.JS par l’exemple|:


"use strict";

// importy
import qs from "qs";

class Dao {
  // konstruktor
  constructor(axios) {
    this.axios = axios;
  }

  // inicjowanie sesji
  async initSession() {
    // opcje żądania HHTP [get /main.php?action=init-session&type=json]
    const options = {
      method: "GET",
      // parametry URL
      params: {
        action: "init-session",
        type: "json"
      }
    };
    // wykonanie zapytania HTTP
    return await this.getRemoteData(options);
  }

  async authentifierUtilisateur(user, password) {
    // opcje zapytania HHTP [post /main.php?action=authentifier-utilisateur]
    const options = {
      method: "POST",
      headers: {
        "Content-type": "application/x-www-form-urlencoded"
      },
      // treść zapytania POST
      data: qs.stringify({
        user: user,
        password: password
      }),
      // parametry URL
      params: {
        action: "authentifier-utilisateur"
      }
    };
    // wykonanie zapytania HTTP
    return await this.getRemoteData(options);
  }

  async getAdminData() {
    // opcje zapytania HHTP  [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // parametry zapytania URL
      params: {
        action: "get-admindata"
      }
    };
    // wykonanie zapytania HTTP
    const data = await this.getRemoteData(options);
    // wynik
    return data;
  }

  async getRemoteData(options) {
    // wykonanie zapytania HTTP
    let response;
    try {
      // zapytanie asynchroniczne
      response = await this.axios.request("main.php", options);
    } catch (error) {
      // parametr [error] jest instancją wyjątku – może przybierać różne formy
      if (error.response) {
        // odpowiedź serwera znajduje się w [error.response]
        response = error.response;
      } else {
        // błąd jest ponownie generowany
        throw error;
      }
    }
    // odpowiedź stanowi całość odpowiedzi serwera HTTP (nagłówki HTTP + sama odpowiedź)
    // odpowiedź serwera znajduje się w [response.data]
    return response.data;
  }
}

// eksport klasy
export default Dao;

Kod ten różni się od warstwy [dao] serwera [nuxt] tym, że nie obsługuje pliku cookie sesji PHP w połączeniu z serwerem obliczającym podatek: zadanie to realizuje przeglądarka.

Podobnie jak w przypadku warstwy [dao] serwera [nuxt], dodamy metodę [finSession]:


// koniec sesji obliczania podatku
  async finSession() {
    // opcje zapytania HHTP  [get /main.php?action=fin-session]
    const options = {
      method: 'GET',
      // parametry zapytania URL
      params: {
        action: 'fin-session'
      }
    }
    // wykonanie zapytania HTTP
    const data = await this.getRemoteData(options)
    // wynik
    return data
  }

Gdy klient [nuxt] wywołuje tę metodę, otrzymuje – podobnie jak serwer [nuxt] – dwa pliki cookie sesji PHP. W rzeczywistości to przeglądarka je odbiera i prawidłowo radzi sobie z tą sytuacją: zachowuje jedynie plik cookie nowej sesji PHP, zainicjowanej przez serwer obliczający podatek. Zatem przy kolejnej akcji klienta [nuxt] skierowanej do serwera PHP plik cookie sesji PHP będzie poprawny, ponieważ to przeglądarka go wysyła. Istnieje jednak problem: serwer [nuxt] nie wie o tym, że plik cookie sesji PHP uległ zmianie. W trakcie komunikacji z serwerem PHP klient wyśle zatem plik cookie sesji PHP, który już nie istnieje, co spowoduje problemy. Klient [nuxt] powinien powiadomić serwer [nuxt] o zmianie pliku cookie sesji PHP i przekazać mu ten plik. Wiemy, jak może to zrobić: za pośrednictwem pliku cookie sesji [nuxt], czyli pliku cookie wymienianego między klientem a serwerem [nuxt]. Klient [nuxt] ma co najmniej dwa sposoby na pobranie nowego pliku cookie sesji PHP:

  1. zwracając się o nie do przeglądarki;
  2. korzystając z metody [getRemoteData] serwera, który wie, jak pobrać nowy plik cookie sesji PHP;

Skorzystamy z drugiego rozwiązania, ponieważ jest ono już gotowe. Metoda [getRemoteData] klienta [nuxt] wygląda wówczas następująco:


async getRemoteData(options) {
    // wykonanie zapytania HTTP
    let response
    try {
      // zapytanie asynchroniczne
      response = await this.axios.request('main.php', options)
    } catch (error) {
      // parametr [error] jest instancją wyjątku – może przybierać różne formy
      if (error.response) {
        // odpowiedź serwera znajduje się w [error.response]
        response = error.response
      } else {
        // błąd jest ponownie generowany
        throw error
      }
    }
    // odpowiedź to cała odpowiedź serwera HTTP (nagłówki HTTP + sama odpowiedź)
    // szukamy pliku cookie sesji PHP wśród otrzymanych plików cookie
    // wszystkie otrzymane pliki cookie
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // pliki cookie to tablica
      // szukamy pliku cookie sesji PHP w tej tablicy
      let trouvé = false
      let i = 0
      while (!trouvé && i < cookies.length) {
        // szukamy pliku cookie sesji o nazwie PHP
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // zapisujemy plik cookie sesji o nazwie PHP
          const phpSessionCookie = results[1]
          // czy występuje w nim słowo [deleted]?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // mamy prawidłowy plik cookie sesji PHP
            this.phpSessionCookie = phpSessionCookie
            // znaleziono
            trouvé = true
          } else {
            // następujący element
            i++
          }
        } else {
          // kolejny element
          i++
        }
      }
    }
    // odpowiedź serwera znajduje się w [response.data]
    return response.data
  }

W [getRemoteData] zachowaliśmy wyłącznie kod, który wykorzystuje odpowiedź serwera PHP w celu wyszukania pliku cookie sesji PHP. Nie zachowano kodu, który zawierał plik cookie sesji PHP w żądaniu wysyłanym do serwera PHP, ponieważ zadanie to realizuje przeglądarka, w której działa klient [nuxt].

Po uzyskaniu pliku cookie sesji PHP przez klienta [nuxt] należy umieścić go w sesji [nuxt], aby serwer [nuxt] mógł z niego skorzystać. Nie zajmuje się tym warstwa [dao], ale udostępnia ona za pomocą metody plik cookie sesji PHP, który zapamiętała:


// dostęp do pliku cookie sesji PHP
  getPhpSessionCookie() {
    return this.phpSessionCookie
}

Funkcja [getPhpSessionCookie] nie zawsze zwraca prawidłowy plik cookie sesji:

  • należy pamiętać, że warstwa [dao] klienta [nuxt] jest trwała. Jest ona instancjonowana jednokrotnie, a następnie pozostaje w pamięci;
  • dopóki serwer PHP nie wyśle pliku cookie sesji PHP do klienta [nuxt], funkcja [getPhpSessionCookie] klienta [nuxt] zwraca wartość [undefined];
  • gdy serwer PHP wysyła plik cookie sesji PHP do klienta [nuxt], jest on zapisywany w [this.phpSessionCookie] i pozostanie tam, dopóki nie zostanie zastąpiony nowym plikiem sesyjnym PHP wysłanym przez serwer PHP. Funkcja [getPhpSessionCookie] klienta [nuxt] zwraca wówczas ostatni otrzymany plik cookie sesji PHP;

Warstwa [dao] klienta [nuxt] różni się od warstwy serwera [nuxt] tylko pod jednym względem: nie wysyła ona sama pliku cookie sesji PHP, ponieważ robi to przeglądarka. Niemniej jednak zdecydowano się zachować dwie odrębne warstwy [dao], ponieważ rozumowanie prowadzące do ich odpowiednich zapisów jest różne.

15.6. Sesja [nuxt]

Image

Sesja [nuxt] (między klientem a serwerem Nuxt) zostanie zamknięta w następującym obiekcie [session]:


/* eslint-disable no-console */
// definicja sesji
const session = {
  // zawartość sesji
  value: {
    // niezainicjowany store
    initStoreDone: false,
    // wartość magazynu Vuex
    store: ''
  },
  // zapisanie sesji w pliku cookie
  save(context) {
    // zapisanie zmiennej sesyjnej w sesji
    this.value.store = context.store.state
    console.log('nuxt-session save=', this.value)
    // zapisanie wartości sesji
    context.app.$cookies.set('nuxt-session', this.value, { path: context.base, maxAge: context.env.maxAge })
  },
  // resetowanie sesji
  reset(context) {
    console.log('nuxt-session reset')
    // resetowanie pamięci
    context.store.commit('reset')
    // zapisanie nowego magazynu w sesji i zapisanie sesji
    this.save(context)
  }
}
// eksport sesji
export default session
  • wiersze 5–10: sesja posiada tylko jedną właściwość [value] z dwiema podwłaściwościami:
    • [initStoreDone], która wskazuje, czy magazyn został zainicjowany, czy nie;
    • [store]: wartość [store.state] magazynu Vuex aplikacji;
  • wiersze 12–18: metoda [save] służy do zapisania sesji [nuxt] w pliku cookie. W tym miejscu wykorzystuje się bibliotekę [cookie-universal-nuxt] do zarządzania plikiem cookie. Należy zwrócić uwagę na nazwę pliku cookie sesji [nuxt]: [nuxt-session] (wiersz 17);
  • wiersze 20–26: metoda [reset] resetuje sesję [nuxt];
    • wiersz 23: statek Vuex jest resetowany, a następnie zapisywany w sesji w wierszu 25;

15.7. Wtyczki do zarządzania sesją [nuxt]

Image

15.7.1. Wtyczka do zarządzania sesją [nuxt] z serveur [nuxt]

Po uruchomieniu aplikacji jako pierwszy uruchamia się serwer [nuxt]. To właśnie on zainicjuje sesję [nuxt]. Skrypt [server/plgSession] wygląda następująco:


/* wyłączenie reguły „no-console” w eslint */

// import sesji
import session from '@/entities/session'

export default (context, inject) => {
  // zarządzanie sesją serwerową
  console.log('[plugin server plgSession]')

  // czy istnieje już sesja?
  const value = context.app.$cookies.get('nuxt-session')
  if (!value) {
    // nowa sesja
    console.log("[plugin server plgSession], démarrage d'une nouvelle session")
  } else {
    // istniejąca sesja
    console.log("[plugin server plgSession], reprise d'une session existante")
    session.value = value
  }

  // wstrzykujemy funkcję do [context, Vue], która ustawi bieżącą sesję
  inject('session', () => session)
}
  • wiersz 4: importujemy kod sesji [nuxt];
  • wiersz 11: pobierana jest wartość pliku cookie sesji [nuxt];
  • wiersze 12–15: jeśli plik cookie sesji [nuxt] nie istnieje, to sesja [nuxt] zaimportowana w wierszu 4 jest wystarczająca. Nie ma nic więcej do zrobienia;
  • wiersze 15–19: jeśli plik cookie sesji [nuxt] istniał, to w wierszu 18 zapisujemy jego wartość w sesji zaimportowanej w wierszu 4;
  • wiersz 22: sesja została zainicjowana lub przywrócona. Udostępniamy ją za pomocą funkcji [$session];

15.7.2. Wtyczka do zarządzania sesją [nuxt] dla klienta [nuxt]

Skrypt [client/plgSession] ma następującą postać:


/* eslint-disable no-console */

// import sesji
import session from '@/entities/session'

export default (context, inject) => {
  // zarządzanie sesją klienta
  console.log('[plugin client plgSession], reprise de la session [nuxt] du serveur')
  // pobieramy istniejącą sesję z serwera Nuxt
  session.value = context.app.$cookies.get('nuxt-session')

  // wstrzykujemy do [context, Vue] funkcję, która ustawi bieżącą sesję
  inject('session', () => session)
}
  • wiersz 4: importowana jest sesja [nuxt];
  • wiersz 10: pobierana jest bieżąca sesja [nuxt] z pliku cookie [nuxt-session];
  • wiersz 13: zwracamy sesję [nuxt] zaimportowaną w wierszu 4 za pomocą wstrzykniętej funkcji [$session];

15.8. Wtyczki warstw [dao]

Image

15.8.1. Wtyczka warstwy [dao] klienta [nuxt]

Skrypt [client/plgDao] ma następującą postać:


/* eslint-disable no-console */
// tworzymy punkt dostępu do warstwy [Dao]
import Dao from '@/api/client/Dao'
export default (context, inject) => {
  // konfiguracja axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  context.$axios.defaults.withCredentials = context.env.withCredentials
  // instancjonowanie warstwy [dao]
  const dao = new Dao(context.$axios)
  // wstrzyknięcie funkcji [$dao] do kontekstu
  inject('dao', () => dao)
  // log
  console.log('[fonction client $dao créée]')
}
  • wiersz 3: importowana jest warstwa [dao] klienta [nuxt];
  • wiersze 6–8: konfiguruje sięobiekt [context.$axios], który będzie wysyłał zapytania HTTP z warstwy [dao] klienta [nuxt], wykorzystując informacje z pliku [nuxt.config]:

// środowisko
  env: {
    // konfiguracja axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // konfiguracja pliku cookie sesji [nuxt]
    maxAge: 60 * 5
  }
  • wiersz 10: instancja warstwy [dao] klienta [nuxt] zostaje utworzona;
  • wiersz 12: funkcja [$dao] jest wstrzykiwana do kontekstu i stron klienta. Funkcja ta zapewnia dostęp do warstwy [dao] z wiersza 10;

Należy zatem pamiętać, że aby uzyskać dostęp do warstwy [dao] klienta [nuxt] podczas jego działania, należy wpisać:

  • [context.app.$dao()], jeśli kontekst jest znany;
  • [this.$dao()] na stronie [Vue.js];

15.8.2. Wtyczka warstwy [dao] dla serveur [nuxt]

Skrypt [server/plgDao] wygląda następująco:


/* eslint-disable no-console */
// tworzymy punkt dostępu do warstwy [Dao]
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // konfiguracja axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // pobieramy plik cookie sesji
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // instancjonowanie warstwy [dao]
  const dao = new Dao(context.$axios, phpSessionCookie)
  // wstrzyknięcie funkcji [$dao] do kontekstu
  inject('dao', () => dao)
  // logowanie
  console.log('[fonction server $dao créée]')
}
  • wiersz 3: importowana jest warstwa [dao] z serwera [nuxt];
  • wiersze 6–7: konfiguruje sięobiekt [context.$axios], który będzie wysyłał żądania HTTP z warstwy [dao] serwera [nuxt], wykorzystując informacje z pliku [nuxt.config]:

// środowisko
  env: {
    // konfiguracja axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // konfiguracja pliku cookie sesji [nuxt]
    maxAge: 60 * 5
  }
  • wiersz 9: pobieramy magazyn aplikacji [nuxt];
  • wiersz 10: jeśli sklep istnieje, pobieramy plik cookie sesji PHP, ponieważ jest on potrzebny do utworzenia instancji warstwy [dao] serwera [nuxt];
  • wiersz 13: tworzymy instancję warstwy [dao] serwera [nuxt];
  • wiersz 15: funkcja [$dao] jest wstrzykiwana do kontekstu i stron serwera [nuxt]. Funkcja ta zapewnia dostęp do warstwy [dao] z wiersza 13;

Należy zatem pamiętać, że aby uzyskać dostęp do warstwy [dao] serwera [nuxt] podczas jego wykonywania, należy napisać:

  • [context.app.$dao()], jeśli kontekst jest znany;
  • [this.$dao()] na stronie [Vue.js];

15.9. Magazyn Vuex

Image

Magazyn [Vuex] będzie przechowywał wszystkie dane, które muszą być współdzielone przez różne komponenty aplikacji [pages, client, serveur], bez konieczności zapewniania reaktywności tych danych.


/* eslint-disable no-console */

// stan rolety
export const state = () => ({
  // rozpoczęta sesja jSON
  jsonSessionStarted: false,
  // użytkownik uwierzytelniony
  userAuthenticated: false,
  // plik cookie sesji PHP
  phpSessionCookie: '',
  // adminData
  adminData: ''
})

// zmiany w sklepie
export const mutations = {
  // aktualizacja stanu
  replace(state, newState) {
    for (const attr in newState) {
      state[attr] = newState[attr]
    }
  },
  // reset pamięci
  reset() {
    this.commit('replace', { jsonSessionStarted: false, userAuthenticated: false, phpSessionCookie: '', adminData: '' })
  }
}

// operacje na pamięci
export const actions = {
  nuxtServerInit(store, context) {
    // kto wykonuje ten kod?
    console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
    // inicjowanie sesji
    initStore(store, context)
  }
}

function initStore(store, context) {
  // pamięć to pamięć, którą należy zainicjować
  // pobieramy sesję
  const session = context.app.$session()
  // czy sesja została już zainicjowana?
  if (!session.value.initStoreDone) {
    // uruchamiamy nowy obiekt przechowujący
    console.log("nuxtServerInit, initialisation d'une nouvelle session")
    // umieszczamy magazyn w sesji
    session.value.store = store.state
    // magazyn jest teraz zainicjowany
    session.value.initStoreDone = true
  } else {
    console.log("nuxtServerInit, reprise d'un store existant")
    // aktualizujemy magazyn danymi z magazynu sesji
    store.commit('replace', session.value.store)
  }
  // zapisujemy sesję
  session.save(context)
  // log
  console.log('initStore terminé, store=', store.state)
}

Dane przechowywane w magazynie to:

  • wiersz 6: [jsonSessionStarted] zostanie ustawiony na wartość „prawda”, gdy tylko zakończy się pomyślnie inicjalizacja sesji jSON z serwerem PHP, niezależnie od tego, czy została ona przeprowadzona przez klienta, czy przez serwer [nuxt]. Po zakończeniu tej inicjalizacji plik cookie sesji z serwerem PHP zostanie pobrany i umieszczony we właściwości [phpSessionCookie], wiersz 10;
  • w wierszu 8: [userAuthenticated] zostanie ustawione na „prawda”, gdy tylko uwierzytelnienie na serwerze PHP zakończy się powodzeniem, niezależnie od tego, czy zostało przeprowadzone przez klienta, czy przez serwer [nuxt];
  • wiersz 12: [adminData] będzie wartością [adminData] uzyskaną od serwera PHP po pomyślnym uwierzytelnieniu;
  • wiersze 18–22: operacja [replace] pozwala zainicjować poprzednie właściwości wartościami obiektu przekazanego jako parametr;
  • wiersze 24–26: operacja [reset] przywraca właściwościom magazynu ich wartości początkowe;
  • wiersze 31–37: funkcja [nuxtServerInit] przekazuje swoje zadanie funkcji [initStore];
  • wiersze 39–60: funkcja [initStore] pełni dwie role:
    • jeśli magazyn nie został zainicjowany, zostaje zainicjowany i umieszczony w sesji;
    • jeśli store został już zainicjowany, jego wartość jest pobierana z sesji [nuxt];
  • wiersz 42: pobierana jest sesja nuxt;
  • wiersz 44: sprawdzamy, czy store został zainicjowany:
    • jeśli nie, umieszczamy początkową wartość store’u w sesji (wiersz 48);
    • następnie w wierszu 50 zaznacza się, że store został zainicjowany;
  • wiersze 51–55: jeśli magazyn został zainicjowany, wykorzystuje się go w wierszu 54 do zainicjowania magazynu wartością zawartą w sesji;
  • w wierszu 57: w każdym przypadku sesja jest zapisywana w pliku cookie o nazwie [nuxt-session] wraz z zawartą w niej zmienną;

15.10. Wtyczka [plgEventBus]

Image

Wtyczka ta ma na celu udostępnienie magistrali zdarzeń klientowi [nuxt] za pośrednictwem funkcji [$eventBus] wstrzykniętej do kontekstu klienta [nuxt]. Nie ma sensu wstrzykiwać jej w kontekst serwera [nuxt], ponieważ nie obsługuje on zdarzeń. Niemniej jednak zauważyliśmy już, że wstrzyknięcie jej po stronie serwera, a następnie jej użycie nie powoduje błędu.


/* eslint-disable no-console */
// tworzy się magistralę zdarzeń między widokami
import Vue from 'vue'
export default (context, inject) => {
  // magistrala zdarzeń
  const eventBus = new Vue()
  // wstrzyknięcie funkcji [$eventBus] do kontekstu
  inject('eventBus', () => eventBus)
  // log
  console.log('[fonction $eventBus créée]')
}

Z tą wtyczką zetknęliśmy się już w akapicie dotyczącym linków. Funkcja [$eventBus] będzie dostępna po stronie klienta poprzez notacje:

  • [context.app.$eventBus()] tam, gdzie dostępny jest kontekst;
  • [this.$eventBus()] na stronach [Vue.js] po stronie klienta;

15.11. Komponenty aplikacji [nuxt]

Image

Komponent [layout] to ten z poprzednich przykładów:


<!-- układ widoków -->
<template>
  <!-- wiersz -->
  <div>
    <b-row>
      <!-- obszar z trzema kolumnami -->
      <b-col v-if="left" cols="3">
        <slot name="left" />
      </b-col>
      <!-- obszar z dziewięcioma kolumnami -->
      <b-col v-if="right" cols="9">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>

<script>
export default {
  // parametry
  props: {
    left: {
      type: Boolean
    },
    right: {
      type: Boolean
    }
  }
}
</script>

Komponent [navigation] wygląda następująco:


<template>
  <!-- menu Bootstrap z trzema opcjami -->
  <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. Układy aplikacji [nuxt]

Image

15.12.1. [default]

Układ [default] jest tym samym, który został użyty w przykładzie [nuxt-11] w akapicie dotyczącym linku:


<template>
  <div class="container">
    <b-card>
      <!-- komunikat -->
      <b-alert show variant="success" align="center">
        <h4>[nuxt-12] : requêtes HTTP avec axios</h4>
      </b-alert>
      <!-- aktualny widok routingu -->
      <nuxt />
      <!-- komunikat oczekiwania -->
      <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łąd operacji asynchronicznej -->
      <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
    }
  },
  // cykl życia
  beforeCreate() {
    console.log('[default beforeCreate]')
  },
  created() {
    console.log('[default created]')
    if (process.client) {
      // słuchamy zdarzenia [loading]
      this.$eventBus().$on('loading', this.mShowLoading)
      // oraz zdarzenie [errorLoadingMessage]
      this.$eventBus().$on('errorLoading', this.mShowErrorLoading)
    }
  },
  beforeMount() {
    console.log('[default beforeMount]')
  },
  mounted() {
    console.log('[default mounted]')
  },
  methods: {
    // obsługa komunikatu oczekującego
    mShowLoading(value) {
      console.log('[default mShowLoading], showLoading=', value)
      this.showLoading = value
    },
    // błąd operacji asynchronicznej
    mShowErrorLoading(value, errorLoadingMessage) {
      console.log('[default mShowErrorLoading], showErrorLoading=', value, 'errorLoadingMessage=', errorLoadingMessage)
      this.showErrorLoading = value
      this.errorLoadingMessage = errorLoadingMessage
    }
  }
}
</script>
  • wiersze 10–14: wyświetlają komunikat o oczekiwaniu na zakończenie operacji asynchronicznej klienta [nuxt];
  • wiersze 15–18: wyświetlają ewentualny komunikat o błędzie operacji asynchronicznej;
  • wiersz 37: funkcja [created] ze strony [default] jest wykonywana przed funkcją [mounted] ze stron;
  • wiersz 39: jeśli wykonawcą jest klient [nuxt], wówczas strona [default] nasłuchuje zdarzeń:
    • [loading], która sygnalizuje początek lub koniec oczekiwania. Następnie wykonywana jest funkcja [mShowLoading];
    • [errorLoading], która sygnalizuje konieczność wyświetlenia komunikatu o błędzie. Następnie wykonywana jest funkcja [mShowErrorLoading];
  • strony [nuxt]:
    • wyświetlają komunikat oczekiwania, wysyłając zdarzenie [‘loading’, true] na magistralę zdarzeń;
    • ukrywają komunikat oczekiwania, wysyłając zdarzenie [‘loading’, false] na magistralę zdarzeń;
    • wyświetlają komunikat o błędzie, wysyłając zdarzenie [‘errorLoading’, true] na magistralę zdarzeń;
    • ukrywają komunikat o błędzie, wysyłając zdarzenie [‘errorLoading’, false] na magistrali zdarzeń;

15.12.2. [error]

Układ [error] wyświetla komunikat o błędzie systemowym (nieobsługiwanym przez programistę):


<!-- definicja widoku HTML -->
<template>
  <!-- układ strony -->
  <Layout :left="true" :right="true">
    <!-- alert w prawej kolumnie -->
    <template slot="right">
      <!-- komunikat na różowym tle -->
      <b-alert show variant="danger" align="center">
        <h4>L'erreur suivante s'est produite : {{ JSON.stringify(error) }}</h4>
      </b-alert>
    </template>
    <!-- menu nawigacyjne w lewej kolumnie -->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  // właściwość [props]
  props: { error: { type: Object, default: () => 'waiting ...' } },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[error beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[error created, error=]', this.error)
  },
  beforeMount() {
    // tylko klient
    console.log('[error beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[error mounted]')
  }
}
</script>

15.13. Strona [index] uruchamiana przez serwer [nuxt]

Image

Strona [index.vue] ma tę cechę, że jest dostępna wyłącznie za pośrednictwem serwera [nuxt]. Użytkownikowi nie jest udostępniony żaden link umożliwiający dostęp do niej za pośrednictwem klienta [nuxt]. Jej kod jest następujący:


<!-- strona główna -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat-->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  // dane asynchroniczne
  async asyncData(context) {
    // log
    console.log('[index asyncData started]')
    try {
      // rozpoczynamy sesję jSON
      const dao = context.app.$dao()
      const response = await dao.initSession()
      // log
      console.log('[index asyncData response=]', response)
      // pobieranie pliku cookie sesji PHP do wykorzystania w kolejnych żądaniach
      const phpSessionCookie = dao.getPhpSessionCookie()
      // zapisujemy plik cookie sesji PHP w sesji [nuxt]
      context.store.commit('replace', { phpSessionCookie })
      // czy wystąpił błąd?
      if (response.état !== 700) {
        // błąd znajduje się w response.réponse
        throw new Error(response.réponse)
      }
      // należy odnotować, że sesja jSON została uruchomiona
      context.store.commit('replace', { jsonSessionStarted: true })
      // zwracamy wynik
      return { result: '[succès]' }
    } catch (e) {
      // log
      console.log('[index asyncData error=]', e)
      // odnotowano, że sesja jSON nie została uruchomiona
      context.store.commit('replace', { jsonSessionStarted: false })
      // zgłaszamy błąd
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // zapisujemy zawartość pamięci
      const session = context.app.$session()
      session.save(context)
      // log
      console.log('[index asyncData finished]')
    }
  },
  // cykl życia
  beforeCreate() {
    console.log('[index beforeCreate]')
  },
  created() {
    console.log('[index created]')
  },
  beforeMount() {
    console.log('[index beforeMount]')
  },
  mounted() {
    console.log('[index mounted]')
    // tylko klient
    if (this.showErrorLoading) {
      console.log('[index mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>
  • wiersz 7: strona wyświetla wynik [result] z zapytania asynchronicznego (wiersze 46 i 51);
  • wiersz 31: operacją asynchroniczną jest otwarcie sesji jSON z serwerem obliczającym podatek;
  • wiersz 25: wiadomo, że gdy strona jest żądana bezpośrednio od serwera [nuxt], funkcja [asyncData] jest wykonywana wyłącznie przez serwer, a nie przez klienta [nuxt], który uruchamia się, gdy przeglądarka otrzyma odpowiedź od serwera [nuxt];
  • wiersz 30: pobierana jest warstwa [dao] w kontekście serwera [nuxt];
  • wiersz 35: jeśli serwer nie wysłał jeszcze żądania do serwera obliczającego podatek, otrzymuje swój pierwszy plik cookie sesji o nazwie PHP, w przeciwnym razie otrzymuje ostatni plik cookie sesji o nazwie PHP, który otrzymał (zobacz kod warstwy [dao] serwera [nuxt] w akapicie z linkiem);
  • wiersz 37: zapisujemy ten plik sesyjny PHP w pamięci;
  • wiersze 39–42: sprawdzamy, czy operacja zakończyła się powodzeniem. Jeśli nie, generowany jest wyjątek, który zostanie przechwycony przez [catch] w wierszu 47;
  • wiersz 44: odnotowuje się w pamięci, że sesja jSON z serwerem PHP została uruchomiona;
  • wiersz 46: zwracany jest wynik [result], który jest wyświetlany w wierszu 7;
  • wiersze 47–54: przetwarzany jest ewentualny wyjątek. Może on mieć dwa rodzaje:
    • operacja HTTP z wiersza 31 zakończyła się niepowodzeniem z powodu błędu komunikacyjnego między serwerem [nuxt] a serwerem PHP;
    • operacja HTTP z wiersza 31 zakończyła się powodzeniem, ale otrzymany wynik zgłosił błąd (wiersze 39–42);
  • wiersz 51: zauważamy, że sesja jSON z serwerem PHP nie została uruchomiona;
  • wiersz 53: zwracany jest wynik [result], który jest wyświetlany w wierszu 7. Ponadto ustawia się właściwości [showErrorLoading] i [errorLoadingMessage], których klient [nuxt] użyje do wyświetlenia komunikatu o błędzie po otrzymaniu strony wysłanej przez serwer [nuxt] (wiersze 72–79);
  • wiersze 54–60: kod wykonywany w każdym przypadku (niezależnie od powodzenia lub niepowodzenia);
  • wiersz 56: pobieramy sesję [nuxt] w kontekście serwera [nuxt];
  • wiersz 57: zapisuje się ją;
  • wiersze 63–68: po zakończeniu działania funkcji [asyncData] serwer [nuxt] wykonuje funkcje [beforeCreate] i [create];

Uwaga: wykonanie strony [index] przez serwer [nuxt] może zakończyć się niepowodzeniem, na przykład jeśli serwer obliczający podatek nie jest uruchomiony w momencie uruchomienia aplikacji [nuxt]:

Image

W takim przypadku jedynym rozwiązaniem jest uruchomienie serwera obliczającego podatek, a następnie samej aplikacji [nuxt], ponieważ menu nawigacyjne nie oferuje opcji zainicjowania sesji jSON z serwerem obliczającym podatek;

15.14. Strona [index] uruchamiana przez klienta [nuxt]

Strona [index] jest uruchamiana przez klienta [nuxt] dopiero po tym, jak serwer [nuxt] ją do niego wyśle. Serwer ten przesłał mu informacje [result] oraz ewentualnie [showErrorLoading] i [errorLoadingMessage].

Wiadomo, że funkcja [asyncData] nie zostanie wykonana. Pozostają zatem funkcje cyklu życia, a w szczególności funkcja [mounted]:


mounted() {
    console.log('[index mounted]')
    // tylko klient
    if (this.showErrorLoading) {
      console.log('[index mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
}
  • klient [nuxt] automatycznie włącza do właściwości strony elementy [result] oraz ewentualnie [showErrorLoading, errorLoadingMessage], które przesłał mu serwer [nuxt]:
  • właściwość [result] jest wyświetlana w wierszu 7;
  • właściwości [showErrorLoading, errorLoadingMessage] są wykorzystywane przez metodę [mounted]: w wierszu 4 sprawdzana jest właściwość [showErrorLoading]. Jeśli jest prawdziwa, w wierszu 6 wykorzystuje się magistralę zdarzeń klienta [nuxt], aby zasygnalizować, że należy wyświetlić komunikat o błędzie;
  • zdarzenie [errorLoading] wywołane w wierszu 6 jest przechwytywane przez stronę [layouts/default] opisaną w akapicie „link”;

15.15. Strona [authentification] jest uruchamiana przez serwer [nuxt]

Strona [authentification] odpowiada za identyfikację użytkownika na serwerze obliczającym podatek. Jej kod jest następujący:


<!-- strona uwierzytelniania -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat-->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  // dane asynchroniczne
  async asyncData(context) {
    // log
    console.log('[authentification asyncData started]')
    if (process.client) {
      // początek oczekiwania na klienta [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // brak błędu
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // autoryzacja na serwerze
      const dao = context.app.$dao()
      const response = await dao.authentifierUtilisateur('admin', 'admin')
      // log
      console.log('[authentification asyncData response=]', response)
      // wynik
      const userAuthenticated = response.état === 200
      // odnotowuje się, czy użytkownik został uwierzytelniony, czy nie
      context.store.commit('replace', { userAuthenticated })
      // zapisywanie magazynu w sesji [nuxt]
      const session = context.app.$session()
      session.save(context)
      // błąd uwierzytelniania?
      if (!userAuthenticated) {
        // błąd znajduje się w response.réponse
        throw new Error(response.réponse)
      }
      // zwracany jest wynik
      return { result: '[succès]' }
    } catch (e) {
      // zgłaszamy błąd
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[authentification asyncData finished]')
      if (process.client) {
        // koniec oczekiwania na klienta [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // cykl życia
  beforeCreate() {
    console.log('[authentification beforeCreate]')
  },
  created() {
    console.log('[authentification created]')
  },
  beforeMount() {
    console.log('[authentification beforeMount]')
  },
  mounted() {
    console.log('[authentification mounted]')
    // tylko klient
    if (this.showErrorLoading) {
      console.log('[authentification mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>
  • wiersz 7: strona wyświetla wynik [result] z asynchronicznego zapytania [asyncData] z wierszy 25–65;
  • wiersze 28–33: serwer nie wykonuje tych wierszy przeznaczonych dla klienta [nuxt];
  • wiersz 36: pobierana jest warstwa [dao] z serwera [nuxt];
  • wiersz 37: następuje uwierzytelnienie na serwerze obliczającym podatek przy użyciu testowych danych uwierzytelniających [admin, admin], które są jedynymi akceptowanymi przez ten serwer;
  • wiersz 41: operacja uwierzytelniania zakończyła się powodzeniem, o ile odpowiedź ma status 200;
  • wiersz 43: do pamięci tymczasowej zapisywana jest właściwość [userAuthenticated];
  • wiersze 44–46: stan jest zapisywany w sesji o identyfikatorze [nuxt];
  • wiersze 48–51: jeśli uwierzytelnianie nie powiodło się, generowany jest wyjątek z komunikatem o błędzie przesłanym przez serwer obliczający podatek;
  • w przeciwnym razie w wierszu 53 zwracany jest wynik pozytywny, który zostanie wyświetlony w wierszu 7;
  • wiersze 54–57: w przypadku błędu ustawiane są trzy właściwości strony [result, showErrorLoading, errorLoadingMessage]. Właściwość [result] zostanie wyświetlona w wierszu 7. Trzy właściwości zostaną wysłane do klienta [nuxt];
  • wiersze 60–63: nie są wykonywane przez serwer [nuxt];
  • gdy [asyncData] zwróci wynik, zostanie on wyświetlony w wierszu 7. Następnie wykonywane są metody [beforeCreate] (wiersze 67–69) i [created] (wiersze 70–72);
  • to wszystko;

Uwaga: wykonanie strony [authentification] przez serwer [nuxt] może się nie powieść, na przykład jeśli sesja jSON z serwerem obliczającym podatek nie została zainicjowana. Można to sprawdzić w następujący sposób:

  • usuń plik cookie sesji PHP ze swojej przeglądarki (aby zacząć od nowa):

Image

  • uruchom aplikację [nuxt], gdy serwer obliczeniowy nie został uruchomiony: pojawi się błąd;
  • uruchom serwer obliczający podatek;
  • wprowadź adres URL [/authentification] bezpośrednio w pasku adresu przeglądarki:

Image

W takim przypadku jedynym rozwiązaniem jest ponowne załadowanie strony [index].

15.16. Strona [authentification] uruchamiana przez klienta [nuxt]

Przyjrzyjmy się ponownie kodowi strony:


<!-- strona uwierzytelniania -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat-->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  // dane asynchroniczne
  async asyncData(context) {
    // log
    console.log('[authentification asyncData started]')
    if (process.client) {
      // początek oczekiwania na klienta [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // brak błędu
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // autoryzacja na serwerze
      const dao = context.app.$dao()
      const response = await dao.authentifierUtilisateur('admin', 'admin')
      // log
      console.log('[authentification asyncData response=]', response)
      // wynik
      const userAuthenticated = response.état === 200
      // odnotowuje się, czy użytkownik został uwierzytelniony, czy nie
      context.store.commit('replace', { userAuthenticated })
      // zapisujemy statek w sesji [nuxt]
      const session = context.app.$session()
      session.save(context)
      // błąd uwierzytelniania?
      if (!userAuthenticated) {
        // błąd znajduje się w response.réponse
        throw new Error(response.réponse)
      }
      // zwracany jest wynik
      return { result: '[succès]' }
    } catch (e) {
      // zgłaszamy błąd
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[authentification asyncData finished]')
      if (process.client) {
        // koniec oczekiwania na klienta [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // cykl życia
  beforeCreate() {
    console.log('[authentification beforeCreate]')
  },
  created() {
    console.log('[authentification created]')
  },
  beforeMount() {
    console.log('[authentification beforeMount]')
  },
  mounted() {
    console.log('[authentification mounted]')
    // tylko dla klientów
    if (this.showErrorLoading) {
      console.log('[authentification mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

Istnieją dwa przypadki wykonania strony [authentification] przez klienta [nuxt]:

  1. klient [nuxt] uruchamia się po tym, jak serwer [nuxt] wysłał do przeglądarki klienta [nuxt] stronę [authentification];
  2. klient [nuxt] uruchamia się, ponieważ użytkownik kliknął link [Authentification] w menu nawigacyjnym:

Image

Przyjrzyjmy się najpierw pierwszemu przypadkowi. W tym przypadku klient [nuxt] nie uruchamia funkcji [asyncData]. Włącza on do właściwości strony elementy [result] oraz ewentualnie [showErrorLoading, errorLoadingMessage], które przesłał mu serwer [nuxt]:

  • właściwość [result] jest wyświetlana w wierszu 7;
  • właściwości [showErrorLoading, errorLoadingMessage] są wykorzystywane przez metodę [mounted]: w wierszu 79 sprawdzana jest właściwość [showErrorLoading]. Jeśli jest ona prawdziwa, w wierszu 81 wykorzystuje się magistralę zdarzeń klienta [nuxt] w celu zasygnalizowania, że należy wyświetlić komunikat o błędzie;

Mechanizm wyświetlania komunikatu o błędzie został wyjaśniony dla strony [index] w akapicie dotyczącym linku.

Przypadek 2 dotyczy klienta [nuxt] uruchamianego, gdy użytkownik kliknie link [Authentification]. W tym przypadku klient [nuxt] uruchamia się samodzielnie, a nie po serwerze [nuxt]. Następnie wykonywana jest funkcja [asyncData]. Podajemy jedynie szczegóły, które różnią się od wyjaśnień dotyczących strony uruchamianej przez serwer [nuxt]:

  • wiersze 28–33: klient [nuxt] żąda wyświetlenia komunikatu o oczekiwaniu oraz usunięcia ewentualnego komunikatu o błędzie, który mógł zostać wcześniej wyświetlony;
  • wiersz 36: w tym miejscu pobierana jest teraz warstwa [dao] klienta [nuxt];
  • wiersze 60–63: klient [nuxt] żąda zakończenia wyświetlania komunikatu o oczekiwaniu;
  • po zakończeniu działania [asyncData] rozpocznie się cykl życia strony. Zostanie wykonana funkcja [mounted] z wierszy 76–83. Jeśli wystąpił błąd, zostanie wyświetlony komunikat o błędzie;

Uwaga: aby wywołać błąd, należy postępować zgodnie z procedurą opisaną dla serwera [nuxt] na końcu akapitu z linkiem, ale zamiast wywoływać stronę [authentification], wpisując jej adres URL w pasku adresu, należy skorzystać z linku [Authentification] w menu nawigacyjnym. Wówczas uruchomi się klient o numerze [nuxt].

15.17. Strona [get-admindata]

Kod strony [get-admindata] jest następujący:


<!-- widok get-admindata -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat -->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  // dane asynchroniczne
  async asyncData(context) {
    // log
    console.log('[get-admindata asyncData started]')
    if (process.client) {
      // początek oczekiwania
      context.app.$eventBus().$emit('loading', true)
      // brak błędu
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // żądanie danych [admindata]
      const response = await context.app.$dao().getAdminData()
      // log
      console.log('[get-admindata asyncData response=]', response)
      // wynik
      const adminData = response.état === 1000 ? response.réponse : ''
      // dane są umieszczane w pamięci tymczasowej
      context.store.commit('replace', { adminData })
      // zapisujemy bufor w sesji [nuxt]
      const session = context.app.$session()
      session.save(context)
      // czy wystąpił błąd?
      if (!adminData) {
        // błąd znajduje się w response.réponse
        throw new Error(response.réponse)
      }
      // zwracamy otrzymaną wartość
      return { result: adminData }
    } catch (e) {
      // zgłaszamy błąd
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[get-admindata asyncData finished]')
      if (process.client) {
        // koniec oczekiwania
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // cykl życia
  beforeCreate() {
    console.log('[get-admindata beforeCreate]')
  },
  created() {
    console.log('[get-admindata created]')
  },
  beforeMount() {
    console.log('[get-admindata beforeMount]')
  },
  mounted() {
    console.log('[get-admindata mounted]')
    // klient
    if (this.showErrorLoading) {
      console.log('[get-admindata mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

Strona ta jest bardzo podobna do strony [authentification]. Objaśnienia są analogiczne zarówno w przypadku jej uruchomienia przez serwer [nuxt], jak i przez klienta [nuxt]. Należy jednak zauważyć, że w wierszu 7 nie wyświetla się komunikat o powodzeniu lub niepowodzeniu, jak poprzednio, ale wartość danych otrzymanych z serwera obliczającego podatek (wiersz 52):

Image

Powyższy wynik uzyskuje się zarówno przy użyciu serwera, jak i klienta [nuxt]. Aby wywołać błąd, należy wywołać stronę [get-admindata] za pośrednictwem serwera lub klienta [nuxt] bez uwierzytelnienia:

Image

15.18. Strona [fin-session]

Kod strony jest następujący:


<!-- strona główna -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat-->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  // dane asynchroniczne
  async asyncData(context) {
    // log
    console.log('[fin-session asyncData started]')
    // przypadek klienta [nuxt]
    if (process.client) {
      // początek oczekiwania
      context.app.$eventBus().$emit('loading', true)
      // brak błędu
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // wysyłamy żądanie o nową sesję PHP do serwera obliczającego podatek
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // log
      console.log('[fin-session asyncData response=]', response)
      // czy wystąpił błąd?
      if (response.état !== 400) {
        // błąd znajduje się w response.réponse
        throw new Error(response.réponse)
      }
      // serwer wysłał nowy plik cookie sesji PHP
      // jest on pobierany zarówno przez serwer, jak i klienta Nuxt
      // jeśli ten kod zostanie wykonany przez klienta [nuxt], plik cookie sesji PHP musi zostać umieszczony w sesji Nuxt
      //, aby wtyczka [plgDao] serwera [nuxt] mogła go pobrać i zainicjować warstwę [dao] za pomocą
      //. Jeśli kod ten jest wykonywany przez serwer [nuxt], plik cookie sesji PHP musi zostać umieszczony w sesji Nuxt
      //, aby routing klienta [nuxt] mógł go pobrać i przekazać do przeglądarki
      const phpSessionCookie = dao.getPhpSessionCookie()
      // w rejestrze odnotowuje się rozpoczęcie sesji jSON i zapisuje się plik cookie sesji PHP
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // zapisuje się zawartość pamięci w sesji [nuxt]
      const session = context.app.$session()
      session.save(context)
      // zwracany jest wynik
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    } catch (e) {
      // log
      console.log('[fin-session asyncData error=]', e)
      // zgłaszamy błąd
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[fin-session asyncData finished]')
      if (process.client) {
        // koniec oczekiwania
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // cykl życia
  beforeCreate() {
    console.log('[fin-session beforeCreate]')
  },
  created() {
    console.log('[fin-session created]')
  },
  beforeMount() {
    console.log('[fin-session beforeMount]')
  },
  mounted() {
    console.log('[fin-session mounted]')
    // tylko klient
    if (this.showErrorLoading) {
      console.log('[fin-session mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

Kod jest bardzo podobny do kodu poprzednich stron, a objaśnienia są takie same. Należy jedynie zwrócić uwagę na jedną kwestię: operacja asynchroniczna w wierszu 38 powoduje, że serwer obliczający podatek wyśle nowy plik cookie sesji o nazwie PHP. Wyjaśnienia dotyczące obsługi tego pliku cookie różnią się w zależności od tego, czy kod ten jest wykonywany przez serwer, czy przez klienta [nuxt].

Zacznijmy od serwera [nuxt]:

  • wiersz 37: instancjonowana jest warstwa [dao] serwera [nuxt]. Przypomnijmy kod jej konstruktora:

// konstruktor
  constructor(axios, phpSessionCookie) {
    // biblioteka axios
    this.axios = axios
    // wartość pliku cookie sesji
    this.phpSessionCookie = phpSessionCookie
    // nazwa pliku cookie sesji serwera PHP
    this.phpSessionCookieName = 'PHPSESSID'
}

W wierszu 1 widać, że konstruktor potrzebuje aktualnego pliku cookie sesji PHP, czyli ostatniego otrzymanego, niezależnie od tego, czy pochodzi on od serwera, czy od klienta [nuxt];

  • w wierszu 52: serwer [nuxt] pobiera plik cookie nowej sesji PHP lub poprzedni plik cookie, jeśli operacja zakończenia sesji nie powiodła się;
  • wiersz 54: plik cookie sesji PHP jest umieszczany w pamięci, a następnie zapisywany w sesji [nuxt] w wierszach 56–57;
  • po serwerze to klient [nuxt] wykonuje stronę [fin-session] z danymi przesłanymi przez serwer. Wiemy, że nie wykona on funkcji [asyncData];
  • ostatecznie, po zakończeniu pracy przez serwer i klienta [nuxt], wiadomo, że plik cookie PHP, niezbędny do komunikacji z serwerem obliczającym podatek, znajduje się w sesji [nuxt];

Fakt, że plik cookie PHP znajduje się w sesji [nuxt], jest wystarczający dla serwera, ponieważ właśnie stamtąd pobierze go jego warstwa [dao]. W wtyczce [server/plgDao], która inicjuje warstwę [dao] serwera, zapisano:


/* eslint-disable no-console */
// tworzymy punkt dostępu do warstwy [Dao]
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // konfiguracja axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // pobieramy plik cookie sesji
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // instancjonowanie warstwy [dao]
  const dao = new Dao(context.$axios, phpSessionCookie)
  // wstrzyknięcie funkcji [$dao] do kontekstu
  inject('dao', () => dao)
  // logowanie
  console.log('[fonction server $dao créée]')
}
  • w wierszu 13 warstwa serwera [dao] jest instancjonowana z plikiem cookie sesji PHP pobranym z sesji [nuxt], wiersze 9–10;

W przypadku klienta [nuxt] sytuacja wygląda inaczej. To nie on bowiem wysyła plik cookie, lecz przeglądarka, w której jest uruchomiony. Przeglądarka ta nie zna jednak pliku cookie nowej sesji PHP otrzymanego przez serwer [nuxt]. Jeśli skorzystamy z linków w menu nawigacyjnym [3]:

Image

Serwer obliczający podatek otrzyma z przeglądarki nieaktualny plik cookie sesji o nazwie PHP i odpowie, że z tym plikiem cookie nie jest powiązana żadna sesja o nazwie jSON. Musimy znaleźć sposób, aby przekazać przeglądarce nowy plik cookie sesji o nazwie PHP.

W tym celu można wykorzystać oprogramowanie pośredniczące do routingu:

Image

Skrypt [client/routing] jest oprogramowaniem pośredniczącym do routingu zadeklarowanym w pliku [nuxt.config]:


// router
  router: {
    // katalog główny aplikacji URL
    base: '/nuxt-12/',
    // oprogramowanie pośredniczące routingu
    middleware: ['routing']
},

Skrypt [middleware/routing] ma następującą postać:


/* eslint-disable no-console */

// importujemy oprogramowanie pośredniczące klienta
import clientRouting from './client/routing'

export default function(context) {
  // kto wykonuje ten kod?
  console.log('[middleware], process.server', process.server, ', process.client=', process.client)
  if (process.client) {
    // routing po stronie klienta
    clientRouting(context)
  }
}
  • wiersze 9–12: przekierowywany jest tylko klient za pomocą funkcji zaimportowanej w wierszu 4;

Skrypt [middleware/client/routing] wygląda następująco:


/* eslint-disable no-console */
export default function(context) {
  // kto wykonuje ten kod?
  console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
  // obsługa pliku cookie sesji PHP w przeglądarce
  // Plik cookie sesji PHP w przeglądarce musi być identyczny z tym znalezionym w sesji Nuxt
  // akcja [fin-session] otrzymuje nowy plik cookie PHP (zarówno serwer, jak i klient Nuxt)
  // jeśli to serwer go odbiera, klient musi przekazać go przeglądarce
  // w celu własnej komunikacji z serwerem PHP
  // mamy tu do czynienia z routingiem po stronie klienta

  // pobieramy plik cookie sesji PHP
  const phpSessionCookie = context.store.state.phpSessionCookie
  if (phpSessionCookie) {
    // jeśli istnieje, przypisujemy plik cookie sesji PHP do przeglądarki
    document.cookie = phpSessionCookie
  }
}

Wróćmy do sytuacji tuż po wykonaniu strony [fin-session] przez serwer [nuxt]:

Image

Jeśli klikniemy jeden z linków w menu [3], kontrolę przejmie klient [nuxt]. Ponieważ nastąpi zmiana strony, uruchomi się skrypt routingu klienta:

  • wiersz 13: plik cookie sesji PHP zostaje znaleziony w magazynie aplikacji [nuxt];
  • wiersz 14: jeśli nie jest pusty, jest przekazywany do przeglądarki (wiersz 16). Od tego momentu przeglądarka klienta [nuxt] posiada prawidłowy plik cookie sesji PHP;

Skrypt [client/routing] jest uruchamiany przy każdej zmianie strony przez klienta [nuxt]. Kod skryptu działa poprawnie niezależnie od strony docelowej: w większości przypadków po prostu przekazuje przeglądarce plik cookie sesji PHP, który już posiada, z wyjątkiem dwóch sytuacji:

  • zaraz po uruchomieniu aplikacji, serwer [nuxt] uruchamia stronę [index] i otrzymuje pierwszy plik cookie sesji PHP, którego nie posiada przeglądarka klienta [nuxt];
  • gdy serwer [nuxt] uruchamia stronę [fin-session], tak jak to właśnie wyjaśniono;

Rozważmy teraz przypadek, w którym strona [fin-session] jest wywoływana wyłącznie przez klienta [nuxt], ponieważ kliknięto jej link w menu nawigacyjnym. Teraz to klient [nuxt] uruchamia funkcję [asyncData]:


try {
      // wysyłamy żądanie o nową sesję PHP do serwera obliczającego podatek
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // log
      console.log('[fin-session asyncData response=]', response)
      // czy wystąpił błąd?
      if (response.état !== 400) {
        // błąd znajduje się w response.réponse
        throw new Error(response.réponse)
      }
      // serwer wysłał nowy plik cookie sesji PHP
      // jest on pobierany zarówno przez serwer, jak i klienta Nuxt
      // jeśli ten kod zostanie wykonany przez klienta [nuxt], plik cookie sesji PHP musi zostać umieszczony w sesji Nuxt
      // aby wtyczka [plgDao] serwera [nuxt] mogła go pobrać i zainicjować warstwę [dao] za pomocą
      //. Jeśli kod ten jest wykonywany przez serwer [nuxt], plik cookie sesji PHP musi zostać umieszczony w sesji Nuxt
      //, aby routing klienta [nuxt] mógł go pobrać i przekazać do przeglądarki
      const phpSessionCookie = dao.getPhpSessionCookie()
      // odnotowujemy w magazynie, że sesja jSON została uruchomiona i zapisujemy plik cookie sesji PHP
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // zapisuje się zawartość pamięci w sesji [nuxt]
      const session = context.app.$session()
      session.save(context)
      // zwracany jest wynik
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    } catch (e) {
      // log
      console.log('[fin-session asyncData error=]', e)
      // zgłaszamy błąd
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[fin-session asyncData finished]')
      if (process.client) {
        // koniec oczekiwania
        context.app.$eventBus().$emit('loading', false)
      }
    }
  • wiersz 3: tutaj uzyskiwana jest warstwa [dao] klienta [nuxt];
  • wiersz 18: plik cookie sesji PHP pobrany przez warstwę [dao] klienta [nuxt] jest zapisywany w pamięci, umieszczany w magazynie (wiersz 20), a następnie zapisany w sesji [nuxt] (wiersze 22–23);
  • od tego momentu wszystko przebiega prawidłowo, ponieważ wiadomo, że warstwa [dao] serwera [nuxt] pobiera plik cookie sesji PHP z sesji [nuxt];

15.19. Exécution

Aby uruchomić ten przykład, przed wykonaniem należy usunąć plik cookie sesji [nuxt] oraz plik cookie PHP z przeglądarki, w której uruchomiony jest klient [nuxt], aby rozpocząć od czystej sytuacji. Poniżej znajduje się przykład z przeglądarką Chrome:

Image

15.20. Conclusion

Ten przykład był szczególnie złożony. Łączył on wiedzę zdobytą w poprzednich przykładach: trwałość pamięci podręcznej w sesji [nuxt], wtyczki do wstrzykiwania funkcji, oprogramowanie pośredniczące do routingu oraz obsługę błędów operacji asynchronicznych. Złożoność została zwiększona przez fakt, że chcieliśmy, aby użytkownik mógł zarówno korzystać z linków w menu nawigacyjnym, jak i ręcznie wpisywać adresy URL, bez powodowania awarii aplikacji. W tym celu musieliśmy przeanalizować, jak zachowuje się każda strona w zależności od tego, czy jest wykonywana przez klienta, czy przez serwer [nuxt].

Ta spójność zachowania klienta i serwera [nuxt] nie jest niezbędna. Można rozważyć częsty przypadek, w którym:

  • pierwsza strona jest dostarczana przez serwer [nuxt];
  • wszystkie kolejne strony są dostarczane przez klienta [nuxt], który działa wówczas w trybie [SPA];

Niemniej jednak nawet w tym przypadku należy sprawdzić, jaki będzie wynik wygenerowania wszystkich stron przez serwer [nuxt], ponieważ właśnie to otrzymają wyszukiwarki, które je zażądają.