Skip to content

12. As funções HTTP em JavaScript

Image

12.1. Escolha de uma biblioteca HTTP

Neste caso, optámos por duas bibliotecas:

O EcmaScript 6 possui, de forma nativa, uma função HTTP denominada [fetch], que não está implementada pelo [node.js] (setembro de 2019). Existe uma biblioteca denominada [node-fetch] que permite utilizar a função [fetch] no Node. Esta biblioteca utiliza certas API específicas do [node.js]. Um código [node-fetch] pode, portanto, não ser 100 % transportável para um ambiente que não seja [node], por exemplo, num navegador;

Existe, além disso, uma biblioteca denominada [axios] dedicada a requisições HTTP, compatível tanto com o [node.js] como com os navegadores. É esta biblioteca que acabaremos por utilizar.

Vamos apresentar um mesmo script escrito com estas duas bibliotecas para mostrar que o processo de codificação com elas é semelhante.

12.2. Configuração de um ambiente de trabalho

12.2.1. Instalação do servidor de cálculo de impostos

Por fim, iremos escrever uma aplicação web com a seguinte arquitetura:

Image

JS: JavaScript

O código JavaScript é do lado do cliente:

  • de um serviço de páginas ou fragmentos estáticos;
  • de um serviço jSON;

O código JavaScript é, portanto, um cliente jSON e, como tal, pode ser organizado em camadas [UI, métier, dao] (UI: Interface do Utilizador), tal como os nossos clientes jSON escritos em PHP.

O servidor será o do cálculo do imposto, do qual já escrevemos 13 versões. Vamos escrever uma 14.ª. Começamos, portanto, por duplicar, no NetBeans, a pasta da versão 13, na pasta da versão 14:

Image

  • em [6], alteramos o ficheiro [config.json] da versão 14 da seguinte forma:

{
    "databaseFilename": "Config/database.json",
    "rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-14",
    "relativeDependencies": [

        "/Entities/BaseEntity.php",
        "/Entities/Simulation.php",
        ...
    "vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
    "vue-erreurs": "vue-erreurs.php"
}
  • na linha 3, alteramos a pasta raiz da aplicação;

Para aceder a este servidor, é necessário iniciar os serviços [Laragon].

Feito isto, podemos testar com o [Postman] (ver artigo no link), esta nova versão do servidor, idêntica, por enquanto, à versão 13. Podemos utilizar o conjunto de consultas utilizado para testar a versão 12 do servidor de cálculo de impostos:

Image

  • em [1-4], utilizar a consulta [init-session-700] para iniciar uma sessão jSON;
  • em [4-5], substituir [version-12] por [version-14] para testar a versão 14 do projeto;
  • durante a execução, devemos receber a resposta jS0N [6] do servidor;

A versão 14 do servidor está agora operacional. Teremos de a modificar ligeiramente. Recordemos o API deste servidor:

Ação
Função
Contexto de execução
init-session
Serve para definir o tipo (json, xml, html) das respostas pretendidas
Pedido GET main.php?action=init-session&type=x
pode ser enviada a qualquer momento
autenticar-utilizador
Autoriza ou não um utilizador a iniciar sessão
Pedido POST main.php?action=autenticar-utilizador
A solicitação deve ter dois parâmetros enviados via POST [user, password]
Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido
calcular-imposto
Efetua uma simulação de cálculo de impostos
Pedido POST main.php?action=calculer-impot
A solicitação deve ter três parâmetros enviados via POST: [marié, enfants, salaire]
Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado
lister-simulações
Solicita a visualização da lista de simulações realizadas desde o início da sessão
Pedido GET main.php?action=lister-simulations
A solicitação não aceita nenhum outro parâmetro
Só pode ser emitida se o tipo da sessão (json, xml, html) for conhecido e o utilizador estiver autenticado
eliminar-simulação
Elimina uma simulação da lista de simulações
Consulta GET main.php?action=lister-simulações&número=x
A solicitação não aceita nenhum outro parâmetro
Só pode ser enviada se o tipo da sessão (json, xml, html) for conhecido e o utilizador estiver autenticado
fim-sessão
Encerra a sessão de simulações.
Tecnicamente, a sessão web anterior é eliminada e é criada uma nova sessão
Só pode ser emitida se o tipo da sessão (json, xml, html) for conhecido e o utilizador estiver autenticado

12.2.2. Instalação das bibliotecas HTTP do cliente JavaScript

Numa primeira fase, iremos trabalhar com a seguinte arquitetura:

Image

  • em [1], um script de consola [node.js] efetua uma solicitação HTTP ao servidor jSON de cálculo do imposto;
  • em [4], recebe essa resposta e apresenta-a na consola;

No exemplo n.º 1, utilizaremos as bibliotecas [node-fetch] e [axios] e, posteriormente, manteremos apenas a [axios] para os exemplos seguintes. Vamos agora instalar estas duas bibliotecas JavaScript a partir do terminal de [VSCode]:

Image

Iremos também utilizar a biblioteca [qs], que permite a codificação URL de uma cadeia de caracteres. Recorde-se que esta codificação é utilizada para codificar os parâmetros de um pedido HTTP, GET ou POST.

Image

12.3. script [fetch-01]

O script [fetch-01] utiliza a biblioteca [node-fetch] para inicializar uma sessão jSON com o servidor de cálculo de impostos. O seu código é o seguinte:


'use strict';

// importações
import fetch from 'node-fetch';
import qs from 'qs';
import { sprintf } from 'sprintf-js';
import moment from 'moment';


// URL base do servidor de cálculo de impostos
const baseUrl = 'http://localhost/php7/scripts-web/impostos/versão-14/main.php?';
// inicialização da sessão
async function initSession() {
  // opções da consulta HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // execução da consulta HTTP [get /main.php?action=init-session&type=json]
  let débutFetch;
  try {
    // pedido assíncrono — [fetch] devolve uma promessa
    débutFetch = moment(Date.now());
    const response = await fetch(baseUrl + qs.stringify({
      action: 'init-session',
      type: 'json'
    }), options);
    // [response] é a resposta completa do servidor HTTP (cabeçalhos HTTP + a própria resposta)
    // esta resposta é apresentada para ver a sua estrutura
    console.log(sprintf("réponse fetch formatée en json,=%j, %s", response, heure(débutFetch)));
    console.log("réponse fetch en javascript=", response);
    // é possível aceder aos cabeçalhos HTTP
    console.log("entêtes de la réponse=", response.headers);
    // se a resposta for do tipo application/json, a resposta JSON do servidor é obtida com a função assíncrona [response.json()]
    // neste caso, o código chamador obtém um objeto [Promise]
    // [await] permite obter a resposta [json] do servidor, em vez da sua promessa
    const débutJson = moment(Date.now());
    const objet = await response.json();
    console.log(sprintf("réponse json=%j, type=%s, %s", objet, typeof (objet), heure(débutJson)));
    return objet;
    // se a resposta for do tipo text/plain, a resposta de texto do servidor é obtida com [response.text()]
    // neste caso, o código chamador obtém um objeto [Promise]
    // [await] permite obter a resposta [texte] do servidor, em vez da sua promessa
    // const text = await response.text();
    // console.log("resposta de texto=", texto);
    // return text;
  } catch (error) {
    // estamos aqui porque o servidor enviou um código de erro [404 Not Found, ...] acompanhado de um corpo vazio — exibimos o erro para ver a sua estrutura
    // ou porque o cliente [fetch] lançou uma exceção (rede inacessível, ...)
    // é apresentada a estrutura do erro
    console.log(sprintf("error fetch en json=%j, %s", error, heure(débutFetch)));
    console.log("error fetch en javascript=", typeof (error), error);
    // lança-se a mensagem de erro recebida
    throw error.message;
  }
}

// a função main executa a função assíncrona [initSession]
async function main() {
  try {
    console.log("requête HTTP vers le serveur en cours ---------------------------------------------");
    const response = await initSession();
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response, typeof (response))
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error, typeof (error));
  }
}

