Skip to content

9. Voorbeeld [nuxt-06]: injectie in de context van een sessiebeheerder

9.1. Présentation

Het voorbeeld [nuxt-05] heeft aangetoond dat de store behouden kan blijven, zelfs wanneer de gebruiker oproepen naar de server forceert. De elementen van de store zijn responsief, zodat ze, indien ze in weergaven zijn geïntegreerd, reageren op wijzigingen in de store. Het kan ook wenselijk zijn om elementen tijdens de communicatie tussen client en server te behouden zonder dat ze responsief hoeven te zijn, simpelweg omdat ze niet door weergaven worden getoond. Deze kunnen dan in de sessie worden opgeslagen zonder dat ze zich in de store bevinden.

De store is eenvoudig toegankelijk via eigenschappen zoals [context.app.$store] buiten de weergaven of [this.$store] in de weergaven. We zouden graag iets soortgelijks willen voor de sessie, zoiets als [context.app.$session] of [this.$session]. We zullen zien dat dit mogelijk is dankzij het concept van injectie. Alleen kunnen we geen objecten in de context injecteren, maar alleen functies. Deze zal dan beschikbaar zijn via de uitdrukkingen [context.app.$session()] of [this.$session()].

Tot slot zullen we het concept [nuxt] van [plugin] presenteren.

Het voorbeeld [nuxt-06] wordt in eerste instantie verkregen door het project [nuxt-05] te kopiëren:

Image

  • in [1] voegen we een map [plugins] toe;

9.2. het begrip ‘plugin’ [nuxt]

[nuxt] benoemt [plugin] alle code die bij het opstarten van de applicatie wordt uitgevoerd, nog vóór de uitvoering van de functie [nuxtServerInit] door de server, die tot nu toe de eerste gebruikersfunctie was die werd uitgevoerd. De plug-ins van de applicatie moeten worden gedeclareerd in de sleutel [plugins] van het configuratiebestand [nuxt.config.js]:


  /*
   ** Plugins to load before mounting the App
   */
  plugins: [
    { src: '~/plugins/client/session', mode: 'client' },
    { src: '~/plugins/server/session', mode: 'server' }
],
  • regels 5-6: een plug-in wordt aangeduid met zijn pad [src] en zijn uitvoermodus [mode]. [mode] kan drie waarden hebben:
    • [client]: de plug-in moet uitsluitend aan de clientzijde worden uitgevoerd;
    • [server]: de plug-in moet uitsluitend aan de serverzijde worden uitgevoerd;
    • ontbreken van de sleutel [mode]: in dit geval moet de plug-in zowel aan de clientzijde als aan de serverzijde worden uitgevoerd;
  • regels 5-6: we hebben onze twee plug-ins in een map [plugins] geplaatst. Dit is niet verplicht. De plug-ins kunnen overal in de projectstructuur worden geplaatst. Ook de namen van de submappen [client, server] zijn hier willekeurig gekozen;

Image

9.3. De plug-in [session] van de server

De plug-in [server / session.js] ziet er als volgt uit:


/* eslint-disable no-console */
export default (context, inject) => {
  // beheer van de serversessie

  // is er een bestaande sessie?
  let value = context.app.$cookies.get('session')
  if (!value) {
    // nieuwe sessie
    console.log("[plugin session server], démarrage d'une nouvelle session")
    value = initValue
  } else {
    // bestaande sessie
    console.log("[plugin session server], reprise d'une session existante")
  }
  // sessie-instellingen
  const session = {
    // inhoud van de sessie
    value,
    // de sessie opslaan in een cookie
    save(context) {
      context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
    }
  }
  // er wordt een functie ingevoegd in [context, Vue] die de huidige sessie actief maakt
  inject('session', () => session)
}

