Skip to content

16. Przykład [nuxt-13]: kontrola nawigacji serwera [nuxt-12]

W tym przykładzie skupiamy się na nawigacji w [nuxt-12]. Nie zrobiliśmy tego w przypadku [nuxt-12], ponieważ sprawdzenie nawigacji skomplikowałoby i tak już złożony przykład.

Cel: chcemy, aby użytkownik mógł wykonywać wyłącznie dozwolone czynności:

  • jeśli sesja jSON nie została uruchomiona, wówczas dozwolone są wyłącznie sesje URL i [/];
  • jeśli sesja jSON została uruchomiona, ale użytkownik nie jest uwierzytelniony, wówczas dozwolone są wyłącznie sesje URL i [/authentification];
  • jeśli sesja jSON została uruchomiona, a użytkownik jest uwierzytelniony, wówczas dozwolone są wyłącznie sesje URL i [/get-admindata, /fin-session];
  • gdy aktualny cel routingu nie jest dozwolony, nastąpi przekierowanie do dozwolonego URL;

Przykład [nuxt-13] uzyskuje się początkowo poprzez skopiowanie przykładu [nuxt-12]:

Image

Zmiany zostaną wprowadzone w folderze routingu [middleware].

16.1. Trasowanie aplikacji [nuxt]

Routing aplikacji jest skonfigurowany w następujący sposób w pliku [nuxt.config]:


// router
  router: {
    // katalog główny aplikacji URL
    base: '/nuxt-13/',
    // oprogramowanie pośredniczące routingu
    middleware: ['routing']
},
  • wiersz 6: routing aplikacji jest kontrolowany przez plik [middleware/routing];

Plik [middleware/routing] ma następującą treść:


/* eslint-disable no-console */

// importujemy oprogramowanie pośredniczące serwera i klienta
import serverRouting from './server/routing'
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.server) {
    // routing po stronie serwera
    serverRouting(context)
  } else {
    // kierowanie po stronie klienta
    clientRouting(context)
  }
}
  • wiersze 10–16: routing klienta i serwera [nuxt] jest traktowany w różny sposób. Jest to punkt, w którym znacznie się one różnią;
  • wiersz 4: routing serwera jest zaimplementowany przez skrypt [middleware/server/routing];
  • wiersz 5: routing po stronie klienta jest realizowany przez skrypt [middleware/client/routing];

16.2. Routing po stronie klienta [nuxt]

Routing klienta [nuxt] pozostaje taki sam jak w [nuxt-12]:


/* eslint-disable no-console */
export default function(context) {
  // kto wykonuje ten kod?
  console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
  // zarządzanie plikiem 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
  }

  ...
}

Aby zapobiec przejściu klienta na niedozwolone trasy, w menu nawigacji klienta udostępnimy mu wyłącznie trasy dozwolone. Komponent [components/navigation] przyjmuje następujący kształt:


<template>
  <!-- menu Bootstrap z trzema opcjami -->
  <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>
  • wiersz 4: opcja [Authentification] jest oferowana tylko wtedy, gdy sesja jSON została uruchomiona, ale użytkownik nie jest uwierzytelniony. Jeśli sesja jSON nie została uruchomiona lub użytkownik jest już uwierzytelniony, opcja ta nie jest dostępna;
  • wiersze 7–11: opcja [Requête AdminData] jest dostępna tylko wtedy, gdy sesja jSON została uruchomiona, użytkownik został uwierzytelniony, a dane [AdminData] nie zostały jeszcze pobrane. Jeśli którykolwiek z tych trzech warunków nie jest spełniony (sesja jSON nie została uruchomiona, użytkownik nie został uwierzytelniony lub dane [AdminData] zostały już pobrane), opcja nie jest dostępna;
  • wiersz 15: opcja [Fin session impôt] jest dostępna, gdy tylko rozpocznie się sesja jSON i użytkownik zostanie uwierzytelniony; w przeciwnym razie nie jest dostępna;

16.3. Trasowanie serwera [nuxt]