// teste
main();

// utilitário de exibição da hora e da duração
function heure(début) {
  // hora atual
  const now = moment(Date.now());
  // formatação da hora
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // é necessário calcular uma duração?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatação da hora + duração
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultado
  return result;
}

Comentários

  • as funções HTTP do JavaScript são funções assíncronas. Aqui, aplicamos o que aprendemos na secção anterior (ver link);
  • linha 24: para aguardar que a resposta da função assíncrona [fetch] seja publicada no ciclo de eventos de [node.js], utilizamos a palavra-chave [await]. Sabemos que esta instrução deve constar num código precedido pela palavra-chave [async] (linha 13);
  • linhas 13-56: encapsulamos o código HTTP na função assíncrona [initSession];
  • linhas 59-69: uma segunda função assíncrona [main] é utilizada para chamar de forma bloqueante (async/await) a função assíncrona [initSession];
  • linha 72: a função assíncrona [main] é chamada;
  • embora todo o código se pareça com código síncrono, são, de facto, funções assíncronas que estão a ser executadas, mas de forma bloqueante;
  • linha 19: para inicializar uma sessão jSON com o servidor de cálculo de impostos, é necessário enviar-lhe o comando HTTP [get /main.php?action=init-session&type=json]. É isso que o código das linhas 24-27 faz. A sintaxe de [fetch] é a seguinte: [fetch(URL, options)] com:
    • [URL]: a consulta URL;
    • [options]: um objeto que define as opções da consulta. É aqui, nomeadamente, que se definem os cabeçalhos HTTP que se pretende enviar para a máquina de destino;
  • linhas 15-18: definem-se as opções da solicitação que se pretende efetuar:
    • [method]: pretende-se efetuar um GET;
    • [timeout]: pretende-se que o cliente [fetch] não aguarde mais de 2 segundos pela resposta do servidor. Se este prazo for excedido, o [fetch] lançará uma exceção;
  • linha 24: para obter o URL [/main.php?action=init-session&type=json], utiliza-se a biblioteca [qs] para obter a codificação URL dos parâmetros [action,type] do GET. A cadeia obtida é [init-session&type=json], que poderíamos ter construído nós próprios. Queríamos simplesmente mostrar como obter uma cadeia URL codificada;
  • linha 24: a palavra-chave [await] indica que aqui é iniciada uma tarefa assíncrona e que se aguarda que esta publique a sua resposta no ciclo de eventos de [node.js];
  • linha 24: em [response], obtém-se um objeto complexo que descreve a totalidade da resposta HTTP recebida (cabeçalhos e documento);
  • linhas 30-31: exibe-se o objeto [response] para ver a sua estrutura, primeiro como cadeia de caracteres e depois como objeto JavaScript;
  • linha 33: exibem-se os cabeçalhos HTTP enviados pelo servidor;
  • linha 38: sabemos que o servidor de cálculo de impostos irá enviar uma cadeia jSON. Esta está encapsulada no objeto [response]. É possível obtê-la com o método [response.json()]. No entanto, este método é assíncrono. Escrevemos, portanto, [await response.json()] para obter a cadeia jSON, que será publicada no ciclo de eventos de [node.js]. Na verdade, não é a cadeia jSON que se obtém, mas sim o objeto JavaScript representado por ela;
  • linha 39: exibição da cadeia jSON recebida;
  • linha 40: devolve-se o objeto JavaScript recebido;
  • linha 47: intercepta-se um eventual erro da instrução [fetch]. Esta só lança uma exceção se a operação HTTP não tiver sido bem-sucedida e não tiver sido recebida qualquer resposta do servidor. Se tiver sido recebida uma resposta, mesmo com um código HTTP diferente de [200 OK], [fetch] não lança uma exceção e a resposta do servidor estará disponível na linha 38;
  • linhas 51-52: é exibido o objeto [error] recebido pela cláusula [catch], primeiro como uma cadeia de caracteres jSON e, em seguida, como um objeto JavaScript;
  • linha 54: a mensagem de erro de [fetch] encontra-se em [error.message];
  • linhas 59-69: a função assíncrona [main] invoca a função assíncrona [initSession] de forma bloqueante (await na linha 62);
  • linha 72: a função assíncrona [main] é iniciada e o código principal do script é então concluído. O script global será concluído quando as tarefas assíncronas iniciadas tiverem publicado os seus resultados no ciclo de eventos;

