Skip to content

12. توابع جاوااسکریپت HTTP

Image

12.1. انتخاب کتابخانه: HTTP

ما در اینجا دو کتابخانه را انتخاب کرده‌ایم:

کتابخانه EcmaScript به‌طور بومی دارای تابعی به نام HTTP است که توسط [node.js] پیاده‌سازی نشده است (سپتامبر ۲۰۱۹). کتابخانه‌ای به نام [node-fetch] وجود دارد که امکان استفاده از تابع [fetch] را در Node فراهم می‌کند. این کتابخانه از برخی ویژگی‌های API که مختص [node.js] هستند، بهره می‌برد. بنابراین، یک کد [node-fetch] ممکن است ۱۰۰ درصد قابل حمل به یک محیط غیر [node]، مانند یک مرورگر وب، نباشد؛

همچنین کتابخانه‌ای به نام [axios] وجود دارد که به پرس‌وجوهای HTTP اختصاص دارد و با هر دو [node.js] و مرورگرها سازگار است. در نهایت از همین کتابخانه استفاده خواهیم کرد.

ما همان اسکریپت را که با استفاده از هر دو این کتابخانه‌ها نوشته شده است، ارائه خواهیم داد تا نشان دهیم رویکرد کدنویسی برای هر دو مشابه است.

12.2. راه‌اندازی محیط کار

12.2.1. نصب سرور محاسبه مالیات

در نهایت، ما یک برنامه وب با معماری زیر خواهیم نوشت:

Image

JS: جاوااسکریپت

کد جاوااسکریپت سمت کلاینت است:

  • یک سرویس که صفحات یا بخش‌های ایستا را ارائه می‌دهد؛
  • یک سرویس jSON؛

بنابراین کد جاوااسکریپت مشتری jSON است و به همین ترتیب می‌توان آن را به لایه‌های [UI, métier, dao] سازماندهی کرد (UI: رابط کاربری) درست همان‌طور که کلاینت‌های jSON ما که در PHP نوشته شده بودند، چنین بودند.

سرور، سرور محاسبه مالیات خواهد بود که قبلاً ۱۳ نسخه از آن را نوشته‌ایم. ما قصد داریم نسخه چهاردهم را بنویسیم. بنابراین با کپی کردن پوشه نسخه ۱۳ در NetBeans به داخل پوشه نسخه ۱۴ شروع می‌کنیم:

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"
}
  • در خط ۳، دایرکتوری ریشهٔ برنامه را تغییر می‌دهیم؛

برای دسترسی به این سرور، باید سرویس‌های [Laragon] را راه‌اندازی کنید.

پس از انجام این کار، می‌توانیم این نسخهٔ جدید سرور را – که در حال حاضر با نسخهٔ ۱۳ یکسان است – با استفاده از [Postman] (به مقالهٔ مرتبط مراجعه کنید) آزمایش کنیم. می‌توانیم از مجموعهٔ پرس‌وجوهایی که برای آزمایش نسخهٔ ۱۲ سرور محاسبهٔ مالیات استفاده شده بود، بهره ببریم:

Image

  • در [1-4]، از پرس‌وجوی [init-session-700] برای راه‌اندازی یک جلسه jSON استفاده کنید؛
  • در [4-5]، [version-12] را با [version-14] جایگزین کنید تا نسخه 14 پروژه را آزمایش کنید؛
  • پس از اجرا، باید پاسخ jS0N [6] را از سرور دریافت کنیم؛

نسخهٔ ۱۴ سرور اکنون عملیاتی است. ما باید آن را کمی تغییر دهیم. بیایید API را از این سرور فراخوانی کنیم:

اقدام
نقش
زمینه اجرا
init-session
برای تعیین نوع (json, xml, html) پاسخ‌های مورد نظر استفاده می‌شود
درخواست GET main.php؟action=init-session&type=x
می‌تواند در هر زمانی صادر شود
احراز هویت کاربر
ورود کاربر را مجاز یا رد می‌کند
درخواست POST main.php?action=authenticate-user
درخواست باید دارای دو پارامتر POST [user, password] باشد
فقط در صورتی قابل ارسال است که نوع جلسه (json, xml, html) مشخص باشد
محاسبه-مالیات
شبیه‌سازی محاسبه مالیات را انجام می‌دهد
درخواست POST main.php?action=calculate-tax
درخواست باید سه پارامتر POST داشته باشد: [marié, enfants, salaire]
فقط در صورتی قابل اجرا است که نوع جلسه (json, xml, html) مشخص باشد و کاربر احراز هویت شده باشد
list-simulations
درخواست فهرستی از شبیه‌سازی‌های انجام‌شده از ابتدای جلسه
درخواست: GET main.php?action=lister-simulations
این درخواست هیچ پارامتر دیگری را نمی‌پذیرد
تنها در صورتی صادر می‌شود که نوع جلسه (json، xml، html) مشخص باشد و کاربر احراز هویت شده باشد.
حذف شبیه‌سازی
حذف یک شبیه‌سازی از فهرست شبیه‌سازی‌ها
درخواست: GET main.php?action=list-simulations&number=x
این درخواست هیچ پارامتر دیگری را نمی‌پذیرد
فقط در صورتی قابل اجرا است که نوع جلسه (json، xml، html) مشخص باشد و کاربر احراز هویت شده باشد
پایان جلسه
پایان جلسه شبیه‌سازی.
از نظر فنی، جلسه وب قدیمی حذف شده و یک جلسه جدید ایجاد می‌شود
فقط در صورتی قابل اجرا است که نوع جلسه (json, xml, html) مشخص باشد و کاربر احراز هویت شده باشد

