اذهب إلى المحتوى

يسمح تقييد معدل التراسل Rate limiting بإدارة حركة مرور البيانات في الشبكة، ويحد من عدد المرات التي يكرر فيها المستخدم عملية ما خلال مدة معينة، مثل استخدام واجهة برمجة التطبيقات API. تُعد الخدمات التي لا تحتوي على إجراءات أمان لتقيد من معدل التراسل معدل التراسل عرضة لزيادة التحميل وإعاقة العمل السليم للتطبيق.

سنتعلم في هذا المقال كيفية إنشاء خادم Node.js قادر على التحقق من عنوان IP الخاص بالطلب وحساب معدل هذه الطلبات من خلال مقارنة العلامات الزمنية للطلبات لكل مستخدم، فإذا تجاوز عنوان IP الحد المعين الذي اخترناه في تطبيقنا، فسوف نتصل بواجهة برمجة تطبيقات Cloudflare API ونضيف عنوان IP إلى القائمة، بعدها سننشئ قاعدة جدار حماية Cloudflare Firewall تحظر جميع الطلبات الواردة من عناوين IP الموجودة في تلك القائمة.

إذًا سننشئ مشروع Node.js على منصة تطبيقات DigitalOcean التي تدعي App Platform والتي تحمي النطاقات المسجلة والمضافة إلى حسابك في Cloudflare بتقيد معدل التراسل.

ستحتاج خلال هذا المقال إلى:

إعداد المشروع والنشر على منصة App Platform

سنوسع خادم Express الأساسي الذي أنشأناه في مقال دليل استخدام Node.js وإطار العمل Express للمبتدئين، في هذه الخطوة، ونرسل الشيفرة إلى مستودع GitHub Repository، ثم ننشر التطبيق على منصة App Platform.

افتح مجلد مشروع خادم Express بواسطة إحدى محررات النصوص البرمجية، ثم أنشئ ملفًا جديدًا في المجلد الرئيسي للمشروع باسم getignore.، وأضف الأسطر التالية:

node_modules/
.env

لاحظ أن السطر الأول في ملف getignore. يوجه git لعدم تتبع المجلد node_modules، ويتيح ذلك عدم زيادة حجم المستودع، إذ يُولَد المجلد node-modules عند الحاجة باستخدام الأمر npm install، أما السطر الثاني فيمنع تتبع ملف متغيرات البيئة، وسوف ننشئ الملف env. في الخطوات التالية.

انتقل إلى ملف server.js في محرر النصوص البرمجية وعدل الأسطر التالية:

...
app.listen(process.env.PORT || 3000, () => {
    console.log(`Example app is listening on port ${process.env.PORT || 3000}`);
});

يتيح الاستخدام الشرطي للمتغير PORT تشغيل الخادم ديناميكيًا على المنفذ المعين PORT في متغير البيئة process.env أو استخدام المنفذ 3000 كمنفذ احتياطي.