Routing serwera jest zazwyczaj bardziej złożony niż routing klienta, ponieważ użytkownik może wpisać dowolny adres URL w pasku adresu przeglądarki. Można to pozostawić bez zmian (w końcu użytkownik nie powinien tego robić) lub spróbować to kontrolować. Właśnie to zrobimy tutaj, na przykładzie, ponieważ w przypadku aplikacji [nuxt-12] można się bez tego obejść, ponieważ serwer obliczający podatek jest dobrze zabezpieczony przed takimi ręcznie wprowadzonymi adresami URL i potrafi wysyłać odpowiednie komunikaty o błędach. Widzieliśmy to już w przypadku [next-12], gdzie nie było żadnej kontroli routingu.

Routing serwera [nuxt] znacznie różni się od routingu klienta [nuxt] pod względem koncepcji przekierowania:

  • gdy serwer [nuxt] jest przekierowywany, wysyła do przeglądarki klienta polecenie przekierowania wraz z adresem docelowym przekierowania. Przeglądarka wysyła wówczas nowe żądanie do serwera [nuxt], prosząc o adres docelowy, który został jej przekazany. Wszystko przebiega tak, jakby użytkownik ręcznie wpisał adres docelowy przekierowania: cała aplikacja [nuxt] uruchamia się od nowa, a tym samym cały jej cykl życia (wtyczki serwera, magazyn, routing serwera, strony);
  • gdy klient [nuxt] jest przekierowywany, nic takiego się nie dzieje. Następuje zwykła zmiana strony, taka sama, jak ta, którą uzyskano by, gdyby użytkownik kliknął link prowadzący do celu przekierowania. Cykl życia jest wówczas inny (routing po stronie klienta, wyświetlenie celu trasy);

Z tego powodu zaleca się oddzielenie routingu po stronie klienta od routingu po stronie serwera, nawet jeśli oba kody mogą wydawać się podobne.

Skrypt routingu serwerowego [middleware/server/routing] będzie wyglądał następująco:


/* eslint-disable no-console */
export default function(context) {
  // kto wykonuje ten kod?
  console.log('[middleware server], process.server', process.server, ', process.client=', process.client)

  // pobieramy kilka informacji ze store’u [nuxt]
  const store = context.store
  // skąd pochodzimy?
  const from = store.state.from || 'nowhere'
  ...
}
  • w routingu po stronie klienta funkcja routingu otrzymuje kontekst [context] wraz z właściwością [context.from], która jest trasą strony, z której pochodzimy. Trasa, do której zmierzamy, jest uzyskiwana poprzez [context.route];
  • w ramach routingu serwera funkcja routingu otrzymuje kontekst [context] bez właściwości [context.from]. Routing serwera ma miejsce tylko wtedy, gdy ręcznie wysyłane jest żądanie o adres URL do serwera [nuxt]. Wiadomo, że wówczas cała aplikacja [nuxt] zostaje zresetowana. To tak, jakbyśmy zaczynali od zera, dlatego nie ma pojęcia „poprzedniej strony”;
  • dzięki sesji [nuxt] wiemy, że serwer może odzyskać tę sesję i dzięki temu nie zaczynać od zera. Właśnie w tej sesji [nuxt], a dokładniej w magazynie tej sesji, będziemy przechowywać nazwę ostatniej strony wyświetlonej przez przeglądarkę klienta przed wysłaniem żądania URL do serwera [nuxt];
  • wiersze 7–9: pobieramy nazwę ostatniej strony wyświetlonej przez przeglądarkę klienta. Po uruchomieniu aplikacji ta informacja [from] nie istnieje w magazynie. Następnie przypisuje się nazwę [nowhere] do zmiennej [from];

Aby serwer [nuxt] mógł pobrać ze sklepu nazwę ostatniej strony wyświetlonej przez przeglądarkę klienta, klient [nuxt] musi również umieścić tę informację w sklepie. Skrypt routingu klienta [nuxt] zostaje zatem uzupełniony w następujący sposób:


