Skip to content

12. JavaScript HTTP functions

Image

12.1. Choosing an HTTP library

We have chosen two libraries here:

ECMAScript 6 natively has an HTTP function called [fetch] that is not implemented by [node.js] (as of September 2019). There is a library called [node-fetch] that allows you to use the [fetch] function in Node. This library uses certain APIs specific to [node.js]. [node-fetch] code may therefore not be 100% portable to a non-[node] environment, such as a browser;

There is also a library called [axios] dedicated to HTTP requests that is compatible with both [node.js] and browsers. This is the library we will ultimately use.

We will present the same script written using both libraries to demonstrate that the coding approach is similar.

12.2. Setting up a development environment

12.2.1. Installing the tax calculation server

Ultimately, we will write a web application with the following architecture:

Image

JS: JavaScript

The JavaScript code is client-side:

  • from a service providing static pages or fragments;
  • a JSON service;

The JavaScript code is therefore a JSON client and, as such, can be organized into layers [UI, business logic, DAO] (UI: User Interface) just like our JSON clients written in PHP.

The server will be the tax calculation server for which we have already written 13 versions. We are going to write a 14th one. We therefore begin by duplicating, in NetBeans, the version 13 folder into the version 14 folder:

Image

  • in [6], we modify the [config.json] file of version 14 as follows:

{
    "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"
}
  • On line 3, we change the application's root directory;

To access this server, you must start the [Laragon] services.

Once this is done, we can test this new version of the server—which is currently identical to version 13—using [Postman] (see linked article). We can use the collection of requests used to test version 12 of the tax calculation server:

Image

  • in [1-4], use the [init-session-700] request to initialize a JSON session;
  • in [4-5], replace [version-12] with [version-14] to test version 14 of the project;
  • upon execution, we should receive the JSON response [6] from the server;

Version 14 of the server is now operational. We will need to modify it slightly. Let’s review this server’s API:

Action
Role
Execution Context
init-session
Used to set the type (json, xml, html) of the desired responses
GET request main.php?action=init-session&type=x
can be sent at any time
authenticate-user
Authorizes or denies a user's login
POST request main.php?action=authenticate-user
The request must have two posted parameters [user, password]
Can only be issued if the session type (json, xml, html) is known
calculate-tax
Performs a tax calculation simulation
POST request to main.php?action=calculate-tax
The request must have three posted parameters [married, children, salary]
Can only be issued if the session type (json, xml, html) is known and the user is authenticated
list-simulations
Request to view the list of simulations performed since the start of the session
GET request main.php?action=list-simulations
The request does not accept any other parameters
Can only be issued if the session type (json, xml, html) is known and the user is authenticated
delete-simulation
Deletes a simulation from the list of simulations
GET request main.php?action=list-simulations&number=x
The request does not accept any other parameters
Can only be issued if the session type (json, xml, html) is known and the user is authenticated
end-session
Ends the simulation session.
Technically, the old web session is deleted and a new session is created
Can only be issued if the session type (json, xml, html) is known and the user is authenticated

12.2.2. Installation of the JavaScript client HTTP libraries

Initially, we will work with the following architecture:

Image

  • In [1], a console script [node.js] makes an HTTP request to the tax calculation JSON server;
  • In [4], it receives this response and displays it in the console;

In Example 1, we will use the [node-fetch] and [axios] libraries, and then we will only keep [axios] for the following examples. We will now install these two JavaScript libraries from the [VSCode] terminal:

Image

We will also use the [qs] library, which allows for URL encoding of a string. Recall that this encoding is used to encode the parameters of an HTTP GET or POST request.

Image

12.3. script [fetch-01]

The [fetch-01] script uses the [node-fetch] library to initialize a JSON session with the tax calculation server. Its code is as follows:


'use strict';
 