لاحظ أن السلسلة الموجودة في التابع console.log ليست محاطة بعلامة اقتباس عادية وإنما بعلامة اقتباس مائلة (`) ، إذ يتيح ذلك استخدام القوالب النصية template literals التي تسمح باستخدام التعابير expressions ضمن السلاسل.

شَغّل التطبيق من واجهة الطرفية Terminal باستخدام الأمر التالي:

node server.js

لاحظ أن واجهة المتصفح ستعرض الرسالة التالية: "Successful response" أي أنه نجحت الاستجابة، وسيظهر الخرج التالي في واجهة الطرفية:

Example app is listening on port 3000

سننشر الآن التطبيق على منصة App platform بعد أن تأكدنا من عمل خادم Express بنجاح.

أولاً ، هيئ git في المجلد الرئيسي للمشروع وأرسل الشيفرة إلى حسابك في GitHub، ثم انتقل إلى لوحة التحكم Dashboard في منصة App Platform في المتصفح وانقر على زر إنشاء التطبيق Create App. ثم اضغط على خيار GitHub وتوثيق مع GitHub، ثم اختر مستودع مشروعك من القائمة المنسدلة.

راجع الإعدادات، ثم سمي التطبيق، وبما أننا سنعمل على مرحلة تطوير التطبيق في هذا المقال يُفضل أن نختار الخطة الأساسية Basic Plan، ثم اضغط على زر تشغيل التطبيق Launch App.

انتقل بعد ذلك إلى صفحة الإعدادات Settings واضغط على قسم النطاقات Domains ثم أضف النطاق الخاص بك الموجه بواسطة Cloudflare في حقل اسم النطاق الرئيسي أو الفرعي Domain or Subdomain Name، ثم اضغط على خيار إدارة النطاق الخاص بك You manage your domain وانسخ سجل CNAME لتضيفه إلى حساب DNS الخاص بنطاقك في Cloudflare.

افتح لوحة تحكم النطاق في Cloudflare في علامة تبويب جديدة، ثم اضغط على تبويبة DNS، واضغط بعدها على زر إضافة سجل Add Record واختر CNAME عند خيار النوع Type، اكتب الرمز @ ثم الصق سجل CNAME الذي نسخته سابقًا من منصة App Platform. اضغط على زر الحفظ Save ثم انتقل إلى صفحة الإعدادات Settings في لوحة تحكم App platform واضغط بعدها على قسم النطاقات Domains، ثم اضغط على زر إضافة نطاق Add Domain.

اضغط على علامة تبويب عمليات النشر Deployments لمشاهدة تفاصيل التطبيقات المنشورة. يمكنك الضغط على نطاقك your_domain لاستعراضه في المتصفح، حالما ينتهي النشر. لاحظ أن واجهة المتصفح ستعرض الرسالة التالية: نجحت الاستجابة "successful response".

ستحصل على الرسالة التالية عند الانتقال إلى علامة تبويب سجلات التشغيل الحالية Runtime Logs في لوحة تحكم App Platform:

Example app is listening on port 8080

لاحظ أن المنفذ 8080 هو المنفذ الافتراضي في App Platform، يمكنك تغييره من الإعدادات قبل النشر.

الآن، سنتعلم كيفية تحديد ذاكرة التخزين المؤقت cache لحساب الطلبات الواردة إلى مُقيِّد معدل التراسل Rate limiter.

تخزين عنوان IP المستخدم وحساب عدد الطلبات في الثانية

سنخزن في هذه الخطوة عنوان IP المستخدم في ذاكرة تخزين مؤقتة cache مع مجموعة من الطوابع الزمنية timestamps لمراقبة عدد الطلبات في الثانية من عنوان IP لكل مستخدم.

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

سنحتاج إلى ثلاثة حزم من برنامج مدير الحزم npm وهي node-cache وis-ip و request-ip. فتلتقط الحزمة request-ip عنوان IP المستخدم الذي يطلب الخادم، أما الحزمة node-cache فتنشئ ذاكرة تخزين مؤقتة تستخدم لتتبع طلبات المستخدم، وتستخدم الحزمة is-ip للتحقق فيما إذا كان عنوان IP هو عنوان IPv6.

ثبت الحزم node-cache وis-ip و request-ip في الطرفية باستخدام مدير الحزم npm عن طريق الأمر التالي:

npm i node-cache is-ip request-ip

افتح ملف server.js بواسطة إحدى محررات النصوص البرمجية وأضف الأسطر التالية بعد سطر const express = require('express');‎:

...
const requestIP = require('request-ip');
const nodeCache = require('node-cache');
const isIp = require('is-ip');
...

يحضّر السطر الأول وحدة requestIP من حزمة request-ip التي ثبتها سابقًا، فتقوم هذه الوحدة بإلتقاط عنوان IP المستخدم الذي يطلب الخادم.

أما السطر الثاني فيحضّر وحدة nodeCache من حزمة node-cache، التي تنشئ ذاكرة تخزين مؤقتة تستخدم لتتبع طلبات المستخدم في الثانية. ويحضّر السطر الثالث وحدة isIp من الحزمة is-ip، ويتحقق فيما إذا كان عنوان IP من الإصدار السادس IPv6 الذي سوف تصوغه وفق ترميز CIDR المعتمد في Cloudflare.

عَرِّف المتغيرات الثابتة في ملف server.js:

...
const TIME_FRAME_IN_S = 10;
const TIME_FRAME_IN_MS = TIME_FRAME_IN_S * 1000;
const MS_TO_S = 1 / 1000;
const RPS_LIMIT = 2;

يحدد المتغير الثابت TIME_FRAME_IN_S الفترة التي يحسب خلالها تطبيقك متوسط الطوابع الزمنية للمستخدم، ويجدر بالذكر أن زيادة الفترة الزمنية سيؤدي إلى زيادة حجم الذاكرة المؤقتة، وبالتالي سيؤدي إلى استهلاك حجم أكبر من الذاكرة.

ويحدد المتغير الثابت TIME_FRAME_IN_MS الفترة التي يحسب خلالها تطبيقك متوسط الطوابع الزمنية للمستخدم، ولكن بالميللي ثانية، وسنستخدم معامل التحويل MS_TO_S لتحويل الزمن من صيغة الميللي ثانية إلى صيغة الثواني. يحدد المتغير RPS_LIMIT حد العتبة الذي سيؤدي إلى تشغيل مقيد معدل التراسل، ويغير قيمة العتبة وفقًا لمتطلبات التطبيق، وقد أسندنا له القيمة 2 والتي تعتبر قيمة معتدلة، في المتغير RPS_LIMIT أثناء مرحلة التطوير.

يمكنك باستخدام إطار العمل Express كتابة دوال وسيطة middleware لها وصول إلى جميع طلبات HTTP الواردة إلى خادمك.

استدعِ التابع ()app.use لتعريف دالة وسيطة، ومررها ضمنه، باتباع هذه الطريقة أنشئ دالة وسيطة باسم ipMiddleware:

...
const ipMiddleware = async function (req, res, next) {
    let clientIP = requestIP.getClientIp(req);
    if (isIp.v6(clientIP)) {
        clientIP = clientIP.split(':').splice(0, 4).join(':') + '::/64';
    }
    next();
};
app.use(ipMiddleware);
...

لاحظ أن الدالة ()getClientIP تستخدم الكائن req من الدالة الوسيطة كمعامل.

نستدعي الدالة ()v6. مع الوحدة is-ip، وتعطي نتيجة صحيحة true إذا كان الوسيط الممرر إليها عنوان IPv6. يجب عليك صياغة عناوين IPv6 وفق قناع شبكة 64/ حسب ترميز CIDR، كالآتي:

aaaa:bbbb:cccc:dddd::/64

ينشئ التابع (':')split. مصفوفة من السلسلة التي تحتوي على عناوين IP ويفصلهم بواسطة المحرف:.

يرجع التابع (splice(0,4. أول أربعة وسطاء في المصفوفة. أما التابع (':')join. فيرجع سلسلة من المصفوفة مع المحرف :.

يوجه التابع ()next الدالة الوسيطة للانتقال إلى الدالة الوسيطة التالية، إن وجدت، ونلاحظ في مثالنا أنها تنقل الطلب إلى المسار / من طلب GET. احرص على تضمين ذلك في التابع، وإلا فلن ينتقل الطلب من الدالة الوسيطة.

هيئ نسخة instance من الوحدة node-cache عن طريق إضافة المتغير التالي بعد الثوابت:

...
const IPCache = new nodeCache({ stdTTL: TIME_FRAME_IN_S, deleteOnExpire: false, checkperiod: TIME_FRAME_IN_S });
...

يمكننا إعادة تعريف المعاملات overriding الأساسية الخاصة بوحدة nodecache وتبديلها إلى خصائص محددة custom properties باستخدام المتغير الثابت IPCache:

  • stdTTL: يعبر عن الفاصل الزمني بالثواني الذي يُحذَف بعده زوج مفتاح-قيمة key-value من عناصر ذاكرة التخزين المؤقت. TTL تعني وقت الحياة Time To Live، وهي مقياس للوقت الذي تنتهي بعده ذاكرة التخزين المؤقت.
  • deleteOnExpire: اضبطه على falseحيث أننا سنكتب تابع استدعاء فيما بعد ليتعامل مع الحدث expired.

*checkperiod يعبر عن الفاصل الزمني بالثواني الذي يُشغَل بعده فحص تلقائي للعناصر منتهية الصلاحية. تكون القيمة الإفتراضية 600، لكن فحص انتهاء الصلاحية في تطبيقك سيحدث في وقت أقصر، لأن القيمة المحددة أصغر.

يمكنك الإطلاع على توثيق node-cache للحصول على مزيد من المعلومات عن المعاملات الإفتراضية لحزمة node-cache. سيساعدك المخطط التالي على تصور كيفية تخزين ذاكرة التخزين المؤقت للبيانات:

image2.png

سننشئ الآن زوج مفتاح-قيمة لعنوان IP الجديد ونضيفه للزوج القديم في حال وجود عنوان IP في الذاكرة المؤقتة. تكون القيمة عبارة عن مصفوفة من العلامات الزمنية timestamps المقابلة لكل طلب وارد إلى تطبيقك.

أنشئ الدالة ()updateCache بعد المتغير الثابت IPCache لإضافة علامات زمنية للطوابع في الذاكرة المؤقتة:

...
const updateCache = (ip) => {
    let IPArray = IPCache.get(ip) || [];
    IPArray.push(new Date());
    IPCache.set(ip, IPArray, (IPCache.getTtl(ip) - Date.now()) * MS_TO_S || TIME_FRAME_IN_S);
};
...

لاحظ أن السطر الأول في الدالة يحضر مجموعة العلامات الزمنية لعنوان IP المحدد أو يهيئ مصفوفة فارغة إذا كان فارغًا. نقوم في السطر التالي بإضافة العلامة الزمنية الحالية التي التقطت بواسطة الدالة ()new Date إلى المصفوفة.

تأخذ الدالة ()set. ثلاثة وسطاء هي : key و value وTTL، فيعيد الوسيط الأخير تعريف قيمة TTL القياسية عن طريق استبدال قيمة stdTTL في المتغير IPCache، ويستخدم TTL الحالية إذا كان عنوان IP موجودًا في الذاكرة المؤقتة، عدا عن ذلك تُحَدد قيمة TTL من المتغير TIME_FRAME_IN_S.

تُحسَب قيمة TTL لزوج مفتاح-قيمة الحالي عن طريق طرح قيمة العلامة الزمنية الحالية من العلامة الزمنية لانتهاء الصلاحية، وتُحَول النتيجة إلى ثواني ثم تُمَرر إلى حقل الوسيط الثالث في الدالة ()set..

تقبل الدالة ()getTtl. مفتاحًا وعنوان IP كوسطاء وترجع زمن TTL للزوج مفتاح-قيمة كعلامة زمنية، وفي حال عدم وجود عنوان IP في الذاكرة المؤقتة، تُرجِع undefined وتستخدم القيمة الاحتياطية في المتغير TIME_FRAME_IN_S.

اقتباس

ملاحظة: ستحتاج التحويل من صيغة الميللي ثانية إلى صيغة الثواني، إذ أن لغة جافاسكربت JavaScript تستخدم العلامات الزمنية بصيغة الميللي ثانية، أما وحدة node-cache فتستخدم الثواني.

أضف الأسطر التالية في الدالة الوسيطة ipMiddleware بعد تعليمة if (isIp.v6(clientIP))‎ لحساب عدد الطلبات في الثانية من عنوان IP الذي يطلب التطبيق:

...
    updateCache(clientIP);
    const IPArray = IPCache.get(clientIP);
    if (IPArray.length > 1) {
        const rps = IPArray.length / ((IPArray[IPArray.length - 1] - IPArray[0]) * MS_TO_S);
        if (rps > RPS_LIMIT) {
            console.log('You are hitting limit', clientIP);
        }
    }
...

يضيف السطر الأول الطابع الزمني للطلب الوارد من عنوان IP إلى ذاكرة التخزين المؤقت عن طريق استدعاء دالة ()updateCache، ويجمع السطر الثاني مجموعة العلامات الزمنية لعنوان IP. إذا كان عدد العناصر في مصفوفة العلامات الزمنية أكبر من واحد (إذ يحتاج حساب الطلبات في الثانية إلى علامتين زمنيتين على الأقل)، وعدد الطلبات في الثانية هو أكبر من قيمة العتبة التي حددناها في الثوابت، إذا سوف يطبع عنوان IP في لوحة التحكم console. يحسب المتغير rps عدد الطلبات في الثانية عن طريق تقسيم عدد الطلبات على فارق الفاصل الزمني، ثم يحول النتيجة إلى الثانية.

بما أننا اسندنا القيمة false إلى الخاصية deleteOnExpire في المتغير IPCache، علينا الآن التعامل يدويُا مع الحدث expired، فتوفر وحدة node-cache دالة استدعاء تُشَغل الحدث expired.

أضف الأسطر التالية بعد المتغير IPCache:

...
IPCache.on('expired', (key, value) => {
    if (new Date() - value[value.length - 1] > TIME_FRAME_IN_MS) {
        IPCache.del(key);
    }
});
...

تقبل دالة رد النداء()on. مفتاح key وقيمة value العنصر منتهي الصلاحية كوسطاء، وتكون value عبارة عن مصفوفة علامات زمنية للطلبات مخزنة في الذاكرة المؤقتة.

يتحقق السطر المحدد الثاني من العنصر الأخير في مصفوفة القيم بمقارنته مع المتغيرTIME_FRAME_IN_S، إذ يطرح آخر عنصر في مصفوفة القيم value من الدالة new Date()‎ (الوقت الحالي)، فإذا كانت النتيجة أصغر من قيمة المتغير TIME_FRAME_IN_S تأخذ الدالة ‎.del()‎ المفتاحkey كوسيط وتحذف العنصر منتهي الصلاحية من ذاكرة التخزين المؤقت.

...
    else {
        const updatedValue = value.filter(function (element) {
            return new Date() - element < TIME_FRAME_IN_MS;
        });
        IPCache.set(key, updatedValue, TIME_FRAME_IN_S - (new Date() - updatedValue[0]) * MS_TO_S);
    }
...

يوفر تابع المصفوفات المحلي filter()‎ في جافاسكريبت دالة استدعاء لترشيح العناصر الموجودة في مصفوفة العلامات الزمنية.

يتحقق السطر الثالث من الشيفرة السابقة من نتيجة طرح العناصر من الدالة new Date()‎ إذا كانت أصغر من المتغير TIME_FRAME_IN_S ويخزن النتيجة في المتغير updatedValue ثم تضاف العناصر التي تمت تصفيتها إلى المتغير updatedValue. يؤدي ذلك إلى تحديث ذاكرة التخزين المؤقت بالعناصر التي تمت تصفيتها في المتغير updatedValue وزمن TTL الجديد.

تحفز قيم TTL التي تطابق العنصر الأول من المتغير updatedValue تشغيل دالة ‎.on('expired')‎عندما تحذف الذاكرة المؤقتة العنصر التالي. وتحسب قيمة زمن TTL الجديد عن طريق حساب الفرق بين المتغير TIME_FRAME_IN_S والوقت منتهي الصلاحية منذ أول علامة زمنية للطلب في المتغير updatedValue.

شَغّل التطبيق من واجهة الطرفية:

node server.js

انتقل إلى العنوان localhost:3000 في متصفح الويب، ولاحظ أن المتصفح سوف يعرض الرسالة: "Successful response"، ثم جرب حَدِث الصفحة عدة مرات حتى تتجاوز قيمة المتغير RPS_LIMIT ولاحظ النتيجة التي ستعرضها الطرفية:

Example app is listening on port 3000
You are hitting limit ::1

ملاحظة: يظهر عنوان localhost IP على الشكل ‎::1، لكن ذلك سيتغير، إذ يلتقط تطبيقك العنوان العام Public IP للمستخدم عند نشره خارج الخادم المحلي localhost.

أصبح بإمكان تطبيقك تتبع طلبات المستخدم وتخزين العلامات الزمنية في ذاكرة التخزين المؤقت.

سنتعلم في الخطوة التالية كيفية استخدام واجهة برمجة تطبيقات Cloudflare لإعداد جدار الحماية Firewall.

إعداد جدار حماية Cloudflare

سنتعلم في هذه الخطوة كيفية إعداد جدار حماية Cloudflare لإيقاف عناوين IP عند الوصول إلى الحد المعين، وكيفية إنشاء متغيرات البيئة environment variables، واستدعاء واجهة برمجة تطبيقات Cloudflare.

سجل الدخول في لوحة تحكم Cloudflare، ثم اذهب إلى الصفحة الرئيسية لحسابك ومنها اضغط على علامة تبويب الإعدادات Configurations، ثم قوائم Lists. أنشئ قائمة جديدة باسم your_list.

اقتباس

ملاحظة: يتوفر خيار القوائم Lists في لوحة تحكم الحساب على Cloudflare، وليس من صفحة لوحة تحكم النطاق.

افتح لوحة تحكم النطاق الخاص بك your_domain من الصفحة الرئيسية Home، ثم اضغط على علامة تبويب الجدار الناري Firewall واضغط على قواعد الجدار الناري Firewall Rules ثم على إنشاء قاعدة جديدة Create a Firewall rule وسَمِها باسم تختاره مثل your_rule_name مجازًا.

اختر عنوان المصدر IP Source Address من القائمة المنسدلة، واختر is in list عند خيار المُشغِل Operator، و your_list عند خيار القيمة Value، ثم اضغط على حجب Block من القائمة المنسدلة للخيار Choose an action، واضغط بعدها على زر نشر Deploy.

أنشئ ملفًا بصيغة env. في المجلد الرئيسي للمشروع وأضف عليه الأسطر التالية حتى نتمكن من استدعاء واجهة تطبيقات Cloudflare من تطبيقنا:

ACCOUNT_MAIL=your_cloudflare_login_mail
API_KEY=your_api_key
ACCOUNT_ID=your_account_id
LIST_ID=your_list_id

انتقل إلى قسم الملف الشخصي My Profile في لوحة تحكم Cloudflare، ثم اضغط على علامة تبويب وحدات واجهة التطبيقات API Tokens للحصول على قيمة API_KEY، ثم اضغط على عرض View في قسم Global API Key واكتب كلمة المرور الخاصة بك.

انتقل إلى علامة تبويب الإعدادات Configuration ثم إلى قسم القوائم Lists، واضغط على زر تعديل Edit بجانب القائمة your_list التي أنشأتها. يمكنك الحصول على معرف الحساب ACCOUNT_IDومعرف القائمة LIST_ID من عنوان URL من المتصفح الذي يكون على الصيغة التالية:

https://dash.cloudflare.com/your_account_id/configurations/lists/your_list_id

تنبيه: احرص على أن يكون محتوى الملف env. سريًا وليس متاحًا للجميع، وذلك عن طريق إضافته ضمن ملف gitignore. الذي أنشأناه في الخطوة الأولى.

ثبت حزمة axios و dotenv في الطرفية بواسطة مدير الحزم:

npm i axios dotenv

أضف الأسطر التالية بعد سطر المتغير nodeCache:

...
const axios = require('axios');
require('dotenv').config();
...

يحضر السطر الأول وحدة axios من حزمة axios المثبتة، وسنستخدم هذه الوحدة لإجراء استدعاءات إلى واجهة برمجة تطبيقات Cloudflare. يُعِد السطر الثاني وحدة dotenv ويفَعّل المتغير العام process.env الذي سيعرف قيم ملف env. في ملف server.js.

أضف الأسطر البرمجية التالية إلى تعليمة If الشرطية التالية if (rps > RPS_LIMIT)‎ فوق السطر:console.log('You are hitting limit', clientIP)‎ حتى تتمكن من استدعاء واجهة برمجة تطبيقات Cloudflare:

...
    const url = `https://api.cloudflare.com/client/v4/accounts/${process.env.ACCOUNT_ID}/rules/lists/${process.env.LIST_ID}/items`;
    const body = [{ ip: clientIP, comment: 'your_comment' }];
    const headers = {
        'X-Auth-Email': process.env.ACCOUNT_MAIL,
        'X-Auth-Key': process.env.API_KEY,
        'Content-Type': 'application/json',
    };
    try {
        await axios.post(url, body, { headers });
    } catch (error) {
        console.log(error);
    }
...

أصبح بإمكاننا الآن الاتصال بواجهة برمجة تطبيقات Cloudflare عن طريق العنوان URL لإضافة عنصر، وفي حالتنا سنضيف عنوان IP إلى القائمة your_list، إذ تظهر واجهة برمجة تطبيقات Cloudflare المتغيران ACCOUNT_MAIL و API_KEY في ترويسة الطلب على الشكل X-Auth-Email و X-Auth-Key. أما جسم الطلب فيتألف من عنوان IP الذي سيضاف إلى القائمة وتعليق comment له القيمة your_comment. ضع في الحسبان أنه يمكنك استبدال التعليق comment بالتعليق الذي يحلو لك لتمييز القيمة المدخلة.

يوضع الطلب POST المستدعى على الشكل ()axios.post ضمن الكتلة البرمجية try-catch لمعالجة الأخطاء، إن وجدت، فتحتاج الدالة axios.post إلى الوسطاء التالية: url، body، headers لإنشاء الطلب.

يجب عليك تغيير قيمة المتغير clientIP عند تجريب الطلبات إلى عنوان IP تجريبي مثل: 198.51.100.0/24 لأن Cloudflare لايقبل عنوان الخادم المحلي localhost:

...
let clientIP = '198.51.100.0/24';
...

شغل التطبيق من واجهة الطرفية:

node server.js

انتقل إلى العنوان localhost:3000 في المتصفح وستلاحظ ظهور الرسالة: "Successful response".

حَدِث الصفحة عدة مرات حتى تتجاوز قيمة المتغير RPS_LIMIT ولاحظ النتيجة التي ستعرضها الطرفية:

Example app is listening on port 3000
You are hitting limit ::1

انتقل الآن إلى صفحة لوحة تحكم Cloudflare ثم إلى صفحة القوائم your_list حيث سيظهر عنوان IP الذي أضفناه في القائمة your_list، ثم ستظهر صفحة الجدار الناري بعد إرسال التغييرات إلى GitHub.

اقتباس

ملاحظة: احرص على تغيير قيم المتغير clientIP إلى (requestIP.getClientIp(req قبل إرسال الشيفرة إلى GitHub.

انشر تطبيقك بعد حفظ التعديلات وارفع الشيفرة على GitHub، وبنا أننا فعّلنا خيار النشر التلقائي auto-deploy سيرسل الكود تلقائيًا من GitHub إلى منصة App Platform لنشره.

ستحتاج إلى إضافة ملف env. إلى منصة App Platform في قسم متغيرات بيئة التطبيق App-Level Environment Variables من تبويبة الإعدادات Settings. أضف زوج مفتاح-قيمة إلى ملف env. كي يستطيع تطبيقك الوصول إلى محتوياته من App Platform.

انتقل إلى نطاقك في المتصفح بعد انتهاء النشر وحَدِث الصفحة باستمرار حتى تصل إلى الحد RPS_LIMIT، عندها سيظهر المستعرض صفحة الجدار الناري من Cloudflare:

image1.png

ستحصل على الرسالة التالية عند الانتقال إلى علامة تبويب سجلات التشغيل الحالية Runtime Logs في لوحة تحكم App Platform:

You are hitting limit your_public_ip

يمكنك التأكد من أن الجدار الناري يحظر عنوان IP المحدد في القائمة your_list فقط، عن طريق الدخول إلى نطاقك your_domain من جهاز أخر أو عبر اتصال VPN، ويمكنك حذف عنوان IP من لوحة تحكم Cloudflare.

قد يتطلب الحصول على رد من الجدار الناري بضع ثوان بسبب الرد المخزن سابقُا في المتصفح.

مبارك! لقد أعددت جدار Cloudflare الناري لحظر عناوين IP عندما يصل المستخدمون إلى حد معدل التراسل عن طريق استدعاء واجهة برمجة تطبيقات Cloudflare.

ختامًا

تعلمنا في هذا المقال كيفية إنشاء مشروع Node.js ونشره على منصة App Platform من DigitalOcean وتوجيهه للاتصال بنطاقك Cloudflare، وطبقنا إجراء حماية للنطاق من زيادة حد معدل التراسل عن طريق إعداد الجدار الناري في Cloudflare.

يمكنك، فيما بعد، تعديل قاعدة الجدار الناري لإظهار تحدٍ JS Challenge أو رمز تحقق كابتشا CAPTCHA بدلاً من حظر المستخدم، اطلع على مستندات Cloudflare لمزيد من التفاصيل حول ذلك.

وللحصول على المساعدة والدعم يمكنك إضافة سؤالك في قسم الأسئلة والأجوبة في أكاديمية حسوب.

ترجمة- وبتصرف للمقال How To Build a Rate Limiter With Node.js on App Platform لصاحبه Abel Mathew

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...