يسمح تقييد معدل التراسل Rate limiting بإدارة حركة مرور البيانات في الشبكة، ويحد من عدد المرات التي يكرر فيها المستخدم عملية ما خلال مدة معينة، مثل استخدام واجهة برمجة التطبيقات API. تُعد الخدمات التي لا تحتوي على إجراءات أمان لتقيد من معدل التراسل معدل التراسل عرضة لزيادة التحميل وإعاقة العمل السليم للتطبيق.
سنتعلم في هذا المقال كيفية إنشاء خادم Node.js قادر على التحقق من عنوان IP الخاص بالطلب وحساب معدل هذه الطلبات من خلال مقارنة العلامات الزمنية للطلبات لكل مستخدم، فإذا تجاوز عنوان IP الحد المعين الذي اخترناه في تطبيقنا، فسوف نتصل بواجهة برمجة تطبيقات Cloudflare API ونضيف عنوان IP إلى القائمة، بعدها سننشئ قاعدة جدار حماية Cloudflare Firewall تحظر جميع الطلبات الواردة من عناوين IP الموجودة في تلك القائمة.
إذًا سننشئ مشروع Node.js على منصة تطبيقات DigitalOcean التي تدعي App Platform والتي تحمي النطاقات المسجلة والمضافة إلى حسابك في Cloudflare بتقيد معدل التراسل.
ستحتاج خلال هذا المقال إلى:
- حساب Cloudflare، ويفي الحساب المجاني Free plan من Cloudflare بالغرض. احرص اختيار الحساب المجاني عند إنشاء حساب جديد.
- نطاق مسجل ومضاف إلى حسابك في Cloudflare. يمكنك الإطلاع على كيف نخفف من هجمات DDoS ضد موقعنا باستخدام CloudFlare مع Cloudflare، وننصح بقراءة هذا المقال للتعرف على أساسيات نظام أسماء النطاقات DNS ومكوناته.
- خادم Express مع بيئة Node.js، اتبع الخطوات في المقال التالي لإعداد خادم Express.
- حساب على GitHub ونسخة مثبتة من برنامج gitعلى جهازك، يُعد ذلك ضروريًا لأننا سنرسل الشيفرة من GitHub على منصة App Platform من DigitalOcean.
- حساب على موقع DigitalOcean.
إعداد المشروع والنشر على منصة 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
. سيساعدك المخطط التالي على تصور كيفية تخزين ذاكرة التخزين المؤقت للبيانات:
سننشئ الآن زوج مفتاح-قيمة لعنوان 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:
ستحصل على الرسالة التالية عند الانتقال إلى علامة تبويب سجلات التشغيل الحالية 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
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.