// imports
import fetch from 'node-fetch';
import qs from 'qs';
import { sprintf } from 'sprintf-js';
import moment from 'moment';
 
 
// URL base of tax calculation server
const baseUrl = 'http://localhost/php7/scripts-web/impots/version-14/main.php?';
// init session
async function initSession() {
  // query options HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // execute query HTTP [get /main.php?action=init-session&type=json]
  let débutFetch;
  try {
    // asynchronous request - [fetch] makes a promise
    débutFetch = moment(Date.now());
    const response = await fetch(baseUrl + qs.stringify({
      action: 'init-session',
      type: 'json'
    }), options);
    // [response] is the entire HTTP response from the server (HTTP headers + response itself)
    // display this answer to see its structure
    console.log(sprintf("réponse fetch formatée en json,=%j, %s", response, heure(débutFetch)));
    console.log("réponse fetch en javascript=", response);
    // you can have HTTP headers
    console.log("entêtes de la réponse=", response.headers);
    // if application/json response, the server's json response is obtained with the asynchronous function [response.json()]
    // in which case the calling code obtains a [Promise] object
    // [await] allows you to obtain the server's [json] response rather than its promise
    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;
    // if text / plain, the text response from the server is obtained with [response.text()]
    // in which case the calling code obtains a [Promise] object
    // [await] allows you to obtain the server's [text] response rather than its promise
    // const text = await response.text();
    // console.log("answer text=", text);
    // return text;
  } catch (error) {
    // we're here because the server has sent an error code [404 Not Found, ...] accompanied by an empty body - we display the error to see its structure
    // or because the [fetch] client has thrown an exception (network inaccessible, ...)
    // the error structure is displayed
    console.log(sprintf("error fetch en json=%j, %s", error, heure(débutFetch)));
    console.log("error fetch en javascript=", typeof (error), error);
    // launch the error message received
    throw error.message;
  }
}
 
// the main function executes the asynchronous function [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));
  }
}
 
// test
main();
 
// time and duration display utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // is it necessary to calculate a duration?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // format time + duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}

Comments

  • JavaScript HTTP functions are asynchronous functions. Here, we’re applying what we learned in the previous section (see link);
  • line 24: to wait for the response from the asynchronous function [fetch] to be published on the [node.js] event loop, we use the keyword [await]. We know that this statement must be within code prefixed by the keyword [async] (line 13);
  • lines 13–56: we encapsulate the HTTP code within the asynchronous function [initSession];
  • lines 59–69: a second asynchronous function [main] is used to call the asynchronous function [initSession] in a blocking manner (async/await);
  • line 72: the asynchronous function [main] is called;
  • although the entire code resembles synchronous code, these are indeed asynchronous functions that are executed, but in a blocking manner;
  • line 19: to initialize a JSON session with the tax calculation server, you must send it the HTTP request [get /main.php?action=init-session&type=json]. This is what the code in lines 24–27 does. The syntax for [fetch] is as follows: [fetch(URL, options)] with:
    • [URL]: the URL being queried;
    • [options]: an object defining the request options. This is where we define the HTTP headers we want to send to the target server;
  • lines 15–18: we define the options for the request we want to make:
    • [method]: we want to perform a GET;
    • [timeout]: we want the [fetch] client to wait no more than 2 seconds for the server’s response. If this timeout is exceeded, [fetch] will throw an exception;
  • line 24: to obtain the URL [/main.php?action=init-session&type=json], we use the [qs] library to get the URL-encoded parameters [action,type] of the GET request. The resulting string is [init-session&type=json], which we could have constructed ourselves. We simply wanted to demonstrate how to obtain a URL-encoded string;
  • line 24: the keyword [await] indicates that an asynchronous task is being launched here and that we are waiting for it to publish its response on the [node.js] event loop;
  • line 24: in [response], we get a complex object that describes the entire received HTTP response (headers and body);
  • lines 30–31: We print the [response] object to see its structure, first as a string and then as a JavaScript object;
  • line 33: we display the HTTP headers sent by the server;
  • Line 38: We know that the tax calculation server will send a JSON string. This string is encapsulated in the [response] object. We can retrieve it using the [response.json()] method. However, this method is asynchronous. We therefore write [await response.json()] to retrieve the JSON string that will be published on the [node.js] event loop. In fact, it is not the JSON string itself that we obtain, but the JavaScript object represented by it;
  • line 39: display of the received JSON string;
  • line 40: returns the received JavaScript object;
  • line 47: we catch any potential errors from the [fetch] statement. This statement only throws an exception if the HTTP operation failed and no response was received from the server. If a response was received, even with an HTTP status code other than [200 OK], [fetch] does not throw an exception, and the server response will be available on line 38;
  • lines 51–52: the [error] object received by the [catch] clause is displayed, first as a JSON string and then as a JavaScript object;
  • line 54: the error message from [fetch] is stored in [error.message];
  • lines 59–69: the asynchronous function [main] calls the asynchronous function [initSession] in a blocking manner (await on line 62);
  • line 72: the asynchronous function [main] is launched, and the main script code is then complete. The overall script will finish when the launched asynchronous tasks have published their results to the event loop;

