Skip to content

12. وظائف HTTP في JavaScript

Image

12.1. اختيار مكتبة HTTP

لقد اخترنا مكتبتين هنا:

يحتوي ECMAScript 6 بشكل أساسي على دالة HTTP تسمى [fetch] لم يتم تنفيذها في [node.js] (حتى سبتمبر 2019). هناك مكتبة تسمى [node-fetch] تسمح لك باستخدام وظيفة [fetch] في Node. تستخدم هذه المكتبة واجهات برمجة تطبيقات (APIs) معينة خاصة بـ [node.js]. لذلك قد لا يكون كود [node-fetch] قابلاً للنقل بنسبة 100% إلى بيئة غير [node]، مثل المتصفح؛

هناك أيضًا مكتبة تسمى [axios] مخصصة لطلبات HTTP وهي متوافقة مع كل من [node.js] والمتصفحات. هذه هي المكتبة التي سنستخدمها في النهاية.

سنقدم نفس البرنامج النصي المكتوب باستخدام كلتا المكتبتين لإثبات أن نهج البرمجة متشابه.

12.2. إعداد بيئة التطوير

12.2.1. تثبيت خادم حساب الضرائب

في النهاية، سنكتب تطبيق ويب بالبنية التالية:

Image

JS: JavaScript

يكون كود JavaScript من جانب العميل:

  • من خدمة تقدم صفحات ثابتة أو أجزاء منها؛
  • خدمة JSON؛

وبالتالي، فإن كود JavaScript هو عميل JSON، وبهذه الصفة، يمكن تنظيمه في طبقات [UI، منطق الأعمال، DAO] (UI: واجهة المستخدم) تمامًا مثل عملاء JSON المكتوبين بلغة PHP.

سيكون الخادم هو خادم حساب الضرائب الذي كتبنا له بالفعل 13 إصدارًا. سنقوم بكتابة الإصدار الرابع عشر. لذلك نبدأ بنسخ مجلد الإصدار 13 في NetBeans إلى مجلد الإصدار 14:

Image

  • في [6]، نقوم بتعديل ملف [config.json] الخاص بالإصدار 14 على النحو التالي:

{
    "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"
}
  • في السطر 3، نقوم بتغيير الدليل الجذري للتطبيق؛

للوصول إلى هذا الخادم، يجب عليك تشغيل خدمات [Laragon].

وبمجرد الانتهاء من ذلك، يمكننا اختبار هذا الإصدار الجديد من الخادم — الذي يتطابق حاليًا مع الإصدار 13 — باستخدام [Postman] (انظر المقالة المرفقة). ويمكننا استخدام مجموعة الطلبات التي استُخدمت لاختبار الإصدار 12 من خادم حساب الضرائب:

Image

  • في [1-4]، استخدم طلب [init-session-700] لتهيئة جلسة JSON؛
  • في [4-5]، استبدل [version-12] بـ [version-14] لاختبار الإصدار 14 من المشروع؛
  • عند التنفيذ، يجب أن نتلقى استجابة JSON [6] من الخادم؛

الإصدار 14 من الخادم جاهز للعمل الآن. سنحتاج إلى تعديله قليلاً. دعونا نراجع واجهة برمجة تطبيقات (API) هذا الخادم:

الإجراء
الدور
سياق التنفيذ
init-session
يُستخدم لتعيين نوع (json، xml، html) الاستجابات المطلوبة
طلب GET main.php?action=init-session&type=x
يمكن إرساله في أي وقت
authenticate-user
يوافق على تسجيل دخول المستخدم أو يرفضه
طلب POST main.php?action=authenticate-user
يجب أن يحتوي الطلب على معلمتين مرسلتين [user, password]
لا يمكن إصداره إلا إذا كان نوع الجلسة (json، xml، html) معروفًا
حساب الضريبة
يقوم بمحاكاة حساب الضريبة
طلب POST إلى main.php?action=calculate-tax
يجب أن يحتوي الطلب على ثلاثة معلمات مرسلة [married، children، salary]
لا يمكن إصداره إلا إذا كان نوع الجلسة (json، xml، html) معروفًا وتم مصادقة المستخدم
list-simulations
طلب لعرض قائمة المحاكاة التي تم إجراؤها منذ بداية الجلسة
طلب GET main.php?action=list-simulations
لا يقبل الطلب أي معلمات أخرى
لا يمكن إصداره إلا إذا كان نوع الجلسة (json، xml، html) معروفًا وتم توثيق المستخدم
delete-simulation
يحذف محاكاة من قائمة المحاكاة
طلب GET main.php?action=list-simulations&number=x
لا يقبل الطلب أي معلمات أخرى
لا يمكن إصداره إلا إذا كان نوع الجلسة (json، xml، html) معروفًا وتم توثيق المستخدم
إنهاء الجلسة
ينهي جلسة المحاكاة.
من الناحية الفنية، يتم حذف جلسة الويب القديمة وإنشاء جلسة جديدة
لا يمكن إصداره إلا إذا كان نوع الجلسة (json، xml، html) معروفًا وتم توثيق المستخدم