Os resultados da execução são os seguintes:

Caso 1: o servidor Laragon não está em execução


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\fetch-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
error fetch en json={"message":"network timeout at: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json","type":"request-timeout"}, heure=10:08:48:180, durée= 2 seconde(s) et 62 millisecondes
error fetch en javascript= object { FetchError: network timeout at: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json
    at Timeout.<anonymous> (c:\Data\st-2019\dev\es6\javascript\node_modules\node-fetch\lib\index.js:1448:13)
    at ontimeout (timers.js:436:11)
    at tryOnTimeout (timers.js:300:5)
    at listOnTimeout (timers.js:263:5)
    at Timer.processTimers (timers.js:223:10)
  message:
   'tempo de espera de rede esgotado em: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
  type: 'request-timeout' }
erreur ---------------------------------------------
erreur= network timeout at: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json string

[Done] exited with code=0 in 2.804 seconds

Comentários

  • linha 3: a solicitação HTTP falha após 2 segundos e 62 milissegundos devido ao tempo limite de 2 segundos que foi imposto à solicitação HTTP;
  • linhas 4-9: o objeto JavaScript [error] foi interceptado pela cláusula [catch(error)]. Este objeto tem duas propriedades:
    • [FetchError]: linha 4;
    • [message]: linhas 10-12;
  • linha 14: a mensagem de erro recebida pela função assíncrona [main];

Caso 2: o servidor Laragon é iniciado


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\fetch-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
réponse fetch formatée en json,={"size":0,"timeout":2000}, heure=10:13:50:814, durée= 0 seconde(s) et 375 millisecondes
réponse fetch en javascript= Response {
  size: 0,
  timeout: 2000,
  [Symbol(Body internals)]:
   { body:
      PassThrough {
        _readableState: [ReadableState],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        _writableState: [WritableState],
        writable: false,
        allowHalfOpen: true,
        _transformState: [Object] },
     disturbed: false,
     error: null },
  [Symbol(Response internals)]:
   { url:
      'http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
     status: 200,
     statusText: 'OK',
     headers: Headers { [Symbol(map)][Object] },
     counter: 0 } }
entêtes de la réponse= Headers {
  [Symbol(map)]:
   [Object: null prototype] {
     date: [ 'Sat, 14 Sep 2019 08:13:50 GMT' ],
     server: [ 'Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11' ],
     'x-powered-by': [ 'PHP/7.2.11' ],
     'cache-control': [ 'max-age=0, private, must-revalidate, no-cache, private' ],
     '«set-cookie»: [ 'PHPSESSID=99q2iinusmhl55fa600aie2mmu; path=/' ],
     '«content-length»: [ '86' ],
     connection: [ 'close' ],
     'content-type': [ 'application/json' ] } }
réponse json={"action":"init-session","état":700,"réponse":"session démarrée avec type [json]"}, type=object, heure=10:13:50:825, durée= 0 seconde(s) et 1 millisecondes
succès ---------------------------------------------
réponse= { action: 'init-session',
  'estado': 700,
  'resposta': 'sessão iniciada com o tipo [json]' } objeto

[Done] exited with code=0 in 1.022 seconds

Comentários

  • linha 3: [fetch] recebe a resposta do servidor após 375 ms;
  • linhas 4-39: a estrutura do objeto JavaScript [response] que encapsula a resposta do servidor. Entre as suas propriedades, algumas podem ser do nosso interesse:
    • [status] (linha 25): código HTTP da resposta do servidor;
    • [statusText] (linha 26): texto associado a este código;
    • [headers] (linha 27): os cabeçalhos HTTP da resposta do servidor;
    • [body] (linha 8): representa o documento enviado pelo servidor. A instrução [fetch] fornece métodos para o utilizar;
  • linhas 29-39: os cabeçalhos HTTP da resposta do servidor;
  • linha 40: a função assíncrona [response.json()] publicou a sua resposta após 1 milésimo de segundo;
  • linhas 42-44: o objeto JavaScript recebido pela função assíncrona [main];

Caso 3: o servidor Laragon está em execução, mas é-lhe enviado um comando incorreto:

Image

  • acima, na linha 26, é passado um tipo de sessão incorreto ao servidor;

Os resultados da execução são os seguintes:


requête HTTP vers le serveur en cours ---------------------------------------------
réponse fetch formatée en json,={"size":0,"timeout":2000}, heure=10:27:54:114, durée= 0 seconde(s) et 136 millisecondes
réponse fetch en javascript= Response {
  size: 0,
  timeout: 2000,
  [Symbol(Body internals)]:
   { body:
      PassThrough {
        _readableState: [ReadableState],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        _writableState: [WritableState],
        writable: false,
        allowHalfOpen: true,
        _transformState: [Object] },
     disturbed: false,
     error: null },
  [Symbol(Response internals)]:
   { url:
      '«http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=x»,
     status: 400,
     statusText: 'Bad Request',
     headers: Headers { [Symbol(map)][Object] },
     counter: 0 } }
entêtes de la réponse= Headers {
  [Symbol(map)]:
   [Object: null prototype] {
     date: [ 'Sat, 14 Sep 2019 08:27:54 GMT' ],
     server: [ 'Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11' ],
     'x-powered-by': [ 'PHP/7.2.11' ],
     'cache-control': [ 'max-age=0, private, must-revalidate, no-cache, private' ],
     '«set-cookie»: [ 'PHPSESSID=5ku9gfok81ikj98hia0meeum57; path=/' ],
     'content-length': [ '79' ],
     connection: [ 'close' ],
     'content-type': [ 'application/json' ] } }
réponse json={"action":"init-session","état":703,"réponse":"paramètre type=[x] invalide"}, type=object, heure=10:27:54:127, durée= 0 seconde(s) et 2 millisecondes
succès ---------------------------------------------
réponse= { action: 'init-session',
  'estado': 703,
  'resposta': 'parâmetro type=[x] inválido' } objeto