12.2.2. نصب کتابخانه‌های کلاینت جاوااسکریپت HTTP

در ابتدا، ما با معماری زیر کار خواهیم کرد:

Image

  • در [1]، یک اسکریپت کنسول [node.js] یک درخواست HTTP را به سرور محاسبه مالیات jSON ارسال می‌کند؛
  • در [4]، این پاسخ دریافت شده و در کنسول نمایش داده می‌شود؛

در مثال ۱، ما از کتابخانه‌های [node-fetch] و [axios] استفاده خواهیم کرد؛ برای مثال‌های بعدی، تنها [axios] را حفظ خواهیم کرد. اکنون این دو کتابخانهٔ جاوااسکریپت را از ترمینال [VSCode] نصب خواهیم کرد:

Image

ما همچنین از کتابخانه [qs] استفاده خواهیم کرد که رمزگذاری URL یک رشته را امکان‌پذیر می‌سازد. به یاد داشته باشید که این رمزگذاری برای رمزگذاری پارامترهای یک درخواست HTTP، GET یا POST استفاده می‌شود.

Image

12.3. اسکریپت [fetch-01]

اسکریپت [fetch-01] از کتابخانه [node-fetch] برای راه‌اندازی یک جلسه jSON با سرور محاسبه مالیات استفاده می‌کند. کد آن به شرح زیر است:


''use strict';

// واردات‌ها
import fetch from 'node-fetch';
import qs from 'qs';
import { sprintf } from 'sprintf-js';
import moment from 'moment';


// سرور محاسبه مالیات پایه URL
const baseUrl = 'http://localhost/php7/scripts-web/impots/version-14/main.php?';
// راه‌اندازی جلسه
async function initSession() {
  // گزینه‌های درخواست HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // اجرای پرس‌وجو HTTP [get /main.php?action=init-session&type=json]
  let débutFetch;
  try {
    // درخواست ناهمزمان – [fetch] یک وعده را بازمی‌گرداند
    débutFetch = moment(Date.now());
    const response = await fetch(baseUrl + qs.stringify({
      action: 'init-session',
      type: 'json'
    }), options);
    // [response] پاسخ کامل HTTP از سرور است (سربرگ‌های HTTP + خود پاسخ)
    // ما این پاسخ را نمایش می‌دهیم تا ساختار آن را ببینیم
    console.log(sprintf("réponse fetch formatée en json,=%j, %s", response, heure(débutFetch)));
    console.log("réponse fetch en javascript=", response);
    // سربرگ‌های HTTP قابل دریافت هستند
    console.log("entêtes de la réponse=", response.headers);
    // اگر پاسخ از نوع application/json باشد، پاسخ JSON سرور با استفاده از تابع غیرهمزمان [response.json()] بازیابی می‌شود
    //در این مورد، کد فراخوانی یک شی [Promise] دریافت می‌کند
    // [await] اجازه می‌دهد پاسخ سرور [json] به‌جای وعدهٔ آن بازیابی شود
    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;
    //اگر پاسخ از نوع text/plain باشد، پاسخ متنی سرور با استفاده از [response.text()] دریافت می‌شود
    //در این مورد، کد فراخوانی یک شی [Promise] دریافت می‌کند
    // [await] به شما امکان می‌دهد پاسخ سرور [texte] را به‌جای promise آن بازیابی کنید
    // const text = await response.text();
    //QZXW2HTMLCY29uc29sZS.sb2cZQX("text response=", text);
    // return text;
  } catch (error) {
    // ما اینجا هستیم چون سرور کد خطا [404 Not Found, ...] را همراه با بدنه خالی ارسال کرد – ما خطا را نمایش می‌دهیم تا ساختار آن را ببینیم
    // یا به این دلیل که کلاینت [fetch] یک استثنا (شبکه در دسترس نیست و غیره) پرتاب کرده است.
    //ساختار خطا نمایش داده می‌شود
    console.log(sprintf("error fetch en json=%j, %s", error, heure(débutFetch)));
    console.log("error fetch en javascript=", typeof (error), error);
    // پیام خطای دریافت‌شده نمایش داده می‌شود
    throw error.message;
  }
}

// توابع اصلی تابع غیرهمزمان [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));
  }
}

// آزمایش
main();

//ابزار نمایش زمان و مدت
function heure(début) {
  //زمان فعلی
  const now = moment(Date.now());
  //قالب‌بندی زمان
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // آیا باید مدت‌زمان محاسبه شود؟
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    //قالب‌بندی زمان و مدت
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // نتیجه
  return result;
}