The results of the execution are as follows:

Case 1: the Laragon server is not running


[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:
   'network timeout at: 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

Comments

  • line 3: the HTTP request fails after 2 seconds and 62 milliseconds due to the 2-second timeout imposed on the HTTP request;
  • lines 4–9: the JavaScript [error] object intercepted by the [catch(error)] clause. This object has two properties:
    • [FetchError]: line 4;
    • [message]: lines 10–12;
  • line 14: the error message received by the asynchronous function [main];

Case 2: The Laragon server is running


[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',
  'état': 700,
  'réponse': 'session démarrée avec type [json]' } object
 
[Done] exited with code=0 in 1.022 seconds

Comments

  • line 3: [fetch] receives the server response after 375 ms;
  • lines 4–39: the structure of the JavaScript object [response] encapsulating the server’s response. Among its properties, some may be of interest to us:
    • [status] (line 25): HTTP status code of the server's response;
    • [statusText] (line 26): text associated with this code;
    • [headers] (line 27): the HTTP headers of the server’s response;
    • [body] (line 8): represents the document sent by the server. The [fetch] statement provides methods to work with it;
  • lines 29–39: the HTTP headers of the server’s response;
  • line 40: the asynchronous function [response.json()] returned its response after 1 millisecond;
  • lines 42–44: the JavaScript object received by the asynchronous function [main];

Case 3: The Laragon server is running, but an incorrect command is sent to it:

Image

  • above, line 26, an incorrect session type is passed to the server;

The results of the execution are as follows:


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',
  'état': 703,
  'réponse': 'paramètre type=[x] invalide' } object
 
[Done] exited with code=0 in 0.712 seconds
  • The server response is received on line 2;
  • line 24: we can see that the HTTP status code of the server response is 400, an error code. However, [fetch] did not throw an exception. As long as [fetch] receives a response from the server, it processes it and does not throw an exception;
  • lines 41–43: the response obtained by the asynchronous function [main];

12.4. script [fetch-02]

The following script reuses the [fetch-01] script while stripping away all unnecessary details:


'use strict';
 
// imports
import fetch from 'node-fetch';
import qs from 'qs';
 
// URL base of tax calculation server
const baseUrl = 'http://localhost/php7/scripts-web/impots/version-14/main.php?';
// init session
async function initSession() {
  // query options HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // execute query HTTP [get /main.php?action=init-session&type=json]
  const response = await fetch(baseUrl + qs.stringify({
    action: 'init-session',
    type: 'json'
  }), options);
  // result received in jSON
  return await response.json();
}
 
// the main function executes the asynchronous function [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);
  }
}
 
// test
main();

Results of a normal execution:


[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',
  'état': 700,
  'réponse': 'session démarrée avec type [json]' }
 
[Done] exited with code=0 in 0.56 seconds

Results of an execution with an exception (stopping the Laragon server):


[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]

Here we revisit the [fetch-01] script, which we rewrite using the [axios] library. As a reminder, our interest in this library lies in its portability between the [node.js] environment and common browsers. This allows:

  • In Phase 1, test our scripts in a [Node.js] environment;
  • in phase 2, to port them to a browser;

The [axios-01] script follows the structure of the [fetch-01] script:


'use strict';
import axios from 'axios';
 
// axios default configuration
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// init session
async function initSession(axios) {
  // query options HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // URL parameters
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  // execute query HTTP [get /main.php?action=init-session&type=json]
  try {
    // asynchronous request
    const response = await axios.request('main.php', options);
    // response is the entire HTTP response from the server (HTTP headers + response itself)
    // display this answer to see its structure
    console.log("réponse axios=", response);
    // the server response is in [response.data]
    return response.data;
  } catch (error) {
    // we're here because the server has sent an error code [404 Not Found, 500 Internal Server Error, ...]
    // the [error] parameter is an exception instance - it can take various forms
    // display it to see its structure
    console.log("axios error=", typeof (error), error);
    if (error.response) {
      // the server reported an error in status HTTP but also sent a response
      // then it is found in [error.response.data]
      // we know that the server sends jSON responses with structure {action, status, response}
      // and that in the event of an error, the error msg is in [reply]
      return error.response.data;
    } else {
      // we launch the error
      throw error;
    }
  }
}
 