12.2.2. تثبيت مكتبات HTTP لعميل JavaScript

في البداية، سنعمل مع البنية التالية:

Image

  • في [1]، يقوم برنامج نصي للوحدة [node.js] بإرسال طلب HTTP إلى خادم JSON لحساب الضرائب؛
  • في [4]، يتلقى هذا الرد ويعرضه في وحدة التحكم؛

في المثال 1، سنستخدم مكتبتَي [node-fetch] و[axios]، ثم سنحتفظ بـ [axios] فقط للأمثلة التالية. سنقوم الآن بتثبيت هاتين المكتبتين من JavaScript من محطة [VSCode]:

Image

سنستخدم أيضًا مكتبة [qs]، التي تسمح بترميز URL لسلسلة نصية. تذكر أن هذا الترميز يُستخدم لترميز معلمات طلب HTTP GET أو POST.

Image

12.3. نص برمجي [fetch-01]

يستخدم البرنامج النصي [fetch-01] مكتبة [node-fetch] لتهيئة جلسة JSON مع خادم حساب الضرائب. وفيما يلي كوده:


'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;
}

تعليقات

  • وظائف HTTP في JavaScript هي وظائف غير متزامنة. هنا، نطبق ما تعلمناه في القسم السابق (انظر الرابط
  • السطر 24: لانتظار نشر استجابة الدالة غير المتزامنة [fetch] في حلقة أحداث [node.js]، نستخدم الكلمة الرئيسية [await]. ونعلم أن هذه العبارة يجب أن تكون ضمن كود مسبوق بالكلمة الرئيسية [async] (السطر 13)؛
  • الأسطر 13-56: نقوم بتغليف كود HTTP داخل الدالة غير المتزامنة [initSession]؛
  • الأسطر 59-69: تُستخدم دالة غير متزامنة ثانية [main] لاستدعاء الدالة غير المتزامنة [initSession] بطريقة حجبية (async/await
  • السطر 72: يتم استدعاء الدالة غير المتزامنة [main]؛
  • على الرغم من أن الكود بأكمله يشبه الكود المتزامن، إلا أن هذه هي في الواقع دوال غير متزامنة يتم تنفيذها، ولكن بطريقة حجبية؛
  • السطر 19: لتهيئة جلسة JSON مع خادم حساب الضرائب، يجب إرسال طلب HTTP [get /main.php?action=init-session&type=json] إليه. وهذا ما يفعله الكود في الأسطر 24–27. صيغة [fetch] هي كما يلي: [fetch(URL, options)] مع:
    • [URL]: عنوان URL الذي يتم الاستعلام عنه؛
    • [options]: كائن يحدد خيارات الطلب. هنا نحدد رؤوس HTTP التي نريد إرسالها إلى الخادم المستهدف؛
  • الأسطر 15–18: نحدد خيارات الطلب الذي نريد إجراؤه:
    • [method]: نريد تنفيذ GET؛
    • [timeout]: نريد أن ينتظر عميل [fetch] ما لا يزيد عن ثانيتين لاستجابة الخادم. إذا تم تجاوز هذا الوقت المحدد، فسوف يرمي [fetch] استثناءً؛
  • السطر 24: للحصول على عنوان URL [/main.php?action=init-session&type=json]، نستخدم مكتبة [qs] للحصول على المعلمات [action,type] المشفرة في عنوان URL لطلب GET. السلسلة الناتجة هي [init-session&type=jsonوالتي كان بإمكاننا تكوينها بأنفسنا. أردنا ببساطة توضيح كيفية الحصول على سلسلة مشفرة في عنوان URL؛
  • السطر 24: تشير الكلمة الرئيسية [await] إلى أنه يتم هنا تشغيل مهمة غير متزامنة وأننا ننتظر نشرها لاستجابتها في حلقة أحداث [node.js]؛
  • السطر 24: في [response]، نحصل على كائن معقد يصف استجابة HTTP المستلمة بالكامل (الرؤوس والنص الأساسي)؛
  • السطران 30-31: نقوم بطباعة كائن [response] لرؤية هيكله، أولاً كسلسلة ثم ككائن JavaScript؛
  • السطر 33: نعرض رؤوس HTTP المرسلة من الخادم؛
  • السطر 38: نعلم أن خادم حساب الضرائب سيرسل سلسلة JSON. هذه السلسلة مغلفة في كائن [response]. يمكننا استردادها باستخدام طريقة [response.json()]. ومع ذلك، فإن هذه الطريقة غير متزامنة. لذلك نكتب [await response.json()] لاسترداد سلسلة JSON التي سيتم نشرها في حلقة أحداث [node.js]. في الواقع، ليس سلسلة JSON نفسها هي ما نحصل عليه، بل كائن JavaScript الذي تمثله؛
  • السطر 39: عرض سلسلة JSON المستلمة؛
  • السطر 40: إرجاع كائن JavaScript المستلم؛
  • السطر 47: نلتقط أي أخطاء محتملة من عبارة [fetch]. لا ترمي هذه العبارة استثناءً إلا إذا فشلت عملية HTTP ولم يتم تلقي أي استجابة من الخادم. إذا تم تلقي استجابة، حتى مع رمز حالة HTTP غير [200 OK]، فإن [fetch] لا ترمي استثناءً، وستكون استجابة الخادم متاحة في السطر 38؛
  • السطران 51-52: يتم عرض كائن [error] الذي تم استلامه بواسطة جملة [catch]، أولاً كسلسلة JSON ثم ككائن JavaScript؛
  • السطر 54: يتم تخزين رسالة الخطأ الواردة من [fetch] في [error.message]؛
  • الأسطر 59–69: تستدعي الدالة غير المتزامنة [main] الدالة غير المتزامنة [initSession] بطريقة حجبية (await في السطر 62)؛
  • السطر 72: يتم تشغيل الدالة غير المتزامنة [main]، ثم يكتمل كود البرنامج النصي الرئيسي. سينتهي البرنامج النصي بأكمله عندما تنشر المهام غير المتزامنة التي تم تشغيلها نتائجها إلى حلقة الأحداث؛

نتائج التنفيذ هي كما يلي:

الحالة 1: خادم 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 ---------------------------------------------
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

تعليقات

  • السطر 3: فشل طلب HTTP بعد 2 ثانية و62 مللي ثانية بسبب مهلة 2 ثانية المفروضة على طلب HTTP؛
  • الأسطر 4–9: كائن [error] في JavaScript الذي تم اعتراضه بواسطة جملة [catch(error)]. يحتوي هذا الكائن على خاصيتين:
    • [FetchError]: السطر 4؛
    • [message]: الأسطر 10–12؛
  • السطر 14: رسالة الخطأ التي تلقتها الدالة غير المتزامنة [main]؛

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

تعليقات

  • السطر 3: [fetch] يتلقى استجابة الخادم بعد 375 مللي ثانية؛
  • الأسطر 4–39: بنية كائن JavaScript [response] الذي يغلف استجابة الخادم. من بين خصائصه، قد تهمنا بعض الخصائص:
    • [status] (السطر 25): رمز حالة HTTP لاستجابة الخادم؛
    • [statusText] (السطر 26): النص المرتبط بهذا الرمز؛
    • [headers] (السطر 27): رؤوس HTTP لاستجابة الخادم؛
    • [body] (السطر 8): يمثل المستند الذي أرسله الخادم. توفر عبارة [fetch] طرقًا للعمل معه؛
  • الأسطر 29-39: رؤوس HTTP لاستجابة الخادم؛
  • السطر 40: أرجعت الدالة غير المتزامنة [response.json()] استجابتها بعد 1 مللي ثانية؛
  • الأسطر 42–44: كائن JavaScript الذي استقبلته الدالة غير المتزامنة [main]؛

الحالة 3: خادم Laragon قيد التشغيل، ولكن تم إرسال أمر غير صحيح إليه:

Image

  • أعلاه، السطر 26، يتم تمرير نوع جلسة غير صحيح إلى الخادم؛

نتائج التنفيذ هي كما يلي:


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
  • تم استلام استجابة الخادم في السطر 2؛
  • السطر 24: يمكننا أن نرى أن رمز حالة HTTP لاستجابة الخادم هو 400، وهو رمز خطأ. ومع ذلك، لم تقم [fetch] بإلقاء استثناء. طالما أن [fetch] تتلقى استجابة من الخادم، فإنها تعالجها ولا تلقي استثناءً؛
  • الأسطر 41–43: الاستجابة التي حصلت عليها الدالة غير المتزامنة [main]؛

12.4. النص البرمجي [fetch-02]

يعيد البرنامج النصي التالي استخدام البرنامج النصي [fetch-01] مع إزالة جميع التفاصيل غير الضرورية:


'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();

نتائج التنفيذ العادي:


[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

نتائج التنفيذ مع وجود استثناء (إيقاف خادم 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. نص برمجي [axios-01]

هنا نعود إلى البرنامج النصي [fetch-01]، الذي سنعيد كتابته باستخدام مكتبة [axios]. للتذكير، يكمن اهتمامنا بهذه المكتبة في قابليتها للتنقل بين بيئة [node.js] والمتصفحات الشائعة. وهذا يتيح:

  • في المرحلة 1، اختبار نصوصنا البرمجية في بيئة [Node.js]؛
  • في المرحلة 2، نقلها إلى متصفح؛

يتبع البرنامج النصي [axios-01] بنية البرنامج النصي [fetch-01]:


'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();

تعليقات

  • السطر 2: نقوم باستيراد مكتبة [axios]؛
  • السطران 5-6: التكوين الافتراضي لطلبات HTTP. تنطبق خيارات [axios.defaults] على جميع طلبات HTTP المرسلة بواسطة كائن [axios] دون الحاجة إلى تحديدها لكل طلب جديد؛
  • السطر 5: مهلة انتظار مدتها ثانيتان لجميع الطلبات؛
  • السطر 6: ستكون جميع عناوين URL نسبية بالنسبة إلى عنوان URL الأساسي؛
  • السطر 9: الدالة غير المتزامنة [initSession]؛
  • الأسطر 11-18: خيارات طلب HTTP المراد إرساله (بالإضافة إلى الخيارات الافتراضية المحددة بالفعل في الأسطر 5-6)؛
  • الأسطر 14-17: معلمات عنوان URL [action=init-session&type=json]. سيتم تحويل كائن [params] تلقائيًا إلى سلسلة مشفرة بعنوان URL؛
  • السطر 22: استدعاء حظر للدالة غير المتزامنة [axios.request]. المعلمة الأولى هي عنوان URL الهدف الذي تم إنشاؤه على أنه [main.php] مضافًا إلى عنوان URL الأساسي المحدد في السطر 6. المعلمة الثانية هي كائن [options] من الأسطر 11–18؛
  • السطر 25: [response] هو كائن JavaScript يغلف استجابة HTTP الكاملة من الخادم (رؤوس HTTP + نص الاستجابة). نعرضه لنرى هيكل JavaScript الخاص به؛
  • السطر 27: إذا أرسل الخادم مستندًا، فسيتم العثور عليه في [response.data]. هنا نعلم أن الخادم يرسل استجابة JSON مصحوبة برأس HTTP [Content-type: application/json]. يؤدي وجود هذا الرأس إلى قيام [axios] تلقائيًا بإلغاء تسلسل [response.data] إلى كائن JavaScript؛
  • السطر 28: يمكن أن ترمي دالة [axios] استثناءً. هذا هو المكان الذي يختلف فيه [axios] عن [fetch]. يتم رمي استثناء في الحالات التالية:
    • تعذر إرسال طلب HTTP (خطأ من جانب العميل)؛
    • أرسل الخادم رمز خطأ HTTP (400، 404، 500، ...) (هذا السلوك قابل للتكوين في الواقع). لاحظ أنه إذا كان رمز حالة HTTP هذا مصحوبًا باستجابة، فإن [fetch] لم يرمي استثناءً، في حين أن [axios] يفعل ذلك. ومع ذلك، إذا كان رمز خطأ HTTP مصحوبًا بوثيقة، يتم وضعها في [error.response]؛
  • السطر 32: نعرض بنية JavaScript لكائن [error]؛
  • الأسطر 33–38: إذا كان كائن [error] يحتوي على كائن [response]، يتم إرجاع هذا الرد إلى الكود المستدعي؛
  • الأسطر 39–42: في جميع الحالات الأخرى، يتم تمرير كائن [error] مرة أخرى إلى الكود المستدعي؛
  • الأسطر 47–57: الدالة غير المتزامنة [main]؛
  • السطر 50: استدعاء معطل للدالة غير المتزامنة [initSession]. يتم استرداد استجابة JSON من الخادم ككائن JavaScript؛
  • الأسطر 53–56: معالجة أي أخطاء. توجد رسالة الخطأ في [error.message]؛

نتائج التنفيذ هي كما يلي:

الحالة 1: خادم 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 ---------------------------------------------
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

تعليقات

  • الأسطر 33-136: يحتوي كائن [error] على الكثير من المعلومات؛
    • [Error]، الأسطر 3-9: وصف للخطأ الذي حدث؛
    • [config]، الأسطر 10–27: تكوين طلب HTTP الذي أدى إلى هذا الخطأ؛
    • [config.url]، الأسطر 11-12: عنوان URL الهدف؛
    • [config.method]، السطر 13: طريقة الطلب؛
    • [config.params]، السطر 14: معلمات عنوان URL؛
    • [config.headers]، الأسطر 16-17: رؤوس HTTP للطلب؛
    • [config.baseURL]، السطر 18: عنوان URL الأساسي لعنوان URL الهدف؛
    • [config.timeout]، السطر 21: مهلة الطلب؛
    • [code]، السطر 28: رمز الخطأ؛
    • [request]، الأسطر 29–133: وصف تفصيلي لطلب HTTP. لاحظ أن معظم الخصائص مسبوقة بشرطة سفلية (_)، مما يشير إلى أنها خصائص داخلية لكائن [request] غير مخصصة للاستخدام المباشر من قبل المطور؛
    • [response]، السطر 134: استجابة الخادم، وهي فارغة هنا؛
  • السطر 138: رسالة الخطأ التي تعرضها الدالة [main]؛

الحالة 2: خادم 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',
     'é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

تعليقات

  • الأسطر 3–203: كائن JavaScript [response] الذي يغلف استجابة HTTP للخادم؛
  • السطر 3، [status]: رمز حالة HTTP للاستجابة؛
  • السطر 4، [statusText]: النص المرتبط برمز حالة HTTP السابق؛
  • الأسطر 5-13، [headers]: رؤوس HTTP للاستجابة:
    • السطر 10، [Set-Cookie]: ملف تعريف ارتباط الجلسة؛
    • السطر 13، [Content-Type]: نوع المستند المرسل من الخادم؛
  • الأسطر 14–31، [config]: تكوين طلب HTTP المرسل؛
  • الأسطر 32-199، [request]: كائن JavaScript الذي يوضح تفاصيل طلب HTTP المرسل؛
  • الأسطر 200–203، [request.data]: كائن JavaScript الذي يغلف استجابة JSON للخادم؛
  • الأسطر 205–207: الاستجابة التي تم استردادها بواسطة الدالة غير المتزامنة [main]؛

الحالة 3: يتم إرسال طلب [init-session] غير صحيح إلى خادم Laragon؛

Image

نتائج التنفيذ هي كما يلي:


[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

نظرًا لأن الخادم استجاب برمز HTTP 400 (السطر 15)، ألقى [axios] استثناءً (مرة أخرى، هذا السلوك قابل للتكوين). على الرغم من أن [axios] ألقى استثناءً، إلا أننا ما زلنا نحصل على استجابة HTTP من الخادم في [error.response] (السطر 14) ووثيقة JSON المرسلة في [error.response.data] (السطر 34). الأسطر 41–43: تسترد الدالة [main] استجابة JSON من الخادم بشكل صحيح.

12.6. نص برمجي [axios-02]

الآن بعد أن قمنا بتفصيل الكائنات التي تتعامل معها مكتبة [axios] أثناء طلب HTTP، يمكننا إعادة كتابة البرنامج النصي [axios-01] على النحو التالي:


'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. نص برمجي [axios-03]

يتبع البرنامج النصي [axios-03] نفس منهجية البرنامج النصي [axios-02]. هذه المرة، نضيف طلب HTTP [authenticate-user] إلى الخادم، والذي يتم إجراؤه باستخدام طلب POST:


'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();

تعليقات

  • الأسطر 38-70: الدالة غير المتزامنة [authenticateUser]؛
  • السطر 39: يجب إجراء الطلب [POST /main.php?action=authenticate-user]؛
  • الأسطر 40–54: خيارات طلب HTTP؛
  • السطر 41: هذا طلب POST؛
  • الأسطر 42-44: سيتم ترميز معلمات POST في عنوان URL في مستند يرسله العميل مع طلبه؛
  • الأسطر 46-49: يجب أن تحتوي الخاصية [data] على سلسلة POST المشفرة في عنوان URL. للقيام بذلك، نستخدم مكتبة [qs] المستوردة في السطر 3؛
  • الأسطر 55–69: لتنفيذ الطلب، نستخدم نفس الكود الموجود في طريقة [initSession]؛
  • الأسطر 73-89: تستدعي طريقة [asynchrone] على التوالي طريقتي [initSession] و[authentifierUtilisateur] بطريقة حجبية، الأسطر 77 و82؛
  • السطر 82: يتم استخدام الزوج (admin, admin) كبيانات اعتماد تسجيل الدخول. ونعلم أن الخادم يتعرف عليهما؛

نتائج التنفيذ هي كما يلي:


[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
  • الأسطر 9-12: فشل مصادقة المستخدم: لم يحتفظ الخادم بحقيقة أن جلسة JSON قد بدأت. ويرجع ذلك إلى أن ملف تعريف ارتباط الجلسة الذي تم إرساله استجابة لطلب [init-session] الأول لم يتم إرساله مرة أخرى؛

12.8. النص البرمجي [axios-04]

يقدم البرنامج النصي [axios-04] تحسينين على البرنامج النصي [axios-03]:

  • يعالج ملف تعريف ارتباط الجلسة؛
  • يدمج في دالة [getRemoteData] العناصر المشتركة بين دالتي [initSession] و [authenticateUser]؛

'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();

تعليقات

  • الأسطر 14–26: دالة [initSession]. وهي الآن تقوم ببساطة بإعداد طلب HTTP ليتم إرساله إلى الخادم ولكنها لا تنفذه. وتفوض هذه المهمة إلى طريقة [getRemoteDate] في الأسطر 49–95؛
  • الأسطر 28–47: تتبع الدالة [authentifierUtilisateur] نفس الإجراء؛
  • السطر 49: تتلقى الدالة [getRemoteData] معلومتين تحتاج إليهما لتنفيذ طلب HTTP:
    • [axios]، الكائن المسؤول عن إرسال الطلب واستلام الرد؛
    • [options]، خيارات التكوين للطلب المراد إرساله إلى الخادم؛
  • السطر 59: تنفيذ الطلب وانتظار استجابة JSON الخاصة به؛
  • الأسطر 60–68: معالجة أي استثناءات؛
  • السطر 64: استرداد الرد، الذي قد يكون مغلفًا في كائن الخطأ؛
  • السطر 67: إذا ألقى الخادم استثناءً دون تضمين استجابة الخادم، يتم نقل الخطأ المستلم إلى الكود المستدعي؛
  • تدير الدالة [getRemoteData] ملف تعريف الارتباط الخاص بالجلسة:
    • تقوم بتخزينه في المتغير [sessionCookie] (السطر 11) عند استلامه لأول مرة؛
    • ثم تعيده مع كل طلب HTTP جديد؛
  • الأسطر 72-92: تحلل [getRemoteData] كل استجابة من الخادم لتحديد ما إذا كان قد أرسل رأس HTTP [Set-Cookie]. نعلم أن الخادم يرسل ملف تعريف ارتباط للجلسة باسم [PHPSESSID] (السطر 10). لذلك، هذا هو ملف تعريف الارتباط الذي نبحث عنه (السطر 10)؛
  • السطر 72: نسترد رؤوس HTTP [Set-Cookie] إذا كانت موجودة (لا يهم استخدام الأحرف الكبيرة أو الصغيرة). قد يكون هناك في الواقع عدة رؤوس [Set-Cookie]، لذا نسترد مصفوفة؛
  • السطر 73: إذا تم استرداد مصفوفة من ملفات تعريف الارتباط؛
  • الأسطر 78-90: نبحث عن ملف تعريف ارتباط الجلسة بين جميع ملفات تعريف الارتباط في المصفوفة؛
  • السطر 80: التعبير العلائقي المستخدم للبحث عن ملف تعريف الارتباط الخاص بالجلسة في ملف تعريف الارتباط #i؛
  • السطر 81: إذا عادت المقارنة بنتائج؛
  • السطر 84: لدينا في results[1]، القوس الأول من نمط التعبير العلائقي، أي (PHPSESSID=xxxx) حتى القوس الختامي (غير مدرج) الذي ينهي ملف تعريف ارتباط الجلسة؛
  • الأسطر 50-54: مع كل طلب، يتم تضمين ملف تعريف ارتباط الجلسة في رؤوس HTTP للطلب. في المرة الأولى، يكون ملف تعريف الارتباط هذا فارغًا وبالتالي سيتم تجاهله من قبل الخادم؛

نتائج التنفيذ هي كما يلي:


[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