نظرات

  • توابع جاوااسکریپت HTTP غیرهمزمان هستند. در اینجا، ما آنچه را که در بخش قبلی آموخته‌ایم (به لینک مراجعه کنید) به کار می‌بریم؛
  • خط ۲۴: برای انتظار کشیدن تا پاسخ تابع غیرهمزمان [fetch] در حلقه رویداد [node.js] منتشر شود، از کلمه کلیدی [await] استفاده می‌کنیم. ما می‌دانیم که این عبارت باید در داخل کدی باشد که با کلمه کلیدی [async] پیش‌فرض شده باشد (خط ۱۳);
  • خطوط ۱۳–۵۶: ما کد HTTP را در داخل تابع غیرهمزمان [initSession] قرار می‌دهیم؛
  • خطوط ۵۹–۶۹: یک تابع غیرهمزمان دوم، [mainبرای فراخوانی تابع غیرهمزمان [initSession] به صورت مسدودکننده (async/await) استفاده می‌شود؛
  • خط ۷۲: تابع غیرهمزمان [main] فراخوانی می‌شود؛
  • اگرچه کد به طور کلی شبیه کد همگام است، اما این‌ها در واقع توابع غیرهمگام هستند که به صورت مسدودکننده اجرا می‌شوند؛
  • خط ۱۹: برای راه‌اندازی یک جلسه jSON با سرور محاسبه مالیات، باید دستور HTTP [get /main.php?action=init-session&type=json] به آن ارسال شود. این کاری است که کد در خطوط 24–27 انجام می‌دهد. نحو [fetch] به شرح زیر است: [fetch(URL, options)] با:
    • [URL]: پرس‌وجوی URL؛
    • [options]: یک شیء که گزینه‌های پرس‌وجو را تعریف می‌کند. اینجاست که به‌ویژه سربرگ‌های HTTP را که می‌خواهیم به ماشین مقصد ارسال کنیم، تعریف می‌کنیم؛
  • خطوط ۱۵–۱۸: گزینه‌های درخواست تعریف می‌شوند:
    • [method]: ما می‌خواهیم یک GET انجام دهیم؛
    • [timeout]: می‌خواهیم کلاینت [fetch] بیش از ۲ ثانیه برای پاسخ سرور منتظر نماند. اگر این محدودیت زمانی فراتر رود، [fetch] یک استثنا پرتاب خواهد کرد؛
  • خط ۲۴: برای به‌دست‌آوردن URL و [/main.php?action=init-session&type=json]، ما از کتابخانه [qs] استفاده می‌کنیم تا رمزگذاری URL را از پارامترهای [action,typeGET به‌دست آوریم. رشتهٔ حاصل [init-session&type=json] است که می‌توانستیم خودمان آن را بسازیم. ما صرفاً می‌خواستیم نشان دهیم چگونه یک رشتهٔ رمزگذاری‌شدهٔ URL را به دست آوریم؛
  • خط ۲۴: کلمه کلیدی [await] نشان می‌دهد که یک وظیفه ناهمزمان در اینجا راه‌اندازی می‌شود و ما در انتظار انتشار پاسخ آن به حلقه رویداد [node.js] هستیم؛
  • خط ۲۴: در [response]، ما یک شیء پیچیده دریافت می‌کنیم که کل پاسخ دریافت‌شده از HTTP (سربرگ‌ها و سند) را توصیف می‌کند؛
  • خطوط ۳۰–۳۱: شیء [response] برای مشاهده ساختار آن نمایش داده می‌شود، ابتدا به صورت یک رشته و سپس به صورت یک شیء جاوااسکریپت؛
  • خط ۳۳: سربرگ‌های HTTP ارسال‌شده توسط سرور نمایش داده می‌شوند؛
  • خط ۳۸: می‌دانیم که سرور محاسبه مالیات یک رشته jSON را ارسال خواهد کرد. این رشته در داخل شیء [response] قرار دارد. ما می‌توانیم آن را با استفاده از متد [response.json()] بازیابی کنیم. با این حال، این متد ناهمزمان است. بنابراین ما [await response.json()] را فراخوانی می‌کنیم تا رشته jSON را به‌دست آوریم، که در حلقه رویداد [node.js] منتشر خواهد شد. در واقع، رشته‌ی jSON به دست نمی‌آید، بلکه شیء جاوااسکریپتی است که توسط آن نمایش داده می‌شود؛
  • خط ۳۹: رشته دریافت‌شده jSON را نمایش می‌دهد؛
  • خط ۴۰: شیء جاوااسکریپت دریافت‌شده بازگردانده می‌شود؛
  • خط ۴۷: هر خطایی از دستور [fetch] گرفته می‌شود. این دستور تنها در صورتی استثنا پرتاب می‌کند که عملیات HTTP ناموفق بوده و هیچ پاسخی از سرور دریافت نشده باشد. اگر پاسخی دریافت شده باشد – حتی با کد HTTP متفاوت از [200 OK] [fetch] خطا ایجاد نمی‌کند و پاسخ سرور در خط ۳۸ در دسترس خواهد بود؛
  • خطوط ۵۱–۵۲: شیء [error] که توسط عبارت [catch] دریافت شده است، ابتدا به صورت یک رشته jSON و سپس به صورت یک شیء جاوااسکریپت نمایش داده می‌شود؛
  • خط ۵۴: پیام خطا برای [fetch] در [error.message] قرار دارد؛
  • خطوط 59–69: تابع غیرهمزمان [main] به صورت مسدودکننده تابع غیرهمزمان [initSession] را فراخوانی می‌کند (`await` در خط 62);
  • خط ۷۲: تابع غیرهمزمان [main] اجرا می‌شود و سپس کد اسکریپت اصلی کامل می‌شود. کل اسکریپت زمانی کامل خواهد شد که وظایف غیرهمزمان اجراشده، نتایج خود را به حلقه رویداد (event loop) منتشر کنند؛

نتایج اجرای کد به شرح زیر است:

مورد ۱: سرور 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:
   'پایان زمان انتظار شبکه در: 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 رشته

[Done] exited with code=0 in 2.804 seconds

توضیحات

  • خط ۳: درخواست HTTP پس از ۲ ثانیه و ۶۲ میلی‌ثانیه به دلیل تایم‌اوت ۲ ثانیه‌ای اعمال‌شده بر روی درخواست HTTP ناموفق می‌شود؛
  • خطوط ۴–۹: شیء جاوااسکریپت [error] توسط عبارت [catch(error)] رهگیری شده است. این شیء دو ویژگی دارد:
    • [FetchError]: خط ۴؛
    • [message]: خطوط ۱۰–۱۲؛
  • خط 14: پیام خطای دریافت‌شده توسط تابع غیرهمزمان [main];

مورد ۲: سرور 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',
  'وضعیت': 700,
  'response': 'session started with type [json]' } object

[Done] exited with code=0 in 1.022 seconds

نظرات

  • خط ۳: [fetch] پاسخ سرور را پس از ۳۷۵ میلی‌ثانیه دریافت می‌کند؛
  • خطوط ۴–۳۹: ساختار شیء JavaScript [response] که پاسخ سرور را در بر می‌گیرد. در میان ویژگی‌های آن، برخی ممکن است برای ما جالب توجه باشند:
    • [status] (خط 25): کد HTTP از پاسخ سرور؛
    • [statusText] (خط ۲۶): متنی که با این کد مرتبط است؛
    • [headers] (خط ۲۷): سربرگ‌های HTTP از پاسخ سرور؛
    • [body] (خط ۸): نمایانگر سندی است که توسط سرور ارسال شده است. دستور [fetch] روش‌هایی را برای پردازش آن فراهم می‌کند؛
  • خطوط ۲۹–۳۹: سربرگ‌های HTTP از پاسخ سرور؛
  • خط ۴۰: تابع غیرهمزمان [response.json()] پاسخ خود را پس از ۱ میلی‌ثانیه بازگرداند؛
  • خطوط ۴۲–۴۴: شیء جاوااسکریپت دریافت‌شده توسط تابع غیرهمزمان [main];

مورد ۳: سرور 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',
  'وضعیت': 703,
  'response': 'invalid parameter type=[x]' } object

