Skip to content

8. Esempio [nuxt-05]: Persistenza dello store con un cookie di sessione

Obiettivo: vogliamo che lo store [Vuex] non venga resettato ad ogni richiesta al server. Per ottenere questo risultato, useremo un cookie di sessione:

  • lo store verrà inizializzato dal server e memorizzato da quest'ultimo in un cookie di sessione;
  • il browser del client riceverà questo cookie di sessione e lo invierà automaticamente ad ogni nuova richiesta al server;
  • il server potrà quindi recuperare questo cookie di sessione e lavorare con lo store in esso contenuto, uno store aggiornato dal client;

8.1. Panoramica

Il progetto [nuxt-05] viene inizialmente creato clonando il progetto [nuxt-04]:

Image

Vedremo che cambierà solo il file [store/index.js].

Per utilizzare i cookie con [nuxt], useremo il modulo [cookie-universal-nuxt], che installiamo con [yarn] in un terminale VSCode:

Image

  • In [4], digita il comando [yarn add cookie-universal-nuxt];

Viene così aggiunto un nuovo modulo al file [package.json] del progetto [dvp]:


...
},
  "dependencies": {
    "@nuxtjs/axios": "^5.3.6",
    "bootstrap": "^4.1.3",
    "bootstrap-vue": "^2.0.0",
    "cookie-universal-nuxt": "^2.0.19",
    "nuxt": "^2.0.0"
},

8.2. Il file di configurazione [nuxt.config.js]

Affinché [nuxt] possa utilizzare i cookie di [cookie-universal-nuxt], è necessario dichiarare questo modulo nel file di configurazione [nuxt.config.js]:


...
],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // Doc: https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    // https://www.npmjs.com/package/cookie-universal-nuxt
    'cookie-universal-nuxt'
  ],
...

  • riga 12, il modulo [cookie-universal-nuxt] viene aggiunto all'array dei moduli [nuxt] [6];

Il file [nuxt.config.js] risulta infine come segue:


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: { color: '#fff' },
  /*
   ** Global CSS
   */
  css: [],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [],
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module'
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // Doc: https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // Doc: 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) {}
  },
  // source code directory
  srcDir: 'nuxt-05',
  // router
  router: {
    // application URL root
    base: '/nuxt-05/'
  },
  // server
  server: {
    // service port, default 3000
    port: 81,
    // network addresses listened to, default localhost: 127.0.0.1
    // 0.0.0.0 = all the machine's network addresses
    host: 'localhost'
  },
  // environment
  env: {
    maxAge: 60 * 5
  }
}
  • riga 79: abbiamo aggiunto la chiave [env] al file. Questa chiave è una parola riservata. Gli elementi dichiarati in questo oggetto sono disponibili tramite l'oggetto [context.env] negli elementi dell'applicazione;
  • riga 80: l'attributo [maxAge] sarà la durata massima del cookie di sessione, misurata dall'ultima volta in cui il cookie è stato inizializzato. Questa durata è espressa in secondi. Qui abbiamo impostato una durata di 5 minuti;

8.3. Il principio della persistenza della memoria

I cookie scambiati tra il client e il server sono disponibili su entrambe le parti (client e server) in:

  • [context.app.$cookies] ovunque sia disponibile l'oggetto [context], ovvero quasi ovunque;
  • [this.$cookies] all'interno di una vista;

Un cookie specifico viene recuperato utilizzando l'espressione [...$cookies.get('cookie_name)']. Il valore di un cookie viene impostato utilizzando l'espressione [...$cookies.set('cookie_name', cookie_value)].

Il principio alla base del cookie di persistenza dello store è il seguente:

  • quando il server inizializza lo store nella funzione [nuxtServerInit], lo stato dello store verrà memorizzato in un cookie denominato ‘session’;
  • il cookie "session" farà quindi parte della risposta HTTP del server. Sappiamo che un browser rinvia al server i cookie che il server gli ha inviato. Lo fa con ogni nuova richiesta che effettua al server. Sappiamo anche che il server invia lo store all'interno della pagina che invia al client;
  • all'interno del browser, l'applicazione client recupera lo store inviato dal server e quindi esegue le sue attività. Ci assicureremo che ogni volta che modifica lo store, il suo nuovo stato venga memorizzato nel cookie "session" salvato dal browser;
  • Se l'utente forza una richiesta al server, il browser client invierà automaticamente tutti i cookie che il server gli ha precedentemente inviato, incluso il cookie denominato "session";
  • quando, a seguito di questa richiesta, il server reimposta nuovamente lo store, recupererà il cookie "session" e inizializzerà lo stato dello store con il suo valore;
  • ci sarà quindi continuità dell'archivio tra il client e il server;

8.4. Inizializzazione dello store

L'archivio è implementato nel file [store / index.js]:


/* eslint-disable no-console */
export const state = () => ({
  // meter
  counter: 0
})
 
export const mutations = {
  // increment counter by one [inc] value
  increment(state, inc) {
    state.counter += inc
  },
  // state replacement
  replace(state, newState) {
    for (const attr in newState) {
      state[attr] = newState[attr]
    }
  }
}
 
export const actions = {
  async nuxtServerInit(store, context) {
    // who executes this code?
    console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
    // waiting for a promise to be fulfilled
    await new Promise(function(resolve, reject) {
      // this is normally an asynchronous function
      // we simulate it with a one-second wait
      setTimeout(() => {
        // init session
        initStore(store, context)
        // success
        resolve()
      }, 1000)
    })
  }
}
 
function initStore(store, context) {
  // is there a session cookie in the current request?
  const cookies = context.app.$cookies
  const session = cookies.get('session')
  if (!session) {
    // no existing session
    console.log("nuxtServerInit, initialisation d'une nouvelle session")
    // initialize the blind
    store.commit('increment', 77)
  } else {
    console.log("nuxtServerInit, reprise d'une session existante")
    // update the store with the session cookie
    store.commit('replace', session.store)
  }
  // put the store in the session cookie
  cookies.set('session', { store: store.state }, { path: context.base, maxAge: context.env.maxAge })
  // log
  console.log('initStore terminé, store=', store.state)
}

Commenti

  • righe 2-5: il negozio sarà costituito da un contatore;
  • righe 9–11: questo contatore può essere incrementato;
  • righe 13-17: lo stato dello store può essere inizializzato da un nuovo stato. Questa funzione è inclusa per dimostrare una possibile inizializzazione dello store quando non è limitato a un semplice contatore, come in questo esempio;
  • righe 21–35: la funzione [nuxtServerInit] non è cambiata;
  • riga 30: allo scadere del timeout di un secondo, lo store viene inizializzato utilizzando la funzione alle righe 38–56;
  • righe 40–41: iniziamo recuperando il cookie denominato "session":
    1. durante la prima esecuzione dell'applicazione e durante la prima richiesta effettuata al server, questo cookie non esisterà ancora. Verrà quindi creato (riga 53) e inviato al browser del client;
    2. durante la stessa esecuzione dell'applicazione e durante le richieste n. 2, n. 3, ... effettuate al server, questo cookie esisterà perché il browser del client lo invierà nuovamente ad ogni nuova richiesta effettuata al server;
    3. Durante una seconda esecuzione dell'applicazione e alla prima richiesta effettuata al server, questo cookie potrebbe anche esistere. Infatti, al termine del passaggio 1, il cookie è stato memorizzato sul browser con una durata specifica. Se tale durata non è scaduta, il cookie denominato "session" verrà inviato con la prima richiesta effettuata al server

In sintesi, per ogni richiesta effettuata al server: se il cookie "session" è già memorizzato nel browser del client, il server lo riceverà; altrimenti, non lo riceverà.

  • Righe 42–47: Se il server non riceve il cookie di sessione, lo store viene inizializzato dalla riga 46;
    • quindi, alla riga 53, verrà creato un cookie denominato "session" e inserito nella risposta HTTP del server. Il valore del cookie è l'oggetto [{ store: store.state }]. È quindi lo stato dello store, e non lo store stesso, che viene inserito nel cookie di sessione;
    • il terzo parametro della funzione [set] è un oggetto options:
      • [path] specifica l'URL a cui questo cookie deve essere inviato. [context.base] è l'URL di base dell'applicazione [nuxt-05]. Questo è definito nel file [nuxt.config.js]:

  // router
  router: {
    // application URL root
    base: '/nuxt-05/'
},
      • [maxAge] è la durata del cookie in secondi sul browser. Trascorso questo tempo, il browser non lo invia più al server. [context.env.maxAge] restituisce un valore specificato nel file [nuxt.config.js]:
1
2
3
4
// environnement
  env: {
    maxAge: 60 * 5
}

[env] è una parola chiave riservata nel file di configurazione. Qui impostiamo la durata a 5 minuti. Questa durata viene misurata dall'ultima volta che il browser ha ricevuto il cookie di sessione. Trascorso questo tempo, il cookie non verrà più inviato al server, che dovrà quindi avviare una nuova sessione;

  • righe 48–50: se il server riceve il cookie di sessione, lo stato dello store viene inizializzato con l'oggetto [store] proveniente dal cookie di sessione. Ricordiamo che questo oggetto contiene lo stato salvato dello store;
    • quindi, alla riga 53, il cookie di sessione viene inserito nella risposta inviata al browser del cliente:
      • la funzione [get] recupera il cookie di sessione dalla richiesta ricevuta dal server;
      • la funzione [set] inserisce il cookie di sessione nella risposta che il server invia al browser del client;

8.5. Incremento del contatore dello store