/* eslint-disable no-console */
export default function(context) {
  // kto wykonuje ten kod?
  console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
  // zarządzanie plikiem 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
  }

  // do sesji zapisujemy nazwę strony, na którą się przechodzimy – bez przekierowania serwerowego
  context.store.commit('replace', { serverRedirection: false, from: context.route.name })
  // zapisujemy statek w sesji [nuxt]
  const session = context.app.$session()
  session.value.store = context.store.state
  session.save(context)
}
  • dodano wiersze 19–24;
  • wiersz 20: do pamięci buforowej zapisujemy nazwę strony [context.route.name], która zostanie wyświetlona i która podczas następnego routingu będzie stroną, z której pochodzimy. Ponadto zauważymy, że podczas routingu serwera [nuxt] serwer ten musi wiedzieć, czy bieżący routing wynika z poprzedniego przekierowania z serwera [nuxt]. W tym przypadku tak nie jest, dlatego ustawiamy właściwość [serverRedirection] na [false];
  • wiersze 22–24: stan pamięci buforowej jest zapisywany w sesji [nuxt] (wiersz 23), a następnie sesja [nuxt] jest zapisywana w pliku cookie (wiersz 24), który z kolei zostanie zapisany w przeglądarce klienta [nuxt];

Wróćmy do skryptu routingu serwera [nuxt]:


/* eslint-disable no-console */
export default function(context) {
  // kto wykonuje ten kod?
  console.log('[middleware server], process.server', process.server, ', process.client=', process.client)

  // pobieramy pewne informacje ze store'u [nuxt]
  const store = context.store
  // skąd pochodzimy?
  const from = store.state.from || 'nowhere'
  // dokąd zmierzamy?
  const to = context.route.name
  // ewentualne przekierowanie
  let redirection = ''
  // zarządzanie routingiem zakończone
  let done = false

  // czy jesteśmy już w trakcie przekierowania z serwera [nuxt]?
  if (store.state.serverRedirection) {
    // nie ma potrzeby podejmowania żadnych działań
    done = true
  }

  // czy to odświeżenie strony?
  if (to === from) {
    // nie ma co robić
    done = true
  }
  
  // kontrola nawigacji serwera [nuxt]
  // wzorujemy się na nawigacji klienta w komponencie [navigation]

  // sytuacja, w której sesja PHP nie została uruchomiona
  if (!done && !store.state.jsonSessionStarted && to !== 'index') {
    // przekierowanie
    redirection = 'index'
    // zadanie zakończone
    done = true
  }

  // w przypadku, gdy użytkownik nie jest uwierzytelniony
  if (!done && store.state.jsonSessionStarted && !store.state.userAuthenticated && to !== 'authentification') {
    // przekierowanie
    redirection = from
    // zadanie zakończone
    done = true
  }

  // sytuacja, w której użytkownik został uwierzytelniony
  if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && to !== 'get-admindata' && to !== 'fin-session') {
    // pozostajemy na tej samej stronie
    redirection = from
    // zadanie zakończone
    done = true
  }

  // przypadek, w którym uzyskano [adminData]
  if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && store.state.adminData && to !== 'fin-session') {
    // pozostajemy na tej samej stronie
    redirection = from
    // praca zakończona
    done = true
  }

  // przeprowadzono wszystkie kontrole ---------------------
  // przekierowanie?
  if (redirection) {
    // odnotowano przekierowanie w sklepie
    store.commit('replace', { serverRedirection: true })
  } else {
    // brak przekierowania
    store.commit('replace', { serverRedirection: false, from: to })
  }
  // zapisujemy stan sklepu w sesji [nuxt]
  const session = context.app.$session()
  session.value.store = store.state
  session.save(context)
  // w razie potrzeby następuje przekierowanie
  if (redirection) {
    context.redirect({ name: redirection })
  }
}
  • wiersze 6–9: pobieramy wartość [from] z pamięci serwera [nuxt];
  • wiersz 11: odnotowuje się cel bieżącego routingu;
  • wiersz 13: routing może skutkować przekierowaniem przeglądarki klienta. [redirection] będzie celem tego przekierowania;
  • wiersz 15: [done] do [true] wskazuje, że routing został zakończony;
  • wiersze 17–21: najpierw sprawdzane jest, czy bieżący proces routingu wynika z żądania przekierowania wysłanego do przeglądarki klienta. Informacja ta jest przechowywana we właściwości [serverRedirection] w magazynie danych. Jeśli wartość tej właściwości wynosi „true”, oznacza to, że serwer [nuxt] wysłał przekierowanie do przeglądarki klienta podczas poprzedniego żądania skierowanego do serwera [nuxt]. W takim przypadku nie ma potrzeby przeprowadzania routingu. Podczas poprzedniego żądania router serwera [nuxt] zdecydował, że przeglądarka klienta powinna zostać przekierowana. Decyzja ta nie musi być podważana przez nowy routing;
  • wiersze 23–27: sprawdzamy, czy bieżący proces routingu jest odświeżeniem strony. Jeśli tak, pozwalamy, aby proces przebiegał dalej;
  • od wiersza 29 stosujemy zasady obowiązujące w komponencie [navigation] klienta [nuxt] (patrz poprzedni akapit);
  • wiersze 32–38: obsługujemy przypadek, w którym sesja jSON nie została uruchomiona, a celem przekierowania nie jest strona [index]. W takim przypadku przekierowuje się przeglądarkę klienta na stronę [index];
  • wiersze 40–46: obsługujemy przypadek, w którym sesja jSON została uruchomiona, użytkownik nie jest uwierzytelniony, a celem bieżącego przekierowania nie jest strona [authentification]. W takim przypadku odrzucamy przekierowanie i pozostajemy w dotychczasowym miejscu;
  • wiersze 48–54: rozpatrujemy przypadek, w którym rozpoczęła się sesja jSON, użytkownik jest uwierzytelniony, a celem bieżącego przekierowania nie jest ani strona [get-admindata], ani strona [fin-session], które są wówczas jedynymi możliwymi miejscami docelowymi. W takim przypadku odrzuca się żądane przekierowanie i powraca się do poprzedniego stanu;
  • wiersze 56–62: rozpatrujemy przypadek, w którym uzyskano [adminData]. W tym przypadku istnieje tylko jeden możliwy cel przekierowania: strona [fin-session]. Jeśli nie była to strona, o którą proszono, odrzuca się przekierowanie i wraca się do poprzedniego miejsca;
  • wiersze 64–72: jeśli nastąpiło przekierowanie, odnotowuje się to w magazynie serwera [nuxt]: [serverRedirection: true]. Należy zauważyć, że nie nadaje się wartości właściwości [from] w magazynie. Powodem tego jest fakt, że nastąpi przekierowanie z przeglądarki klienta, a jak widzieliśmy, w tym przypadku nie ma routingu (wiersze 17–20), a właściwość [from] w magazynie nie jest wykorzystywana;
  • wiersze 66–69: jeśli nie ma przekierowania, to odnotowuje się to również w magazynie serwera [nuxt]: [serverRedirection: false]. Ponadto bieżący routing spowoduje wyświetlenie strony [to], która dla następnego żądania (klienta lub serwera [nuxt]) stanie się stroną poprzednią. Dlatego zapisujemy [from: to];
  • wiersze 73–76: zapisujemy statek w sesji [nuxt], która sama jest zapisana w pliku cookie;
  • wiersze 77–80: jeśli [redirection] nie jest puste, wówczas przeglądarka otrzymuje polecenie przekierowania. W przeciwnym razie (czego tutaj nie widać) cykl życia serwera [nuxt] będzie kontynuowany: strona [to] zostanie przetworzona przez serwer [nuxt] i wysłana do przeglądarki klienta [nuxt] wraz z plikiem cookie sesji [nuxt];