[Done] exited with code=0 in 0.712 seconds
  • پاسخ سرور در خط ۲ دریافت می‌شود؛
  • خط ۲۴: می‌بینیم که کد HTTP در پاسخ سرور ۴۰۰ است که یک کد خطا است. با این وجود، [fetch] استثنا پرتاب نکرد. تا زمانی که [fetch] پاسخی از سرور دریافت می‌کند، آن را پردازش می‌کند و خطا ایجاد نمی‌کند؛
  • خطوط ۴۱–۴۳: پاسخی که توسط تابع غیرهمزمان [main] دریافت شده است؛

12.4. اسکریپت [fetch-02]

اسکریپت زیر بر اساس اسکریپت [fetch-01] و بدون جزئیات غیرضروری تهیه شده است:


''use strict';

// واردات‌ها
import fetch from 'node-fetch';
import qs from 'qs';

// URL سرور محاسبه مالیات پایه
const baseUrl = 'http://localhost/php7/scripts-web/impots/version-14/main.php?';
// ابتدای جلسه
async function initSession() {
  // گزینه‌های پرس‌وجو برای HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    timeout: 2000
  };
  // اجرای پرس‌وجو HTTP [get /main.php?action=init-session&type=json]
  const response = await fetch(baseUrl + qs.stringify({
    action: 'init-session',
    type: 'json'
  }), options);
  // نتیجه دریافت‌شده به صورت jSON
  return await response.json();
}

// توابع اصلی تابع غیرهمزمان [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);
  }
}

// آزمایش
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',
  'وضعیت: 700,
  'پاسخ: 'جلسه با نوع [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] و محیط مرورگرهای رایج است. این امر به ما امکان می‌دهد:

  • در فاز ۱، برای آزمایش اسکریپت‌هایمان در محیط [node.js]؛
  • در فاز ۲، انتقال آن‌ها به یک مرورگر؛