// de beginwaarde van de sessie
const initValue = {
  initSessionDone: false
}
  • regel 2: de plug-ins worden uitgevoerd telkens wanneer er een verzoek aan de server wordt gedaan: bij het opstarten en telkens wanneer de gebruiker handmatig een verzoek aan de server forceert door een URL in te voeren:
    • eerst worden de plug-in(s) van de server uitgevoerd;
    • wanneer de clientbrowser het antwoord van de server heeft ontvangen, is het de beurt aan de clientplug-in(s) om te worden uitgevoerd;
  • regel 2: elke plug-in, zowel aan de client- als aan de serverzijde, ontvangt twee parameters:
    • [context]: de context van de server of de client, afhankelijk van wie de plug-in uitvoert;
    • [inject]: een functie waarmee een functie in de context van de server of de client kan worden geïnjecteerd;
  • de plug-in [server / session] heeft twee doelen:
    • een sessie definiëren (regels 16-23);
    • binnen de context een functie [$session] definiëren die als resultaat de sessie uit regel 16 retourneert. Dit gebeurt in regel 25;
  • regels 16-23: de sessie verpakt haar gegevens in het object [value] uit regel 18;
  • regels 20-22: de sessie beschikt over een functie [save] die als parameter een object [context] ontvangt. De aanroepende code levert deze context aan de functie. Met deze context slaat de functie [save] de sessiewaarde, het object [value], op in de sessiecookie;
  • regel 6: wanneer de plug-in [server / session] wordt uitgevoerd, controleert deze eerst of de server een sessiecookie heeft ontvangen;
    • zo ja, dan vertegenwoordigt het object [value] uit regel 6 de sessiewaarde, namelijk alle gegevens die daarin zijn ingekapseld;
    • zo niet, dan wordt in de regels 7-11 de beginwaarde van de sessie vastgesteld. Dit is het object [initValue] uit de regels 29-31. De elementen van de sessie worden gedefinieerd in de functie [nuxtServerInit], die na de serverplugin wordt uitgevoerd;
  • regel 18: de notatie [value] is een afkorting voor de notatie [value:value]. De [value] aan de linkerkant is de naam van een object-ID, de [value] aan de rechterkant is het object [value] dat in regel 6 is gedeclareerd;
  • regel 25: wanneer deze regel wordt bereikt, is de sessie ofwel aangemaakt omdat deze nog niet bestond, ofwel opgehaald uit de aanvraag HTTP van de clientbrowser;
  • regel 25: er wordt een nieuwe functie in de servercontext geïnjecteerd:
    • de eerste parameter van [inject] is de naam van de functie die wordt aangemaakt, in dit geval ‘session’. [nuxt] zal deze functie in feite de naam ‘$session’ geven;
    • de tweede parameter is de definitie van de functie. Hier zal de functie [$session]
      • heeft geen parameters;
      • het object [session] uit regel 16 zal retourneren;
  • zodra de plug-in is uitgevoerd:
    • is de functie [$session] beschikbaar in [context.app.$session], waar het object [context] beschikbaar is, of [this.$session] in een weergave of in de store [vuex];
    • de functie [$session] retourneert een object [session] met een unieke sleutel [value];
    • bij het aanmaken van de sessie heeft het object [value] slechts één sleutel [initStoreDone] (regels 29-31). De sleutel [initStoreDone:false] geeft aan dat de store nog niet in de sessie is geplaatst. Dit gebeurt door de functie [nuxtServerInit];

9.4. Initialisatie van de sessie

Zodra de plug-in [session / server] door de server is uitgevoerd, voert deze het volgende script [store / index.js] uit:


/* eslint-disable no-console */
export const state = () => ({
  // teller
  counter: 0
})

export const mutations = {
  // de teller met één waarde verhogen [inc]
  increment(state, inc) {
    state.counter += inc
  },
  // vervanging van de state
  replace(state, newState) {
    for (const attr in newState) {
      state[attr] = newState[attr]
    }
  }
}

export const actions = {
  async nuxtServerInit(store, context) {
    // wie voert deze code uit?
    console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
    // we wachten op het einde van een belofte
    await new Promise(function(resolve, reject) {
      // normaal gesproken hebben we hier een asynchrone functie
      // we simuleren deze met een wachttijd van één seconde
      setTimeout(() => {
        // sessie initialiseren
        initSession(store, context)
        // geslaagd
        resolve()
      }, 1000)
    })
  }
}

function initSession(store, context) {
  // store is de store die moet worden geïnitialiseerd

  // de sessie wordt opgehaald
  const session = context.app.$session()
  // is de sessie al geïnitialiseerd?
  if (!session.value.initSessionDone) {
    // er wordt een nieuwe store gestart
    console.log("nuxtServerInit, initialisation d'une nouvelle session")
    // de store wordt geïnitialiseerd
    store.commit('increment', 77)
    // de store wordt in de sessie geplaatst
    session.value.store = store.state
    // er wordt een nieuwe sessie geïnitialiseerd
    session.value.somethingImportant = { x: 2, y: 4 }
    // de sessie is nu geïnitialiseerd
    session.value.initSessionDone = true
  } else {
    console.log("nuxtServerInit, reprise d'un store existant")
    // de store wordt bijgewerkt met de store van de sessie
    store.commit('replace', session.value.store)
  }
  // de sessie wordt opgeslagen
  session.save(context)
  // log
  console.log('initSession terminé, store=', store.state, 'session=', session.value)
}