// the main function executes the asynchronous function [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);
  }
}
 
// test
main();

Comments

  • line 2: we import the [axios] library;
  • lines 5-6: default configuration for HTTP requests. The [axios.defaults] options apply to all HTTP requests sent by the [axios] object without needing to be specified for each new request;
  • line 5: 2-second timeout for all requests;
  • line 6: all URLs will be relative to the base URL;
  • line 9: the asynchronous [initSession] function;
  • lines 11–18: the options for the HTTP request to be sent (in addition to the default options already defined in lines 5–6);
  • lines 14–17: the URL parameters [action=init-session&type=json]. The [params] object will be automatically converted to a URL-encoded string;
  • line 22: blocking call to the asynchronous function [axios.request]. The first parameter is the target URL constructed as [main.php] appended to the base URL defined on line 6. The second parameter is the [options] object from lines 11–18;
  • line 25: [response] is a JavaScript object encapsulating the entire HTTP response from the server (HTTP headers + response body). We display it to see its JavaScript structure;
  • line 27: if the server sent a document, then it is found in [response.data]. Here we know that the server sends a JSON response accompanied by the HTTP header [Content-type: application/json]. The presence of this header causes [axios] to automatically deserialize [response.data] into a JavaScript object;
  • line 28: the [axios] function can throw an exception. This is where [axios] differs from [fetch]. An exception is thrown in the following cases:
    • the HTTP request could not be sent (client-side error);
    • the server sent an HTTP error code (400, 404, 500, …) (this behavior is actually configurable). Note that if this HTTP status code is accompanied by a response, [fetch] did not throw an exception, whereas [axios] does. However, if the HTTP error code is accompanied by a document, it is placed in [error.response];
  • line 32: we display the JavaScript structure of the [error] object;
  • lines 33–38: if the [error] object contains a [response] object, then this response is returned to the calling code;
  • lines 39–42: in all other cases, the [error] object is passed back to the calling code;
  • lines 47–57: the asynchronous function [main];
  • line 50: blocking call to the asynchronous function [initSession]. The JSON response from the server is retrieved as a JavaScript object;
  • lines 53–56: handling any errors. The error message is in [error.message];

The results of the execution are as follows:

Case 1: The Laragon server is not running


[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/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],
        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

Comments

  • lines 33-136: the [error] object contains a lot of information;
    • [Error], lines 3-9: a description of the error that occurred;
    • [config], lines 10–27: the configuration of the HTTP request that led to this error;
    • [config.url], lines 11–12: the target URL;
    • [config.method], line 13: request method;
    • [config.params], line 14: the URL parameters;
    • [config.headers], lines 16–17: the HTTP headers of the request;
    • [config.baseURL], line 18: the base URL of the target URL;
    • [config.timeout], line 21: the request timeout;
    • [code], line 28: an error code;
    • [request], lines 29–133: a detailed description of the HTTP request. Note that most properties are prefixed with an underscore (_), indicating that they are internal properties of the [request] object not intended to be used directly by the developer;
    • [response], line 134: the server response, which is empty here;
  • line 138: the error message displayed by the [main] function;

Case 2: The Laragon server is running


[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',
     'état': 700,
     'réponse': 'session démarrée avec type [json]' } }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'état': 700,
  'réponse': 'session démarrée avec type [json]' } object
 
[Done] exited with code=0 in 1.115 seconds

Comments

  • lines 3–203: the JavaScript object [response] that encapsulates the server’s HTTP response;
  • line 3, [status]: the HTTP status code of the response;
  • line 4, [statusText]: the text associated with the previous HTTP status code;
  • lines 5-13, [headers]: the HTTP headers of the response:
    • line 10, [Set-Cookie]: the session cookie;
    • line 13, [Content-Type]: the type of document sent by the server;
  • lines 14–31, [config]: the configuration of the HTTP request sent;
  • lines 32–199, [request]: the JavaScript object detailing the HTTP request sent;
  • lines 200–203, [request.data]: the JavaScript object encapsulating the server’s JSON response;
  • lines 205–207: the response retrieved by the asynchronous function [main];