اسکریپت [axios-01] ساختار اسکریپت [fetch-01] را دنبال می‌کند:


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

//پیکربندی پیش‌فرض Axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// راه‌اندازی جلسه
async function initSession(axios) {
  // گزینه‌های درخواست HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    //URL پارامترها
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  //اجرای درخواست HTTP [get /main.php?action=init-session&type=json]
  try {
    // درخواست ناهمزمان
    const response = await axios.request('main.php', options);
    //پاسخ کامل HTTP از سرور است (سربرگ‌های HTTP + خود پاسخ)
    // این پاسخ نمایش داده می‌شود تا ساختار آن مشاهده شود
    console.log("réponse axios=", response);
    //پاسخ سرور در [response.data] قرار دارد
    return response.data;
  } catch (error) {
    // ما اینجا هستیم زیرا سرور کد خطای [404 Not Found, 500 Internal Server Error, ...] را ارسال کرده است
    //پارامتر [error] یک نمونه استثنا است – می‌تواند اشکال مختلفی داشته باشد
    // این مقدار نمایش داده می‌شود تا ساختار آن نشان داده شود
    console.log("axios error=", typeof (error), error);
    if (error.response) {
      // سرور خطایی را در وضعیت HTTP گزارش کرد اما همچنین پاسخی ارسال کرد
      //بنابراین این در [error.response.data] یافت می‌شود
      //می‌دانیم که سرور پاسخ‌هایی jSON را با ساختار {action, status, response} ارسال می‌کند
      //و اینکه در صورت بروز خطا، پیام خطا در [réponse] قرار دارد
      return error.response.data;
    } else {
      //خطا فعال می‌شود
      throw error;
    }
  }
}

// تابع اصلی تابع غیرهمزمان [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();

توضیحات

  • خط ۲: کتابخانه [axios] وارد می‌شود؛
  • خطوط ۵–۶: پیکربندی پیش‌فرض برای پرس‌وجوهای HTTP. گزینه‌های [axios.defaults] برای تمام درخواست‌های HTTP که توسط شیء [axios] صادر می‌شوند، اعمال می‌گردند، بدون نیاز به مشخص کردن آن‌ها برای هر درخواست جدید؛
  • خط ۵: تایم‌اوت ۲ ثانیه‌ای برای همه درخواست‌ها؛
  • خط ۶: تمام درخواست‌های URL نسبت به پایه URL بیان خواهند شد؛
  • خط ۹: تابع غیرهمزمان [initSession];
  • خطوط ۱۱–۱۸: گزینه‌های درخواست HTTP که باید صادر شود (علاوه بر گزینه‌های پیش‌فرض که قبلاً در خطوط ۵–۶ تعریف شده‌اند)؛
  • خطوط ۱۴–۱۷: پارامترها برای URL و [action=init-session&type=json]. شیء [params] به طور خودکار به یک رشته رمزگذاری‌شده URL تبدیل خواهد شد؛
  • خط ۲۲: فراخوانی مسدودکننده تابع غیرهمزمان [axios.request]. پارامتر اول، هدف URL است که با افزودن [main.php] به پایه URL که در خط ۶ تعریف شده، ساخته می‌شود. پارامتر دوم، شیء [options] از خطوط ۱۱ تا ۱۸ است؛
  • خط ۲۵: [response] یک شی JavaScript است که کل پاسخ HTTP از سرور (سربرگ‌های HTTP + سند پاسخ) را در بر می‌گیرد. این شی برای مشاهده ساختار JavaScript آن نمایش داده می‌شود؛
  • خط ۲۷: اگر سرور یک سند ارسال کرده باشد، آن در [response.data] یافت می‌شود. در اینجا می‌دانیم که سرور یک پاسخ jSON را به همراه سربرگ‌های HTTP و [Content-type : application/json] ارسال می‌کند. وجود این هدر باعث می‌شود که [axios] به طور خودکار [response.data] را به یک شی جاوااسکریپت تبدیل کند؛
  • خط ۲۸: تابع [axios] ممکن است یک استثنا پرتاب کند. اینجاست که [axios] با [fetch] متفاوت است. یک استثنا در موارد زیر پرتاب می‌شود:
    • درخواست HTTP نتوانست صادر شود (خطای سمت کلاینت)؛
    • سرور کد خطای HTTP (۴۰۰، ۴۰۴، ۵۰۰، …) را بازگرداند (این تنظیم در واقع قابل پیکربندی است). شایان ذکر است که در حالی که این کد HTTP با یک پاسخ همراه است، [fetch] استثنا ایجاد نمی‌کند، در حالی که [axios] یک استثنا ایجاد می‌کند. با این حال، اگر کد خطای HTTP با یک سند همراه باشد، این سند در [error.response] قرار می‌گیرد؛
  • خط ۳۲: ساختار جاوااسکریپت شیء [error] نمایش داده می‌شود؛
  • خطوط ۳۳–۳۸: اگر شیء [error] شامل یک شیء [response] باشد، آنگاه این پاسخ به کد فراخوانی‌کننده بازگردانده می‌شود؛
  • خطوط ۳۹–۴۲: در سایر موارد، شیء [error] به کد فراخوانی‌کننده بازگردانده می‌شود؛
  • خطوط ۴۷–۵۷: تابع غیرهمزمان [main];
  • خط ۵۰: فراخوانی مسدودکننده تابع غیرهمزمان [initSession]. پاسخ jSON از سرور به عنوان یک شی JavaScript بازیابی می‌شود؛
  • خطوط ۵۳–۵۶: رسیدگی به هرگونه خطا. پیام خطا در [error.message] قرار دارد؛

