Skip to content

12. Funkcje HTTP w języku JavaScript

Image

12.1. Wybór biblioteki HTTP

W tym przypadku wybraliśmy dwie biblioteki:

EcmaScript 6 posiada natywną funkcję HTTP o nazwie [fetch], która nie jest zaimplementowana przez [node.js] (wrzesień 2019 r.). Istnieje biblioteka o nazwie [node-fetch], która umożliwia korzystanie z funkcji [fetch] w środowisku Node. Biblioteka ta wykorzystuje pewne elementy specyficzne dla API. Kod [node-fetch] może zatem nie być w 100% przenośny w środowisku innym niż [node], na przykład w przeglądarce;

Istnieje ponadto biblioteka o nazwie [axios] przeznaczona do obsługi zapytań HTTP, kompatybilna zarówno z [node.js], jak i z przeglądarkami. To właśnie tej biblioteki ostatecznie użyjemy.

Przedstawimy ten sam skrypt napisany przy użyciu obu bibliotek, aby pokazać, że proces kodowania z ich wykorzystaniem jest analogiczny.

12.2. Przygotowanie środowiska pracy

12.2.1. Instalacja serwera do obliczania podatków

Ostatecznie napiszemy aplikację internetową o następującej architekturze:

Image

JS: JavaScript

Kod JavaScript działa po stronie klienta:

  • usługi obsługującej statyczne strony lub fragmenty;
  • usługi jSON;

Kod JavaScript jest zatem klientem jSON i jako taki może być zorganizowany w warstwy [UI, métier, dao] (UI: Interfejs użytkownika), podobnie jak nasze klienckie aplikacje jSON napisane w PHP.

Serwerem będzie ten służący do obliczania podatku, którego napisaliśmy już 13 wersji. Zamierzamy napisać 14. wersję. Zaczynamy więc od skopiowania w programie NetBeans folderu wersji 13 do folderu wersji 14:

Image

  • na [6], a następnie modyfikujemy plik [config.json] z wersji 14 w następujący sposób:

{
    "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"
}
  • w wierszu 3 zmieniamy katalog główny aplikacji;

Aby uzyskać dostęp do tego serwera, należy uruchomić usługi [Laragon].

Po wykonaniu tej czynności możemy przetestować za pomocą [Postman] (patrz artykuł pod linkiem) tę nową wersję serwera, która na razie jest identyczna z wersją 13. Możemy wykorzystać zbiór zapytań używanych do testowania wersji 12 serwera obliczającego podatki:

Image

  • w [1-4] należy użyć zapytania [init-session-700] w celu zainicjowania sesji jSON;
  • w [4-5] należy zastąpić [version-12] na [version-14], aby przetestować wersję 14 projektu;
  • podczas wykonywania należy otrzymać odpowiedź jS0N [6] z serwera;

Wersja 14 serwera jest już gotowa do działania. Będziemy musieli wprowadzić w niej niewielkie zmiany. Przypomnijmy kod API tego serwera:

Akcja
Rola
Kontekst wykonania
init-session
Służy do określenia typu (json, xml, html) oczekiwanych odpowiedzi
Żądanie GET main.php?action=init-session&type=x
może zostać wysłane w dowolnym momencie
authentifier-utilisateur
Zezwala lub nie zezwala użytkownikowi na zalogowanie się
Żądanie POST main.php?action=authentifier-utilisateur
Żądanie musi zawierać dwa parametry wysyłane metodą POST [user, password]
Może zostać wysłane tylko wtedy, gdy typ sesji (json, xml, html) jest znany
oblicz-podatek
Przeprowadza symulację obliczenia podatku
Żądanie POST main.php?action=calculer-impot
Żądanie musi zawierać trzy parametry wysyłane metodą POST: [marié, enfants, salaire]
Może zostać wysłane tylko wtedy, gdy znany jest typ sesji (json, xml, html) i użytkownik jest uwierzytelniony
lister-simulations
Żądanie wyświetlenia listy symulacji przeprowadzonych od początku sesji
Żądanie GET main.php?action=lister-simulations
Żądanie nie akceptuje żadnych innych parametrów
Może zostać wygenerowana tylko wtedy, gdy typ sesji (json, xml, html) jest znany, a użytkownik uwierzytelniony
usuń-symulację
Usuwa symulację z listy symulacji
Żądanie GET main.php?action=lister-simulations&numéro=x
Żądanie nie akceptuje żadnych innych parametrów
Może zostać wysłane tylko wtedy, gdy typ sesji (json, xml, html) jest znany, a użytkownik jest uwierzytelniony
zakończenie-sesji
Zakończenie sesji symulacji.
Z technicznego punktu widzenia poprzednia sesja internetowa zostaje usunięta, a tworzona jest nowa sesja
Może zostać wysłane tylko wtedy, gdy typ sesji (json, xml, html) jest znany, a użytkownik jest uwierzytelniony