[Done] exited with code=0 in 0.712 seconds
  • a resposta do servidor é recebida na linha 2;
  • linha 24: pode-se ver que o código HTTP da resposta do servidor é 400, um código de erro. No entanto, o [fetch] não lançou nenhuma exceção. Enquanto o [fetch] receber uma resposta do servidor, processa-a e não lança uma exceção;
  • linhas 41-43: a resposta obtida pela função assíncrona [main];

12.4. script [fetch-02]

O script seguinte retoma o script [fetch-01], eliminando todos os detalhes desnecessários:


'use strict';

// importações
import fetch from 'node-fetch';
import qs from 'qs';

// URL base do servidor de cálculo de impostos
const baseUrl = 'http://localhost/php7/scripts-web/impostos/versão-14/main.php?';
// inicialização da sessão
async function initSession() {
  // opções da consulta HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // execução da consulta HTTP [get /main.php?action=init-session&type=json]
  const response = await fetch(baseUrl + qs.stringify({
    action: 'init-session',
    type: 'json'
  }), options);
  // resultado recebido em jSON
  return await response.json();
}

// a função main executa a função assíncrona [initSession]
async function main() {
  try {
    console.log("requête HTTP vers le serveur en cours ---------------------------------------------");
    const response = await initSession();
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response)
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error.message);
  }
}

// teste
main();

Os resultados de uma execução normal:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\fetch-02.js"
requête HTTP vers le serveur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'init-session',
  'estado': 700,
  'resposta: 'sessão iniciada com o tipo [json]' }

[Done] exited with code=0 in 0.56 seconds

Resultados de uma execução com exceção (o servidor Laragon é interrompido):


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\fetch-02.js"
requête HTTP vers le serveur en cours ---------------------------------------------
erreur ---------------------------------------------
erreur= network timeout at: http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json

[Done] exited with code=0 in 2.701 seconds

12.5. script [axios-01]

Retomamos aqui o script [fetch-01], que reescrevemos utilizando a biblioteca [axios]. Recorde-se que o nosso interesse nesta biblioteca reside no facto de ser portável entre o ambiente [node.js] e os dos navegadores habituais. Isto permite:

  • numa fase 1, testar os nossos scripts num ambiente [node.js];
  • numa fase 2, portá-los para um navegador;

O script [axios-01] retoma a estrutura do script [fetch-01]:


'use strict';
import axios from 'axios';

// configuração padrão do axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// inicialização da sessão
async function initSession(axios) {
  // opções da solicitação HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // parâmetros da URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  // execução da consulta HTTP [get /main.php?action=init-session&type=json]
  try {
    // pedido assíncrono
    const response = await axios.request('main.php', options);
    // a resposta é o conjunto da resposta HTTP do servidor (cabeçalhos HTTP + a própria resposta)
    // esta resposta é apresentada para se ver a sua estrutura
    console.log("réponse axios=", response);
    // a resposta do servidor encontra-se em [response.data]
    return response.data;
  } catch (error) {
    // estamos aqui porque o servidor enviou um código de erro [404 Not Found, 500 Internal Server Error, ...]
    // o parâmetro [error] é uma instância de exceção — pode assumir várias formas
    // é apresentado para se ver a sua estrutura
    console.log("axios error=", typeof (error), error);
    if (error.response) {
      // o servidor sinalizou um erro no estado HTTP, mas também enviou uma resposta
      // por isso, esta encontra-se em [error.response.data]
      // sabe-se que o servidor envia respostas jSON com a estrutura {ação, estado, resposta}
      // e que, em caso de erro, a mensagem de erro está em [réponse]
      return error.response.data;
    } else {
      // é gerado o erro
      throw error;
    }
  }
}

// a função main executa a função assíncrona [initSession]
async function main() {
  try {
    console.log("requête HTTP vers le serveur en cours ---------------------------------------------");
    const response = await initSession(axios);
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response, typeof (response))
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error.message);
  }
}

// teste
main();

Comentários

  • linha 2: importa-se a biblioteca [axios];
  • linhas 5-6: configuração por predefinição das consultas HTTP. As opções [axios.defaults] são válidas para todas as consultas HTTP emitidas pelo objeto [axios], sem necessidade de as repetir em cada nova consulta;
  • linha 5: tempo limite de 2 segundos para todas as solicitações;
  • linha 6: todas as URL serão expressas em relação à URL de base;
  • linha 9: a função assíncrona [initSession];
  • linhas 11-18: as opções da solicitação HTTP que será emitida (para além das opções predefinidas já definidas nas linhas 5-6);
  • linhas 14-17: os parâmetros da função URL [action=init-session&type=json]. O objeto [params] será automaticamente convertido numa cadeia de caracteres codificada URL;
  • linha 22: chamada bloqueante da função assíncrona [axios.request]. O primeiro parâmetro é o URL de destino, construído a partir do [main.php] adicionado ao URL de base definido na linha 6. O segundo parâmetro é o objeto [options] das linhas 11 a 18;
  • linha 25: [response] é um objeto JavaScript que encapsula a totalidade da resposta HTTP do servidor (cabeçalhos HTTP + documento de resposta). É apresentado para ver a sua estrutura JavaScript;
  • linha 27: se o servidor enviou um documento, este encontra-se em [response.data]. Aqui sabemos que o servidor envia uma resposta jSON acompanhada do cabeçalho HTTP [Content-type : application/json]. A presença deste cabeçalho faz com que [axios] deserialize automaticamente [response.data] num objeto JavaScript;
  • linha 28: a função [axios] pode lançar uma exceção. É aqui que a [axios] difere da [fetch]. É lançada uma exceção nos seguintes casos:
    • a solicitação HTTP não pôde ser enviada (erro do lado do cliente);
    • O servidor enviou um código de erro HTTP (400, 404, 500, …) (este aspeto é, na verdade, configurável). Recorde-se que, se este código de erro HTTP for acompanhado de uma resposta, o código [fetch] não lançava uma exceção, ao passo que o código [axios] lança uma. No entanto, se o código de erro HTTP for acompanhado por um documento, este é colocado em [error.response];
  • linha 32: é apresentada a estrutura JavaScript do objeto [error];
  • linhas 33-38: se o objeto [error] contiver um objeto [response], então é essa resposta que é devolvida ao código chamador;
  • linhas 39-42: nos restantes casos, devolve-se o objeto [error] ao código chamador;
  • linhas 47-57: a função assíncrona [main];
  • linha 50: chamada bloqueante à função assíncrona [initSession]. Recupera-se a resposta jSON do servidor como um objeto JavaScript;
  • linhas 53-56: interceção de um eventual erro. A mensagem de erro encontra-se em [error.message];