نتایج اجرای برنامه به شرح زیر است:

مورد ۱: سرور 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

نظرات

  • خطوط ۳۳–۱۳۶: شیء [error] حاوی اطلاعات زیادی است؛
    • [Error]، خطوط ۳–۹: توضیحی درباره خطایی که رخ داده است؛
    • [config]، خطوط ۱۰–۲۷: پیکربندی پرس‌وجوی HTTP که منجر به این خطا شده است؛
    • [config.url]، خطوط ۱۱–۱۲: هدف URL؛
    • [config.method]، خط ۱۳: روش درخواست؛
    • [config.params]، خط 14: پارامترهای URL;
    • [config.headers]، خطوط ۱۶–۱۷: سربرگ‌های درخواست برای HTTP;
    • [config.baseURL]، خط ۱۸: پایه URL برای هدف URL;
    • [config.timeout]، خط ۲۱: زمان‌انتظار درخواست؛
    • [code]، خط ۲۸: یک کد خطا؛
    • [request]، خطوط 29–133: شرح مفصل درخواست HTTP. شایان ذکر است که بیشتر ویژگی‌ها با یک زیرخط (_) شروع می‌شوند، که نشان می‌دهد این ویژگی‌ها، ویژگی‌های داخلی شیء [request] هستند و قرار نیست مستقیماً توسط توسعه‌دهنده استفاده شوند؛
    • [response]، خط 134: پاسخ سرور، که در اینجا وجود ندارد؛
  • خط ۱۳۸: پیام خطایی که توسط تابع [main] نمایش داده می‌شود؛

مورد ۲: سرور 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,
     'response': 'session started with type [json]' } }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'وضعیت': 700,
  'response': 'session started with type [json]' } object

[Done] exited with code=0 in 1.115 seconds

توضیحات

  • خطوط ۳–۲۰۳: شیء جاوا اسکریپت [responseکه پاسخ سرور HTTP را در بر می‌گیرد؛
  • خط ۳، [status]: کد HTTP از پاسخ؛
  • خط ۴، [statusText]: متنی که با کد قبلی HTTP مرتبط است؛
  • خطوط ۵–۱۳، [headers]: سربرگ‌های HTTP در پاسخ:
    • خط ۱۰، [Set-Cookie]: کوکی جلسه؛
    • خط ۱۳، [Content-Type]: نوع سند ارسال‌شده توسط سرور؛
  • خطوط 14–31، [config]: پیکربندی درخواست HTTP که ارسال شده است؛
  • خطوط ۳۲–۱۹۹، [request]: شیء جاوااسکریپت که جزئیات درخواست ارسال‌شده HTTP را نشان می‌دهد؛
  • خطوط 200–203، [request.data]: شیء جاوااسکریپتی که پاسخ سرور jSON را در بر می‌گیرد؛
  • خطوط ۲۰۵–۲۰۷: پاسخی که توسط تابع غیرهمزمان [main] بازیابی شده است؛

مورد ۳: یک درخواست نادرست [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',
        'status': 703,
        'response: 'invalid type=[x] parameter' } },
  isAxiosError: true,
  toJSON: [Function] }
succès ---------------------------------------------
réponse= { action: 'init-session',
  'وضعیت': 703,
  'response': 'پارامتر نوع=[x] نامعتبر' } object

[Done] exited with code=0 in 0.69 seconds

چون سرور با کد وضعیت 400 (خط 15) پاسخ داد، HTTP و [axios] یک استثنا پرتاب کردند (باز هم، این رفتار قابل پیکربندی است). اگرچه [axios] یک استثنا پرتاب کرد، ما در واقع پاسخ سرور HTTP را در [error.response] (خط 14) و سند jSON را که به [error.response.data] (خط 34) ارسال شده است، دریافت می‌کنیم. خطوط ۴۱–۴۳: تابع [main] به‌درستی پاسخ jSON را از سرور بازیابی می‌کند.

12.6. اسکریپت [axios-02]

اکنون که اشیاء مورد پردازش توسط کتابخانه [axios] در طول یک درخواست HTTP را تشریح کردیم، می‌توانیم اسکریپت [axios-01] را به صورت زیر بازنویسی کنیم:


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

// پیکربندی پیش‌فرض axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

// ابتدای جلسه
async function initSession(axios) {
  // گزینه‌های درخواست HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    //URL پارامترها
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    //اجرای پرس‌وجوی HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    //پاسخ سرور در [response.data] است
    return response.data;
  } catch (error) {
    //پاسخ سرور
    if (error.response) {
      //پاسخ jSON در [error.response.data] است
      return error.response.data;
    } else {
      //خطا دوباره فعال می‌شود
      throw error;
    }
  }
}

