Skip to content

8. Exemplo [nuxt-05]: Persistência do store com um cookie de sessão

Objetivo: Queremos que o armazenamento [Vuex] não seja reiniciado a cada solicitação ao servidor. Para isso, utilizaremos um cookie de sessão:

  • o armazenamento será inicializado pelo servidor e armazenado por este num cookie de sessão;
  • o navegador do cliente receberá este cookie de sessão e enviá-lo-á automaticamente com cada nova solicitação ao servidor;
  • o servidor pode então recuperar este cookie de sessão e trabalhar com o armazenamento que ele contém, um armazenamento atualizado pelo cliente;

8.1. Visão geral

O projeto [nuxt-05] é inicialmente criado através da clonagem do projeto [nuxt-04]:

Image

Veremos que apenas o ficheiro [store/index.js] será alterado.

Para utilizar cookies com o [nuxt], utilizaremos o módulo [cookie-universal-nuxt], que instalamos com o [yarn] num terminal do VSCode:

Image

  • Em [4], digite o comando [yarn add cookie-universal-nuxt];

Um novo módulo é assim adicionado ao ficheiro [package.json] do projeto [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. O ficheiro de configuração [nuxt.config.js]

Para que o [nuxt] utilize cookies do [cookie-universal-nuxt], deve declarar este módulo no ficheiro de configuração [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'
  ],
...

  • na linha 12, o módulo [cookie-universal-nuxt] é adicionado à matriz de módulos [nuxt] [6];

O ficheiro [nuxt.config.js] fica, por fim, da seguinte forma:


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
  }
}
  • linha 79: adicionámos a chave [env] ao ficheiro. Esta chave é uma palavra reservada. Os elementos declarados neste objeto estão disponíveis através do objeto [context.env] nos elementos da aplicação;
  • linha 80: o atributo [maxAge] será o tempo de vida máximo do cookie de sessão, medido a partir da última vez que o cookie foi inicializado. Esta duração é expressa em segundos. Aqui, definimos um tempo de vida de 5 minutos;

8.3. O princípio da persistência do armazenamento

Os cookies trocados entre o cliente e o servidor estão disponíveis em ambos os lados (cliente e servidor) em:

  • [context.app.$cookies] sempre que o objeto [context] estiver disponível, ou seja, em quase todo o lado;
  • [this.$cookies] dentro de uma vista;

Um cookie específico é recuperado utilizando a expressão [...$cookies.get('cookie_name')]. O valor de um cookie é definido utilizando a expressão [...$cookies.set('cookie_name', cookie_value)].

O princípio subjacente ao cookie de persistência da loja é o seguinte:

  • quando o servidor inicializa o store na função [nuxtServerInit], o estado do store será armazenado num cookie chamado «session»;
  • o cookie «session» passará então a fazer parte da resposta HTTP do servidor. Sabemos que um navegador reenvia ao servidor os cookies que o servidor lhe enviou. Faz isso com cada novo pedido que faz ao servidor. Sabemos também que o servidor envia o armazenamento dentro da página que envia ao cliente;
  • dentro do navegador, a aplicação cliente recupera o store enviado pelo servidor e, em seguida, executa as suas tarefas. Asseguraremos que, sempre que modificar o store, o seu novo estado seja armazenado no cookie «session» guardado pelo navegador;
  • Se o utilizador forçar uma solicitação ao servidor, o navegador do cliente reenviará automaticamente todos os cookies que o servidor lhe enviou anteriormente, incluindo o cookie denominado «session»;
  • quando, na sequência desta solicitação, o servidor reiniciar o armazenamento novamente, ele recuperará o cookie «session» e inicializará o estado do armazenamento com o seu valor;
  • haverá, portanto, continuidade do armazenamento entre o cliente e o servidor;

8.4. Inicialização do armazenamento

O armazenamento está implementado no ficheiro [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)
}

Comentários

  • linhas 2-5: a loja consistirá num balcão;
  • linhas 9–11: este contador pode ser incrementado;
  • linhas 13-17: o estado do store pode ser inicializado a partir de um novo estado. Esta função foi incluída para demonstrar uma possível inicialização do store quando este não se limita apenas a um contador, como neste exemplo;
  • linhas 21–35: a função [nuxtServerInit] não sofreu alterações;
  • linha 30: quando o tempo limite de um segundo tiver decorrido, a loja é inicializada utilizando a função nas linhas 38–56;
  • linhas 40–41: começamos por recuperar o cookie denominado «session»:
    1. durante a primeira execução da aplicação e durante o primeiro pedido feito ao servidor, este cookie ainda não existirá. Será então criado (linha 53) e enviado para o navegador do cliente;
    2. durante a mesma execução da aplicação e durante as solicitações n.º 2, n.º 3, ... feitas ao servidor, este cookie existirá porque o navegador do cliente o reenviará com cada nova solicitação feita ao servidor;
    3. Durante uma segunda execução da aplicação e na primeira solicitação feita ao servidor, este cookie também pode existir. De facto, no final do passo 1, o cookie foi armazenado no navegador com um tempo de vida específico. Se este tempo de vida não tiver expirado, o cookie chamado «session» será enviado com a primeira solicitação feita ao servidor