Os resultados da execução são os seguintes:

Caso 1: o servidor Laragon não está a funcionar


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
axios error= object { Error: timeout of 2000ms exceeded
    at createError (c:\Data\st-2019\dev\es6\javascript\node_modules\axios\lib\core\createError.js:16:15)
    at Timeout.handleRequestTimeout (c:\Data\st-2019\dev\es6\javascript\node_modules\axios\lib\adapters\http.js:252:16)
    at ontimeout (timers.js:436:11)
    at tryOnTimeout (timers.js:300:5)
    at listOnTimeout (timers.js:263:5)
    at Timer.processTimers (timers.js:223:10)
  config:
   { url:
      'http://localhost/php7/scripts-web/impots/version-14/main.php',
     method: 'get',
     params: { action: 'init-session', type: 'json' },
     headers:
      { Accept: 'application/json, text/plain, */*',
        'User-Agent': 'axios/0.19.0' },
     baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
     transformRequest: [ [Function: transformRequest] ],
     transformResponse: [ [Function: transformResponse] ],
     timeout: 2000,
     adapter: [Function: httpAdapter],
     xsrfCookieName: 'XSRF-TOKEN',
     xsrfHeaderName: 'X-XSRF-TOKEN',
     maxContentLength: -1,
     validateStatus: [Function: validateStatus],
     data: undefined },
  code: 'ECONNABORTED',
  request:
   Writable {
     _writableState:
      WritableState {
        objectMode: false,
        highWaterMark: 16384,
        finalCalled: false,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        destroyed: false,
        decodeStrings: true,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: true,
        bufferProcessing: false,
        onwrite: [Function: bound onwrite],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false,
        emitClose: true,
        bufferedRequestCount: 0,
        corkedRequestsFree: [Object] },
     writable: true,
     domain: null,
     _events:
      [Object: null prototype] {
        response: [Function: handleResponse],
        error: [Function: handleRequestError] },
     _eventsCount: 2,
     _maxListeners: undefined,
     _options:
      { protocol: 'http:',
        maxRedirects: 21,
        maxBodyLength: 10485760,
        path:
         '/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
        method: 'GET',
        headers: [Object],
        agent: undefined,
        auth: undefined,
        hostname: 'localhost',
        port: null,
        nativeProtocols: [Object],
        pathname: '/php7/scripts-web/impots/version-14/main.php',
        search: '?action=init-session&type=json' },
     _redirectCount: 0,
     _redirects: [],
     _requestBodyLength: 0,
     _requestBodyBuffers: [],
     _onNativeResponse: [Function],
     _currentRequest:
      ClientRequest {
        domain: null,
        _events: [Object],
        _eventsCount: 6,
        _maxListeners: undefined,
        output: [],
        outputEncodings: [],
        outputCallbacks: [],
        outputSize: 0,
        writable: true,
        _last: true,
        chunkedEncoding: false,
        shouldKeepAlive: false,
        useChunkedEncodingByDefault: false,
        sendDate: false,
        _removedConnection: false,
        _removedContLen: false,
        _removedTE: false,
        _contentLength: 0,
        _hasBody: true,
        _trailer: '',
        finished: true,
        _headerSent: true,
        socket: [Socket],
        connection: [Socket],
        _header:
         'GET /php7/scripts-web/impostos/versão-14/main.php?action=init-session&type=json HTTP/1.1\r\nAccept: application/json, text/plain, */*\r\nUser-Agent: axios/0.19.0\r\nHost: localhost\r\nConnection: close\r\n\r\n',
        _onPendingData: [Function: noopPendingOutput],
        agent: [Agent],
        socketPath: undefined,
        timeout: undefined,
        method: 'GET',
        path:
         '/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
        _ended: false,
        res: null,
        aborted: 1568528450762,
        timeoutCb: null,
        upgradeOrConnect: false,
        parser: [HTTPParser],
        maxHeadersCount: null,
        _redirectable: [Circular],
        [Symbol(isCorked)]: false,
        [Symbol(outHeadersKey)][Object] },
     _currentUrl:
      'http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json' },
  response: undefined,
  isAxiosError: true,
  toJSON: [Function] }
erreur ---------------------------------------------
erreur= timeout of 2000ms exceeded

[Done] exited with code=0 in 2.784 seconds

Comentários

  • linhas 33-136: o objeto [error] contém muitas informações;
    • [Error], linhas 3-9: uma descrição do erro que ocorreu;
    • [config], linhas 10-27: a configuração da consulta HTTP que conduziu a este erro;
    • [config.url], linhas 11-12: o destino URL;
    • [config.method], linha 13: método da consulta;
    • [config.params], linha 14: os parâmetros de URL;
    • [config.headers], linhas 16-17: os cabeçalhos HTTP da consulta;
    • [config.baseURL], linha 18: o URL de base do URL de destino;
    • [config.timeout], linha 21: o tempo limite da solicitação;
    • [code], linha 28: um código de erro;
    • [request], linhas 29-133: uma descrição detalhada da solicitação HTTP. Note-se que a maioria das propriedades é precedida pelo sublinhado _, indicando que se trata de propriedades internas do objeto [request], não destinadas a serem utilizadas diretamente pelo programador;
    • [response], linha 134: a resposta do servidor, neste caso inexistente;
  • linha 138: a mensagem de erro apresentada pela função [main];