Case 3: An incorrect [init-session] request is sent to the Laragon server;

Image

The execution results are as follows:


[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',
        'état': 703,
        'réponse': 'paramètre type=[x] invalide' } },
  isAxiosError: true,
  toJSON: [Function] }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'état': 703,
  'réponse': 'paramètre type=[x] invalide' } object
 
[Done] exited with code=0 in 0.69 seconds

Because the server responded with an HTTP 400 code (line 15), [axios] threw an exception (again, this behavior is configurable). Although [axios] threw an exception, we still get the HTTP response from the server in [error.response] (line 14) and the sent JSON document in [error.response.data] (line 34). Lines 41–43: the [main] function correctly retrieves the JSON response from the server.

12.6. script [axios-02]

Now that we have detailed the objects handled by the [axios] library during an HTTP request, we can rewrite the [axios-01] script as follows:


'use strict';
import axios from 'axios';
 
// axios default configuration
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';
 
// init session
async function initSession(axios) {
  // query options HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // URL parameters
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    // execute query HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    // the server response is in [response.data]
    return response.data;
  } catch (error) {
    // server response
    if (error.response) {
      // the answer jSON is in [error.response.data]
      return error.response.data;
    } else {
      // error restart
      throw error;
    }
  }
}
 
// the main function executes the asynchronous function [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);
  }
}
 
// test
main();

12.7. script [axios-03]

The [axios-03] script follows the same methodology as the [axios-02] script. This time, we add the [authenticate-user] HTTP request to the server, which is made using a POST request:


'use strict';
import axios from 'axios';
import qs from 'qs'
 
// axios configuration
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';
 
 
// init session
async function initSession(axios) {
  // query options HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // URL parameters
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    // execute query HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    // the server response is in [response.data]
    return response.data;
  } catch (error) {
    // server response
    if (error.response) {
      // the answer jSON is in [error.response.data]
      return error.response.data;
    } else {
      // error restart
      throw error;
    }
  }
}
 
async function authentifierUtilisateur(axios, user, password) {
  // query options HHTP [POST /main.php?action=authenticate-user]
  const options = {
    method: "POST",
    headers: {
      'Content-type': 'application/x-www-form-urlencoded',
    },
    // body of POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    // URL parameters
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  try {
    // execute query HTTP [post /main.php?action=authenticate-user]
    const response = await axios.request('main.php', options);
    // the server response is in [response.data]
    return response.data;
  } catch (error) {
    // server response
    if (error.response) {
      // the answer jSON is in [error.response.data]
      return error.response.data;
    } else {
      // error restart
      throw error;
    }
  }
}
 
// the main function executes asynchronous functions one by one
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);
    // authenticate-user
    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);
  }
}
 
// test
main();

Comments

  • lines 38-70: the asynchronous function [authenticateUser];
  • line 39: the request must be made [POST /main.php?action=authenticate-user];
  • lines 40–54: the HTTP request options;
  • line 41: this is a POST request;
  • lines 42–44: the POST parameters will be URL-encoded in a document that the client sends with its request;
  • lines 46–49: the [data] property must contain the URL-encoded POST string. To do this, we use the [qs] library imported on line 3;
  • lines 55–69: to execute the request, we use the same code as in the [initSession] method;
  • lines 73–89: the [asynchrone] method successively calls the [initSession] and [authentifierUtilisateur] methods in a blocking manner, lines 77 and 82;
  • line 82: the pair (admin, admin) is used as the login credentials. We know that they are recognized by the server;

The results of the execution are as follows:


[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',
  'état': 700,
  'réponse': 'session démarrée avec type [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'état': 103,
  'réponse':
   [ 'pas de session en cours. Commencer par action [init-session]' ] }
 
[Done] exited with code=0 in 0.834 seconds
  • lines 9-12: user authentication fails: the server did not retain the fact that a JSON session had been initiated. This is because the session cookie sent in response to the first [init-session] request was not sent back;

12.8. script [axios-04]

The [axios-04] script introduces two improvements to the [axios-03] script:

  • it handles the session cookie;
  • it factors into a [getRemoteData] function what is common to the [initSession] and [authenticateUser] functions;