12.2.2. Instalacja bibliotek HTTP klienta JavaScript

Na początku będziemy pracować z następującą architekturą:

Image

  • w [1] skrypt konsolowy [node.js] wysyła żądanie HTTP do serwera jSON odpowiedzialnego za obliczanie podatku;
  • w [4] odbiera tę odpowiedź i wyświetla ją na konsoli;

W przykładzie nr 1 wykorzystamy biblioteki [node-fetch] i [axios], a w kolejnych przykładach zachowamy tylko [axios]. Teraz zainstalujemy te dwie biblioteki JavaScript z terminala [VSCode]:

Image

Wykorzystamy również bibliotekę [qs], która umożliwia kodowanie URL ciągu znaków. Przypomnijmy, że kodowanie to służy do kodowania parametrów żądania HTTP, GET lub POST.

Image

12.3. skrypt [fetch-01]

Skrypt [fetch-01] wykorzystuje bibliotekę [node-fetch] do zainicjowania sesji jSON z serwerem obliczającym podatek. Jego kod wygląda następująco:


'use strict';

// importy
import fetch from 'node-fetch';
import qs from 'qs';
import { sprintf } from 'sprintf-js';
import moment from 'moment';


// Podstawowy serwer obliczania podatków URL
const baseUrl = 'http://localhost/php7/scripts-web/impots/version-14/main.php?';
// inicjowanie sesji
async function initSession() {
  // opcje zapytania HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // wykonanie zapytania HTTP [get /main.php?action=init-session&type=json]
  let débutFetch;
  try {
    // żądanie asynchroniczne – [fetch] zwraca obietnicę
    débutFetch = moment(Date.now());
    const response = await fetch(baseUrl + qs.stringify({
      action: 'init-session',
      type: 'json'
    }), options);
    // [response] to cała odpowiedź serwera HTTP (nagłówki HTTP + sama odpowiedź)
    // wyświetlamy tę odpowiedź, aby zapoznać się z jej strukturą
    console.log(sprintf("réponse fetch formatée en json,=%j, %s", response, heure(débutFetch)));
    console.log("réponse fetch en javascript=", response);
    // można uzyskać nagłówki HTTP
    console.log("entêtes de la réponse=", response.headers);
    // jeśli odpowiedź jest typu application/json, odpowiedź JSON z serwera uzyskuje się za pomocą funkcji asynchronicznej [response.json()]
    // w tym przypadku kod wywołujący otrzymuje obiekt [Promise]
    // [await] pozwala uzyskać odpowiedź serwera [json] zamiast jego obietnicy
    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;
    // jeśli odpowiedź jest typu text/plain, tekstową odpowiedź serwera uzyskuje się za pomocą [response.text()]
    // w tym przypadku kod wywołujący otrzymuje obiekt [Promise]
    // [await] pozwala uzyskać odpowiedź [texte] z serwera zamiast jego obietnicy
    // const text = await response.text();
    // console.log("odpowiedź tekstowa=", text);
    // return text;
  } catch (error) {
    // znajdujemy się tutaj, ponieważ serwer wysłał kod błędu [404 Not Found, ...] wraz z pustą treścią – wyświetlamy błąd, aby sprawdzić jego strukturę
    // lub też dlatego, że klient [fetch] zgłosił wyjątek (brak dostępu do sieci itp.)
    // wyświetla się struktura błędu
    console.log(sprintf("error fetch en json=%j, %s", error, heure(débutFetch)));
    console.log("error fetch en javascript=", typeof (error), error);
    // wysyłamy otrzymany komunikat o błędzie
    throw error.message;
  }
}

// funkcja main wywołuje funkcję asynchroniczną [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();

// narzędzie do wyświetlania godziny i czasu trwania
function heure(début) {
  // aktualna godzina
  const now = moment(Date.now());
  // formatowanie godziny
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // czy należy obliczyć czas trwania?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatowanie godziny i czasu trwania
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // wynik
  return result;
}