In vergelijking met de store van project [nuxt-05] verandert alleen de functie [initSession] (voorheen initStore) in de regels 38-60:

  • regel 42: de sessie wordt opgehaald met behulp van de functie [$session], die in de servercontext is geïnjecteerd;
  • regel 44: er wordt gecontroleerd of de sessie al is geïnitialiseerd;
  • regels 45-54: als dat niet het geval is:
    • regel 48: de store wordt geïnitialiseerd;
    • regel 50: de status van de store wordt in de sessie opgeslagen;
    • regel 52: er wordt nog een [somethingImportant]-object aan de sessie toegevoegd. Dit object maakt geen deel uit van de store;
    • regel 54: er wordt genoteerd dat de sessie nu is geïnitialiseerd;
  • regels 55-59: als de sessie al was geïnitialiseerd:
    • regel 58: de nieuwe store wordt geïnitialiseerd met de inhoud van de sessie;
  • regel 61: de sessie wordt opgeslagen in de sessiecookie. Ter herinnering: dit houdt in dat de cookie wordt geplaatst in het antwoord HTTP dat de server naar de browser van de klant stuurt;

9.5. De [client / session]-plugin van de client

Zodra de server de scripts [plugins / server / session] en [store / index] heeft uitgevoerd, stuurt hij een van de pagina’s [index, page1] naar de browser van de klant. In het antwoord HTTP van de server zit de sessiecookie. Zodra de pagina door de browser van de klant is ontvangen, worden de in de pagina ingebedde scripts van de klant uitgevoerd. De plug-in [client / session] wordt dan uitgevoerd:


/* eslint-disable no-console */
export default (context, inject) => {
  // beheer van de clientsessie

  // de sessie bestaat altijd, geïnitialiseerd door de server
  console.log('[plugin session client], reprise de la session du serveur')

  // definitie van de sessie
  const session = {
    // sessie-inhoud
    value: context.app.$cookies.get('session'),
    // sessie opslaan in een cookie
    save(context) {
      context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
    }
  }

  // er wordt een functie ingevoegd in [context, Vue] die de huidige sessie zal activeren
  inject('session', () => session)
}
  • wanneer de client-plugin wordt uitgevoerd, is de sessiecookie al door de browser van de klant ontvangen;
  • het doel van de plug-in [client] is om op zijn beurt een functie [$session] in de clientcontext te injecteren. Deze functie zou de door de server verzonden sessie weergeven;
  • regel 19: de geïnjecteerde functie [$session] zal de sessie van de regels 9-16 teruggeven;
  • regels 9-16: het door de client beheerde object [session]. Dit is een kopie van de door de server verzonden sessie;
  • regel 11: de waarde van de clientsessie wordt opgehaald uit de door de server verzonden sessiecookie [nuxt];
  • regels 13-15: net als bij de serversessie heeft de clientsessie een functie [save] waarmee de waarde van de sessie, [this.value] (regel 14), kan worden opgeslagen in de sessiecookie die in de browser is opgeslagen;

9.6. De pagina [index]

De pagina [index] verloopt als volgt:


<!-- pagina [index] -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigatie -->
    <Navigation slot="left" />
    <!-- bericht-->
    <template slot="right">
      <b-alert show variant="warning"> Home - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
      <!-- knop -->
      <b-button @click="incrementCounter" class="ml-3" variant="primary">Incrémenter</b-button>
    </template>
  </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: 'Home',
  // gebruikte componenten
  components: {
    Layout,
    Navigation
  },
  computed: {
    jsonSession() {
      return JSON.stringify(this.$session().value)
    }
  },
  // levenscyclus
  beforeCreate() {
    // client en server
    console.log('[home beforeCreate]')
  },
  created() {
    // client en server
    console.log('[home created], session=', this.$session().value)
  },
  beforeMount() {
    // alleen client
    console.log('[home beforeMount]')
  },
  mounted() {
    // alleen client
    console.log('[home mounted]')
  },
  // gebeurtenisbeheer
  methods: {
    incrementCounter() {
      console.log('incrementCounter')
      // teller met 1 verhogen
      this.$store.commit('increment', 1)
      // sessie wijzigen
      const session = this.$session()
      session.value.store = this.$store.state
      session.value.somethingImportant.x++
      session.value.somethingImportant.y++
      // sessie opslaan in de sessiecookie
      session.save(this.$nuxt.context)
    }
  }
}
</script>