Caso 2: o servidor Laragon está em execução


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
réponse axios= { status: 200,
  statusText: 'OK',
  headers:
   { date: 'Sun, 15 Sep 2019 07:09:26 GMT',
     server: 'Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11',
     'x-powered-by': 'PHP/7.2.11',
     'cache-control': 'max-age=0, private, must-revalidate, no-cache, private',
     'set-cookie': [ 'PHPSESSID=uas6lugtblstktcifpd8e5irm6; path=/' ],
     'content-length': '86',
     connection: 'close',
     'content-type': 'application/json' },
  config:
   { url:
      '«http://localhost/php7/scripts-web/impots/version-14/main.php»,
     method: 'get',
     params: { action: 'init-session', type: 'json' },
     headers:
      { Accept: 'application/json, text/plain, */*',
        ''User-Agent': 'axios/0.19.0' },
     baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
     transformRequest: [ [Function: transformRequest] ],
     transformResponse: [ [Function: transformResponse] ],
     timeout: 2000,
     adapter: [Function: httpAdapter],
     xsrfCookieName: 'XSRF-TOKEN',
     xsrfHeaderName: 'X-XSRF-TOKEN',
     maxContentLength: -1,
     validateStatus: [Function: validateStatus],
     data: undefined },
  request:
   ClientRequest {
     domain: null,
     _events:
      [Object: null prototype] {
        socket: [Function],
        abort: [Function],
        aborted: [Function],
        error: [Function],
        timeout: [Function],
        prefinish: [Function: requestOnPrefinish] },
     _eventsCount: 6,
     _maxListeners: undefined,
     output: [],
     outputEncodings: [],
     outputCallbacks: [],
     outputSize: 0,
     writable: true,
     _last: true,
     chunkedEncoding: false,
     shouldKeepAlive: false,
     useChunkedEncodingByDefault: false,
     sendDate: false,
     _removedConnection: false,
     _removedContLen: false,
     _removedTE: false,
     _contentLength: 0,
     _hasBody: true,
     _trailer: '',
     finished: true,
     _headerSent: true,
     socket:
      Socket {
        connecting: false,
        _hadError: false,
        _handle: [TCP],
        _parent: null,
        _host: 'localhost',
        _readableState: [ReadableState],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 7,
        _maxListeners: undefined,
        _writableState: [WritableState],
        writable: false,
        allowHalfOpen: false,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        parser: null,
        _httpMessage: [Circular],
        [Symbol(asyncId)]: 6,
        [Symbol(lastWriteQueueSize)]: 0,
        [Symbol(timeout)]: null,
        [Symbol(kBytesRead)]: 0,
        [Symbol(kBytesWritten)]: 0 },
     connection:
      Socket {
        connecting: false,
        _hadError: false,
        _handle: [TCP],
        _parent: null,
        _host: 'localhost',
        _readableState: [ReadableState],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 7,
        _maxListeners: undefined,
        _writableState: [WritableState],
        writable: false,
        allowHalfOpen: false,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        parser: null,
        _httpMessage: [Circular],
        [Symbol(asyncId)]: 6,
        [Symbol(lastWriteQueueSize)]: 0,
        [Symbol(timeout)]: null,
        [Symbol(kBytesRead)]: 0,
        [Symbol(kBytesWritten)]: 0 },
     _header:
      'GET /php7/scripts-web/impots/version-14/main.php?action=init-session&type=json HTTP/1.1\r\nAccept: application/json, text/plain, */*\r\nUser-Agent: axios/0.19.0\r\nHost: localhost\r\nConnection: close\r\n\r\n',
     _onPendingData: [Function: noopPendingOutput],
     agent:
      Agent {
        domain: null,
        _events: [Object],
        _eventsCount: 1,
        _maxListeners: undefined,
        defaultPort: 80,
        protocol: 'http:',
        options: [Object],
        requests: {},
        sockets: [Object],
        freeSockets: {},
        keepAliveMsecs: 1000,
        keepAlive: false,
        maxSockets: Infinity,
        maxFreeSockets: 256 },
     socketPath: undefined,
     timeout: undefined,
     method: 'GET',
     path:
      '/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
     _ended: true,
     res:
      IncomingMessage {
        _readableState: [ReadableState],
        readable: false,
        domain: null,
        _events: [Object],
        _eventsCount: 3,
        _maxListeners: undefined,
        socket: [Socket],
        connection: [Socket],
        httpVersionMajor: 1,
        httpVersionMinor: 0,
        httpVersion: '1.0',
        complete: true,
        headers: [Object],
        rawHeaders: [Array],
        trailers: {},
        rawTrailers: [],
        aborted: false,
        upgrade: false,
        url: '',
        method: null,
        statusCode: 200,
        statusMessage: 'OK',
        client: [Socket],
        _consuming: false,
        _dumped: false,
        req: [Circular],
        responseUrl:
         'http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json',
        redirects: [] },
     aborted: undefined,
     timeoutCb: null,
     upgradeOrConnect: false,
     parser: null,
     maxHeadersCount: null,
     _redirectable:
      Writable {
        _writableState: [WritableState],
        writable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        _options: [Object],
        _redirectCount: 0,
        _redirects: [],
        _requestBodyLength: 0,
        _requestBodyBuffers: [],
        _onNativeResponse: [Function],
        _currentRequest: [Circular],
        _currentUrl:
         'http://localhost/php7/scripts-web/impots/version-14/main.php?action=init-session&type=json' },
     [Symbol(isCorked)]: false,
     [Symbol(outHeadersKey)]:
      [Object: null prototype] { accept: [Array], 'user-agent': [Array], host: [Array] } },
  data:
   { action: 'init-session',
     'estado': 700,
     'resposta': 'sessão iniciada com o tipo [json]' } }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'estado': 700,
  'resposta': 'sessão iniciada com o tipo [json]' } objeto

[Done] exited with code=0 in 1.115 seconds

Comentários

  • linhas 3-203: o objeto JavaScript [response] que encapsula a resposta HTTP do servidor;
  • linha 3, [status]: o código HTTP da resposta;
  • linha 4, [statusText]: o texto associado ao código HTTP anterior;
  • linhas 5-13, [headers]: os cabeçalhos HTTP da resposta:
    • linha 10, [Set-Cookie]: o cookie de sessão;
    • linha 13, [Content-Type]: o tipo do documento enviado pelo servidor;
  • linhas 14-31, [config]: a configuração do pedido HTTP enviado;
  • linhas 32-199, [request]: o objeto JavaScript que detalha a solicitação HTTP enviada;
  • linhas 200-203, [request.data]: o objeto JavaScript que encapsula a resposta jSON do servidor;
  • linhas 205-207: a resposta recuperada pela função assíncrona [main];