Komentarze

  • Funkcje HTTP w JavaScript są funkcjami asynchronicznymi. Wykorzystujemy tutaj wiedzę zdobytą w poprzedniej sekcji (patrz link);
  • wiersz 24: aby poczekać, aż odpowiedź funkcji asynchronicznej [fetch] zostanie opublikowana w pętli zdarzeń funkcji [node.js], używamy słowa kluczowego [await]. Wiemy, że instrukcja ta musi znajdować się w kodzie poprzedzonym słowem kluczowym [async] (wiersz 13);
  • wiersze 13–56: umieszczamy kod HTTP w funkcji asynchronicznej [initSession];
  • wiersze 59–69: druga funkcja asynchroniczna [main] służy do wywołania blokującego (async/await) funkcji asynchronicznej [initSession];
  • wiersz 72: wywoływana jest funkcja asynchroniczna [main];
  • chociaż cały kod wygląda jak kod synchroniczny, to jednak wykonywane są funkcje asynchroniczne, ale w trybie blokującym;
  • wiersz 19: aby zainicjować sesję jSON z serwerem obliczającym podatek, należy wysłać do niego polecenie HTTP [get /main.php?action=init-session&type=json]. Tak właśnie działa kod w wierszach 24–27. Składnia polecenia [fetch] jest następująca: [fetch(URL, options)] z:
    • [URL]: zapytanie URL;
    • [options]: obiekt definiujący opcje zapytania. To właśnie tam definiuje się nagłówki HTTP, które mają zostać wysłane do maszyny docelowej;
  • wiersze 15–18: definiuje się opcje zapytania, które ma zostać wysłane:
    • [method]: chcemy wykonać GET;
    • [timeout]: chcemy, aby klient [fetch] nie czekał dłużej niż 2 sekundy na odpowiedź serwera. Jeśli ten limit zostanie przekroczony, [fetch] wygeneruje wyjątek;
  • wiersz 24: aby uzyskać kodowanie URL [/main.php?action=init-session&type=json], używamy biblioteki [qs], aby uzyskać kodowanie URL dla parametrów [action,type] z GET. Otrzymany ciąg to [init-session&type=json], który mogliśmy skonstruować samodzielnie. Chcieliśmy po prostu pokazać, jak uzyskać zakodowany ciąg URL;
  • wiersz 24: słowo kluczowe [await] wskazuje, że uruchamiane jest tutaj zadanie asynchroniczne i oczekuje się, że opublikuje ono swoją odpowiedź w pętli zdarzeń [node.js];
  • wiersz 24: w [response] otrzymujemy złożony obiekt opisujący całą otrzymaną odpowiedź HTTP (nagłówki i dokument);
  • wiersze 30–31: wyświetlany jest obiekt [response] w celu zapoznania się z jego strukturą, najpierw jako ciąg znaków, a następnie jako obiekt JavaScript;
  • wiersz 33: wyświetla się nagłówki HTTP wysłane przez serwer;
  • wiersz 38: wiadomo, że serwer obliczający podatek wyśle ciąg znaków jSON. Jest on zawarty w obiekcie [response]. Można go uzyskać za pomocą metody [response.json()]. Metoda ta jest jednak asynchroniczna. Wpisujemy zatem [await response.json()], aby uzyskać ciąg znaków jSON, który zostanie opublikowany w pętli zdarzeń obiektu [node.js]. W rzeczywistości nie otrzymujemy ciągu znaków jSON, ale obiekt JavaScript reprezentowany przez ten ciąg;
  • wiersz 39: wyświetlenie otrzymanego ciągu znaków jSON;
  • wiersz 40: zwracamy otrzymany obiekt JavaScript;
  • wiersz 47: przechwytujemy ewentualny błąd instrukcji [fetch]. Instrukcja ta zgłasza wyjątek tylko wtedy, gdy operacja HTTP nie powiodła się i nie otrzymano żadnej odpowiedzi z serwera. Jeśli otrzymano odpowiedź, nawet z kodem HTTP różnym od [200 OK], [fetch] nie generuje wyjątku, a odpowiedź serwera będzie dostępna w wierszu 38;
  • wiersze 51–52: wyświetlany jest obiekt [error] otrzymany przez klauzulę [catch], najpierw jako ciąg znaków jSON, a następnie jako obiekt JavaScript;
  • wiersz 54: komunikat o błędzie z [fetch] znajduje się w [error.message];
  • wiersze 59–69: funkcja asynchroniczna [main] uruchamia funkcję asynchroniczną [initSession] w trybie blokującym (await w wierszu 62);
  • wiersz 72: uruchamiana jest funkcja asynchroniczna [main], a główny kod skryptu zostaje wówczas zakończony. Cały skrypt zostanie zakończony, gdy uruchomione zadania asynchroniczne opublikują swoje wyniki w pętli zdarzeń;

Wyniki wykonania są następujące:

Przypadek 1: serwer Laragon nie został uruchomiony


