16. Exemplo [nuxt-13]: verificação da navegação de [nuxt-12]
Neste exemplo, estamos interessados na navegação do [nuxt-12]. Não o fizemos no [nuxt-12] porque a verificação da navegação teria complicado um exemplo já de si complexo.
Objetivo: queremos que o utilizador só possa realizar ações autorizadas:
- se a sessão jSON não tiver sido iniciada, então apenas a sessão URL e a sessão [/] são autorizadas;
- se a sessão jSON tiver sido iniciada, mas o utilizador não estiver autenticado, então apenas as sessões URL e [/authentification] são autorizadas;
- se a sessão jSON tiver sido iniciada e o utilizador estiver autenticado, então apenas as sessões URL e [/get-admindata, /fin-session] são autorizadas;
- quando o destino do encaminhamento atual não for autorizado, proceder-se-á a um redirecionamento para uma URL autorizada;
O exemplo [nuxt-13] é obtido inicialmente por cópia do exemplo [nuxt-12]:

É na pasta de encaminhamento [middleware] que serão efetuadas as alterações.
16.1. Roteamento da aplicação [nuxt]
O encaminhamento da aplicação está configurado da seguinte forma no ficheiro [nuxt.config]:
// router
router: {
// raiz dos URL da aplicação
base: '/nuxt-13/',
// middleware de encaminhamento
middleware: ['routing']
},
- linha 6: o encaminhamento da aplicação é controlado pelo ficheiro [middleware/routing];
O ficheiro [middleware/routing] é o seguinte:
/* eslint-disable no-console */
// importamos os middlewares do servidor e do cliente
import serverRouting from './server/routing'
import clientRouting from './client/routing'
export default function(context) {
// quem executa este código?
console.log('[middleware], process.server', process.server, ', process.client=', process.client)
if (process.server) {
// roteamento do servidor
serverRouting(context)
} else {
// roteamento do cliente
clientRouting(context)
}
}
- linhas 10-16: o encaminhamento do cliente e do servidor [nuxt] é tratado de forma diferente. Este é um ponto em que diferem significativamente;
- linha 4: o encaminhamento do servidor é implementado pelo script [middleware/server/routing];
- linha 5: o encaminhamento do cliente é implementado pelo script [middleware/client/routing];
16.2. Roteamento do cliente [nuxt]
O encaminhamento do cliente [nuxt] permanece tal como estava no [nuxt-12]:
/* eslint-disable no-console */
export default function(context) {
// quem executa este código?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// gestão do cookie de sessão PHP no navegador
// o cookie de sessão PHP do navegador deve ser idêntico ao encontrado na sessão Nuxt
// a ação [fin-session] recebe um novo cookie PHP (tanto no servidor como no cliente Nuxt)
// se for o servidor a recebê-lo, o cliente deve transmiti-lo ao navegador
// para as suas próprias comunicações com o servidor PHP
// estamos aqui num encaminhamento do cliente
// recuperamos o cookie da sessão PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// se existir, atribui-se o cookie de sessão PHP ao navegador
document.cookie = phpSessionCookie
}
...
}
Para evitar que o cliente aceda a percursos não autorizados, basta apresentar-lhe, no menu de navegação do cliente, apenas os percursos autorizados. O componente [components/navigation] passa a ter o seguinte aspeto:
<template>
<!-- menu Bootstrap com três opções -->
<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>
- linha 4: a opção [Authentification] só é apresentada se a sessão jSON tiver sido iniciada, mas o utilizador não estiver autenticado. Se a sessão jSON não tiver sido iniciada ou se o utilizador já estiver autenticado, a opção não é apresentada;
- linhas 7-11: a opção [Requête AdminData] só é disponibilizada se a sessão jSON tiver sido iniciada, se o utilizador estiver autenticado e se os dados [AdminData] ainda não tiverem sido recuperados. Se uma destas três condições não for satisfeita (sessão jSON não iniciada, utilizador não autenticado ou os dados [AdminData] já recuperados), a opção não é disponibilizada;
- linha 15: a opção [Fin session impôt] é disponibilizada assim que a sessão jSON for iniciada e o utilizador estiver autenticado; caso contrário, não é disponibilizada;
16.3. Roteamento do servidor [nuxt]
O encaminhamento do servidor é, em geral, mais complexo do que o do cliente, pois o utilizador pode digitar qualquer URL na barra de endereços do seu navegador. Podemos deixar isso acontecer (afinal, o utilizador não deve fazer isso) ou tentar controlar a situação. É isso que vamos fazer aqui, a título de exemplo, pois no caso da aplicação [nuxt-12], podemos muito bem dispensar esse controlo, uma vez que o servidor de cálculo de impostos está bem protegido contra esses URL introduzidos manualmente e sabe enviar as mensagens de erro adequadas. Vimos isso no [next-12], onde não havia qualquer controlo de encaminhamento.
O encaminhamento de um servidor [nuxt] é muito diferente do de um cliente [nuxt] no que diz respeito ao conceito de redirecionamento:
- quando um servidor [nuxt] é redirecionado, envia uma ordem de redirecionamento ao navegador do cliente com o destino do redirecionamento. O navegador faz então uma nova solicitação ao servidor [nuxt], pedindo-lhe o destino que lhe foi transmitido. Tudo acontece como se o utilizador tivesse digitado manualmente o endereço URL do destino da redireção: toda a aplicação [nuxt] é reiniciada e, consequentemente, todo o seu ciclo de vida (plugins do servidor, armazenamento, encaminhamento do servidor, páginas);
- quando um cliente [nuxt] é redirecionado, nada disso acontece. Ocorre uma simples mudança de página, a mesma que teria ocorrido se o utilizador tivesse clicado num link que levasse ao destino da redireção. O ciclo de vida é, então, diferente (roteamento do cliente, exibição do destino da rota);
Por esta razão, é preferível separar o encaminhamento do cliente do encaminhamento do servidor, mesmo que os dois códigos possam parecer semelhantes.
O script de encaminhamento do servidor [middleware/server/routing] será o seguinte:
/* eslint-disable no-console */
export default function(context) {
// quem executa este código?
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// recuperamos algumas informações do store [nuxt]
const store = context.store
// de onde viemos?
const from = store.state.from || 'nowhere'
...
}
- no encaminhamento do cliente, a função de encaminhamento recebe o contexto [context] com a propriedade [context.from], que é a rota da página de onde se vem. A rota para onde se vai é obtida por [context.route];
- no encaminhamento do servidor, a função de encaminhamento recebe o contexto [context] sem a propriedade [context.from]. O encaminhamento do servidor só intervém quando um URL é solicitado manualmente ao servidor [nuxt]. Sabe-se que, nessa altura, toda a aplicação [nuxt] é reiniciada. É como se se recomeçasse do zero e, por isso, não existe a noção de «página anterior»;
- graças à sessão [nuxt], sabemos que o servidor pode recuperar essa sessão e, assim, não recomeçar do zero. É, portanto, nesta sessão [nuxt] e, mais especificamente, no armazenamento desta sessão, que iremos guardar o nome da última página apresentada pelo navegador do cliente antes de uma URL ser solicitada ao servidor [nuxt];
- linhas 7-9: recuperamos o nome da última página apresentada pelo navegador do cliente. Ao iniciar a aplicação, esta informação [from] não existe no armazenamento. Atribui-se então o nome [nowhere] à variável [from];
Para que o servidor [nuxt] possa recuperar do armazenamento o nome da última página apresentada pelo navegador do cliente, é necessário que o cliente [nuxt] também coloque essa informação no armazenamento. O script de encaminhamento do cliente [nuxt] é, portanto, completado da seguinte forma:
/* eslint-disable no-console */
export default function(context) {
// Quem está a executar este código?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// gestão do cookie de sessão PHP no navegador
// O cookie de sessão PHP do navegador deve ser idêntico ao encontrado na sessão Nuxt
// a ação [fin-session] recebe um novo cookie PHP (tanto no servidor como no cliente Nuxt)
// se for o servidor a recebê-lo, o cliente deve transmiti-lo ao navegador
// para as suas próprias comunicações com o servidor PHP
// estamos aqui num encaminhamento do cliente
// recuperamos o cookie da sessão PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// se existir, atribui-se o cookie de sessão PHP ao navegador
document.cookie = phpSessionCookie
}
// coloca-se na sessão o nome da página para onde se vai - sem redirecionamento do servidor
context.store.commit('replace', { serverRedirection: false, from: context.route.name })
// guardamos o store na sessão [nuxt]
const session = context.app.$session()
session.value.store = context.store.state
session.save(context)
}
- são adicionadas as linhas 19-24;
- linha 20: insere-se na memória o nome da página [context.route.name] que vai ser apresentada e que será, assim, no encaminhamento seguinte, a página de onde se vem. Além disso, veremos que, no encaminhamento do servidor [nuxt], este precisa de saber se o encaminhamento em curso resulta de um redirecionamento anterior do servidor [nuxt]. Neste caso, não é esse o caso, pelo que definimos a propriedade [serverRedirection] como [false];
- linhas 22-24: o estado do armazenamento é colocado na sessão [nuxt] (linha 23) e, em seguida, a sessão [nuxt] é guardada num cookie (linha 24), que, por sua vez, será guardado no navegador do cliente [nuxt];
Voltemos ao script de encaminhamento do servidor [nuxt]:
/* eslint-disable no-console */
export default function(context) {
// Quem executa este código?
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// recuperamos algumas informações do armazenamento [nuxt]
const store = context.store
// de onde viemos?
const from = store.state.from || 'nowhere'
// Para onde vamos?
const to = context.route.name
// eventual redirecionamento
let redirection = ''
// gestão do encaminhamento concluída
let done = false
// já estamos num redirecionamento do servidor [nuxt]?
if (store.state.serverRedirection) {
// não há nada a fazer
done = true
}
// trata-se de uma atualização da página?
if (to === from) {
// não há nada a fazer
done = true
}
// controlo da navegação do servidor [nuxt]
// baseia-se na navegação do cliente no componente [navigation]
// caso em que a sessão PHP não tenha sido iniciada
if (!done && !store.state.jsonSessionStarted && to !== 'index') {
// redirecionamento
redirection = 'index'
// trabalho concluído
done = true
}
// caso em que o utilizador não esteja autenticado
if (!done && store.state.jsonSessionStarted && !store.state.userAuthenticated && to !== 'authentification') {
// redirecionamento
redirection = from
// trabalho concluído
done = true
}
// caso em que o utilizador foi autenticado
if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && to !== 'get-admindata' && to !== 'fin-session') {
// permanece-se na mesma página
redirection = from
// trabalho concluído
done = true
}
// caso em que [adminData] foi obtido
if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && store.state.adminData && to !== 'fin-session') {
// permanece-se na mesma página
redirection = from
// trabalho concluído
done = true
}
// foram realizadas todas as verificações ---------------------
// redirecionamento?
if (redirection) {
// regista-se o redirecionamento na loja
store.commit('replace', { serverRedirection: true })
} else {
// sem redirecionamento
store.commit('replace', { serverRedirection: false, from: to })
}
// guardamos o armazenamento na sessão [nuxt]
const session = context.app.$session()
session.value.store = store.state
session.save(context)
// é efetuado o eventual redirecionamento
if (redirection) {
context.redirect({ name: redirection })
}
}
- linhas 6-9: recupera-se o valor de [from] na memória do servidor [nuxt];
- linha 11: regista-se o destino do encaminhamento atual;
- linha 13: o encaminhamento pode levar a um redirecionamento do navegador do cliente. [redirection] será o destino desse redirecionamento;
- linha 15: [done] para [true] indica que o encaminhamento está concluído;
- linhas 17-21: verifica-se primeiro se o encaminhamento atual resulta de um pedido de redirecionamento enviado ao navegador do cliente. Esta informação está armazenada na propriedade [serverRedirection] do armazenamento. Se esta propriedade for verdadeira, significa que o servidor [nuxt] enviou um redirecionamento para o navegador do cliente durante a solicitação anterior ao servidor [nuxt]. Neste caso, não há encaminhamento a efetuar. Na solicitação anterior, o encaminhador do servidor [nuxt] decidiu que o navegador do cliente deveria ser redirecionado. Esta decisão não deve ser posta em causa por um novo encaminhamento;
- linhas 23-27: verifica-se se o encaminhamento em curso corresponde a uma atualização da página. Se for o caso, deixa-se que o processo siga o seu curso;
- a partir da linha 29, retomam-se as regras aplicadas no componente [navigation] do cliente [nuxt] (ver parágrafo anterior);
- linhas 32-38: trata-se do caso em que a sessão jSON não foi iniciada e o destino do encaminhamento não é a página [index]. Neste caso, redireciona-se o navegador do cliente para a página [index];
- linhas 40-46: trata-se do caso em que a sessão jSON foi iniciada, o utilizador não está autenticado e o destino do encaminhamento atual não é a página [authentification]. Neste caso, o encaminhamento é recusado e permanece-se na página atual;
- linhas 48-54: tratamos o caso em que a sessão jSON foi iniciada, o utilizador está autenticado e o destino do encaminhamento atual não é nem a página [get-admindata], nem a página [fin-session], que são, nesse momento, os únicos destinos possíveis. Neste caso, rejeita-se o encaminhamento solicitado e regressa-se ao ponto em que se estava anteriormente;
- linhas 56-62: trata-se do caso em que se obteve [adminData]. Neste caso, existe apenas um destino possível para o encaminhamento: a página [fin-session]. Se não fosse esta a página solicitada, rejeita-se o encaminhamento e regressa-se ao ponto anterior;
- linhas 64-72: se tiver havido redirecionamento, regista-se no armazenamento do servidor [nuxt]: [serverRedirection: true]. Note-se que não se atribui qualquer valor à propriedade [from] do armazenamento. A razão para isso é que vai haver um redirecionamento do navegador do cliente e vimos que, neste caso, não houve encaminhamento (linhas 17-20) e a propriedade [from] do armazenamento não é utilizada;
- linhas 66-69: se não houver redirecionamento, então isso também é registado no armazenamento do servidor [nuxt]: [serverRedirection: false]. Além disso, o encaminhamento em curso irá apresentar a página [to], que, para o pedido seguinte (cliente ou servidor [nuxt]), se tornará a página anterior. É por isso que se escreve [from: to];
- linhas 73-76: guardamos o store na sessão [nuxt], que por sua vez é guardada num cookie;
- linhas 77-80: se [redirection] não estiver vazio, solicita-se ao navegador que se redirecione. Caso contrário (o que não se vê aqui), o ciclo de vida do servidor [nuxt] continuará: a página [to] será processada pelo servidor [nuxt] e enviada para o navegador do cliente [nuxt] com o cookie de sessão [nuxt];
O encaminhamento aqui escolhido para o servidor [nuxt] é arbitrário. Poderia ter-se escolhido outro ou, como já foi referido, não se poderia ter feito qualquer encaminhamento. O escolhido acima tem a vantagem de manter sempre a aplicação num estado estável, independentemente da página URL solicitada pelo utilizador.
É possível melhorar um aspeto quando a página carregada no final é a página original. Existem dois casos:
- o utilizador provocou uma recarga da página (to===from);
- existem redirecionamentos para a página original (redirecionamento===from);
Em ambos os casos, a página original será novamente executada com a sua chamada assíncrona ao servidor de cálculo do imposto. Vejamos um exemplo. Se, uma vez autenticado, o utilizador recarregar a página (F5). Neste caso, no roteamento acima, temos: [to]=[from]=[authentification]. Não há redirecionamento. A página [to=authentification] será executada pelo servidor [nuxt]. Se não for tomada nenhuma medida, a função [asyncData] será executada novamente. Isso é desnecessário, uma vez que a autenticação já foi efetuada.
É possível melhorar a situação alterando ligeiramente a página [authentification]:
// dados assíncronos
async asyncData(context) {
// registo
console.log('[authentification asyncData started]')
// não se repetem as operações se a página já tiver sido solicitada
if (process.server && context.store.state.userAuthenticated) {
console.log('[authentification asyncData canceled]')
return { result: '[succès]' }
}
// cliente [nuxt]
if (process.client) {
// início da espera
context.app.$eventBus().$emit('loading', true)
// sem erros
context.app.$eventBus().$emit('errorLoading', false)
}
try {
// autenticação no servidor
...
- linhas 6-9: se a página for executada pelo servidor [nuxt] e se for detetado na memória que a autenticação já foi efetuada, então devolve-se diretamente o resultado pretendido (linha 8);
Faz-se o mesmo para todas as páginas:
Página [index]:
// dados assíncronos
async asyncData(context) {
// registo
console.log('[index asyncData started]')
// não se repete o processo se a página já tiver sido solicitada
if (process.server && context.store.state.jsonSessionStarted) {
console.log('[index asyncData canceled]')
return { result: '[succès]' }
}
try {
...
Página [get-admindata]
// dados assíncronos
async asyncData(context) {
// registo
console.log('[get-admindata asyncData started]')
// não se faz as coisas duas vezes se a página já tiver sido solicitada
if (process.server && context.store.state.adminData) {
console.log('[get-admindata asyncData canceled]')
return { result: context.store.state.adminData }
}
// cliente
if (process.client) {
// início da espera
context.app.$eventBus().$emit('loading', true)
// sem erros
context.app.$eventBus().$emit('errorLoading', false)
}
try {
...
Página [fin-session]
// dados assíncronos
async asyncData(context) {
// registo
console.log('[fin-session asyncData started]')
// não se repete o processo se a página já tiver sido solicitada
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)." }
}
// caso do cliente [nuxt]
if (process.client) {
// início da espera
context.app.$eventBus().$emit('loading', true)
// sem erros
context.app.$eventBus().$emit('errorLoading', false)
}
try {
16.4. Exécution
Para executar este exemplo, é necessário, antes da execução, eliminar o cookie de sessão [nuxt] e o cookie PHP do navegador que está a executar o cliente [nuxt], de modo a partir de uma situação limpa. Segue-se um exemplo com o navegador Chrome:

16.5. Conclusion
O encaminhamento do servidor [nuxt] é complexo, pois é necessário prever todos os URL que o utilizador possa digitar manualmente. Trata-se de um caso clássico. Uma aplicação [nuxt] não se destina a ser utilizada desta forma. Assim que a página [index] for servida pelo router do servidor [nuxt], seria possível redirecionar as chamadas seguintes feitas ao servidor para uma página de erro.
No caso específico do nosso exemplo [nuxt-13], o encaminhamento do servidor [nuxt] era desnecessário. O encaminhamento por predefinição (ou seja, a ausência de encaminhamento) no exemplo [nuxt-12] era perfeitamente adequado.