Caso 3: é enviada uma solicitação [init-session] incorreta ao servidor Laragon;

Image

Os resultados da execução são os seguintes:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-01.js"
requête HTTP vers le serveur en cours ---------------------------------------------
axios error= object { Error: Request failed with status code 400
   ...
  config:
   { url:
      '«http://localhost/php7/scripts-web/impots/version-14/main.php»,
     ...
     data: undefined },
  request:
   ...
     [Symbol(outHeadersKey)]:
      [Object: null prototype] { accept: [Array], 'user-agent': [Array], host: [Array] } },
  response:
   { status: 400,
     statusText: 'Bad Request',
     headers:
      { date: 'Sun, 15 Sep 2019 07:25:58 GMT',
        server: 'Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11',
        'x-powered-by': 'PHP/7.2.11',
        'cache-control': 'max-age=0, private, must-revalidate, no-cache, private',
        'set-cookie': [Array],
        'content-length': '79',
        connection: 'close',
        'content-type': 'application/json' },
     config:
      { url:
         '«http://localhost/php7/scripts-web/impots/version-14/main.php»,
        ...
        data: undefined },
     request:
      ...
        [Symbol(outHeadersKey)][Object] },
     data:
      { action: 'init-session',
        '«estado»: 703,
        'resposta': 'parâmetro type=[x] inválido' } },
  isAxiosError: true,
  toJSON: [Function] }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'estado': 703,
  'resposta': 'parâmetro type=[x] inválido' } objeto

[Done] exited with code=0 in 0.69 seconds

Como o servidor respondeu com um código HTTP 400 (linha 15), o [axios] lançou uma exceção (mais uma vez, este comportamento é configurável). Embora o [axios] tenha lançado uma exceção, obtém-se, de facto, a resposta HTTP do servidor em [error.response] (linha 14) e o documento jSON enviado por [error.response.data] (linha 34). Linhas 41-43: a função [main] recupera corretamente a resposta jSON do servidor.

12.6. script [axios-02]

Agora que detalhámos os objetos manipulados pela biblioteca [axios] durante uma consulta HTTP, podemos reescrever o script [axios-01] da seguinte forma:


'use strict';
import axios from 'axios';

// configuração padrão do axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/versão-14';

// inicialização da sessão
async function initSession(axios) {
  // opções da solicitação HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // parâmetros da URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    // execução da consulta HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    // a resposta do servidor está em [response.data]
    return response.data;
  } catch (error) {
    // resposta do servidor
    if (error.response) {
      // a resposta jSON encontra-se em [error.response.data]
      return error.response.data;
    } else {
      // o erro é reenviado
      throw error;
    }
  }
}

// a função main executa a função assíncrona [initSession]
async function main() {
  try {
    console.log("requête HTTP vers le serveur en cours ---------------------------------------------");
    const response = await initSession(axios);
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response, typeof (response))
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error.message);
  }
}

// teste
main();

12.7. script [axios-03]

O script [axios-03] segue a metodologia do script [axios-02]. Desta vez, adiciona-se a solicitação HTTP [authentifier-utilisateur] ao servidor, o que é feito através de um POST:


'use strict';
import axios from 'axios';
import qs from 'qs'

// configuração do axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/versão-14';


// inicialização da sessão
async function initSession(axios) {
  // opções da solicitação HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // parâmetros da URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    // execução da consulta HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    // a resposta do servidor está em [response.data]
    return response.data;
  } catch (error) {
    // resposta do servidor
    if (error.response) {
      // a resposta jSON encontra-se em [error.response.data]
      return error.response.data;
    } else {
      // o erro é reenviado
      throw error;
    }
  }
}

async function authentifierUtilisateur(axios, user, password) {
  // opções da solicitação HHTP [POST /main.php?action=authentifier-utilisateur]
  const options = {
    method: "POST",
    headers: {
      '«Content-type»: «application/x-www-form-urlencoded»,
    },
    // corpo do POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    // parâmetros do URL
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  try {
    // execução da consulta HTTP [post /main.php?action=authentifier-utilisateur]
    const response = await axios.request('main.php', options);
    // a resposta do servidor está em [response.data]
    return response.data;
  } catch (error) {
    // resposta do servidor
    if (error.response) {
      // a resposta jSON encontra-se em [error.response.data]
      return error.response.data;
    } else {
      // o erro é reenviado
      throw error;
    }
  }
}

// a função main executa as funções assíncronas uma a uma
async function main() {
  try {
    // init-session
    console.log("action init-session en cours ---------------------------------------------");
    const response1 = await initSession(axios);
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response1);
    // autenticar-utilizador
    console.log("action authentifier-utilisateur en cours ---------------------------------------------");
    const response2 = await authentifierUtilisateur(axios, 'admin', 'admin');
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response2)
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error);
  }
}

// teste
main();

Comentários

  • linhas 38-70: a função assíncrona [authentifierUtilisateur];
  • linha 39: é necessário efetuar a consulta [POST /main.php?action=authentifier-utilisateur];
  • linhas 40-54: as opções da consulta HTTP;
  • linha 41: trata-se de um POST;
  • linhas 42-44: os parâmetros do POST serão codificados como URL num documento que o cliente envia juntamente com a sua consulta;
  • linhas 46-49: a propriedade [data] deve conter a cadeia de caracteres POST URL codificada. Para tal, utiliza-se aqui a biblioteca [qs] importada na linha 3;
  • linhas 55-69: para a execução da consulta, encontramos o mesmo código que no método [initSession];
  • linhas 73-89: o método [asynchrone] chama sucessivamente, de forma bloqueante, os métodos [initSession, authentifierUtilisateur], nas linhas 77 e 82;
  • linha 82: utiliza-se o par (admin, admin) como credenciais de ligação. Sabe-se que estas são reconhecidas pelo servidor;

Os resultados da execução são os seguintes:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-03.js"
action init-session en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'init-session',
  'estado': 700,
  'resposta': 'sessão iniciada com o tipo [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'estado': 103,
  'resposta':
   [ 'pas de session en cours. Commencer par action [init-session]' ] }