Wybrany tutaj sposób routingu dla serwera [nuxt] jest dowolny. Można było wybrać inny lub, jak już wspomniano, w ogóle go nie stosować. Wybrany powyżej sposób ma tę zaletę, że zawsze utrzymuje aplikację w stabilnym stanie, niezależnie od tego, jaki adres URL zostanie żądany przez użytkownika.

Można to nieco ulepszyć, gdy ostatecznie ładowana strona jest stroną oryginalną. Istnieją dwa przypadki:

  • użytkownik spowodował ponowne załadowanie strony (to===from);
  • występują przekierowania do strony źródłowej (redirection===from);

W obu przypadkach strona źródłowa zostanie ponownie wygenerowana wraz z asynchronicznym wywołaniem serwera obliczającego podatek. Weźmy przykład. Jeśli po uwierzytelnieniu użytkownik odświeży stronę (F5). W tym przypadku w powyższym schemacie routingu mamy: [to]=[from]=[authentification]. Nie ma przekierowania. Strona [to=authentification] zostanie wykonana przez serwer [nuxt]. Jeśli nie podejmiemy żadnych działań, funkcja [asyncData] zostanie ponownie wykonana. Jest to zbędne, ponieważ uwierzytelnienie zostało już przeprowadzone.

Można to poprawić, wprowadzając niewielką zmianę w stronie [authentification]:


// dane asynchroniczne
  async asyncData(context) {
    // log
    console.log('[authentification asyncData started]')
    // nie wykonuje się tych czynności dwukrotnie, jeśli strona została już wywołana
    if (process.server && context.store.state.userAuthenticated) {
      console.log('[authentification asyncData canceled]')
      return { result: '[succès]' }
    }
     // klient [nuxt]
    if (process.client) {
      // początek oczekiwania
      context.app.$eventBus().$emit('loading', true)
      // brak błędu
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // autoryzacja na serwerze
...
  • wiersze 6–9: jeśli strona jest wykonywana przez serwer [nuxt] i w pamięci tymczasowej stwierdzono, że uwierzytelnienie zostało już przeprowadzone, wówczas bezpośrednio zwracany jest pożądany wynik (wiersz 8);

To samo dotyczy wszystkich stron:

Strona [index]:


// dane asynchroniczne
  async asyncData(context) {
    // log
    console.log('[index asyncData started]')
    // nie wykonuje się tej samej czynności dwukrotnie, jeśli strona została już wywołana
    if (process.server && context.store.state.jsonSessionStarted) {
      console.log('[index asyncData canceled]')
      return { result: '[succès]' }
    }
    try {
...

Strona [get-admindata]


// dane asynchroniczne
  async asyncData(context) {
    // log
    console.log('[get-admindata asyncData started]')
    // nie wykonuje się czynności dwukrotnie, jeśli strona została już wywołana
    if (process.server && context.store.state.adminData) {
      console.log('[get-admindata asyncData canceled]')
      return { result: context.store.state.adminData }
    }
    // klient
    if (process.client) {
      // początek oczekiwania
      context.app.$eventBus().$emit('loading', true)
      // brak błędu
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
   ...  

Strona [fin-session]


// dane asynchroniczne
  async asyncData(context) {
    // log
    console.log('[fin-session asyncData started]')
    // nie wykonuje się tej samej operacji dwukrotnie, jeśli strona została już wywołana
    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)." }
    }
    // 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 {
   

16.4. Exécution

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

Image

16.5. Conclusion

Trasowanie serwera [nuxt] jest skomplikowane, ponieważ należy uwzględnić wszystkie wartości URL, które użytkownik może wpisać ręcznie. Jest to klasyczny przykład. Aplikacja [nuxt] nie jest przeznaczona do używania w ten sposób. Po wyświetleniu strony [index] przez router serwera [nuxt] można by przekierowywać kolejne wywołania kierowane do serwera na stronę błędu.

W konkretnym przypadku naszego przykładu [nuxt-13] routing serwera [nuxt] był zbędny. Routing domyślny (w rzeczywistości brak routingu) w przykładzie [nuxt-12] sprawdzał się bardzo dobrze.