Em resumo, para cada pedido feito ao servidor: se o cookie «session» já estiver armazenado no navegador do cliente, o servidor irá recebê-lo; caso contrário, não o receberá.

  • Linhas 42–47: Se o servidor não receber o cookie de sessão, então o store é inicializado pela linha 46;
    • depois, na linha 53, será criado um cookie chamado «session» e colocado na resposta HTTP do servidor. O valor do cookie é o objeto [{ store: store.state }]. É, portanto, o estado do armazenamento, e não o próprio armazenamento, que é colocado no cookie de sessão;
    • o terceiro parâmetro da função [set] é um objeto de opções:
      • [path] especifica a URL para a qual este cookie deve ser enviado. [context.base] é a URL base da aplicação [nuxt-05]. Esta é definida no ficheiro [nuxt.config.js]:

  // router
  router: {
    // application URL root
    base: '/nuxt-05/'
},
      • [maxAge] é o tempo de vida do cookie em segundos no navegador. Após esse tempo, o navegador deixa de o enviar de volta ao servidor. [context.env.maxAge] devolve um valor especificado no ficheiro [nuxt.config.js]:
1
2
3
4
// environnement
  env: {
    maxAge: 60 * 5
}

[env] é uma palavra-chave reservada no ficheiro de configuração. Aqui, definimos o tempo de vida para 5 minutos. Esta duração é medida a partir da última vez que o navegador recebeu o cookie de sessão. Após este período, o cookie não será reenviado para o servidor, que terá então de iniciar uma nova sessão;

  • linhas 48–50: se o servidor receber o cookie de sessão, o estado da loja é inicializado com o objeto [store] proveniente do cookie de sessão. Recorde-se que este objeto contém o estado guardado da loja;
    • depois, na linha 53, o cookie de sessão é colocado na resposta enviada ao navegador do cliente:
      • a função [get] recupera o cookie de sessão da solicitação recebida pelo servidor;
      • a função [set] coloca o cookie de sessão na resposta que o servidor envia ao navegador do cliente;

8.5. Incrementar o contador da loja

O incremento do contador na página [index.vue] funciona da seguinte forma:


  // 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 })
    }
}

No lado do cliente, sempre que o store é modificado, deve ser guardado no cookie de sessão. Isto porque o utilizador pode solicitar manualmente um URL a qualquer momento, e temos de ser capazes de enviar um store atualizado para o servidor. É por isso que, na linha 10, após incrementar o contador do store, guardamos o seu estado no cookie de sessão:

  • os cookies estão disponíveis na propriedade [this.$cookies];
  • o estado do store [this.$store.state] é guardado no cookie associado à chave [store];
  • o caminho do cookie é [context.base]. Numa vista, o contexto está disponível em [this.$nuxt.context];
  • A duração do cookie é [context.env.maxAge], que está disponível aqui na propriedade [this.$nuxt.context.env.maxAge];

8.6. Executar o exemplo [nuxt-05]

Lançamos a aplicação [nuxt-05]:

Image

As capturas de ecrã seguintes foram tiradas no navegador Chrome. Solicitamos o URL [http://localhost:81/nuxt-05/]. Não se esqueça do / final após /nuxt-05, caso contrário não obterá os resultados esperados:

Image

  • em [4], obtivemos o valor inicial do store (77);

Vamos examinar os registos do navegador (F12):

Image

  • em [5-6], os registos do servidor;
  • em [7], vemos que o servidor está a iniciar uma nova sessão. Isto significa que não recebeu um cookie de sessão;
  • em [8], inicialização do contador com o valor 77;
  • em [9], a página [index] do servidor (9) e a página [index] do cliente (10) apresentam ambas o mesmo valor do contador;

Agora, vamos ver os cookies recebidos pelo navegador:

Image

  • Em [1], selecione o separador [Aplicação] e, em seguida, a opção [Cookies] [2]. De todos os cookies no seu navegador, selecione aquele do domínio [http://localhost:81];
  • em [4], o cookie chamado «session». Se não o vir, atualize a página [F5]: talvez o seu tempo de vida de 5 minutos tenha expirado;
  • em [5], o valor do cookie. Embora não seja muito legível devido à codificação dos caracteres { :, é possível distinguir o valor 77 do contador;
  • em [6], o URL do cookie: sempre que este URL for solicitado, o navegador enviará o cookie para o servidor;
  • em [7], a hora de expiração do cookie. Assim que este tempo tiver passado, o cookie será eliminado do navegador;

Certifique-se de que tem este cookie. Se não o tiver, atualize a página (F5). Assim que tiver a página com o cookie, atualize a página novamente (F5). Os registos ficarão então assim:

Image

Desta vez, em [3], o servidor recuperou com sucesso o cookie de sessão. Este foi-lhe enviado pelo navegador do cliente.

Agora, aumente o contador e, de vez em quando, atualize a página atual (F5) — seja ela [index] ou [page1] — e deverá notar que o contador não volta a 77 como no exemplo [nuxt-04], mas mantém o valor que tinha no navegador do cliente antes de a página ser atualizada:

Image

Image

Os registos do navegador ficam então assim:

Image

Nota: Para efeitos de teste, poderá ser necessário eliminar [5] o cookie de sessão armazenado no navegador para iniciar uma nova sessão, inicializada pelo servidor, durante o próximo pedido de acesso ao mesmo.

Por fim, vamos demonstrar o efeito da função [incrementCounter] na página [index] sobre o cookie de sessão armazenado no navegador do cliente:


// 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 })
    }
  }
  • Linha 10: A alteração no contador é refletida no cookie de sessão;

Vamos verificar isto. Começamos com a seguinte situação:

Image

  • em [4], o contador do cookie de sessão reflete corretamente o valor exibido [1];

Agora, vamos incrementar o contador uma vez [5]. O cookie de sessão em [4] muda da seguinte forma:

Image

  • em [7], o contador do cookie de sessão mudou efetivamente para 84. Para ver isso, é necessário atualizar a visualização [8]. Para tal, selecione outra opção em [Armazenamento] [9] e, em seguida, volte a selecionar a opção [8]. O novo valor do cookie de sessão deverá então aparecer;