[Done] exited with code=0 in 0.834 seconds
  • linhas 9-12: a autenticação do utilizador falha: o servidor não registou o facto de termos iniciado uma sessão jSON. Isto deve-se ao facto de não termos reenviado o cookie de sessão enviado em resposta à primeira solicitação [init-session];

12.8. script [axios-04]

O script [axios-04] introduz duas melhorias no script [axios-03]:

  • gerencia o cookie de sessão;
  • fatoriza numa função [getRemoteData] o que é comum às funções [initSession] e [authentifierUtilisateur];

'use strict';
import axios from 'axios';
import qs from 'qs'

// configuração do Axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/versão-14';

// cookie de sessão
const sessionCookieName = "PHPSESSID";
let sessionCookie = '';

// inicialização da sessão
async function initSession(axios) {
  // opções da solicitação HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // parâmetros da URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  // execução da consulta HTTP
  return await getRemoteData(axios, options);
}

async function authentifierUtilisateur(axios, user, password) {
  // opções da consulta HHTP [post /main.php?action=authentifier-utilisateur]
  const options = {
    method: "POST",
    headers: {
      '«Content-type»: «application/x-www-form-urlencoded»,
    },
    // corpo do POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    // parâmetros do URL
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  // execução do pedido HTTP
  return await getRemoteData(axios, options);
}

async function getRemoteData(axios, options) {
  // para o cookie de sessão
  if (!options.headers) {
    options.headers = {};
  }
  options.headers.Cookie = sessionCookie;
  // execução da solicitação HTTP
  let response;
  try {
    // solicitação assíncrona
    response = await axios.request('main.php', options);
  } catch (error) {
    // o parâmetro [error] é uma instância de exceção — pode assumir várias formas
    if (error.response) {
      // a resposta do servidor está em [error.response]
      response = error.response;
    } else {
      // o erro é reenviado
      throw error;
    }
  }
  // a resposta é o conjunto completo da resposta HTTP do servidor (cabeçalhos HTTP + a própria resposta)
  // recupera-se o cookie de sessão, caso exista
  const setCookie = response.headers['set-cookie'];
  if (setCookie) {
    // setCookie é um array
    // procura-se o cookie de sessão neste array
    let trouvé = false;
    let i = 0;
    while (!trouvé && i < setCookie.length) {
      // procura-se o cookie de sessão
      const results = RegExp('^(' + sessionCookieName + '.+?);').exec(setCookie[i]);
      if (results) {
        // guarda-se o cookie de sessão
        // eslint-disable-next-line require-atomic-updates
        sessionCookie = results[1];
        // encontrado
        trouvé = true;
      } else {
        // elemento seguinte
        i++;
      }
    }
  }
  // a resposta do servidor está em [response.data]
  return response.data;
}

// a função main executa as funções assíncronas uma a uma
async function main() {
  try {
    // init-session
    console.log("action init-session en cours ---------------------------------------------");
    const response1 = await initSession(axios);
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response1);
    // autenticar-utilizador
    console.log("action authentifier-utilisateur en cours ---------------------------------------------");
    const response2 = await authentifierUtilisateur(axios, 'admin', 'admin');
    console.log("succès ---------------------------------------------");
    console.log("réponse=", response2)
  } catch (error) {
    console.log("erreur ---------------------------------------------");
    console.log("erreur=", error.message);
  }
}

// teste
main();

Comentários

  • linhas 14-26: a função [initSession]. Agora, limita-se a preparar a consulta HTTP para enviar ao servidor, mas não a executa. Confia essa função ao método [getRemoteDate] das linhas 49-95;
  • linhas 28-47: a função [authentifierUtilisateur] segue o mesmo procedimento;
  • linha 49: a função [getRemoteData] recebe as duas informações que lhe permitem executar uma solicitação HTTP:
    • [axios], o objeto responsável por enviar a consulta e receber a resposta;
    • [options], as opções de configuração da consulta a enviar ao servidor;
  • linha 59: execução da consulta e espera bloqueante pela sua resposta jSON;
  • linhas 60-68: gestão de uma eventual exceção;
  • linha 64: recupera-se a resposta, que pode estar encapsulada no objeto de erro;
  • linha 67: se o servidor tiver lançado uma exceção sem incluir a resposta do servidor, então o erro recebido é transmitido ao código chamador;
  • a função [getRemoteData] gere o cookie de sessão:
    • armazena-o na variável [sessionCookie] (linha 11) quando o recebe pela primeira vez;
    • em seguida, reenvia-o em cada nova solicitação HTTP;
  • linhas 72-92: o [getRemoteData] analisa cada resposta do servidor para verificar se este enviou o cabeçalho HTTP [Set-Cookie]. Sabe-se que o servidor envia um cookie de sessão denominado [PHPSESSID] (linha 10). É, portanto, este cookie que se procura (linha 10);
  • linha 72: recuperam-se os cabeçalhos HTTP e [Set-Cookie], caso existam (não importa se são maiúsculas ou minúsculas). De facto, podem existir vários cabeçalhos [Set-Cookie], pelo que o que se recupera é uma matriz;
  • linha 73: se tiver sido recuperada uma matriz de cookies;
  • linhas 78-90: procura-se o cookie de sessão entre todos os cookies da tabela;
  • linha 80: a expressão relacional que permite procurar o cookie de sessão no cookie n.º i;
  • linha 81: se a comparação tiver produzido resultados;
  • linha 84: temos em results[1] o primeiro parêntese do modelo da expressão relacional, ou seja, (PHPSESSID=xxxx) até ao ponto (não incluído) que termina o cookie de sessão;
  • linhas 50-54: em cada pedido, o cookie de sessão é incluído nos cabeçalhos HTTP do pedido. Na primeira vez, este cookie está vazio e será, portanto, ignorado pelo servidor;

Os resultados da execução são os seguintes:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\http\axios-04.js"
action init-session en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'init-session',
  'estado': 700,
  'resposta': 'sessão iniciada com o tipo [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'estado': 200,
  'resposta': 'Autenticação bem-sucedida [admin, admin]' }

[Done] exited with code=0 in 0.982 seconds