'use strict';
import axios from 'axios';
import qs from 'qs'
 
// axios configuration
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';
 
// session cookie
const sessionCookieName = "PHPSESSID";
let sessionCookie = '';
 
// init session
async function initSession(axios) {
  // query options HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // URL parameters
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  // execute query HTTP
  return await getRemoteData(axios, options);
}
 
async function authentifierUtilisateur(axios, user, password) {
  // query options HHTP [post /main.php?action=authenticate-user]
  const options = {
    method: "POST",
    headers: {
      'Content-type': 'application/x-www-form-urlencoded',
    },
    // body of POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    // URL parameters
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  // execute query HTTP
  return await getRemoteData(axios, options);
}
 
async function getRemoteData(axios, options) {
  // for the session cookie
  if (!options.headers) {
    options.headers = {};
  }
  options.headers.Cookie = sessionCookie;
  // execute query HTTP
  let response;
  try {
    // asynchronous request
    response = await axios.request('main.php', options);
  } catch (error) {
    // the [error] parameter is an exception instance - it can take various forms
    if (error.response) {
      // the server response is in [error.response]
      response = error.response;
    } else {
      // error restart
      throw error;
    }
  }
  // response is the entire HTTP response from the server (HTTP headers + response itself)
  // retrieve the session cookie if it exists
  const setCookie = response.headers['set-cookie'];
  if (setCookie) {
    // setCookie is an array
    // look for the session cookie in this table
    let trouvé = false;
    let i = 0;
    while (!trouvé && i < setCookie.length) {
      // look for the session cookie
      const results = RegExp('^(' + sessionCookieName + '.+?);').exec(setCookie[i]);
      if (results) {
        // the session cookie is stored
        // eslint-disable-next-line require-atomic-updates
        sessionCookie = results[1];
        // we found
        trouvé = true;
      } else {
        // next item
        i++;
      }
    }
  }
  // the server response is in [response.data]
  return response.data;
}
 
// the main function executes asynchronous functions one by one
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);
    // authenticate-user
    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);
  }
}
 
// test
main();

Comments

  • lines 14–26: the [initSession] function. It now simply prepares the HTTP request to be sent to the server but does not execute it. It delegates this task to the [getRemoteDate] method in lines 49–95;
  • lines 28–47: the [authentifierUtilisateur] function follows the same procedure;
  • line 49: the [getRemoteData] function receives the two pieces of information it needs to execute an HTTP request:
    • [axios], the object responsible for sending the request and receiving the response;
    • [options], the configuration options for the request to be sent to the server;
  • line 59: executing the request and waiting for its JSON response;
  • lines 60–68: handling any exceptions;
  • line 64: retrieve the response, which may be encapsulated in the error object;
  • line 67: if the server threw an exception without including the server response, then the received error is propagated to the calling code;
  • The [getRemoteData] function manages the session cookie:
    • it stores it in the [sessionCookie] variable (line 11) when it receives it for the first time;
    • it then returns it with each new HTTP request;
  • lines 72–92: [getRemoteData] analyzes each server response to determine if it sent the [Set-Cookie] HTTP header. We know that the server sends a session cookie named [PHPSESSID] (line 10). This is therefore the cookie we are looking for (line 10);
  • line 72: we retrieve the [Set-Cookie] HTTP headers if they exist (case is not sensitive). There may in fact be multiple [Set-Cookie] headers, so we retrieve an array;
  • line 73: if an array of cookies has been retrieved;
  • lines 78–90: we search for the session cookie among all the cookies in the array;
  • line 80: the relational expression used to search for the session cookie in cookie #i;
  • line 81: if the comparison returned results;
  • line 84: we have in results[1], the first parenthesis of the relational expression pattern, i.e., (PHPSESSID=xxxx) up to the closing parenthesis (not included) that ends the session cookie;
  • lines 50–54: with each request, the session cookie is included in the request’s HTTP headers. The first time, this cookie is empty and will therefore be ignored by the server;

The execution results are as follows:


[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',
  'état': 700,
  'réponse': 'session démarrée avec type [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'état': 200,
  'réponse': 'Authentification réussie [admin, admin]' }
 
[Done] exited with code=0 in 0.982 seconds