Houd er rekening mee dat deze pagina zowel aan de serverzijde als aan de clientzijde wordt uitgevoerd.

  • regel 8: nu worden zowel de sessie als de store weergegeven;
  • regel 30: [jsonSession] is een berekende eigenschap die de tekenreeks jSON de waarde van de sessie geeft;
  • regel 41: de sessiewaarde wordt weergegeven met behulp van de geïnjecteerde functie [this.$session]. Deze functie bestaat zowel in de servercontext als in de clientcontext;
  • regel 53: de methode [incrementCounter] wordt alleen aan de clientzijde uitgevoerd;
  • regel 56: de teller van de store wordt verhoogd en weergegeven zoals eerder;
  • regel 58: de sessie wordt opgehaald met behulp van de geïnjecteerde functie [this.$session];
  • regel 59: de store van de sessie wordt bijgewerkt;
  • regels 60-61: de attributen [somethingImportant.x, somethingImportant.y] van de sessie worden verhoogd. Dit is alleen om te laten zien dat een sessie ook andere gegevens dan de store kan bevatten;
  • regel 63: de sessie wordt opgeslagen in de sessiecookie die in de browser is opgeslagen. Vanuit het perspectief van de klant is de context hiervan beschikbaar in [this.$nuxt.context];

Het doel van de pagina [index] is om aan te tonen dat de sessie niet reactief is, terwijl de store dat wel is. Wanneer we de elementen van de sessie verhogen, zullen we merken dat de weergave niet wordt bijgewerkt. De weergave [page1] biedt een oplossing voor dit probleem.

9.7. De pagina [page1]

De pagina [page1] is verkregen door de pagina [index] te kopiëren en vervolgens enigszins aan te passen:


<!-- pagina [index] -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigatie -->
    <Navigation slot="left" />
    <!-- bericht-->
    <template slot="right">
      <b-alert show variant="warning"> Page1 - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
      <!-- knop -->
      <b-button @click="incrementCounter" class="ml-3" variant="primary">Incrémenter</b-button>
    </template>
  </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: 'Page1',
  // gebruikte componenten
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      session: {}
    }
  },
  computed: {
    jsonSession() {
      return JSON.stringify(this.session.value)
    }
  },
  // levenscyclus
  beforeCreate() {
    // client en server
    console.log('[page1 beforeCreate]')
  },
  created() {
    // client en server
    // de sessie wordt in de reactieve eigenschappen van de pagina geplaatst
    this.session = this.$session()
    // logboek
    console.log('[page1 created], session=', this.session.value)
  },
  beforeMount() {
    // alleen client
    console.log('[page1 beforeMount]')
  },
  mounted() {
    // alleen client
    console.log('[page1 mounted]')
  },
  // gebeurtenisbeheer
  methods: {
    incrementCounter() {
      console.log('incrementCounter')
      // teller met 1 verhogen
      this.$store.commit('increment', 1)
      // sessie wijzigen
      this.session.value.store = this.$store.state
      this.session.value.somethingImportant.x++
      this.session.value.somethingImportant.y++
      // sessie opslaan in de sessiecookie
      this.session.save(this.$nuxt.context)
    }
  }
}
</script>
  • regel 47: het belangrijkste verschil is dat de huidige sessie in de eigenschappen van de pagina wordt opgenomen (regels 29-33). Dit heeft tot gevolg dat de sessie voortaan reactief wordt. Wanneer de functie [incrementCounter] de elementen van de sessie verhoogt, wordt de weergave [page1] bijgewerkt;

9.8. Het project uitvoeren

Controleer, voordat u het project uitvoert, de sessiecookie in uw browser en verwijder deze indien aanwezig, zodat de server een nieuwe sessie aanmaakt:

Image