//تابع `main` تابع غیرهمزمان `[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 [authentifier-utilisateur] با استفاده از POST به سرور ارسال می‌شود:


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

//پیکربندی axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';


// ابتدای جلسه
async function initSession(axios) {
  // گزینه‌های درخواست HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    //URL پارامترها
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  try {
    // اجرای پرس‌وجوی HTTP [get /main.php?action=init-session&type=json]
    const response = await axios.request('main.php', options);
    //پاسخ سرور در [response.data] است
    return response.data;
  } catch (error) {
    // پاسخ سرور
    if (error.response) {
      //پاسخ jSON در [error.response.data] است
      return error.response.data;
    } else {
      //خطا دوباره فعال می‌شود
      throw error;
    }
  }
}

async function authentifierUtilisateur(axios, user, password) {
  // گزینه‌های درخواست: HHTP [POST /main.php?action=authentifier-utilisateur]
  const options = {
    method: "POST",
    headers: {
      'نوع محتوا: 'application/x-www-form-urlencoded',
    },
    // بدنهٔ POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    // پارامترهای URL
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  try {
    // اجرای درخواست HTTP [post /main.php?action=authentifier-utilisateur]
    const response = await axios.request('main.php', options);
    //پاسخ سرور در [response.data] است
    return response.data;
  } catch (error) {
    // پاسخ سرور
    if (error.response) {
      //پاسخ jSON در [error.response.data] است
      return error.response.data;
    } else {
      //خطا دوباره فعال می‌شود
      throw error;
    }
  }
}

//تابع `main` توابع غیرهمزمان را یکی‌یکی اجرا می‌کند
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);
    // احرازهویت-کاربر
    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();

