Skip to content

15. مثال [nuxt-12]: طلبات HTTP باستخدام axios

15.1. مقدمة

في هذا المثال الجديد، سنستكشف كيفية إجراء طلبات HTTP باستخدام مكتبة [axios] داخل دوال [asyncData]. بالإضافة إلى ذلك، سنطبق المفاهيم التي تمت تغطيتها بالفعل:

  • استخدام المكونات الإضافية من المثال [nuxt-06]:
  • تخزين المخزن في ملف تعريف ارتباط الجلسة من المثال [nuxt-06
  • التحكم في التنقل باستخدام البرامج الوسيطة من مثال [nuxt-09
  • معالجة الأخطاء من مثال [nuxt-11

ستكون بنية المثال كما يلي:

Image

  • سيتم استضافة تطبيق [nuxt] على خادم [node.js] [3]، ويتم تنزيله بواسطة المتصفح [1]، الذي سيقوم بعد ذلك بتنفيذه؛
  • سيقوم كل من عميل [nuxt] [1] وخادم [nuxt] [3] بإرسال طلبات HTTP إلى خادم البيانات [2]. سيكون هذا الخادم هو خادم حساب الضرائب الذي تم تطويره في قسم PHP 7. سنستخدم أحدث إصدار منه، الإصدار 14، مع تمكين طلبات CORS؛

يمكن تبسيط بنية المثال على النحو التالي:

Image

  • في [1]، يقوم خادم [node.js] بتسليم صفحات [nuxt] إلى المتصفح [2]. إن طبقة [web] [8] في الخادم هي التي تقوم بتسليم هذه الصفحات. لتسليم الصفحة، قد يكون الخادم قد طلب بيانات خارجية من خادم البيانات [3]. إن طبقة [DAO] [9] هي التي تقوم بإجراء طلبات HTTP الضرورية؛
  • مع كل طلب صفحة إلى خادم [Node.js] [1]، يتلقى المتصفح [2] تطبيق [Nuxt] بالكامل، والذي يعمل بعد ذلك في وضع SPA. تعرض كتلة [UI] (واجهة المستخدم) [4] صفحات [Vue.js] للمستخدم. يمكن أن تؤدي إجراءات المستخدم أو دورة الحياة الطبيعية للصفحات إلى تشغيل طلبات للحصول على بيانات خارجية من خادم البيانات [3]. ثم تقوم طبقة [DAO] [5] بإجراء طلبات HTTP اللازمة؛

15.2. هيكل دليل المشروع

Image

15.3. ملف التكوين [nuxt.config.js]

سيتم التحكم في المشروع بواسطة ملف [nuxt.config.js] التالي:


export default {
  mode: 'universal',
  /*
   ** Headers of the page
   */
  head: {
    title: 'Introduction à [nuxt.js]',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: 'ssr routing loading asyncdata middleware plugins store'
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },
  /*
   ** Customize the progress-bar color
   */
  loading: false,
 
  /*
   ** Global CSS
   */
  css: [],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [
    { src: '@/plugins/client/plgSession', mode: 'client' },
    { src: '@/plugins/server/plgSession', mode: 'server' },
    { src: '@/plugins/client/plgDao', mode: 'client' },
    { src: '@/plugins/server/plgDao', mode: 'server' },
    { src: '@/plugins/client/plgEventBus', mode: 'client' }
  ],
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module'
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // Doc: https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    // https://www.npmjs.com/package/cookie-universal-nuxt
    'cookie-universal-nuxt'
  ],
  /*
   ** Axios module configuration
   ** See https://axios.nuxtjs.org/options
   */
  axios: {},
  /*
   ** Build configuration
   */
  build: {
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) { }
  },
  // source code directory
  srcDir: 'nuxt-12',
  // router
  router: {
    // application URL root
    base: '/nuxt-12/',
    // routing middleware
    middleware: ['routing']
  },
  // server
  server: {
    // service port, default 3000
    port: 81,
    // network addresses listened to, default localhost: 127.0.0.1
    // 0.0.0.0 = all the machine's network addresses
    host: 'localhost'
  },
  // environment
  env: {
    // axios configuration
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // session cookie configuration [nuxt]
    maxAge: 60 * 5
  }
}
  • السطر 22: نتولى بأنفسنا معالجة الإشعار بانتهاء إجراء غير متزامن؛
  • السطر 31: سنستخدم العديد من المكونات الإضافية المتخصصة إما للعميل أو للخادم، ولكن ليس لكليهما في نفس الوقت؛
  • السطر 52: تم دمج وحدة [axios] في [nuxt]. ونتيجة لذلك، سيكون كائن [axios]، الذي سيتولى معالجة طلبات HTTP من تطبيق [nuxt] إلى خادم حساب الضرائب PHP، متاحًا في [context.$axios
  • السطر 54: ستسمح لنا وحدة [cookie-universal-nuxt] بحفظ جلسة [nuxt] في ملف تعريف ارتباط؛
  • السطر 60: تسمح لنا الخاصية [axios] بتكوين وحدة [@nuxtjs/axios] من السطر 52. لن نستخدم هذا الخيار، بل نفضل بدلاً من ذلك الخاصية [env] من السطر 88؛
  • السطر 90: الحد الأقصى لوقت الانتظار لاستجابة من خادم حساب الضرائب؛
  • السطر 91: مطلوب لعميل [nuxt] — يتيح استخدام ملفات تعريف الارتباط في الاتصالات مع خادم حساب الضرائب؛
  • السطر 92: عنوان URL الأساسي لخادم حساب الضرائب؛
  • السطر 94: مدة جلسة Nuxt (5 دقائق)؛
  • السطر 77: سيتم التحكم في تنقل العميل وخادم [nuxt] بواسطة برمجيات وسيطة للتوجيه؛

15.4. طبقة [UI] للتطبيق

Image

سنمنح تطبيق [nuxt] حق الوصول إلى واجهة برمجة تطبيقات خادم حساب الضرائب عبر العرض التالي:

Image

  • في [2]، القائمة التي توفر الوصول إلى واجهة برمجة تطبيقات خادم حساب الضرائب:
    • [المصادقة]: تتوافق مع صفحة [المصادقة]. ترسل هذه الصفحة طلب مصادقة إلى خادم حساب الضرائب باستخدام بيانات الاعتماد [admin, admin]، وهي البيانات الوحيدة المصرح بها حاليًا. النتيجة المعروضة مشابهة لـ [3]؛
    • [طلب بيانات الإدارة]: تتوافق مع صفحة [get-admindata]. تطلب هذه الصفحة بيانات من خادم حساب الضرائب — المشار إليها هنا بـ [adminData] — والتي تتيح حساب الضرائب. النتيجة المعروضة مشابهة لـ [3]؛
    • [إنهاء جلسة الضرائب]: تتوافق مع صفحة [end-session]. ترسل هذه الصفحة طلب إنهاء الجلسة PHP إلى خادم حساب الضرائب. ثم يقوم الخادم بإلغاء جلسة PHP الحالية وتهيئة جلسة جديدة فارغة؛

15.5. طبقات [dao] لتطبيق [nuxt]

كما ذكر أعلاه، ستكون بنية تطبيق [nuxt] على النحو التالي:

Image

  • في [1]، يقوم خادم [node.js] بتسليم صفحات [nuxt] إلى المتصفح [2]. طبقة [web] [8] في الخادم هي التي تقوم بتسليم هذه الصفحات. لتسليم الصفحة، قد يكون الخادم قد طلب بيانات خارجية من خادم البيانات [3]. طبقة [DAO] [9] هي التي تقوم بإجراء طلبات HTTP اللازمة؛
  • مع كل طلب صفحة إلى خادم [Node.js] [1]، يتلقى المتصفح [2] تطبيق [Nuxt] بالكامل، والذي يعمل بعد ذلك في وضع SPA. تعرض كتلة [UI] (واجهة المستخدم) [4] صفحات [Vue.js] للمستخدم. قد تؤدي إجراءات المستخدم أو دورة حياة الصفحة إلى تشغيل طلبات للحصول على بيانات خارجية من خادم البيانات [3]. ثم تقوم طبقة [DAO] [5] بإجراء طلبات HTTP اللازمة؛

سنستخدم الإصدار 14 من خادم حساب الضرائب الذي تم تطويره في الوثيقة |مقدمة إلى PHP7 من خلال الأمثلة|. سنستخدم جزءًا فقط من واجهة برمجة التطبيقات (API) JSON الخاصة به:

الطلب
الاستجابة
1
2
3
4
5
6
[تهيئة جلسة JSON مع
خادم حساب الضرائب]
[يتم إنشاء جلسة عمل PHP مع
خادم حساب الضرائب]

GET main.php?action=init-session&type=json

{
    "action": "init-session",
    "الحالة": 700،
    "response": "بدأت الجلسة بنوع [json]"
}

[مصادقة المستخدم]
[يتم تخزين المصادقة في
جلسة PHP]

POST main.php?action=authenticate-user
المعلمات المرسلة: user, admin

{
    "action": "authenticate-user",
    "status": 200,
    "response": "تمت المصادقة بنجاح [admin, admin]"
}

[طلب بيانات إدارة الضرائب
البيانات]
[يتم تخزين البيانات المستلمة في
جلسة PHP]

GET main.php?action=get-admindata

{
    "action": "get-admindata",
    "status": 1000,
    "response": {
        "الحدود": [
            9964,
            27519,
            73779,
            156244
            0
        ],
        "coeffR": [
            0,
            0.14,
            0.3،
            0.41,
            0.45
        ],
        "coeffN": [
            0،
            1394.96،
            5798،
            13913.69,
            20163.45
        ],
        "half-share-income-limit": 1551,
        "حد_الدخل_للفرد_للتخفيض": 21037،
        "حد_الدخل_للزوجين_للتخفيض": 42074،
        "قيمة تخفيض نصف الحصة": 3797،
        "حد_الخصم_للفرد": 1196،
        "حد الخصم للزوجين": 1970،
        "الحد الأقصى للضريبة للزوجين للحصول على الخصم": 2627،
        "الحد الأقصى الضريبي الموحد للخصم": 1595،
        "الحد الأقصى للخصم بنسبة 10%": 12502،
        "الحد الأدنى للخصم بنسبة 10%": 437
    }
}

[نهاية جلسة PHP مع
حساب الضريبة]
[حذف جلسة PHP الحالية وإنشاء
جلسة PHP جديدة. في هذه الجلسة،
تظل جلسة JSON نشطة، لكن المستخدم
لم يعد مصدقاً عليه]

GET main.php?action=end-session

{
    "action": "end-session",
    "status": 400,
    "response": "تم حذف الجلسة"
}

15.5.1. طبقة [DAO] لخادم [Nuxt]

Image

سيستخدم خادم [node.js] [1] طبقة [DAO] الموضحة في الوثيقة |مقدمة إلى إطار عمل VUE.JS من خلال أمثلة|. وإليك الكود مرة أخرى:


'use strict';
 
// imports
import qs from 'qs'
 
class Dao {
 
  // manufacturer
  constructor(axios) {
    this.axios = axios;
    // session cookie
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
  }
 
  // init session
  async  initSession() {
    // 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 this.getRemoteData(options);
  }
 
  async  authentifierUtilisateur(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 this.getRemoteData(options);
  }
 
  async getAdminData() {
    // query options HHTP [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: 'get-admindata'
      }
    };
    // execute query HTTP
    const data = await this.getRemoteData(options);
    // result
    return data;
  }
 
  async  getRemoteData(options) {
    // for the session cookie
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.Cookie = this.sessionCookie;
    // execute query HTTP
    let response;
    try {
      // asynchronous request
      response = await this.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('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // the session cookie is stored
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // we found
          trouvé = true;
        } else {
          // next item
          i++;
        }
      }
    }
    // the server response is in [response.data]
    return response.data;
  }
}
 