Laten we nu URL en [http://localhost:81/nuxt-06/] opvragen:

Image

De logbestanden in de browser zien er dan als volgt uit:

Image

  • in [2] start de server een nieuwe sessie in de plug-in [session] van de server;
  • in [3] wordt deze nieuwe sessie geïnitialiseerd in [nuxtServerInit];
  • in [4], de nieuwe sessie zoals deze op de server bekend is;
  • in [5] heeft de client deze sessie correct opgehaald;

Laten we de teller nu drie keer verhogen:

Image

  • in [3] is de teller inderdaad verhoogd, maar de sessie in [2] niet. Terwijl [3] de store weergeeft die actief is, toont [2] de sessie die niet actief is:

Laten we nu de pagina opnieuw laden (F5). Dit zijn de logbestanden na het opnieuw laden:

Image

  • in [2] zien we dat de server een sessiecookie heeft ontvangen die door de clientbrowser is verzonden;
  • in [4] zien we dat de store niet wordt gereset, maar wordt overgenomen uit de ontvangen sessie;
  • in [4-5]: we zien dat de attributen van de sessie inderdaad allemaal drie keer zijn verhoogd;

De door de server verzonden pagina is dan als volgt;

Image

De conclusie die uit deze pagina kan worden getrokken, is dat de sessie andere elementen dan de store kan bevatten, maar dat deze niet reactief zijn.

Laten we nu op de link [Page 1] [4] klikken. De nieuwe pagina die wordt weergegeven, is dan de volgende:

Image

Laten we vervolgens de knop [Incrémenter] drie keer gebruiken. De pagina ziet er dan als volgt uit:

Image

Deze keer wordt de sessie correct weergegeven als [2]. Ze reageert hier. Dit is te zien in de logbestanden:

Image

  • in [1-3], de waarden van de sessie;
  • in [4-6], de reactieve getters en setters van de sessie-elementen;

Laten we nu op de link [Home] [4] klikken. We krijgen de volgende pagina te zien:

Image

Laten we vervolgens dubbelklikken op de knop [Incrémenter] [4]. De pagina ziet er dan als volgt uit:

Image

We zien dat ook hier de sessie weer actief is geworden: [2].

Laten we de waarde opvragen die door de functie [this.$session()] wordt geretourneerd:

Image

  • in het tabblad [Vue] selecteren we de huidige pagina [Home] om de referentie [$vm0] [3] te verkrijgen;

Vervolgens vragen we in het tabblad [Console] [4] de waarde van de functie [$vm0.$session()] op:

Image

  • in [5] zien we dat de sessie nu actief is, terwijl dat aanvankelijk niet het geval was;
  • in [6] vragen we de waarde van de sessie op;
  • in [7-8] blijkt dat ook deze waarde reactief is geworden;

We hebben hier dus te maken met een onverwacht resultaat: als een element op een pagina reactief wordt omdat het in de eigenschappen van de pagina is opgenomen, dan wordt het ook reactief op pagina’s waar het geen deel uitmaakt van de eigenschappen.

9.9. Conclusion

Het voorbeeld [nuxt-05] heeft aangetoond dat de store behouden kan blijven tijdens de verzoeken die aan de server worden gedaan. Het voorbeeld [nuxt-06] doet hetzelfde met een object dat we [session] hebben genoemd, naar analogie met de websessie. We hebben gezien dat deze sessie dezelfde eigenschappen kon hebben als de store [Vuex] en ook reactief kon worden, terwijl deze van nature niet reactief was.

Wat is dan het nut van de store [Vuex]? Ik moet toegeven dat het me voorlopig nog niet duidelijk is. Waarschijnlijk is me iets ontgaan. Dus bij twijfel raad ik aan om het volgende te gebruiken:

  • een store [Vuex] om daarin alles op te slaan wat tussen de pagina’s van de client moet worden gedeeld, en wat eventueel tussen de client en de server moet worden gedeeld;
  • een sessiecookie als de store behouden moet blijven tijdens een verzoek van de client naar de server, waarbij de sessie dan alleen de store bevat;

De voorbeelden [nuxt-05] en [nuxt-06] waren bedoeld om te laten zien hoe de continuïteit van de applicatie kan worden gewaarborgd wanneer de gebruiker de aanroep naar de server forceert door handmatig URL in te voeren. Ter herinnering: het standaardgedrag in dit geval is een herstart van de applicatie, waarbij de huidige status verloren gaat.