8. Przykład [nuxt-05]: zachowanie stanu magazynu danych za pomocą pliku cookie sesji
Cel: chcielibyśmy, aby magazyn [Vuex] nie był resetowany przy każdym żądaniu wysyłanym do serwera. W tym celu wykorzystamy plik cookie sesji:
- magazyn danych zostanie zainicjowany przez serwer i umieszczony przez niego w pliku cookie sesji;
- przeglądarka klienta otrzyma ten plik cookie sesji i automatycznie będzie go wysyłać przy każdym nowym żądaniu kierowanym do serwera;
- serwer będzie mógł wówczas odzyskać ten plik cookie sesji i pracować z zawartym w nim magazynem danych, zaktualizowanym przez klienta;
8.1. Présentation
Projekt [nuxt-05] powstaje początkowo poprzez skopiowanie projektu [nuxt-04]:

Zobaczymy, że zmieni się tylko plik [store / index.js].
Aby korzystać z plików cookie w projekcie [nuxt], wykorzystamy moduł [cookie-universal-nuxt], który instalujemy wraz z [yarn] w terminalu VSCode:

- w [4] wpisujemy polecenie [yarn add cookie-universal-nuxt];
W ten sposób do pliku [package.json] projektu [dvp] dodawany jest nowy moduł:
...
},
"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. Plik konfiguracyjny [nuxt.config.js]
Aby moduł [nuxt] mógł korzystać z plików cookie modułu [cookie-universal-nuxt], należy zadeklarować ten moduł w pliku konfiguracyjnym [nuxt.config.js]:
...
],
/*
** 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'
],
...
- w wierszu 12 moduł [cookie-universal-nuxt] zostaje dodany do tablicy modułów [6] modułu [nuxt];
Ostateczna wersja pliku [nuxt.config.js] wygląda następująco:
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: [
// 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-05',
// router
router: {
// katalog główny aplikacji URL
base: '/nuxt-05/'
},
// serwer
server: {
// port usługi, domyślnie 3000
port: 81,
// adresy sieciowe, na których nasłuchuje, domyślnie localhost: 127.0.0.1
// 0.0.0.0 = wszystkie adresy sieciowe komputera
host: 'localhost'
},
// środowisko
env: {
maxAge: 60 * 5
}
}
- wiersz 79: do pliku dodano klucz [env]. Klucz ten jest słowem zastrzeżonym. Elementy zadeklarowane w tym obiekcie są dostępne poprzez obiekt [context.env] w elementach aplikacji;
- wiersz 80: atrybut [maxAge] określa maksymalny czas życia pliku cookie sesji, mierzony od momentu ostatniej inicjalizacji pliku cookie. Czas ten jest wyrażony w sekundach. W tym przypadku ustawiono czas życia wynoszący 5 minut;
8.3. Zasada trwałości pamięci
Pliki cookie wymieniane między klientem a serwerem są dostępne po obu stronach (klienta i serwera) w:
- [context.app.$cookies] tam, gdzie dostępny jest obiekt [context], czyli praktycznie wszędzie;
- [this.$cookies] wewnątrz widoku;
Określony plik cookie uzyskuje się za pomocą wyrażenia [...$cookies.get(‘nom_du_cookie’)]. Wartość pliku cookie ustawia się za pomocą wyrażenia [...$cookies.set(‘nom_du_cookie’, valeur_du_cookie)].
Zasada działania pliku cookie przechowującego stan będzie następująca:
- gdy serwer zainicjuje magazyn w funkcji [nuxtServerInit], stan magazynu zostanie zapisany w pliku cookie o nazwie „session”;
- plik cookie „session” stanie się wówczas częścią odpowiedzi serwera o nazwie HTTP. Wiadomo, że przeglądarka odsyła do serwera pliki cookie, które ten jej przesłał. Czyni to przy każdym nowym żądaniu kierowanym do serwera. Wiadomo również, że serwer wysyła magazyn danych wraz ze stroną, którą przesyła do klienta;
- w przeglądarce aplikacja kliencka pobiera obiekt „store” przesłany przez serwer, a następnie wykonuje swoje zadanie. Zadbamy o to, aby za każdym razem, gdy aplikacja modyfikuje obiekt „store”, jego nowy stan był zapisywany w pliku cookie o nazwie „session” przechowywanym przez przeglądarkę;
- jeśli użytkownik wymusi wywołanie serwera, przeglądarka kliencka automatycznie odeśle wszystkie pliki cookie, które serwer wcześniej jej przesłał, w szczególności plik cookie o nazwie „session”;
- gdy w wyniku tego wywołania serwer ponownie zresetuje magazyn, pobierze plik cookie o nazwie „session” i zainicjuje stan magazynu na podstawie jego wartości;
- w ten sposób zapewniona zostanie ciągłość stanu pamięci między klientem a serwerem;
8.4. Inicjalizacja pamięci
Pamięć jest zaimplementowana w pliku [store / index.js]:
/* eslint-disable no-console */
export const state = () => ({
// licznik
counter: 0
})
export const mutations = {
// zwiększenie licznika o jedną wartość [inc]
increment(state, inc) {
state.counter += inc
},
// zmiana stanu
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// kto wykonuje ten kod?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// czekamy na zakończenie obietnicy
await new Promise(function(resolve, reject) {
// zazwyczaj mamy tu do czynienia z funkcją asynchroniczną
// symulujemy ją, wprowadzając sekundę oczekiwania
setTimeout(() => {
// inicjowanie sesji
initStore(store, context)
// powodzenie
resolve()
}, 1000)
})
}
}
function initStore(store, context) {
// czy w bieżącym żądaniu znajduje się plik cookie sesji
const cookies = context.app.$cookies
const session = cookies.get('session')
if (!session) {
// nie ma istniejącej sesji
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// inicjujemy magazyn
store.commit('increment', 77)
} else {
console.log("nuxtServerInit, reprise d'une session existante")
// aktualizujemy magazyn za pomocą pliku cookie sesji
store.commit('replace', session.store)
}
// zapisywanie pamięci w pliku cookie sesji
cookies.set('session', { store: store.state }, { path: context.base, maxAge: context.env.maxAge })
// log
console.log('initStore terminé, store=', store.state)
}
Komentarze
- wiersze 2–5: magazyn będzie składał się z licznika;
- wiersze 9–11: licznik ten będzie można zwiększać;
- wiersze 13–17: stan magazynu danych można zainicjować na podstawie nowego stanu. Funkcja ta ma na celu pokazanie możliwej inicjalizacji magazynu danych, gdy nie ogranicza się on wyłącznie do licznika, jak w tym przypadku;
- wiersze 21–35: funkcja [nuxtServerInit] nie uległa zmianie;
- wiersz 30: po upływie sekundowego czasu oczekiwania inicjujemy pamięć za pomocą funkcji z wierszy 38–56;
- wiersze 40–41: najpierw pobierany jest plik cookie o nazwie „session”:
- podczas pierwszego uruchomienia aplikacji oraz przy pierwszym żądaniu wysłanym do serwera plik ten jeszcze nie będzie istniał. Zostanie wówczas utworzony (wiersz 53) i wysłany do przeglądarki klienta;
- podczas tego samego uruchomienia aplikacji oraz przy żądaniach nr 2, 3, ... kierowanych do serwera ten plik cookie będzie już istniał, ponieważ przeglądarka klienta będzie go odsyłać wraz z każdym nowym żądaniem kierowanym do serwera;
- podczas drugiego uruchomienia aplikacji i przy pierwszym żądaniu wysłanym do serwera ten plik cookie może również istnieć. W rzeczywistości po zakończeniu etapu 1 plik cookie został zapisany w przeglądarce z określonym czasem życia. Jeśli ten czas życia nie upłynął, plik cookie o nazwie „session” zostanie wysłany wraz z pierwszym żądaniem wysłanym do serwera
Podsumowując, dla każdego żądania wysłanego do serwera: jeśli plik cookie o nazwie „session” jest już zapisany w przeglądarce klienta, serwer go otrzyma; w przeciwnym razie nie otrzyma go.
- wiersze 42–47: jeśli serwer nie otrzyma pliku cookie sesji, wówczas zmienna `store` jest inicjowana w wierszu 46;
- następnie w wierszu 53 zostanie utworzony plik cookie o nazwie „session” i umieszczony w odpowiedzi serwera HTTP. Wartością tego pliku cookie jest obiekt [{ store: store.state }]. W pliku cookie sesji umieszczany jest zatem stan magazynu, a nie sam magazyn;
- trzecim parametrem funkcji [set] jest obiekt opcji:
- [path] wskazuje, do którego obiektu URL ten plik cookie ma zostać odesłany. [context.base] jest podstawowym obiektem URL aplikacji [nuxt-05]. Jest on zdefiniowany w pliku [nuxt.config.js]:
// router
router: {
// katalog główny plików URL aplikacji
base: '/nuxt-05/'
},
- [maxAge] to czas życia pliku cookie w przeglądarce wyrażony w sekundach. Po upływie tego czasu przeglądarka nie wysyła go już z powrotem do serwera. [context.env.maxAge] ponownie zwraca wartość zapisaną w pliku [nuxt.config.js]:
[env] to zarezerwowane słowo kluczowe w pliku konfiguracyjnym. W tym miejscu ustawia się czas życia na 5 minut. Czas ten jest mierzony od momentu, kiedy przeglądarka ostatnio otrzymała plik cookie sesji. Po upływie tego czasu plik cookie nie zostanie odesłany do serwera, który będzie musiał wówczas rozpocząć nową sesję;
- wiersze 48–50: jeśli serwer otrzyma plik cookie sesji, wówczas stan magazynu jest inicjowany za pomocą obiektu [store] z pliku cookie sesji. Należy pamiętać, że obiekt ten zawiera zapisany stan magazynu;
- następnie w wierszu 53 plik cookie sesji zostanie umieszczony w odpowiedzi wysyłanej do przeglądarki klienta:
- funkcja [get] pobiera plik cookie sesji z żądania otrzymanego przez serwer;
- funkcja [set] umieszcza plik cookie sesji w odpowiedzi, którą serwer wysyła do przeglądarki klienta;
- następnie w wierszu 53 plik cookie sesji zostanie umieszczony w odpowiedzi wysyłanej do przeglądarki klienta:
8.5. Zwiększanie wartości licznika sklepu
Inkrementacja licznika na stronie [index.vue] przebiega w następujący sposób:
// zarządzanie zdarzeniami
methods: {
incrementCounter() {
console.log('incrementCounter')
// zwiększenie licznika o 1
this.$store.commit('increment', 1)
// zmiana wyświetlanej wartości
this.value = this.$store.state.counter
// zapisanie stanu w pliku cookie sesji
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
Po stronie klienta za każdym razem, gdy modyfikuje się stan magazynu, należy go zapisać w pliku cookie sesji. Użytkownik może bowiem w dowolnym momencie ręcznie zażądać wysłania żądania URL, a my musimy być w stanie wysłać do serwera aktualny stan magazynu. Dlatego w linii 10, po zwiększeniu wartości licznika store, zapisujemy jego stan w pliku cookie sesji:
- pliki cookie są dostępne we właściwości [this.$cookies];
- stan tablicy [this.$store.state] jest zapisany w pliku cookie powiązanym z kluczem [store];
- ścieżka pliku cookie to [context.base]. W widoku kontekst jest dostępny w [this.$nuxt.context];
- czas życia pliku cookie to [context.env.maxAge], dostępny tutaj we właściwości [this.$nuxt.context.env.maxAge];
8.6. Uruchomienie przykładu [nuxt-05]
Uruchamiamy aplikację [nuxt-05]:

Poniższe zrzuty ekranu pochodzą z przeglądarki Chrome. Wpisujemy adres URL [http://localhost:81/nuxt-05/]. Nie zapomnij o ostatnim znaku „/” po „/nuxt-05”, w przeciwnym razie nie uzyskasz oczekiwanych wyników:

- w [4] uzyskaliśmy początkową wartość zmiennej store (77);
Przyjrzyjmy się logom przeglądarki (F12):

- w [5-6] – logi serwera;
- w [7] widać, że serwer rozpoczyna nową sesję. Oznacza to, że nie otrzymał pliku cookie sesji;
- w pliku [8] następuje zainicjowanie licznika wartością 77;
- w pliku [9] strona [index] na serwerze (9) i strona klienta (10) wyświetlają tę samą wartość licznika;
Teraz przyjrzyjmy się plikom cookie otrzymanym przez przeglądarkę:

- w [1] wybierz zakładkę [Application], a następnie opcję [Cookies] [2]. Spośród wszystkich plików cookie w przeglądarce wybierz ten z domeny [http://localhost:81];
- w [4] plik cookie o nazwie „session”. Jeśli go nie masz, odśwież stronę [F5]: być może upłynął już jego czas życia, który wynosi 5 minut;
- w pliku [5] wartość pliku cookie. Chociaż nie jest ona zbyt czytelna z powodu kodowania znaków { :, można dostrzec wartość 77 licznika;
- w pliku [6] – identyfikator pliku cookie URL: za każdym razem, gdy zostanie wysłane żądanie dotyczące tego identyfikatora URL, przeglądarka wyśle plik cookie do serwera;
- w [7] – czas wygaśnięcia pliku cookie. Po upływie tego czasu plik cookie zostanie usunięty z przeglądarki;
Upewnij się, że posiadasz ten plik cookie. Jeśli go nie masz, odśwież stronę (F5). Gdy strona wyświetli się wraz z plikiem cookie, odśwież ją ponownie (F5). Wlogi będą wtedy wyglądały następująco:

Tym razem w pliku [3] serwer pomyślnie pobrał plik cookie sesji. Przesłała mu go przeglądarka klienta.
Teraz zwiększaj wartość licznika, a następnie od czasu do czasu odświeżaj bieżącą stronę (F5), niezależnie od tego, czy jest to [index], czy [page1], powinieneś zauważyć, że licznik nie wraca do wartości 77, jak w przykładzie [nuxt-04], ale zachowuje wartość, jaką miał w przeglądarce klienta przed odświeżeniem strony:


Logi przeglądarki wyglądają wówczas następująco:

Uwaga: w celu przeprowadzenia testów może zaistnieć potrzeba usunięcia pliku cookie sesji [5] zapisanego w przeglądarce, aby rozpocząć nową sesję, zainicjowaną przez serwer, podczas następnego żądania wysłanego do niego.
Na koniec pokażmy wpływ funkcji [incrementCounter] ze strony [index] na plik cookie sesji przechowywany w przeglądarce klienta:
// obsługa zdarzeń
methods: {
incrementCounter() {
console.log('incrementCounter')
// zwiększenie licznika o 1
this.$store.commit('increment', 1)
// zmiana wyświetlanej wartości
this.value = this.$store.state.counter
// zapisanie wartości w pliku cookie sesji
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
- wiersz 10: zmiana licznika jest odzwierciedlana w pliku cookie sesji;
Sprawdźmy to. Wychodzimy od następującej sytuacji:

- w [4] licznik w pliku cookie sesji prawidłowo odzwierciedla wyświetlaną wartość [1];
Teraz zwiększmy licznik o jeden: [5]. Plik cookie sesji o wartości [4] zmienia się w następujący sposób:

- na [7], licznik pliku sesyjnego rzeczywiście wzrósł do 84. Aby to sprawdzić, należy odświeżyć widok [8]. W tym celu należy wybrać inną opcję z widoku [Storage] lub [9], a następnie ponownie wybrać opcję [8]. Powinna wtedy pojawić się nowa wartość pliku cookie sesji;