L'incremento del contatore nella pagina [index.vue] funziona come segue:


  // event management
  methods: {
    incrementCounter() {
      console.log('incrementCounter')
        // counter increment of 1
      this.$store.commit('increment', 1)
       // change display value
      this.value = this.$store.state.counter
       // store in session cookie
      this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
    }
}

Sul lato client, ogni volta che lo store viene modificato, deve essere salvato nel cookie di sessione. Questo perché l'utente può richiedere manualmente un URL in qualsiasi momento e noi dobbiamo quindi essere in grado di inviare uno store aggiornato al server. Ecco perché, alla riga 10, dopo aver incrementato il contatore dello store, salviamo il suo stato nel cookie di sessione:

  • i cookie sono disponibili nella proprietà [this.$cookies];
  • lo stato dello store [this.$store.state] viene salvato nel cookie associato alla chiave [store];
  • il percorso del cookie è [context.base]. In una vista, il contesto è disponibile in [this.$nuxt.context];
  • La durata del cookie è [context.env.maxAge], disponibile qui nella proprietà [this.$nuxt.context.env.maxAge];

8.6. Esecuzione dell'esempio [nuxt-05]

Avviamo l'applicazione [nuxt-05]:

Image

Le schermate che seguono sono state catturate dal browser Chrome. Richiediamo l'URL [http://localhost:81/nuxt-05/]. Non dimenticare il / finale dopo /nuxt-05, altrimenti non otterrai i risultati previsti:

Image

  • in [4], abbiamo ottenuto il valore iniziale dello store (77);

Esaminiamo i log del browser (F12):

Image

  • in [5-6], i log del server;
  • in [7], vediamo che il server sta avviando una nuova sessione. Ciò significa che non ha ricevuto un cookie di sessione;
  • in [8], inizializzazione del contatore con il valore 77;
  • in [9], la pagina [index] del server (9) e la pagina [index] del client (10) mostrano entrambe lo stesso valore del contatore;

Ora diamo un'occhiata ai cookie ricevuti dal browser:

Image

  • In [1], seleziona la scheda [Applicazione], quindi l'opzione [Cookie] [2]. Tra tutti i cookie presenti nel tuo browser, seleziona quello proveniente dal dominio [http://localhost:81];
  • in [4], il cookie denominato "session". Se non lo vedi, ricarica la pagina [F5]: forse la sua durata di 5 minuti è scaduta;
  • in [5], il valore del cookie. Sebbene non sia molto leggibile a causa della codifica dei caratteri { :, è possibile distinguere il valore 77 dal contatore;
  • in [6], l'URL del cookie: ogni volta che questo URL viene richiesto, il browser invierà il cookie al server;
  • in [7], il tempo di scadenza del cookie. Una volta trascorso questo tempo, il cookie verrà eliminato dal browser;

Assicurati di avere questo cookie. Se non ce l'hai, ricarica la pagina (F5). Una volta che hai la pagina con il cookie, ricarica di nuovo la pagina (F5). I log appariranno quindi così:

Image

Questa volta, in [3], il server ha recuperato con successo il cookie di sessione. È stato inviato dal browser del client.

Ora, incrementa il contatore, quindi ricarica di tanto in tanto la pagina corrente (F5) — che sia [index] o [page1] — e dovresti notare che il contatore non si azzera a 77 come nell'esempio [nuxt-04], ma mantiene il valore che aveva nel browser del client prima che la pagina venisse ricaricata:

Image

Image

I log del browser sono quindi i seguenti:

Image

Nota: a scopo di test, potrebbe essere necessario eliminare [5] il cookie di sessione memorizzato nel browser per avviare una nuova sessione, inizializzata dal server, durante la successiva richiesta inviata ad esso.

Infine, dimostriamo l'effetto della funzione [incrementCounter] nella pagina [index] sul cookie di sessione memorizzato nel browser del client:


// event management
  methods: {
    incrementCounter() {
      console.log('incrementCounter')
      // counter increment of 1
      this.$store.commit('increment', 1)
      // change display value
      this.value = this.$store.state.counter
      // store in session cookie
      this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
    }
  }
  • Riga 10: La modifica al contatore viene riportata nel cookie di sessione;

Verifichiamolo. Partiamo dalla seguente situazione:

Image

  • in [4], il contatore del cookie di sessione riflette correttamente il valore visualizzato [1];

Ora, incrementiamo il contatore di una volta [5]. Il cookie di sessione in [4] cambia come segue:

Image

  • in [7], il contatore del cookie di sessione è effettivamente cambiato a 84. Per verificarlo, è necessario aggiornare la visualizzazione [8]. A tal fine, selezionare un'altra opzione da [Storage] [9], quindi riselezionare l'opzione [8]. Dovrebbe quindi apparire il nuovo valore del cookie di sessione;