// class export
export default Dao;
  • تُرجع جميع الطرق في طبقة [dao] الكائن المرسل من خادم البيانات [{action: ‘xx’, status: nn, response: {...}] مع:
    • [action]: اسم الإجراء الذي نفذه خادم البيانات؛
    • [state]: مؤشر رقمي:
      • [initSession]: الحالة=700 للإشارة إلى استجابة خالية من الأخطاء؛
      • [authenticateUser]: الحالة = 200 للاستجابة بدون أخطاء؛
      • [getAdminData]: الحالة = 1000 للاستجابة بدون أخطاء؛
      • [end-session]: الحالة = 400 للاستجابة بدون أخطاء؛
    • [response]: استجابة مرتبطة بالمؤشر الرقمي [status]. قد تختلف اعتمادًا على هذا المؤشر الرقمي؛

دعونا نفحص منشئ فئة [Dao]:


// constructeur
  constructor(axios) {
    this.axios = axios;
    // cookie de session
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
}
  • السطر 2: يتم توفير الكائن [axios] الذي يتم تمريره كحجة إلى المنشئ بواسطة الكود المستدعي. وهذا الكائن هو الذي سيقوم بإجراء طلبات HTTP؛
  • السطر 5: اسم ملف تعريف ارتباط الجلسة الذي أرسله خادم البيانات المكتوب بلغة PHP؛
  • السطر 6: ملف تعريف الارتباط الخاص بالجلسة الذي يتم تبادله بين طبقة [dao] وخادم البيانات. يتم تهيئة هذا بواسطة الدالة [getRemoteData] في الأسطر 67–113؛

بالنسبة لملف تعريف الارتباط الخاص بالجلسة، نحتاج إلى النظر في طبقتين منفصلتين من [dao]:

  • طبقة المتصفح؛
  • طبقة الخادم؛

سنحتاج إلى إدارة ثلاث ملفات تعريف ارتباط للجلسة:

  1. ملف تعريف الارتباط الذي يتم تبادله بين عميل [nuxt] وخادم PHP
  2. الملف الذي يتم تبادله بين خادم [nuxt] وخادم PHP
  3. الملف الذي يتم تبادله بين عميل [nuxt] وخادم [nuxt

سنضمن أن ملف تعريف الارتباط الخاص بالجلسة مع خادم PHP هو نفسه لكل من العميل وخادم [nuxt]. سنسمي ملف تعريف الارتباط هذا ملف تعريف الارتباط الخاص بجلسة PHP. ملف تعريف الارتباط هذا هو الملف المذكور في الحالتين 1 و 2. سنسمي ملف تعريف الارتباط المذكور في الحالة 3 ملف تعريف الارتباط الخاص بجلسة [nuxt]. وبالتالي سيكون لدينا جلستان:

  • جلسة عمل PHP مع ملف تعريف ارتباط جلسة العمل PHP؛
  • جلسة [nuxt] مع ملف تعريف ارتباط جلسة [nuxt

لماذا نستخدم ملف تعريف الارتباط نفسه لجلسات PHP الخاصة بالعميل وجلسات متصفح [nuxt]؟ نريد أن يكون التطبيق قادرًا على التواصل مع خادم PHP 7 بغض النظر عما إذا كان العميل أو خادم [nuxt]:

  • إذا أدى الإجراء A من خادم [nuxt] إلى وضع خادم PHP في الحالة E، فإن هذه الحالة تنعكس في جلسة عمل PHP التي يديرها خادم PHP؛
  • باستخدام ملف تعريف ارتباط جلسة PHP نفسه الذي يستخدمه الخادم، فإن الإجراء B [nuxt] للعميل الذي يتبع الإجراء A [nuxt] للخادم سيجد خادم PHP في الحالة E التي تركها خادم [nuxt]، وبالتالي يمكنه البناء على العمل الذي أنجزه خادم [nuxt] بالفعل؛
  • إذا تبع الإجراء B من عميل [nuxt] إجراء C من خادم [nuxt]، لنفس السبب السابق، فسيتمكن هذا الإجراء من البناء على العمل الذي أنجزه الإجراء B من عميل [nuxt

لتمكين متصفح عميل [nuxt] من التواصل مع خادم PHP لحساب الضرائب، سنستخدم الإصدار 14 من هذا الخادم، الذي يسمح بالمكالمات عبر النطاقات — أي المكالمات من المتصفح إلى خادم PHP. ومع ذلك، فإن المكالمات من خادم [nuxt] إلى خادم PHP ليست مكالمات عبر النطاقات. ينطبق هذا المفهوم فقط على المكالمات التي تتم من المتصفح.

لنعد إلى كود المنشئ لفئة [Dao] السابقة:


// constructeur
  constructor(axios) {
    this.axios = axios;
    // cookie de session
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
}
  • السطران 5 و6 يتوافقان مع ملف تعريف ارتباط جلسة العمل في PHP مع خادم حساب الضرائب؛

إدارة ملف تعريف ارتباط جلسة PHP أعلاه غير مناسبة لخادم [nuxt]: حيث يتم إنشاء مثيل لطبقة [dao] الخاصة به مع كل طلب جديد يتم إرساله إلى خادم [nuxt]. تذكر أن طلب صفحة من خادم [nuxt] يؤدي فعليًا إلى إعادة تعيين تطبيق [nuxt]. وبالتالي، عندما يقوم خادم [nuxt] بإرسال طلبه الأول إلى خادم البيانات، يتم تهيئة ملف تعريف ارتباط جلسة PHP لطبقة [dao]، وتُفقد هذه القيمة أثناء طلب HTTP التالي من نفس خادم [nuxt]، لأنه في غضون ذلك تم إعادة إنشاء طبقة [dao] الخاصة به، وإعادة تنفيذ المنشئ، وإعادة تعيين ملف تعريف ارتباط جلسة PHP إلى سلسلة فارغة (السطر 6)؛

أحد الحلول هو استخدام مُنشئ مختلف لطبقة [dao] في الخادم:


// constructeur
  constructor(axios, phpSessionCookie) {
    // bibliothèque axios
    this.axios = axios
    // valeur du cookie de session
    this.phpSessionCookie = phpSessionCookie
    // nom du cookie de session du serveur PHP
    this.phpSessionCookieName = 'PHPSESSID'
  }
  • السطر 2: هذه المرة، سيتم توفير ملف تعريف ارتباط جلسة عمل PHP إلى مُنشئ طبقة [DAO] لخادم البيانات؛

كيف سيقدم خادم [nuxt] ملف تعريف ارتباط جلسة عمل PHP هذا إلى مُنشئ طبقة [dao] الخاصة به؟ سنقوم بتخزين ملف تعريف ارتباط جلسة عمل PHP في ملف تعريف ارتباط جلسة عمل [nuxt] الذي يتم تبادله بين المتصفح وخادم [nuxt]. وتتم العملية على النحو التالي:

  1. يتم تشغيل تطبيق [nuxt
  2. عندما يقوم خادم [nuxt] بإرسال أول طلب HTTP إلى خادم PHP، فإنه يخزن ملف تعريف ارتباط جلسة عمل PHP الذي تلقّاه في ملف تعريف ارتباط جلسة عمل [nuxt] الذي يتبادله مع عميل [nuxt
  3. يستقبل المتصفح الذي يستضيف عميل [nuxt] ملف تعريف ارتباط جلسة [nuxt] هذا، وبالتالي يعيده تلقائيًا مع كل طلب جديد إلى خادم [nuxt
  4. عندما يحتاج خادم [nuxt] إلى إرسال طلب جديد إلى خادم PHP، سيجد ملف تعريف ارتباط جلسة عمل PHP داخل ملف تعريف ارتباط جلسة عمل [nuxt] الذي أرسله المتصفح إليه. ثم سيرسله إلى خادم PHP؛

هناك بالفعل ملفان لملفات تعريف الارتباط للجلسة، ويجب عدم الخلط بينهما:

  • ملف تعريف ارتباط جلسة [nuxt] الذي يتم تبادله بين خادم [nuxt] ومتصفح عميل [nuxt
  • ملف تعريف ارتباط جلسة عمل PHP الذي يتم تبادله بين خادم [nuxt] وخادم PHP أو بين عميل [nuxt] وخادم PHP؛

لنعد الآن إلى كود طريقة فئة [Dao]. لا تتضمن هذه الطريقة دالة لإغلاق جلسة PHP مع خادم حساب الضرائب. سنضيف هذه الدالة:


// end of tax calculation session
  async finSession() {
    // query options HHTP [get /main.php?action=end-session]
    const options = {
      method: 'GET',
      // URL parameters
      params: {
        action: 'fin-session'
      }
    }
    // execute query HTTP
    const data = await this.getRemoteData(options)
    // result
    return data
  }

أثناء الاختبار، اكتشفنا أن الدالة [getRemoteData] التي تم استدعاؤها في السطر 12 غير مناسبة لطريقة [finSession]:


async  getRemoteData(options) {
    // for the session cookie
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.Cookie = this.sessionCookie;
    // execute query HTTP
    let response;
    try {
      // asynchronous request
      response = await this.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('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // the session cookie is stored
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // we found
          trouvé = true;
        } else {
          // next item
          i++;
        }
      }
    }
    // the server response is in [response.data]
    return response.data;
  }
  • الأسطر 30–43: نبحث عن ملف تعريف الارتباط [PHPSESSID=xxx]. إذا تم العثور عليه، يتم تخزينه في الفئة (السطر 36)؛

هذا الرمز غير مناسب لطريقة [finSession] الجديدة لأن خادم PHP يرسل، عند إجراء [fin-sessionملفين من ملفات تعريف الارتباط باسم [PHPSESSID]. فيما يلي مثال تم الحصول عليه باستخدام عميل [Postman]:

Image

  • في [1]، الطلب من عميل [Postman
  • في [3]، استجابة خادم PHP؛
  • في [4]، رؤوس HTTP لاستجابة خادم PHP؛

Image

  • في [5]، يُشير خادم PHP أولاً إلى أنه قد حذف جلسة PHP الحالية؛
  • في [6]، يرسل خادم PHP ملف تعريف الارتباط الخاص بجلسة PHP الجديدة؛

مع الكود الحالي، تسترد الدالة [getRemoteData] ملف تعريف الارتباط [5]، في حين أن ملف تعريف الارتباط [6] هو الذي يجب تخزينه.

لذلك يجب علينا تحديث الكود الخاص بوظيفة [getRemoteData]:


async getRemoteData(options) {
    // is there a PHP session cookie?
    if (this.phpSessionCookie) {
      // are there headers?
      if (!options.headers) {
        // create an empty object
        options.headers = {}
      }
      // session cookie header PHP
      options.headers.Cookie = this.phpSessionCookie
    }
    // execute query HTTP
    let response
    try {
      // asynchronous request
      response = await this.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)
    // look for session cookie PHP in received cookies
    // all cookies received
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // cookies is a picture
      // look for the PHP session cookie in this array
      let trouvé = false
      let i = 0
      while (!trouvé && i < cookies.length) {
        // look for the PHP session cookie
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // we store the PHP session cookie
          const phpSessionCookie = results[1]
          // is the word [deleted] in it?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // we have the right session cookie PHP
            this.phpSessionCookie = phpSessionCookie
            // we found
            trouvé = true
          } else {
            // next item
            i++
          }
        } else {
          // next item
          i++
        }
      }
    }
    // the server response is in [response.data]
    return response.data
  }
  • السطر 41: عثرنا على ملف تعريف ارتباط باسم [PHPSESSID]. نقوم بتخزينه محليًا؛
  • السطر 43: نتحقق مما إذا كان ملف تعريف الارتباط المحفوظ يحتوي على السلسلة [PHPSESSID=deleted
  • السطر 46: إذا كانت الإجابة لا، فهذا يعني أننا عثرنا على ملف تعريف الارتباط الصحيح [PHPSESSID]. نقوم بتخزينه في الفئة؛

بعد دالة [getRemoteData]، يتم تخزين ملف تعريف ارتباط جلسة عمل PHP في الفئة، في [this.phpSessionCookie]. ذكرنا أن الفئة يتم إنشاء مثيل لها مع كل طلب HTTP جديد من خادم [nuxt]. لذلك يجب استخراج ملف تعريف ارتباط جلسة عمل PHP من الفئة. للقيام بذلك، نضيف طريقة جديدة إليها:


// accès au cookie de la session PHP
  getPhpSessionCookie() {
    return this.phpSessionCookie
}
  • يطلب خادم [nuxt] إجراءً من طبقة [dao] الخاصة به عن طريق تزويد منشئه بملف تعريف ارتباط جلسة عمل PHP، إن وُجد؛
  • بمجرد اكتمال الإجراء، يسترد خادم [nuxt] ملف تعريف ارتباط جلسة عمل PHP المخزن بواسطة طبقة [dao] باستخدام طريقة [getPhpSessionCookie] السابقة. قد يكون ملف تعريف الارتباط هذا هو نفسه الملف السابق أو ملفًا مختلفًا. تحدث الحالة الأخيرة في حالتين:
    • عند تنفيذ طريقة [initSession] (لم يكن هناك ملف تعريف ارتباط جلسة عمل PHP من قبل)؛
    • عند تنفيذ طريقة [finSession] (يقوم خادم PHP بتغيير ملف تعريف ارتباط جلسة عمل PHP

لاحظ ميزة خاصة تتعلق بملف تعريف ارتباط جلسة عمل PHP. لا يتلقى خادم [nuxt] دائمًا ملف تعريف الارتباط هذا من خادم PHP. في الواقع، يرسله خادم PHP مرة واحدة فقط. بعد ذلك، لا يرسله مرة أخرى. عند النظر إلى كود [getRemoteData] و [getPhpSessionCookie]، يمكننا أن نرى أنه عندما لا يرسل خادم PHP ملف تعريف ارتباط الجلسة، فإن دالة [getPhpSessionCookie] تُرجع ملف تعريف ارتباط جلسة PHP المقدم إلى المُنشئ. هكذا يرسل الخادم دائمًا إلى خادم PHP آخر ملف تعريف ارتباط جلسة PHP أرسله خادم PHP إليه.

15.5.2. طبقة [dao] لعميل [nuxt]

Image

بالنسبة لعميل [nuxt] الذي يعمل في متصفح، نستخدم الكود من فئة [Dao] في الوثيقة |مقدمة إلى إطار عمل VUE.JS من خلال أمثلة|:


"use strict";
 
// imports
import qs from "qs";
 
class Dao {
  // manufacturer
  constructor(axios) {
    this.axios = axios;
  }
 
  // init session
  async initSession() {
    // 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 this.getRemoteData(options);
  }
 
  async authentifierUtilisateur(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 this.getRemoteData(options);
  }
 
  async getAdminData() {
    // query options HHTP [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: "get-admindata"
      }
    };
    // execute query HTTP
    const data = await this.getRemoteData(options);
    // result
    return data;
  }
 
  async getRemoteData(options) {
    // execute query HTTP
    let response;
    try {
      // asynchronous request
      response = await this.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)
    // the server response is in [response.data]
    return response.data;
  }
}
 
// class export
export default Dao;

يختلف هذا الكود عن طبقة [dao] في خادم [nuxt] من حيث أنه لا يدير ملف تعريف ارتباط جلسة PHP مع خادم حساب الضرائب: فالمتصفح هو الذي يتولى ذلك.

سنقوم، كما فعلنا مع طبقة [dao] في خادم [nuxt]، بإضافة طريقة [finSession]:


// end of tax calculation session
  async finSession() {
    // query options HHTP [get /main.php?action=end-session]
    const options = {
      method: 'GET',
      // URL parameters
      params: {
        action: 'fin-session'
      }
    }
    // execute query HTTP
    const data = await this.getRemoteData(options)
    // result
    return data
  }

عندما ينفذ عميل [nuxt] هذه الطريقة، يتلقى، تمامًا مثل خادم [nuxt]، ملفين من ملفات تعريف الارتباط لجلسة عمل PHP. في الواقع، المتصفح هو الذي يتلقاهما، وهو يتعامل مع الموقف بشكل صحيح: فهو يحتفظ فقط بملف تعريف الارتباط الخاص بجلسة عمل PHP الجديدة التي بدأها خادم حساب الضرائب. لذا، في المرة التالية التي يرسل فيها عميل [nuxt] طلبًا إلى خادم PHP، سيكون ملف تعريف الارتباط لجلسة عمل PHP صحيحًا لأن المتصفح هو الذي يرسله. ومع ذلك، هناك مشكلة: خادم [nuxt] غير مدرك أن ملف تعريف ارتباط جلسة عمل PHP قد تغير. وفي اتصالاته مع خادم PHP، سيرسل ملف تعريف ارتباط جلسة عمل PHP لم يعد موجودًا، وهذا سيؤدي إلى حدوث مشكلات. يحتاج عميل [nuxt] إلى إخطار خادم [nuxt] بأن ملف تعريف ارتباط جلسة عمل PHP قد تغير وتمريره إلى الخادم. ونحن نعرف كيف يمكنه القيام بذلك: عبر ملف تعريف ارتباط جلسة عمل [nuxt]، وهو ملف تعريف الارتباط الذي يتم تبادله بين العميل وخادم [nuxt]. لدى عميل [nuxt] طريقتان على الأقل لاسترداد ملف تعريف ارتباط جلسة عمل PHP الجديد:

  1. عن طريق طلبه من المتصفح؛
  2. باستخدام طريقة [getRemoteData] الخاصة بالخادم، والتي تعرف كيفية استرداد ملف تعريف ارتباط جلسة عمل PHP الجديد؛

سنستخدم الحل الثاني لأنه جاهز بالفعل. تصبح طريقة [getRemoteData] الخاصة بعميل [nuxt] كما يلي:


async getRemoteData(options) {
    // execute query HTTP
    let response
    try {
      // asynchronous request
      response = await this.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)
    // look for session cookie PHP in received cookies
    // all cookies received
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // cookies is a picture
      // look for session cookie PHP in this array
      let trouvé = false
      let i = 0
      while (!trouvé && i < cookies.length) {
        // look for the PHP session cookie
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // we store the PHP session cookie
          const phpSessionCookie = results[1]
          // is the word [deleted] in it?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // we have the right session cookie PHP
            this.phpSessionCookie = phpSessionCookie
            // we found
            trouvé = true
          } else {
            // next item
            i++
          }
        } else {
          // next item
          i++
        }
      }
    }
    // the server response is in [response.data]
    return response.data
  }

احتفظنا فقط بالكود الموجود في [getRemoteData] الذي يعالج استجابة خادم PHP لاسترداد ملف تعريف ارتباط جلسة عمل PHP. لم نحتفظ بالكود الذي تضمن ملف تعريف ارتباط جلسة عمل PHP في الطلب الموجه إلى خادم PHP لأن المتصفح الذي يستضيف عميل [nuxt] يتولى ذلك.

بمجرد حصول عميل [nuxt] على ملف تعريف ارتباط جلسة عمل PHP، يجب وضعه في جلسة عمل [nuxt] حتى يتمكن خادم [nuxt] من استخدامه. لا تتولى طبقة [dao] معالجة ذلك، ولكنها توفر الوصول عبر طريقة إلى ملف تعريف ارتباط جلسة عمل PHP الذي قامت بتخزينه:


// accès au cookie de la session PHP
  getPhpSessionCookie() {
    return this.phpSessionCookie
}

لا تعرض الدالة [getPhpSessionCookie] دائمًا ملف تعريف ارتباط جلسة عمل صالحًا:

  • من المهم أن نتذكر هنا أن طبقة [dao] لعميل [nuxt] هي طبقة ثابتة. يتم إنشاء مثيل لها مرة واحدة ثم تبقى في الذاكرة؛
  • طالما أن خادم PHP لا يرسل ملف تعريف ارتباط جلسة عمل PHP إلى عميل [nuxt]، فإن دالة [getPhpSessionCookie] في عميل [nuxt] تُرجع قيمة [undefined
  • عندما يرسل خادم PHP ملف تعريف ارتباط جلسة عمل PHP إلى عميل [nuxt]، يتم تخزينه في [this.phpSessionCookie] وسيبقى هناك حتى يتم استبداله بملف تعريف ارتباط جلسة عمل PHP جديد مرسَل من خادم PHP. ثم تُرجع الدالة [getPhpSessionCookie] الخاصة بعميل [nuxt] آخر ملف تعريف ارتباط جلسة عمل PHP تم استلامه؛

تختلف طبقة [dao] لعميل [nuxt] عن طبقة خادم [nuxt] في جانب واحد فقط: فهي لا ترسل ملف تعريف ارتباط جلسة عمل PHP بنفسها، حيث يتولى المتصفح هذه المهمة. ومع ذلك، اخترنا الحفاظ على طبقتين [dao] منفصلتين لأن الأسباب الكامنة وراء تطبيقهما تختلف.

15.6. جلسة [nuxt]

Image

سيتم تغليف جلسة [nuxt] (بين العميل وخادم Nuxt) في كائن [session] التالي:


/* eslint-disable no-console */
// définition de la session
const session = {
  // contenu de la session
  value: {
    // store non initialisé
    initStoreDone: false,
    // valeur du store Vuex
    store: ''
  },
  // sauvegarde de la session dans un cookie
  save(context) {
    // sauvegarde du store en session
    this.value.store = context.store.state
    console.log('nuxt-session save=', this.value)
    // sauvegarde de la valeur de la session
    context.app.$cookies.set('nuxt-session', this.value, { path: context.base, maxAge: context.env.maxAge })
  },
  // reset de la session
  reset(context) {
    console.log('nuxt-session reset')
    // reset du store
    context.store.commit('reset')
    // sauvegarde du nouveau store en session et sauvegarde de la session
    this.save(context)
  }
}
// export de la session
export default session
  • الأسطر 5–10: تحتوي الجلسة على خاصية واحدة فقط [value] مع خاصيتين فرعيتين:
    • [initStoreDone]، والتي تشير إلى ما إذا كان المتجر قد تم تهيئته أم لا؛
    • [store]: قيمة [store.state] لمخزن Vuex الخاص بالتطبيق؛
  • الأسطر 12–18: تُستخدم طريقة [save] لحفظ جلسة [nuxt] في ملف تعريف ارتباط. هنا، نستخدم مكتبة [cookie-universal-nuxt] لإدارة ملف تعريف الارتباط. لاحظ اسم ملف تعريف ارتباط جلسة [nuxt]: [nuxt-session] (السطر 17)؛
  • الأسطر 20-26: تعيد طريقة [reset] تعيين جلسة [nuxt
    • السطر 23: يتم إعادة تعيين مخزن Vuex ثم حفظه في الجلسة في السطر 25؛

15.7. مكونات إضافية لإدارة جلسة [nuxt]

Image

15.7.1. ملحق إدارة جلسة [nuxt] لخادم [nuxt]

عند بدء تشغيل التطبيق، يتم تشغيل خادم [nuxt] أولاً. وبالتالي فهو مسؤول عن تهيئة جلسة [nuxt]. النص البرمجي [server/plgSession] هو كما يلي:


/* eslint-disable no-console */
 
// import de la session
import session from '@/entities/session'
 
export default (context, inject) => {
  // gestion de la session serveur
  console.log('[plugin server plgSession]')
 
  // y-a-t-il une session existante ?
  const value = context.app.$cookies.get('nuxt-session')
  if (!value) {
    // nouvelle session
    console.log("[plugin server plgSession], démarrage d'une nouvelle session")
  } else {
    // session existante
    console.log("[plugin server plgSession], reprise d'une session existante")
    session.value = value
  }
 
  // on injecte une fonction dans [context, Vue] qui rendra la session courante
  inject('session', () => session)
}
  • السطر 4: استيراد كود جلسة [nuxt
  • السطر 11: استرداد قيمة ملف تعريف ارتباط جلسة [nuxt
  • الأسطر 12–15: إذا لم يكن ملف تعريف ارتباط جلسة [nuxt] موجودًا، فإن جلسة [nuxt] المستوردة في السطر 4 كافية. لا يوجد شيء آخر للقيام به؛
  • الأسطر 15–19: إذا كان ملف تعريف ارتباط جلسة [nuxt] موجودًا، فإننا نقوم في السطر 18 بتخزين قيمته في الجلسة المستوردة في السطر 4؛
  • السطر 22: تم تهيئة الجلسة أو استعادتها. نجعلها متاحة عبر الدالة [$session

15.7.2. المكوّن الإضافي لإدارة جلسة [nuxt] للعميل [nuxt]

النص البرمجي [client/plgSession] هو كما يلي:


/* eslint-disable no-console */
 
// import de la session
import session from '@/entities/session'
 
export default (context, inject) => {
  // gestion de la session client
  console.log('[plugin client plgSession], reprise de la session [nuxt] du serveur')
  // on récupère la session existante du serveur nuxt
  session.value = context.app.$cookies.get('nuxt-session')
 
  // on injecte une fonction dans [context, Vue] qui rendra la session courante
  inject('session', () => session)
}
  • السطر 4: يتم استيراد جلسة [nuxt
  • السطر 10: نسترد جلسة [nuxt] الحالية من ملف تعريف الارتباط [nuxt-session
  • السطر 13: نُرجع جلسة [nuxt] المستوردة في السطر 4 عبر الدالة المُدرجة [$session

15.8. المكونات الإضافية لطبقات [dao]

Image

15.8.1. ملحق طبقة [dao] لعميل [nuxt]

النص البرمجي [client/plgDao] هو كما يلي:


/* eslint-disable no-console */
// on crée un point d'accès à la couche [Dao]
import Dao from '@/api/client/Dao'
export default (context, inject) => {
  // configuration axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  context.$axios.defaults.withCredentials = context.env.withCredentials
  // instanciation de la couche [dao]
  const dao = new Dao(context.$axios)
  // injection d'une fonction [$dao] dans le contexte
  inject('dao', () => dao)
  // log
  console.log('[fonction client $dao créée]')
}
  • السطر 3: يتم استيراد طبقة [dao] لعميل [nuxt
  • الأسطر 6-8: نقوم بتكوين كائن [context.$axios]، الذي سيقوم بإجراء طلبات HTTP لطبقة [dao] لعميل [nuxt] باستخدام المعلومات الموجودة في ملف [nuxt.config]:

// environnement
  env: {
    // configuration axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // configuration du cookie de session [nuxt]
    maxAge: 60 * 5
  }
  • السطر 10: يتم إنشاء مثيل لطبقة [dao] الخاصة بعميل [nuxt
  • السطر 12: يتم إدخال الدالة [$dao] في سياق العميل وصفحاته. توفر هذه الدالة الوصول إلى طبقة [dao] من السطر 10؛

لذا، للوصول إلى طبقة [dao] لعميل [nuxt] أثناء تشغيله، نكتب:

  • [context.app.$dao()] حيث يكون السياق معروفًا؛
  • [this.$dao()] في صفحة [Vue.js

15.8.2. المكوّن الإضافي لطبقة [dao] لخادم [nuxt]

النص البرمجي [server/plgDao] هو كما يلي:


/* eslint-disable no-console */
// on crée un point d'accès à la couche [Dao]
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // configuration axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // on récupère le cookie de session
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // instanciation de la couche [dao]
  const dao = new Dao(context.$axios, phpSessionCookie)
  // injection d'une fonction [$dao] dans le contexte
  inject('dao', () => dao)
  // log
  console.log('[fonction server $dao créée]')
}
  • السطر 3: يتم استيراد طبقة [dao] من خادم [nuxt
  • السطران 6-7: يتم تكوين الكائن [context.$axios] لإجراء طلبات HTTP لطبقة [dao] لخادم [nuxt] باستخدام المعلومات الموجودة في ملف [nuxt.config]:

// environnement
  env: {
    // configuration axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // configuration du cookie de session [nuxt]
    maxAge: 60 * 5
  }
  • السطر 9: استرداد مخزن التطبيق [nuxt
  • السطر 10: إذا كان المخزن موجودًا، فإننا نسترد ملف تعريف ارتباط جلسة عمل PHP لأننا نحتاجه لإنشاء مثيل لطبقة [dao] لخادم [nuxt
  • السطر 13: إنشاء مثيل لطبقة [dao] لخادم [nuxt
  • السطر 15: يتم إدخال الدالة [$dao] في سياق وصفحات خادم [nuxt]. توفر هذه الدالة الوصول إلى طبقة [dao] من السطر 13؛

لذا، للوصول إلى طبقة [dao] لخادم [nuxt] أثناء تشغيله، نكتب:

  • [context.app.$dao()] حيث يكون السياق معروفًا؛
  • [this.$dao()] في صفحة [Vue.js

15.9. مخزن Vuex

Image

سيخزن مخزن [Vuex] جميع البيانات التي يجب مشاركتها بين المكونات المختلفة للتطبيق [الصفحات، العميل، الخادم] دون أن تكون هذه البيانات تفاعلية.


/* eslint-disable no-console */
 
// awning status
export const state = () => ({
  // session jSON started
  jsonSessionStarted: false,
  // authenticated user
  userAuthenticated: false,
  // session cookie PHP
  phpSessionCookie: '',
  // adminData
  adminData: ''
})
 
// changes in the awning
export const mutations = {
  // state replacement
  replace(state, newState) {
    for (const attr in newState) {
      state[attr] = newState[attr]
    }
  },
  // awning reset
  reset() {
    this.commit('replace', { jsonSessionStarted: false, userAuthenticated: false, phpSessionCookie: '', adminData: '' })
  }
}
 
// awning actions
export const actions = {
  nuxtServerInit(store, context) {
    // who executes this code?
    console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
    // init session
    initStore(store, context)
  }
}
 
function initStore(store, context) {
  // store is the blind to be initialized
  // retrieve the session
  const session = context.app.$session()
  // has the session already been initiated?
  if (!session.value.initStoreDone) {
    // start a new blind
    console.log("nuxtServerInit, initialisation d'une nouvelle session")
    // put the blind in the session
    session.value.store = store.state
    // the blind is now initialized
    session.value.initStoreDone = true
  } else {
    console.log("nuxtServerInit, reprise d'un store existant")
    // update the store with the session store
    store.commit('replace', session.value.store)
  }
  // save the session
  session.save(context)
  // log
  console.log('initStore terminé, store=', store.state)
}

البيانات المخزنة في المخزن هي كما يلي:

  • السطر 6: سيتم تعيين [jsonSessionStarted] على "true" فور إتمام تهيئة جلسة JSON مع خادم PHP بنجاح، سواء تمت المبادرة بها من جانب العميل أو خادم [nuxt]. وعند الانتهاء من عملية التهيئة هذه، سيتم استرداد ملف تعريف الارتباط الخاص بالجلسة لخادم PHP وتخزينه في الخاصية [phpSessionCookie]، السطر 10؛
  • السطر 8: سيتم تعيين [userAuthenticated] على true بمجرد نجاح المصادقة مع خادم PHP، سواء تمت بواسطة العميل أو خادم [nuxt
  • السطر 12: ستكون [adminData] هي قيمة [adminData] التي تم الحصول عليها من خادم PHP بمجرد نجاح المصادقة؛
  • الأسطر 18-22: تعمل عملية [replace] على تهيئة الخصائص السابقة بخصائص كائن تم تمريره كمعلمة؛
  • الأسطر 24–26: تعيد عملية [reset] خصائص المخزن إلى قيمها الأولية؛
  • الأسطر 31–37: تفوض الدالة [nuxtServerInit] عملها إلى الدالة [initStore
  • الأسطر 39–60: وظيفة [initStore] لها دوران:
    • إذا لم يتم تهيئة المخزن، يتم تهيئته وإضافته إلى الجلسة؛
    • إذا كان المخزن قد تم تهيئته بالفعل، يتم استرداد قيمته من جلسة [nuxt
  • السطر 42: يتم استرداد جلسة nuxt؛
  • السطر 44: نتحقق مما إذا كان المخزن قد تم تهيئته:
    • إذا لم يكن كذلك، يتم وضع المخزن الأولي في الجلسة (السطر 48)؛
    • ثم، في السطر 50، نشير إلى أن المخزن قد تم تهيئته؛
  • الأسطر 51-55: إذا تم تهيئة المخزن، فإننا نستخدمه، في السطر 54، لتهيئة المخزن بالقيمة الموجودة في الجلسة؛
  • السطر 57: في جميع الحالات، يتم حفظ الجلسة في ملف تعريف الارتباط [nuxt-session]، جنبًا إلى جنب مع المخزن الذي تحتوي عليه؛

15.10. المكوّن الإضافي [plgEventBus]

Image

يهدف هذا المكون الإضافي إلى إتاحة ناقل الأحداث لعميل [nuxt] عبر دالة [$eventBus] يتم إدخالها في سياق عميل [nuxt]. لا توجد حاجة لإدخالها في سياق خادم [nuxt] لأن الخادم لا يمكنه معالجة الأحداث. ومع ذلك، فقد رأينا بالفعل أن إدخالها على جانب الخادم ثم استخدامها لا يتسبب في حدوث خطأ.


/* eslint-disable no-console */
// on crée un bus d'événements entre les vues
import Vue from 'vue'
export default (context, inject) => {
  // le bus d'événements
  const eventBus = new Vue()
  // injection d'une fonction [$eventBus] dans le contexte
  inject('eventBus', () => eventBus)
  // log
  console.log('[fonction $eventBus créée]')
}

لقد سبق أن تناولنا هذا المكون الإضافي في قسم الروابط. ستكون الدالة [$eventBus] متاحة للعميل عبر الترميز التالي:

  • [context.app.$eventBus()] حيثما كان السياق متاحًا؛
  • [this.$eventBus()] في صفحات [Vue.js] الخاصة بالعميل؛

15.11. مكونات تطبيق [Nuxt]

Image

مكون [layout] هو المكون الموجود في الأمثلة السابقة:


<!-- view layout -->
<template>
  <!-- line -->
  <div>
    <b-row>
      <!-- three-column zone -->
      <b-col v-if="left" cols="3">
        <slot name="left" />
      </b-col>
      <!-- nine-column zone -->
      <b-col v-if="right" cols="9">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>
 
<script>
export default {
  // paramètres
  props: {
    left: {
      type: Boolean
    },
    right: {
      type: Boolean
    }
  }
}
</script>

مكون [navigation] هو كما يلي:


<template>
  <!-- bootstrap menu with three options -->
  <b-nav vertical>
    <b-nav-item to="/authentification" exact exact-active-class="active">
      Authentification
    </b-nav-item>
    <b-nav-item to="/get-admindata" exact exact-active-class="active">
      Requête AdminData
    </b-nav-item>
    <b-nav-item to="/fin-session" exact exact-active-class="active">
      Fin session impôt
    </b-nav-item>
  </b-nav>
</template>

15.12. تخطيطات تطبيق [nuxt]

Image

15.12.1. [default]

تخطيط [default] هو التخطيط المستخدم في مثال [nuxt-11] في الفقرة المرتبطة:


<template>
  <div class="container">
    <b-card>
      <!-- un message -->
      <b-alert show variant="success" align="center">
        <h4>[nuxt-12] : requêtes HTTP avec axios</h4>
      </b-alert>
      <!-- la vue courante du routage -->
      <nuxt />
      <!-- message d’attente -->
      <b-alert v-if="showLoading" show variant="light">
        <strong>Requête au serveur de données en cours...</strong>
        <div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
      </b-alert>
      <!-- erreur d’une opération asynchrone -->
      <b-alert v-if="showErrorLoading" show variant="danger">
        <strong>La requête au serveur de données a échoué : {{ errorLoadingMessage }}</strong>
      </b-alert>
    </b-card>
  </div>
</template>
 
<script>
/* eslint-disable no-console */
export default {
  name: 'App',
  data() {
    return {
      showLoading: false,
      showErrorLoading: false
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[default beforeCreate]')
  },
  created() {
    console.log('[default created]')
    if (process.client) {
      // listen to the evt [loading]
      this.$eventBus().$on('loading', this.mShowLoading)
      // and event [errorLoadingMessage]
      this.$eventBus().$on('errorLoading', this.mShowErrorLoading)
    }
  },
  beforeMount() {
    console.log('[default beforeMount]')
  },
  mounted() {
    console.log('[default mounted]')
  },
  methods: {
    // message waiting management
    mShowLoading(value) {
      console.log('[default mShowLoading], showLoading=', value)
      this.showLoading = value
    },
    // asynchronous operation error
    mShowErrorLoading(value, errorLoadingMessage) {
      console.log('[default mShowErrorLoading], showErrorLoading=', value, 'errorLoadingMessage=', errorLoadingMessage)
      this.showErrorLoading = value
      this.errorLoadingMessage = errorLoadingMessage
    }
  }
}
</script>
  • الأسطر 10–14: عرض رسالة تشير إلى أن العميل [nuxt] ينتظر اكتمال عملية غير متزامنة؛
  • الأسطر 15–18: عرض أي رسالة خطأ ناتجة عن عملية غير متزامنة؛
  • السطر 37: يتم تنفيذ الدالة [created] للصفحة [default] قبل الدالة [mounted] للصفحات؛
  • السطر 39: إذا كان المنفذ هو عميل [nuxt]، فإن الصفحة [default] تستمع إلى
    • [loading]، التي تشير إلى بداية أو نهاية فترة الانتظار. ثم يتم تنفيذ الدالة [mShowLoading
    • [errorLoading]، والتي تشير إلى ضرورة عرض رسالة خطأ. ثم يتم تنفيذ الدالة [mShowErrorLoading
  • صفحات [nuxt]:
    • تعرض رسالة التحميل عن طريق إصدار الحدث [‘loading’, true] على ناقل الأحداث؛
    • إخفاء رسالة التحميل عن طريق إرسال الحدث [‘loading’, false] على ناقل الأحداث؛
    • عرض رسالة خطأ عن طريق إرسال الحدث [‘errorLoading’, true] على ناقل الأحداث؛
    • إخفاء رسالة الخطأ عن طريق إصدار الحدث [‘errorLoading’, false] على ناقل الأحداث؛

15.12.2. [error]

يعرض تخطيط [error] رسالة خطأ في النظام (لا يديرها المطور):


<!-- définition HTML de la vue -->
<template>
  <!-- mise en page -->
  <Layout :left="true" :right="true">
    <!-- alerte dans la colonne de droite -->
    <template slot="right">
      <!-- message sur fond rose -->
      <b-alert show variant="danger" align="center">
        <h4>L'erreur suivante s'est produite : {{ JSON.stringify(error) }}</h4>
      </b-alert>
    </template>
    <!-- menu de navigation dans la colonne de gauche -->
    <Navigation slot="left" />
  </Layout>
</template>
 
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */
 
import Layout from '@/components/layout'
import Navigation from '@/components/navigation'
 
export default {
  name: 'Error',
  // components used
  components: {
    Layout,
    Navigation
  },
  // property [props]
  props: { error: { type: Object, default: () => 'waiting ...' } },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[error beforeCreate]')
  },
  created() {
    // client and server
    console.log('[error created, error=]', this.error)
  },
  beforeMount() {
    // customer only
    console.log('[error beforeMount]')
  },
  mounted() {
    // customer only
    console.log('[error mounted]')
  }
}
</script>

15.13. الصفحة [index] التي يقدمها خادم [nuxt]

Image

تتميز صفحة [index.vue] بأنها لا يمكن الوصول إليها إلا عبر خادم [nuxt]. ولا يتوفر للمستخدم أي رابط للوصول إليها عبر عميل [nuxt]. وفيما يلي شفرة البرمجة الخاصة بها:


<!-- page principale -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Initialisation de la session avec le serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'InitSession',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[index asyncData started]')
    try {
      // start a jSON session
      const dao = context.app.$dao()
      const response = await dao.initSession()
      // log
      console.log('[index asyncData response=]', response)
      // retrieve session cookie PHP for future requests
      const phpSessionCookie = dao.getPhpSessionCookie()
      // we store the PHP session cookie in the [nuxt] session
      context.store.commit('replace', { phpSessionCookie })
      // was there a mistake?
      if (response.état !== 700) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // note that the jSON session has started
      context.store.commit('replace', { jsonSessionStarted: true })
      // we return the result
      return { result: '[succès]' }
    } catch (e) {
      // log
      console.log('[index asyncData error=]', e)
      // note that session jSON has not started
      context.store.commit('replace', { jsonSessionStarted: false })
      // we report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // save the blind
      const session = context.app.$session()
      session.save(context)
      // log
      console.log('[index asyncData finished]')
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[index beforeCreate]')
  },
  created() {
    console.log('[index created]')
  },
  beforeMount() {
    console.log('[index beforeMount]')
  },
  mounted() {
    console.log('[index mounted]')
    // customer only
    if (this.showErrorLoading) {
      console.log('[index mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>
  • السطر 7: تعرض الصفحة نتيجة [result] لطلب غير متزامن (السطران 46 و51)؛
  • السطر 31: العملية غير المتزامنة هي فتح جلسة JSON مع خادم حساب الضرائب؛
  • السطر 25: نعلم أنه عند طلب الصفحة مباشرة من خادم [nuxt]، يتم تنفيذ دالة [asyncData] بواسطة الخادم فقط وليس بواسطة عميل [nuxt]، الذي يعمل بمجرد استلام المتصفح للاستجابة من خادم [nuxt
  • السطر 30: نسترد طبقة [dao] من سياق خادم [nuxt
  • السطر 35: إذا لم يكن الخادم قد أرسل طلبًا إلى خادم حساب الضرائب بعد، فإنه يتلقى ملف تعريف ارتباط جلسة PHP الأول؛ وإلا، فإنه يتلقى آخر ملف تعريف ارتباط جلسة PHP تلقّاه (انظر كود طبقة [dao] لخادم [nuxt] في القسم المرتبط
  • السطر 37: يتم تخزين ملف تعريف ارتباط جلسة PHP هذا في المخزن؛
  • الأسطر 39-42: نتحقق مما إذا كانت العملية ناجحة. إذا لم تكن كذلك، يتم إصدار استثناء، والذي سيتم التقاطه بواسطة [catch] في السطر 47؛
  • السطر 44: نسجل في المخزن أن جلسة JSON مع خادم PHP قد بدأت؛
  • السطر 46: يتم إرجاع النتيجة [result] وعرضها في السطر 7؛
  • الأسطر 47-54: معالجة أي استثناءات. يمكن أن تكون هذه الاستثناءات من نوعين:
    • فشلت عملية HTTP في السطر 31 بسبب خطأ في الاتصال بين خادم [nuxt] وخادم PHP؛
    • نجحت عملية HTTP في السطر 31 ولكن النتيجة المستلمة أبلغت عن خطأ (السطور 39-42)؛
  • السطر 51: نلاحظ أن جلسة JSON مع خادم PHP لم تبدأ؛
  • السطر 53: يتم إرجاع النتيجة [result] وعرضها في السطر 7. بالإضافة إلى ذلك، يتم تعيين الخصائص [showErrorLoading] و [errorLoadingMessage]، والتي سيستخدمها عميل [nuxt] لعرض رسالة خطأ عند استلامه للصفحة المرسلة من خادم [nuxt] (الأسطر 72–79)؛
  • الأسطر 54–60: يتم تنفيذ الكود في جميع الحالات (النجاح أو الفشل)؛
  • السطر 56: نسترد جلسة [nuxt] من سياق خادم [nuxt
  • السطر 57: نقوم بحفظها؛
  • الأسطر 63–68: بمجرد انتهاء وظيفة [asyncData]، يقوم خادم [nuxt] بتنفيذ وظيفتي [beforeCreate] و [create

ملاحظة: قد يفشل تنفيذ خادم [nuxt] لصفحة [index]، على سبيل المثال، إذا لم يكن خادم حساب الضرائب قيد التشغيل عند تشغيل تطبيق [nuxt]:

Image

في هذه الحالة، الحل الوحيد هو بدء تشغيل خادم حساب الضرائب ثم تطبيق [nuxt] نفسه، نظرًا لأن قائمة التنقل لا توفر خيارًا لبدء جلسة JSON مع خادم حساب الضرائب؛

15.14. الصفحة [index] التي ينفذها عميل [nuxt]

لا يتم تنفيذ صفحة [index] من قِبل عميل [nuxt] إلا بعد أن يرسلها خادم [nuxt] إلى العميل. وقد أرسل الخادم معلومات [result]، وكذلك [showErrorLoading] و[errorLoadingMessage] إن وجدتا.

نعلم أن دالة [asyncData] لن يتم تنفيذها. وهذا يترك دورات الحياة، ولا سيما دالة [mounted]:


mounted() {
    console.log('[index mounted]')
    // client seulement
    if (this.showErrorLoading) {
      console.log('[index mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
}
  • يقوم عميل [nuxt] تلقائيًا بتضمين عنصر [result]، وإذا كان ذلك ممكنًا، عناصر [showErrorLoading] و[errorLoadingMessage] التي أرسلها إليه خادم [nuxt] في خصائص الصفحة:
  • يتم عرض خاصية [result] في السطر 7؛
  • يتم استخدام الخصائص [showErrorLoading، errorLoadingMessage] بواسطة الطريقة [mounted]: في السطر 4، يتم التحقق من الخاصية [showErrorLoading]. إذا كانت صحيحة، في السطر 6، يتم استخدام ناقل أحداث [nuxt] الخاص بالعميل للإشارة إلى وجود رسالة خطأ لعرضها؛
  • يتم اعتراض الحدث [errorLoading] الذي تم تشغيله في السطر 6 بواسطة الصفحة [layouts/default] الموضحة في القسم المرتبط؛

15.15. صفحة [authentication] التي يتم تنفيذها بواسطة خادم [nuxt]

تتولى صفحة [authentication] مسؤولية مصادقة المستخدم مع خادم حساب الضرائب. وفيما يلي كودها:


<!-- page d’authentification -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Authentification auprès du serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'Authentification',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[authentification asyncData started]')
    if (process.client) {
      // start waiting for customer [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // authenticate to the server
      const dao = context.app.$dao()
      const response = await dao.authentifierUtilisateur('admin', 'admin')
      // log
      console.log('[authentification asyncData response=]', response)
      // result
      const userAuthenticated = response.état === 200
      // we note whether the user is authenticated or not
      context.store.commit('replace', { userAuthenticated })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // authentication error?
      if (!userAuthenticated) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // we return the result
      return { result: '[succès]' }
    } catch (e) {
      // we report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[authentification asyncData finished]')
      if (process.client) {
        // end customer waiting [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[authentification beforeCreate]')
  },
  created() {
    console.log('[authentification created]')
  },
  beforeMount() {
    console.log('[authentification beforeMount]')
  },
  mounted() {
    console.log('[authentification mounted]')
    // customer only
    if (this.showErrorLoading) {
      console.log('[authentification mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>
  • السطر 7: تعرض الصفحة نتيجة [result] الطلب غير المتزامن [asyncData] من الأسطر 25–65؛
  • الأسطر 28–33: لا يقوم الخادم بتنفيذ هذه الأسطر المخصصة للعميل [nuxt
  • السطر 36: يتم استرداد طبقة [dao] من خادم [nuxt
  • السطر 37: نقوم بالمصادقة مع خادم حساب الضرائب باستخدام بيانات اعتماد الاختبار [admin, admin]، وهي البيانات الوحيدة التي يقبلها خادم حساب الضرائب؛
  • السطر 41: لا تنجح عملية المصادقة إلا إذا كانت حالة الاستجابة 200؛
  • السطر 43: نقوم بتعيين الخاصية [userAuthenticated] في المخزن؛
  • الأسطر 44-46: يتم حفظ المخزن في جلسة [nuxt
  • الأسطر 48-51: إذا فشلت المصادقة، يتم إلقاء استثناء مع رسالة الخطأ المرسلة من خادم حساب الضرائب؛
  • وإلا، في السطر 53، يتم إرجاع نتيجة ناجحة سيتم عرضها في السطر 7؛
  • الأسطر 54-57: في حالة حدوث خطأ، يتم تعيين ثلاث خصائص للصفحة [result، showErrorLoading، errorLoadingMessage]. سيتم عرض الخاصية [result] في السطر 7. سيتم إرسال الخصائص الثلاث إلى العميل [nuxt
  • الأسطر 60-63: لا يتم تنفيذها بواسطة خادم [nuxt
  • بمجرد أن تعيد [asyncData] نتيجتها، يتم عرضها في السطر 7. ثم يتم تنفيذ طريقتي [beforeCreate] (السطور 67–69) و[created] (السطور 70–72)؛
  • هذا كل شيء؛

ملاحظة: قد تفشل صفحة [authentification] في التنفيذ على خادم [nuxt]، على سبيل المثال، إذا لم يتم تهيئة جلسة JSON مع خادم حساب الضرائب. يمكن القيام بذلك على النحو التالي:

  • احذف ملف تعريف ارتباط جلسة عمل PHP من متصفحك (للبدء من الصفر):

Image

  • قم بتشغيل تطبيق [nuxt] بينما لم يتم تشغيل خادم الحساب: ستظهر لك رسالة خطأ؛
  • قم بتشغيل خادم حساب الضرائب؛
  • أدخل عنوان URL [/authentication] مباشرةً في شريط عنوان المتصفح:

Image

في هذه الحالة، الحل الوحيد هو إعادة تحميل صفحة [index] مرة أخرى.

15.16. صفحة [authentication] التي يتم تنفيذها بواسطة عميل [nuxt]

دعونا نلقي نظرة على كود الصفحة مرة أخرى:


<!-- page d’authentification -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Authentification auprès du serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'Authentification',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[authentification asyncData started]')
    if (process.client) {
      // start waiting for customer [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // authenticate to the server
      const dao = context.app.$dao()
      const response = await dao.authentifierUtilisateur('admin', 'admin')
      // log
      console.log('[authentification asyncData response=]', response)
      // result
      const userAuthenticated = response.état === 200
      // we note whether the user is authenticated or not
      context.store.commit('replace', { userAuthenticated })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // authentication error?
      if (!userAuthenticated) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // we return the result
      return { result: '[succès]' }
    } catch (e) {
      // we report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[authentification asyncData finished]')
      if (process.client) {
        // end customer waiting [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[authentification beforeCreate]')
  },
  created() {
    console.log('[authentification created]')
  },
  beforeMount() {
    console.log('[authentification beforeMount]')
  },
  mounted() {
    console.log('[authentification mounted]')
    // customer only
    if (this.showErrorLoading) {
      console.log('[authentification mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

هناك حالتان يتم فيهما تنفيذ صفحة [المصادقة] بواسطة عميل [nuxt]:

  1. يتم تشغيل عميل [nuxt] بعد أن يرسل خادم [nuxt] صفحة [المصادقة] إلى متصفح عميل [nuxt
  2. عميل [nuxt] لأن المستخدم نقر على رابط [Authentication] في قائمة التنقل:

Image

دعونا أولاً ندرس السيناريو الأول. في هذه الحالة، لا يقوم عميل [nuxt] بتنفيذ دالة [asyncData]. بل يدمج عنصر [result]، وعند الاقتضاء، عناصر [showErrorLoading] و[errorLoadingMessage] التي أرسلها إليه خادم [nuxt] في خصائص الصفحة:

  • يتم عرض خاصية [result] في السطر 7؛
  • يتم استخدام خصائص [showErrorLoading، errorLoadingMessage] بواسطة طريقة [mounted]: في السطر 79، يتم التحقق من خاصية [showErrorLoading]. إذا كانت صحيحة، في السطر 81، يتم استخدام ناقل أحداث عميل [nuxt] للإشارة إلى وجود رسالة خطأ لعرضها؛

تم شرح آلية عرض رسالة الخطأ لصفحة [index] في قسم الروابط.

الحالة 2 هي عندما يعمل عميل [nuxt] بعد أن ينقر المستخدم على رابط [Authentication]. في هذه الحالة، يعمل عميل [nuxt] بشكل مستقل وليس بعد خادم [nuxt]. ثم يتم تنفيذ الدالة [asyncData]. نقدم فقط التفاصيل التي تختلف عن التفسيرات المقدمة للصفحة التي ينفذها خادم [nuxt]:

  • الأسطر 28-33: يطلب عميل [nuxt] عرض رسالة التحميل ومسح أي رسالة خطأ تم عرضها مسبقًا؛
  • السطر 36: يتم الآن استرداد طبقة [dao] لعميل [nuxt] هنا؛
  • الأسطر 60-63: يطلب عميل [nuxt] إزالة رسالة التحميل؛
  • بمجرد انتهاء [asyncData]، ستستمر دورة حياة الصفحة. سيتم تنفيذ الدالة [mounted] في الأسطر 76–83. إذا حدث خطأ، فسيتم عرض رسالة الخطأ؛

ملاحظة: لإحداث خطأ، اتبع الإجراء الموضح لخادم [nuxt] في نهاية قسم الروابط، ولكن بدلاً من طلب صفحة [authentication] عن طريق كتابة عنوان URL الخاص بها في شريط العناوين، استخدم رابط [Authentication] في قائمة التنقل. سيؤدي هذا إلى تشغيل عميل [nuxt].

15.17. صفحة [get-admindata]

فيما يلي كود صفحة [get-admindata]:


<!-- vue get-admindata -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <b-alert slot="right" show variant="secondary"> Demande de [adminData] au serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'GetAdmindata',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[get-admindata asyncData started]')
    if (process.client) {
      // start waiting
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // the data [admindata] is requested
      const response = await context.app.$dao().getAdminData()
      // log
      console.log('[get-admindata asyncData response=]', response)
      // result
      const adminData = response.état === 1000 ? response.réponse : ''
      // put the data in the store
      context.store.commit('replace', { adminData })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // was there a mistake?
      if (!adminData) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // return the value received
      return { result: adminData }
    } catch (e) {
      // we report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[get-admindata asyncData finished]')
      if (process.client) {
        // end waiting
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[get-admindata beforeCreate]')
  },
  created() {
    console.log('[get-admindata created]')
  },
  beforeMount() {
    console.log('[get-admindata beforeMount]')
  },
  mounted() {
    console.log('[get-admindata mounted]')
    // customer
    if (this.showErrorLoading) {
      console.log('[get-admindata mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

هذه الصفحة تشبه إلى حد كبير صفحة [authentication]. التفسيرات هي نفسها سواء تم تنفيذها بواسطة خادم [nuxt] أو بواسطة عميل [nuxt]. لاحظ، مع ذلك، أن السطر 7 لا يعرض النجاح/الفشل كما في السابق، بل يعرض قيمة البيانات المستلمة من خادم حساب الضرائب (السطر 52):

Image

يتم الحصول على النتيجة أعلاه باستخدام كل من الخادم وعميل [nuxt]. لإحداث خطأ، اطلب صفحة [get-admindata] — إما عبر الخادم أو عميل [nuxt] — دون المصادقة:

Image

15.18. صفحة [fin-session]

فيما يلي كود الصفحة:


<!-- page principale -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Fin de la session avec le serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'FinSession',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[fin-session asyncData started]')
    // customer case [nuxt]
    if (process.client) {
      // start waiting
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // a new session PHP is requested from the tax calculation server
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // log
      console.log('[fin-session asyncData response=]', response)
      // was there a mistake?
      if (response.état !== 400) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // the server has sent a new session cookie PHP
      // we retrieve it for both the server and the nuxt client
      // if this code is executed by the [nuxt] client, the PHP session cookie must be set in the nuxt session
      // so that the [plgDao] plugin on the [nuxt] server can retrieve it and initialize the [dao] layer with
      // if this code is executed by the [nuxt] server, the PHP session cookie must be set in the nuxt session
      // so that the client routing [nuxt] can retrieve it and pass it to the browser
      const phpSessionCookie = dao.getPhpSessionCookie()
      // we note in the store that the session jSON has been started and we store the session cookie PHP
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // we return the result
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    } catch (e) {
      // log
      console.log('[fin-session asyncData error=]', e)
      // report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[fin-session asyncData finished]')
      if (process.client) {
        // end waiting
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[fin-session beforeCreate]')
  },
  created() {
    console.log('[fin-session created]')
  },
  beforeMount() {
    console.log('[fin-session beforeMount]')
  },
  mounted() {
    console.log('[fin-session mounted]')
    // customer only
    if (this.showErrorLoading) {
      console.log('[fin-session mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

يشبه هذا الكود إلى حد كبير الكود الموجود في الصفحات السابقة، والتفسيرات هي نفسها. هناك نقطة واحدة فقط يجب ملاحظتها: تؤدي العملية غير المتزامنة في السطر 38 إلى قيام خادم حساب الضرائب بإرسال ملف تعريف ارتباط جلسة عمل PHP جديد. تختلف تعليمات التعامل مع ملف تعريف الارتباط هذا اعتمادًا على ما إذا كان الخادم أو عميل [nuxt] هو الذي ينفذ هذا الكود.

لنبدأ بخادم [nuxt]:

  • السطر 37: هذه هي طبقة [dao] لخادم [nuxt] التي يتم إنشاء مثيل لها. دعونا نستذكر كود منشئها:

// constructeur
  constructor(axios, phpSessionCookie) {
    // bibliothèque axios
    this.axios = axios
    // valeur du cookie de session
    this.phpSessionCookie = phpSessionCookie
    // nom du cookie de session du serveur PHP
    this.phpSessionCookieName = 'PHPSESSID'
}

في السطر 1، نرى أن المنشئ يحتاج إلى ملف تعريف ارتباط جلسة PHP الحالي — آخر ملف تم استلامه، سواء من الخادم أو من عميل [nuxt

  • السطر 52: يسترد خادم [nuxt] ملف تعريف الارتباط الخاص بجلسة PHP الجديدة أو ملف تعريف الارتباط القديم إذا فشلت عملية إنهاء الجلسة؛
  • السطر 54: يتم وضع ملف تعريف ارتباط جلسة عمل PHP في المخزن ثم يتم حفظه في جلسة عمل [nuxt] في السطرين 56–57؛
  • بعد الخادم، يقوم عميل [nuxt] بتنفيذ صفحة [end-session] بالبيانات المرسلة من الخادم. ونعلم أنه لن يقوم بتنفيذ دالة [asyncData
  • في النهاية، بعد أن ينتهي الخادم والعميل [nuxt] من عملهما، نعلم أن ملف تعريف الارتباط PHP المطلوب للتواصل مع خادم حساب الضرائب موجود في جلسة [nuxt

حقيقة أن ملف تعريف الارتباط PHP موجود في جلسة [nuxt] كافية للخادم، لأن ذلك هو المكان الذي ستسترده منه طبقة [dao] الخاصة به. في المكون الإضافي [server/plgDao] الذي يقوم بتهيئة طبقة [dao] الخاصة بالخادم، كتبنا:


/* eslint-disable no-console */
// on crée un point d'accès à la couche [Dao]
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // configuration axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // on récupère le cookie de session
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // instanciation de la couche [dao]
  const dao = new Dao(context.$axios, phpSessionCookie)
  // injection d'une fonction [$dao] dans le contexte
  inject('dao', () => dao)
  // log
  console.log('[fonction server $dao créée]')
}
  • السطر 13: يتم إنشاء مثيل لطبقة [dao] لخادم [nuxt] باستخدام ملف تعريف ارتباط جلسة العمل PHP المأخوذ من جلسة عمل [nuxt]، السطور 9–10؛

أما بالنسبة لعميل [nuxt]، فالأمر يختلف. فليس العميل هو الذي يرسل ملف تعريف الارتباط، بل المتصفح هو الذي ينفذه. ومع ذلك، فإن هذا المتصفح لا يعرف ملف تعريف الارتباط الخاص بجلسة PHP الجديدة التي استلمها خادم [nuxt]. إذا استخدمنا الروابط الموجودة في قائمة التنقل [3]:

Image

سيتلقى خادم حساب الضرائب ملف تعريف ارتباط جلسة عمل PHP قديمًا من المتصفح وسيرد بأنه لا توجد جلسة عمل JSON مرتبطة بملف تعريف الارتباط هذا. نحتاج إلى إيجاد طريقة لتمرير ملف تعريف ارتباط جلسة عمل PHP الجديد إلى المتصفح.

يمكننا استخدام برمجيات وسيطة للتوجيه للقيام بذلك:

Image

البرنامج النصي [client/routing] هو برنامج الوسيط للتوجيه المُعلن في ملف [nuxt.config]:


// router
  router: {
    // application URL root
    base: '/nuxt-12/',
    // routing middleware
    middleware: ['routing']
},

النص البرمجي [middleware/routing] هو كما يلي:


/* eslint-disable no-console */
 
// on importe le middleware du client
import clientRouting from './client/routing'
 
export default function(context) {
  // qui exécute ce code ?
  console.log('[middleware], process.server', process.server, ', process.client=', process.client)
  if (process.client) {
    // routage client
    clientRouting(context)
  }
}
  • الأسطر 9–12: نقوم بتوجيه العميل فقط باستخدام دالة مستوردة في السطر 4؛

النص البرمجي [middleware/client/routing] هو كما يلي:


/* eslint-disable no-console */
export default function(context) {
  // who executes this code?
  console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
  // management of the PHP session cookie in the browser
  // the browser's PHP session cookie must be identical to the one found in the nuxt session
  // acion [fin-session] receives a new cookie PHP (server as nuxt client)
  // if the server receives it, the client must pass it on to the browser
  // for its own exchanges with the PHP server
  // this is customer routing
 
  // retrieve the session cookie PHP
  const phpSessionCookie = context.store.state.phpSessionCookie
  if (phpSessionCookie) {
    // if it exists, we assign the PHP session cookie to the browser
    document.cookie = phpSessionCookie
  }
}

لنعد إلى الوضع الذي يلي مباشرةً تنفيذ صفحة [fin-session] بواسطة خادم [nuxt]:

Image

إذا نقرت على أحد الروابط في القائمة [3]، سيتولى عميل [nuxt] زمام الأمور. وبما أنه سيكون هناك تغيير في الصفحة، فسيتم تنفيذ برنامج توجيه العميل:

  • السطر 13: يتم العثور على ملف تعريف ارتباط جلسة عمل PHP في مخزن تطبيق [nuxt
  • السطر 14: إذا لم تكن فارغة، يتم إرسالها إلى المتصفح (السطر 16). من هذه النقطة فصاعدًا، يمتلك متصفح عميل [nuxt] ملف تعريف ارتباط جلسة عمل PHP الصحيح؛

يتم تشغيل البرنامج النصي [client/routing] في كل مرة يغير فيها عميل [nuxt] الصفحات. يعمل كود البرنامج النصي بغض النظر عن الصفحة المستهدفة: في الأساس، في معظم الأحيان، يرسل للمتصفح ملف تعريف ارتباط جلسة عمل PHP موجود لديه بالفعل، باستثناء حالتين:

  • مباشرة بعد بدء تشغيل التطبيق، يقوم خادم [nuxt] بتنفيذ صفحة [index] ويتلقى ملف تعريف ارتباط جلسة عمل PHP الأول الذي لا يمتلكه متصفح عميل [nuxt
  • عندما يقوم خادم [nuxt] بتنفيذ صفحة [end-session] كما هو موضح للتو؛

الآن دعونا نفحص الحالة التي يتم فيها تنفيذ صفحة [end-session] بواسطة عميل [nuxt] فقط، لأن المستخدم نقر على الرابط الخاص بها في قائمة التنقل. الآن أصبح عميل [nuxt] هو الذي ينفذ دالة [asyncData]:


try {
      // a new session PHP is requested from the tax calculation server
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // log
      console.log('[fin-session asyncData response=]', response)
      // was there a mistake?
      if (response.état !== 400) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // the server has sent a new session cookie PHP
      // we retrieve it for both the server and the nuxt client
      // if this code is executed by the [nuxt] client, the PHP session cookie must be set in the nuxt session
      // so that the [plgDao] plugin on the [nuxt] server can retrieve it and initialize the [dao] layer with
      // if this code is executed by the [nuxt] server, the PHP session cookie must be set in the nuxt session
      // so that the client routing [nuxt] can retrieve it and pass it to the browser
      const phpSessionCookie = dao.getPhpSessionCookie()
      // we note in the store that the session jSON has been started and we store the session cookie PHP
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // we return the result
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    } catch (e) {
      // log
      console.log('[fin-session asyncData error=]', e)
      // report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[fin-session asyncData finished]')
      if (process.client) {
        // end waiting
        context.app.$eventBus().$emit('loading', false)
      }
    }
  • السطر 3: هذه هي طبقة [dao] لعميل [nuxt] التي يتم استردادها هنا؛
  • السطر 18: يتم تخزين ملف تعريف ارتباط جلسة عمل PHP الذي تم استرداده بواسطة طبقة [dao] لعميل [nuxt]، ووضعه في المخزن (السطر 20)، ثم حفظه في جلسة عمل [nuxt] (السطران 22-23)؛
  • منذ ذلك الحين، يعمل كل شيء على ما يرام لأننا نعلم أن طبقة [dao] لخادم [nuxt] ستسترد ملف تعريف ارتباط جلسة عمل PHP من جلسة عمل [nuxt

15.19. التنفيذ

لتشغيل هذا المثال، يجب أولاً حذف ملف تعريف ارتباط جلسة [nuxt] وملف تعريف ارتباط PHP من المتصفح الذي يشغل عميل [nuxt] للبدء من الصفر. فيما يلي مثال باستخدام متصفح Chrome:

Image

15.20. الخلاصة

كان هذا المثال معقدًا بشكل خاص. فقد جمع بين المعرفة المكتسبة في الأمثلة السابقة: تخزين الاستمرارية في جلسة [nuxt]، ومكونات إضافية لحقن الوظائف، وبرمجيات وسيطة للتوجيه، ومعالجة الأخطاء للعمليات غير المتزامنة. زادت التعقيدات بسبب رغبتنا في تمكين المستخدم من استخدام روابط قائمة التنقل وكتابة عناوين URL يدويًا دون تعطيل التطبيق. لتحقيق ذلك، كان علينا فحص سلوك كل صفحة اعتمادًا على ما إذا كانت تُنفَّذ بواسطة العميل أو الخادم [nuxt].

هذا الاتساق في سلوك العميل وخادم [Nuxt] ليس ضروريًا. ضع في اعتبارك السيناريو الشائع التالي:

  • يتم تقديم الصفحة الأولى بواسطة خادم [nuxt
  • يتم تقديم جميع الصفحات اللاحقة بواسطة عميل [nuxt]، الذي يعمل بعد ذلك في وضع [SPA

ومع ذلك، حتى في هذه الحالة، يجب عليك التحقق من كيفية عرض جميع الصفحات عند تنفيذها بواسطة خادم [nuxt]، لأن هذا هو ما ستراه محركات البحث عند طلبها.