نظرات

  • خطوط ۳۸–۷۰: تابع غیرهمزمان [authentifierUtilisateur];
  • خط ۳۹: پرس‌وجوی [POST /main.php?action=authentifier-utilisateur] باید اجرا شود؛
  • خطوط ۴۰–۵۴: گزینه‌های درخواست HTTP؛
  • خط ۴۱: این یک POST است؛
  • خطوط ۴۲–۴۴: پارامترها برای POST به صورت URL در سندی که کلاینت با درخواست خود ارسال می‌کند، رمزگذاری خواهند شد؛
  • خطوط ۴۶–۴۹: خاصیت [data] باید حاوی رشتهٔ رمزگذاری‌شده از POST URL باشد. برای این کار، از کتابخانه [qs] که در خط ۳ وارد شده است، استفاده می‌کنیم؛
  • خطوط ۵۵–۶۹: برای اجرای پرس‌وجو، از همان کد موجود در متد [initSession] استفاده می‌شود؛
  • خطوط ۷۳–۸۹: متد [asynchrone] متدهای [initSession, authentifierUtilisateur] را به صورت متوالی و به شیوه مسدودکننده (blocking) در خطوط ۷۷ و ۸۲ فراخوانی می‌کند؛
  • خط ۸۲: جفت (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',
  'وضعیت': ۷۰۰,
  'response: 'session started with type [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'وضعیت': 103,
  'response':
   [ 'pas de session en cours. Commencer par action [init-session]' ] }

[Done] exited with code=0 in 0.834 seconds
  • خطوط ۹–۱۲: احراز هویت کاربر ناموفق است: سرور این واقعیت را که یک جلسه از طریق jSON آغاز شده بود، حفظ نکرده است. این به این دلیل است که کوکی جلسه که در پاسخ به درخواست اول، [init-session]، ارسال شده بود، بازگردانده نشد؛

12.8. اسکریپت [axios-04]

اسکریپت [axios-04] دو بهبود را به اسکریپت [axios-03] معرفی می‌کند:

  • این اسکریپت کوکی جلسه را مدیریت می‌کند؛
  • عناصر مشترک را از توابع [initSession] و [authentifierUtilisateur] استخراج کرده و در یک تابع واحد به نام [getRemoteData] قرار می‌دهد؛

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

//پیکربندی Axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';

//کوکی جلسه
const sessionCookieName = "PHPSESSID";
let sessionCookie = '';

// ابتدای جلسه
async function initSession(axios) {
  // گزینه‌های درخواست HHTP [get /main.php?action=init-session&type=json]
  const options = {
    method: "GET",
    //URL پارامترها
    params: {
      action: 'init-session',
      type: 'json'
    }
  };
  // اجرای پرس‌وجو HTTP
  return await getRemoteData(axios, options);
}

async function authentifierUtilisateur(axios, user, password) {
  // گزینه‌های درخواست HHTP [post /main.php?action=authentifier-utilisateur]
  const options = {
    method: "POST",
    headers: {
      'Content-type': 'application/x-www-form-urlencoded',
    },
    // بدنهٔ POST
    data: qs.stringify({
      user: user,
      password: password
    }),
    //پارامترهای URL
    params: {
      action: 'authentifier-utilisateur'
    }
  };
  //اجرای درخواست HTTP
  return await getRemoteData(axios, options);
}

async function getRemoteData(axios, options) {
  //برای کوکی جلسه
  if (!options.headers) {
    options.headers = {};
  }
  options.headers.Cookie = sessionCookie;
  //اجرای درخواست HTTP
  let response;
  try {
    //درخواست ناهمزمان
    response = await axios.request('main.php', options);
  } catch (error) {
    //پارامتر [error] یک نمونه استثنا است – ممکن است اشکال مختلفی داشته باشد
    if (error.response) {
      // پاسخ سرور در [error.response] است
      response = error.response;
    } else {
      //خطا مجدداً فعال می‌شود
      throw error;
    }
  }
  //پاسخ، کل پاسخ HTTP از سرور است (سربرگ‌های HTTP + خود پاسخ)
  //کوکی جلسه را در صورت وجود بازیابی می‌کند
  const setCookie = response.headers['set-cookie'];
  if (setCookie) {
    // setCookie یک آرایه است
    // کوکی جلسه در این آرایه جستجو می‌شود
    let trouvé = false;
    let i = 0;
    while (!trouvé && i < setCookie.length) {
      //جستجو برای کوکی جلسه
      const results = RegExp('^(' + sessionCookieName + '.+?);').exec(setCookie[i]);
      if (results) {
        // کوکی جلسه ذخیره می‌شود
        //eslint-disable-next-line require-atomic-updates
        sessionCookie = results[1];
        // پیدا شد
        trouvé = true;
      } else {
        // عنصر بعدی
        i++;
      }
    }
  }
  //پاسخ سرور در [response.data] است
  return response.data;
}

// تابع اصلی توابع غیرهمزمان را یکی‌یکی اجرا می‌کند
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);
    //احراز-هویت-کاربر
    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 را برای ارسال به سرور آماده می‌کند اما آن را اجرا نمی‌کند. این کار را در خطوط 49–95 به متد [getRemoteDate] واگذار می‌کند؛
  • خطوط 28–47: تابع [authentifierUtilisateur] همان رویه را دنبال می‌کند؛
  • خط ۴۹: تابع [getRemoteData] دو مورد اطلاعات را دریافت می‌کند که به آن امکان اجرای درخواست HTTP را می‌دهد:
    • [axios]، ابجکتی که مسئول ارسال درخواست و دریافت پاسخ است؛
    • [options]، گزینه‌های پیکربندی برای درخواستی که باید به سرور ارسال شود؛
  • خط ۵۹: اجرای درخواست و انتظار مسدودکننده برای پاسخ آن jSON;
  • خطوط ۶۰–۶۸: رسیدگی به هرگونه خطا؛
  • خط ۶۴: بازیابی پاسخ، که ممکن است در شی خطا (error object) جای گرفته باشد؛
  • خط ۶۷: اگر سرور بدون درج پاسخ خود، استثناء‌ای را پرتاب کرده باشد، خطای دریافتی به کد فراخواننده بازگردانده می‌شود؛
  • تابع [getRemoteData] کوکی جلسه را مدیریت می‌کند:
    • هنگامی که آن را برای اولین بار دریافت می‌کند، آن را در متغیر [sessionCookie] (خط ۱۱) ذخیره می‌کند؛
    • سپس آن را با هر درخواست جدید بازمی‌گرداند، HTTP؛
  • خطوط ۷۲–۹۲: [getRemoteData] هر پاسخ از سرور را تحلیل می‌کند تا بررسی کند که آیا هدر HTTP [Set-Cookie] را ارسال کرده است یا خیر. ما می‌دانیم که سرور یک کوکی جلسه به نام [PHPSESSID] (خط ۱۰) ارسال می‌کند. بنابراین این کوکی است که ما به دنبال آن هستیم (خط ۱۰)؛
  • خط ۷۲: ما سربرگ‌های HTTP و [Set-Cookie] را در صورت وجود بازیابی می‌کنیم (حالت حروف مهم نیست). در واقع ممکن است چندین هدر [Set-Cookie] وجود داشته باشد، بنابراین یک آرایه را بازیابی می‌کنیم؛
  • خط ۷۳: اگر آرایه‌ای از کوکی‌ها بازیابی شده باشد؛
  • خطوط ۷۸–۹۰: ما در میان تمام کوکی‌های آرایه به دنبال کوکی جلسه می‌گردیم؛
  • خط ۸۰: عبارت رابطی که برای جستجوی کوکی جلسه در کوکی شماره i استفاده می‌شود؛
  • خط ۸۱: اگر مقایسه هر نتیجه‌ای برگرداند؛
  • خط ۸۴: در `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',
  'وضعیت': ۷۰۰,
  'response: 'session started with type [json]' }
action authentifier-utilisateur en cours ---------------------------------------------
succès ---------------------------------------------
réponse= { action: 'authentifier-utilisateur',
  'وضعیت': 200,
  'response': 'احراز هویت موفقیت‌آمیز [admin, admin]' }

[Done] exited with code=0 in 0.982 seconds