[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:
   'przekroczenie limitu czasu połączenia sieciowego pod adresem: 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 ciąg znaków

[Done] exited with code=0 in 2.804 seconds

Komentarze

  • wiersz 3: żądanie HTTP kończy się niepowodzeniem po 2 sekundach i 62 milisekundach z powodu limitu czasu wynoszącego 2 sekundy, który został nałożony na żądanie HTTP;
  • wiersze 4–9: obiekt JavaScript [error] przechwycony przez klauzulę [catch(error)]. Obiekt ten ma dwie właściwości:
    • [FetchError]: wiersz 4;
    • [message]: wiersze 10–12;
  • wiersz 14: komunikat o błędzie odebrany przez funkcję asynchroniczną [main];

Przypadek 2: uruchomiono serwer Laragon


[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',
  'status: 700,
  'odpowiedź: 'sesja rozpoczęta z typem [json]' } obiekt

[Done] exited with code=0 in 1.022 seconds

Komentarze

  • wiersz 3: funkcja [fetch] otrzymuje odpowiedź z serwera po 375 ms;
  • wiersze 4–39: struktura obiektu JavaScript [response] zawierająca odpowiedź serwera. Niektóre z jego właściwości mogą nas zainteresować:
    • [status] (wiersz 25): kod HTTP z odpowiedzi serwera;
    • [statusText] (wiersz 26): tekst powiązany z tym kodem;
    • [headers] (wiersz 27): nagłówki HTTP odpowiedzi serwera;
    • [body] (wiersz 8): reprezentuje dokument wysłany przez serwer. Instrukcja [fetch] udostępnia metody jego przetwarzania;
  • wiersze 29–39: nagłówki HTTP z odpowiedzi serwera;
  • wiersz 40: funkcja asynchroniczna [response.json()] zwróciła swoją odpowiedź po upływie 1 milisekundy;
  • wiersze 42–44: obiekt JavaScript odebrany przez funkcję asynchroniczną [main];

Przypadek 3: serwer Laragon został uruchomiony, ale wysłano do niego błędne polecenie:

Image

  • powyżej, w wierszu 26, przekazujemy serwerowi nieprawidłowy typ sesji;

Wyniki wykonania są następujące:


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',
  'status: 703,
  'odpowiedź: 'nieprawidłowy parametr type=[x]' } obiekt

[Done] exited with code=0 in 0.712 seconds
  • odpowiedź serwera otrzymano w wierszu 2;
  • w wierszu 24 widać, że kod HTTP w odpowiedzi serwera to 400, czyli kod błędu. Niemniej jednak [fetch] nie zgłosił wyjątku. Dopóki [fetch] otrzymuje odpowiedź z serwera, przetwarza ją i nie zgłasza wyjątku;
  • wiersze 41–43: odpowiedź uzyskana przez funkcję asynchroniczną [main];

12.4. skrypt [fetch-02]

Poniższy skrypt stanowi wersję skryptu [fetch-01] pozbawioną wszystkich zbędnych szczegółów:


'use strict';

// importy
import fetch from 'node-fetch';
import qs from 'qs';

// URL podstawowy serwer obliczania podatku
const baseUrl = 'http://localhost/php7/scripts-web/impots/version-14/main.php?';
// inicjowanie sesji
async function initSession() {
  // opcje zapytania HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // wykonanie zapytania HTTP [get /main.php?action=init-session&type=json]
  const response = await fetch(baseUrl + qs.stringify({
    action: 'init-session',
    type: 'json'
  }), options);
  // wynik otrzymany w jSON
  return await response.json();
}

// funkcja main wywołuje funkcję asynchroniczną [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();

Wyniki normalnego wykonania:


[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',
  'stan: 700,
  'odpowiedź: 'sesja uruchomiona z typem [json]' }

[Done] exited with code=0 in 0.56 seconds

Wyniki wykonania z wyjątkiem (zatrzymujemy serwer Laragon):


[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. skrypt [axios-01]

W tym miejscu powracamy do skryptu [fetch-01], który przepisujemy przy użyciu biblioteki [axios]. Przypominamy, że nasza zainteresowanie tą biblioteką wynika z tego, że jest ona przenośna między środowiskiem [node.js] a środowiskami popularnych przeglądarek. Pozwala to:

  • w fazie 1 na testowanie naszych skryptów w środowisku [node.js];
  • w fazie 2 – przeniesienie ich do przeglądarki;

Skrypt [axios-01] opiera się na strukturze skryptu [fetch-01]:


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

// domyślna konfiguracja axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// inicjowanie sesji
async function initSession(axios) {
  // opcje żądania HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // parametry URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  // wykonanie zapytania HTTP [get /main.php?action=init-session&type=json]
  try {
    // żądanie asynchroniczne
    const response = await axios.request('main.php', options);
    // odpowiedź stanowi całość odpowiedzi HTTP z serwera (nagłówki HTTP + sama odpowiedź)
    // wyświetlamy tę odpowiedź, aby zapoznać się z jej strukturą
    console.log("réponse axios=", response);
    // odpowiedź serwera znajduje się w [response.data]
    return response.data;
  } catch (error) {
    // znajdujemy się tutaj, ponieważ serwer wysłał kod błędu [404 Not Found, 500 Internal Server Error, ...]
    // parametr [error] jest instancją wyjątku – może przybierać różne formy
    // wyświetlamy go, aby sprawdzić jego strukturę
    console.log("axios error=", typeof (error), error);
    if (error.response) {
      // serwer zgłosił błąd w statusie HTTP, ale wysłał również odpowiedź
      // więc ta znajduje się w [error.response.data]
      // wiemy, że serwer wysyła odpowiedzi jSON o strukturze {akcja, stan, odpowiedź}
      // oraz że w przypadku błędu komunikat o błędzie znajduje się w [réponse]
      return error.response.data;
    } else {
      // wywołuje się błąd
      throw error;
    }
  }
}

// funkcja main wywołuje funkcję asynchroniczną [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();

Komentarze

  • wiersz 2: importujemy bibliotekę [axios];
  • wiersze 5–6: domyślna konfiguracja zapytań HTTP. Opcje [axios.defaults] mają zastosowanie do wszystkich zapytań HTTP wysyłanych przez obiekt [axios], bez konieczności ponownego ich podawania przy każdym nowym zapytaniu;
  • wiersz 5: limit czasu wynoszący 2 sekundy dla wszystkich zapytań;
  • wiersz 6: wszystkie URL będą wyrażane względem podstawowego URL;
  • wiersz 9: funkcja asynchroniczna [initSession];
  • wiersze 11–18: opcje zapytania HTTP, które zostanie wysłane (oprócz opcji domyślnych już zdefiniowanych w wierszach 5–6);
  • wiersze 14–17: parametry funkcji URL [action=init-session&type=json]. Obiekt [params] zostanie automatycznie przekształcony w zakodowany ciąg znaków URL;
  • wiersz 22: blokujące wywołanie funkcji asynchronicznej [axios.request]. Pierwszym parametrem jest obiekt docelowy URL, utworzony poprzez dodanie obiektu [main.php] do obiektu bazowego URL zdefiniowanego w wierszu 6. Drugim parametrem jest obiekt [options] z wierszy 11–18;
  • wiersz 25: [response] to obiekt JavaScript zawierający całą odpowiedź serwera HTTP (nagłówki HTTP + dokument odpowiedzi). Wyświetlamy go, aby zapoznać się z jego strukturą JavaScript;
  • wiersz 27: jeśli serwer wysłał dokument, to znajduje się on w [response.data]. W tym przypadku wiemy, że serwer wysyła odpowiedź jSON wraz z nagłówkiem HTTP [Content-type : application/json]. Obecność tego nagłówka powoduje, że [axios] automatycznie deserializuje [response.data] do obiektu JavaScript;
  • wiersz 28: funkcja [axios] może wygenerować wyjątek. W tym właśnie aspekcie [axios] różni się od [fetch]. Wyjątek jest generowany w następujących przypadkach:
    • nie udało się wysłać żądania HTTP (błąd po stronie klienta);
    • serwer wysłał kod błędu HTTP (400, 404, 500, …) (tę opcję można w rzeczywistości skonfigurować). Przypominamy, że o ile kodowi HTTP towarzyszy odpowiedź, to kod [fetch] nie wywołuje wyjątku, podczas gdy kod [axios] go wywołuje. Niemniej jednak, jeśli kod błędu HTTP jest połączony z dokumentem, dokument ten jest umieszczany w [error.response];
  • wiersz 32: wyświetlana jest struktura JavaScript obiektu [error];
  • wiersze 33–38: jeśli obiekt [error] zawiera obiekt [response], to właśnie tę odpowiedź zwraca się do kodu wywołującego;
  • wiersze 39–42: w pozostałych przypadkach obiekt [error] jest przekazywany z powrotem do kodu wywołującego;
  • wiersze 47–57: funkcja asynchroniczna [main];
  • wiersz 50: wywołanie blokujące funkcji asynchronicznej [initSession]. Odbiera się odpowiedź jSON z serwera jako obiekt JavaScript;
  • wiersze 53–56: przechwycenie ewentualnego błędu. Komunikat o błędzie znajduje się w [error.message];

Wyniki wykonania są następujące:

Przypadek 1: serwer Laragon nie został uruchomiony


[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

Komentarze

  • wiersze 33–136: obiekt [error] zawiera wiele informacji;
    • [Error], wiersze 3–9: opis błędu, który wystąpił;
    • [config], wiersze 10–27: konfiguracja zapytania HTTP, które doprowadziło do tego błędu;
    • [config.url], wiersze 11–12: cel URL;
    • [config.method], wiersz 13: metoda zapytania;
    • [config.params], wiersz 14: parametry URL;
    • [config.headers], wiersze 16–17: nagłówki zapytania HTTP;
    • [config.baseURL], wiersz 18: podstawowy URL dla docelowego URL;
    • [config.timeout], wiersz 21: limit czasu żądania;
    • [code], wiersz 28: kod błędu;
    • [request], wiersze 29–133: szczegółowy opis żądania HTTP. Warto zauważyć, że większość właściwości ma przedrostek w postaci znaku podkreślenia _, co wskazuje, że są to właściwości wewnętrzne obiektu [request], które nie są przeznaczone do bezpośredniego wykorzystania przez programistę;
    • [response], wiersz 134: odpowiedź serwera, w tym przypadku nieobecna;
  • wiersz 138: komunikat o błędzie wyświetlany przez funkcję [main];

Przypadek 2: uruchomiono serwer Laragon


[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',
     'status': 700,
     'odpowiedź': 'sesja uruchomiona z typem [json]' } }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'stan': 700,
  'odpowiedź: 'sesja rozpoczęta z typem [json]' } obiekt

[Done] exited with code=0 in 1.115 seconds

Komentarze

  • wiersze 3–203: obiekt JavaScript [response], który zawiera odpowiedź serwera HTTP;
  • wiersz 3, [status]: kod HTTP odpowiedzi;
  • wiersz 4, [statusText]: tekst powiązany z poprzednim kodem HTTP;
  • wiersze 5–13, [headers]: nagłówki odpowiedzi HTTP:
    • wiersz 10, [Set-Cookie]: plik cookie sesji;
    • wiersz 13, [Content-Type]: typ dokumentu wysłanego przez serwer;
  • wiersze 14–31, [config]: konfiguracja wysłanego żądania HTTP;
  • wiersze 32–199, [request]: obiekt JavaScript zawierający szczegóły wysłanego żądania HTTP;
  • wiersze 200–203, [request.data]: obiekt JavaScript zawierający odpowiedź jSON z serwera;
  • wiersze 205–207: odpowiedź pobrana przez funkcję asynchroniczną [main];

Przypadek 3: wysyłamy błędne żądanie [init-session] do serwera Laragon;

Image

Wyniki wykonania są następujące:


[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',
        '„status”: 703,
        'odpowiedź: 'nieprawidłowy parametr type=[x]' } },
  isAxiosError: true,
  toJSON: [Function] }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'stan': 703,
  'odpowiedź: 'nieprawidłowy parametr typu=[x]' } obiekt

[Done] exited with code=0 in 0.69 seconds

Ponieważ serwer zwrócił kod 400 HTTP (wiersz 15), program [axios] zgłosił wyjątek (również to zachowanie można skonfigurować). Chociaż program [axios] zgłosił wyjątek, w [error.response] (wiersz 14) otrzymujemy prawidłową odpowiedź HTTP z serwera, a dokument jSON został wysłany do [error.response.data] (wiersz 34). Wiersze 41–43: funkcja [main] poprawnie pobiera odpowiedź jSON z serwera.

12.6. skrypt [axios-02]

Teraz, gdy omówiliśmy szczegółowo obiekty przetwarzane przez bibliotekę [axios] podczas żądania HTTP, możemy przepisać skrypt [axios-01] w następujący sposób:


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

// domyślna konfiguracja axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// inicjowanie sesji
async function initSession(axios) {
  // opcje żądania HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // parametry URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    // wykonanie zapytania HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    // odpowiedź serwera znajduje się w [response.data]
    return response.data;
  } catch (error) {
    // odpowiedź serwera
    if (error.response) {
      // odpowiedź jSON znajduje się w [error.response.data]
      return error.response.data;
    } else {
      // błąd jest ponownie generowany
      throw error;
    }
  }
}

// funkcja main wywołuje funkcję asynchroniczną [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. skrypt [axios-03]

Skrypt [axios-03] opiera się na metodologii skryptu [axios-02]. Tym razem dodajemy zapytanie HTTP [authentifier-utilisateur] do serwera, co odbywa się za pomocą POST:


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

// konfiguracja axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';


// inicjowanie sesji
async function initSession(axios) {
  // opcje żądania HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // parametry URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    // wykonanie zapytania HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    // odpowiedź serwera znajduje się w [response.data]
    return response.data;
  } catch (error) {
    // odpowiedź serwera
    if (error.response) {
      // odpowiedź jSON znajduje się w [error.response.data]
      return error.response.data;
    } else {
      // błąd jest ponownie generowany
      throw error;
    }
  }
}

async function authentifierUtilisateur(axios, user, password) {
  // opcje żądania HHTP [POST /main.php?action=authentifier-utilisateur]
  const options = {
    method: "POST",
    headers: {
      'Content-type': 'application/x-www-form-urlencoded',
    },
    // treść POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    // parametry URL
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  try {
    // wykonanie zapytania HTTP [post /main.php?action=authentifier-utilisateur]
    const response = await axios.request('main.php', options);
    // odpowiedź serwera znajduje się w [response.data]
    return response.data;
  } catch (error) {
    // odpowiedź serwera
    if (error.response) {
      // odpowiedź jSON znajduje się w [error.response.data]
      return error.response.data;
    } else {
      // błąd jest ponownie generowany
      throw error;
    }
  }
}

// funkcja main wykonuje funkcje asynchroniczne jedna po drugiej
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);
    // uwierzytelnianie-użytkownika
    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();

Komentarze

  • wiersze 38–70: funkcja asynchroniczna [authentifierUtilisateur];
  • wiersz 39: należy wykonać zapytanie [POST /main.php?action=authentifier-utilisateur];
  • wiersze 40–54: opcje zapytania HTTP;
  • wiersz 41: jest to POST;
  • wiersze 42–44: parametry POST zostaną zakodowane jako URL w dokumencie, który klient wysyła wraz ze swoim zapytaniem;
  • wiersze 46–49: właściwość [data] musi zawierać zakodowany ciąg znaków POST URL. W tym celu wykorzystuje się tutaj bibliotekę [qs] zaimportowaną w wierszu 3;
  • wiersze 55–69: w celu wykonania zapytania stosuje się ten sam kod, co w metodzie [initSession];
  • wiersze 73–89: metoda [asynchrone] wywołuje kolejno, w trybie blokującym, metody [initSession, authentifierUtilisateur], wiersze 77 i 82;
  • wiersz 82: jako identyfikatory logowania używana jest para (admin, admin). Wiadomo, że są one rozpoznawane przez serwer;

Wyniki wykonania są następujące:


[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',
  'stan: 700,
  'odpowiedź: 'sesja uruchomiona z typem [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'stan': 103,
  'odpowiedź':
   [ 'pas de session en cours. Commencer par action [init-session]' ] }

[Done] exited with code=0 in 0.834 seconds
  • wiersze 9–12: uwierzytelnianie użytkownika nie powiodło się: serwer nie zapamiętał, że zainicjowano sesję jSON. Wynika to z faktu, że nie odesłano pliku cookie sesji wysłanego w odpowiedzi na pierwsze żądanie [init-session];

12.8. skrypt [axios-04]

Skrypt [axios-04] wprowadza dwa ulepszenia w stosunku do skryptu [axios-03]:

  • obsługuje plik cookie sesji;
  • wyodrębnia do funkcji [getRemoteData] elementy wspólne dla funkcji [initSession] i [authentifierUtilisateur];

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

// konfiguracja axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// plik cookie sesji
const sessionCookieName = "PHPSESSID";
let sessionCookie = '';

// inicjowanie sesji
async function initSession(axios) {
  // opcje żądania HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    // parametry URL
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  // wykonanie zapytania HTTP
  return await getRemoteData(axios, options);
}

async function authentifierUtilisateur(axios, user, password) {
  // opcje zapytania HHTP [post /main.php?action=authentifier-utilisateur]
  const options = {
    method: "POST",
    headers: {
      'Content-type': 'application/x-www-form-urlencoded',
    },
    // treść POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    // parametry URL
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  // wykonanie żądania HTTP
  return await getRemoteData(axios, options);
}

async function getRemoteData(axios, options) {
  // dla pliku cookie sesji
  if (!options.headers) {
    options.headers = {};
  }
  options.headers.Cookie = sessionCookie;
  // wykonanie żądania HTTP
  let response;
  try {
    // żądanie asynchroniczne
    response = await axios.request('main.php', options);
  } catch (error) {
    // parametr [error] jest instancją wyjątku – może przybierać różne formy
    if (error.response) {
      // odpowiedź serwera znajduje się w [error.response]
      response = error.response;
    } else {
      // błąd jest ponownie generowany
      throw error;
    }
  }
  // odpowiedź to cała odpowiedź serwera w formacie HTTP (nagłówki HTTP + sama odpowiedź)
  // pobieramy plik cookie sesji, jeśli istnieje
  const setCookie = response.headers['set-cookie'];
  if (setCookie) {
    // setCookie to tablica
    // w tej tablicy szuka się pliku cookie sesji
    let trouvé = false;
    let i = 0;
    while (!trouvé && i < setCookie.length) {
      // szukamy pliku cookie sesji
      const results = RegExp('^(' + sessionCookieName + '.+?);').exec(setCookie[i]);
      if (results) {
        // zapisujemy plik cookie sesji
        // eslint-disable-next-line require-atomic-updates
        sessionCookie = results[1];
        // znaleziono
        trouvé = true;
      } else {
        // kolejny element
        i++;
      }
    }
  }
  // odpowiedź serwera znajduje się w [response.data]
  return response.data;
}

// funkcja main wykonuje funkcje asynchroniczne jedna po drugiej
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);
    // uwierzytelnianie użytkownika
    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();

Komentarze

  • wiersze 14–26: funkcja [initSession]. Obecnie ogranicza się ona jedynie do przygotowania zapytania HTTP, które ma zostać wysłane do serwera, ale nie wykonuje go. Zadanie to powierza metodzie [getRemoteDate] w wierszach 49–95;
  • wiersze 28–47: funkcja [authentifierUtilisateur] działa w ten sam sposób;
  • wiersz 49: funkcja [getRemoteData] otrzymuje dwie informacje, które pozwalają jej wykonać żądanie HTTP:
    • [axios] – obiekt odpowiedzialny za wysłanie zapytania i odebranie odpowiedzi;
    • [options] – opcje konfiguracyjne zapytania, które ma zostać wysłane do serwera;
  • wiersz 59: wykonanie zapytania i blokujące oczekiwanie na odpowiedź jSON;
  • wiersze 60–68: obsługa ewentualnego wyjątku;
  • wiersz 64: pobieranie odpowiedzi, która może być zawarta w obiekcie błędu;
  • wiersz 67: jeśli serwer zgłosił wyjątek bez dołączenia odpowiedzi serwera, wówczas otrzymany błąd jest przekazywany do kodu wywołującego;
  • funkcja [getRemoteData] obsługuje plik cookie sesji:
    • zapisuje go w zmiennej [sessionCookie] (wiersz 11) po pierwszym otrzymaniu;
    • następnie odsyła go przy każdym nowym żądaniu HTTP;
  • wiersze 72–92: [getRemoteData] analizuje każdą odpowiedź serwera, aby sprawdzić, czy wysłał on nagłówek HTTP [Set-Cookie]. Wiadomo, że serwer wysyła plik cookie sesji o nazwie [PHPSESSID] (wiersz 10). Szukamy więc właśnie tego pliku cookie (wiersz 10);
  • wiersz 72: pobieramy nagłówki HTTP i [Set-Cookie], jeśli istnieją (wielkość liter nie ma znaczenia). Może bowiem istnieć kilka nagłówków o nazwie [Set-Cookie], dlatego pobieramy tablicę;
  • wiersz 73: jeśli pobrano tablicę plików cookie;
  • wiersze 78–90: szukamy pliku cookie sesji wśród wszystkich plików cookie w tablicy;
  • wiersz 80: wyrażenie relacyjne umożliwiające wyszukanie pliku cookie sesji w pliku cookie o numerze i;
  • wiersz 81: jeśli porównanie zwróciło wyniki;
  • wiersz 84: w results[1] mamy pierwszą nawiasową część wzorca wyrażenia relacyjnego, tj. (PHPSESSID=xxxx) aż do znaku ; (nie wliczone), który kończy plik cookie sesji;
  • wiersze 50–54: przy każdym żądaniu plik cookie sesji jest dołączany do nagłówków żądania HTTP. Za pierwszym razem plik ten jest pusty i zostanie zignorowany przez serwer;

Wyniki wykonania są następujące:


[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',
  'stan': 700,
  'odpowiedź: 'sesja uruchomiona z typem [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'stan': 200,
  'odpowiedź': 'Uwierzytelnienie zakończone sukcesem [admin, admin]' }

[Done] exited with code=0 in 0.982 seconds