كانت خوادمنا تستجدي التحديثات: كيف أنقذتنا ‘خطافات الويب’ (Webhooks) من جحيم الاستطلاع المستمر (Polling)؟

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

قبل كم سنة، كنا بنبني نظام متجر إلكتروني كبير لأحد العملاء. واحد من أهم المتطلبات كان تحديث المخزون بشكل فوري. يعني لو حدا اشترى آخر قطعة من منتج معين، لازم المنتج يظهر “نفذ من المخزم” فوراً على كل صفحات الموقع، عشان ما نبيع نفس القطعة مرتين. المنطق بسيط، لكن التطبيق كان كابوس.

في البداية، لجأنا للحل البديهي اللي كلنا بنعرفه: الاستطلاع المستمر أو الـ Polling. كتبنا سكربت بسيط، كل 5 ثواني يروح يسأل قاعدة البيانات: “يا قاعدة البيانات، هل صار تغيير على المخزون؟”، “طيب هسّا؟”، “طيب كمان شوي؟”… وهكذا دواليك. في البداية، الأمور كانت ماشية. لكن مع نمو الموقع وزيادة عدد المنتجات والزوار، بدأت خوادمنا تصرخ وتستغيث. المعالج (CPU) كان دائماً مشغول، وحركة البيانات (Traffic) في الشبكة الداخلية صارت “عجقة” ما إلها أول من آخر. كنا حرفياً بنحرق موارد وطاقة وفلوس على أسئلة 99% من إجاباتها كانت “لأ، ما فيه إشي جديد”. الوضع كان زي اللي قاعد في السيارة وبيسأل كل دقيقة: “وصلنا؟”، إشي بيشلّ والله.

لحد ما في يوم، وإحنا في عز الأزمة، واحد من المبرمجين الشباب في الفريق قال: “يا جماعة، سمعتوا بإشي اسمه Webhooks؟”. بصراحة، وقتها كانت معرفتي فيه سطحية. لكن لما تعمقنا فيه، كانت لحظة “وجدتها!” الحقيقية. اكتشفنا إننا كنا بنجبر خوادمنا تستجدي المعلومة، بينما كان فيه طريقة تخلي المعلومة هي اللي تيجي لعندنا لما تكون جاهزة. ومن يومها، تغير كل شيء.

ما هو الجحيم الذي كنا نعيشه؟ شرح مبسط للاستطلاع (Polling)

قبل ما نحكي عن المنقذ، خلينا نفصّل شوي في الكابوس اللي اسمه Polling عشان الصورة تكون واضحة، وخصوصاً للمبتدئين. تخيل أنك تنتظر رسالة مهمة على البريد. طريقة الـ Polling هي إنك تروح على صندوق البريد كل 10 دقائق، تفتحه، تلاقي فاضي، تسكره، وترجع بعد 10 دقائق تكرر نفس العملية. مجهود ضائع، صح؟

هذا بالضبط ما يفعله الـ Polling في عالم البرمجة. العميل (تطبيقنا) يظل يرسل طلبات HTTP (عادةً GET) إلى الخادم (السيرفر) على فترات زمنية منتظمة ليسأله: “هل هناك بيانات جديدة؟”.

أنواع الاستطلاع (Polling)

  • الاستطلاع قصير المدى (Short Polling): هذا هو النوع الكلاسيكي. العميل يرسل طلب، الخادم يرد فوراً (ببيانات جديدة أو برسالة “لا يوجد جديد”)، والعميل ينتظر فترة ثم يكرر الطلب. هذا هو أكثر نوع استنزافاً للموارد.
  • الاستطلاع طويل المدى (Long Polling): هذا تحسين طفيف. العميل يرسل طلب، لكن الخادم لا يرد فوراً. الخادم يُبقي الاتصال مفتوحاً وينتظر. إذا حدث شيء جديد، يرسل الخادم الرد فوراً ويُغلق الاتصال. إذا لم يحدث شيء خلال فترة معينة (مثلاً 30 ثانية)، يرد الخادم برسالة “لا جديد” ويُغلق الاتصال. العميل بعد ذلك يفتح اتصالاً جديداً على الفور. هو أفضل من النوع الأول، لكنه لا يزال معقداً ويستهلك موارد الخادم لإبقاء الاتصالات مفتوحة.

ببساطة، مشكلة الـ Polling الأساسية هي أنه يتبع نموذج “السحب” (Pull). أنت من يجب أن تذهب وتتحقق وتطلب المعلومة، مما يؤدي إلى ضياع هائل في الموارد وتأخير في وصول البيانات.

مثال كود بسيط لعملية Polling بـ JavaScript

هذا مجرد مثال توضيحي لفكرة الـ Polling في الواجهة الأمامية (Frontend):


// لا تستخدم هذا الكود في الإنتاج بهذه البساطة! إنه مجرد مثال.
async function checkForUpdates() {
    try {
        const response = await fetch('https://api.example.com/v1/inventory/status');
        const data = await response.json();

        if (data.hasUpdates) {
            console.log('تحديث جديد وصل!');
            // هنا تقوم بتحديث واجهة المستخدم
            updateUI(data.items);
        } else {
            console.log('لا يوجد جديد، سأتحقق مرة أخرى بعد 5 ثوانٍ.');
        }
    } catch (error) {
        console.error('حدث خطأ أثناء التحقق من التحديثات:', error);
    }
}

// ابدأ عملية الاستطلاع كل 5 ثوانٍ
setInterval(checkForUpdates, 5000);

تخيل آلاف المستخدمين يقومون بتشغيل هذا الكود في متصفحاتهم. إنها وصفة لكارثة أداء.

المنقذ وصل: أهلاً بـ ‘خطافات الويب’ (Webhooks)

الآن نأتي إلى الحل السحري، خطافات الويب أو الـ Webhooks. إذا كان الـ Polling هو أن تذهب لصندوق البريد كل 10 دقائق، فالـ Webhook هو أن تعطي ساعي البريد عنوانك ورقم هاتفك، وتقول له: “يا عمي، بس توصل رسالتي، رن عليّ وجيبلي إياها على البيت”. فرق شاسع، أليس كذلك؟

الـ Webhook هو آلية تسمح لتطبيق ما بإرسال بيانات إلى تطبيق آخر بشكل فوري عند وقوع حدث معين. لهذا السبب، يُطلق عليها أحياناً “واجهات برمجة التطبيقات العكسية” (Reverse APIs). بدلاً من أن يقوم تطبيقنا بطلب البيانات (Pull)، يقوم التطبيق الآخر بدفع (Push) البيانات إلينا بمجرد وقوع الحدث.

كيف يعمل الـ Webhook؟

  1. التسجيل (Registration): أنت، بصفتك العميل، تقوم بتزويد الخدمة (مثل بوابة دفع، GitHub، Slack) بعنوان URL خاص بك. هذا الـ URL يسمى “نقطة نهاية خطاف الويب” (Webhook Endpoint).
  2. الحدث (Event): يقع حدث معين في تلك الخدمة. مثلاً: عملية دفع ناجحة في Stripe، أو push جديد على مستودع في GitHub.
  3. الإشعار (Notification): تقوم الخدمة فوراً بتجميع معلومات حول هذا الحدث (تسمى Payload)، وتُرسل طلب HTTP POST إلى الـ URL الذي قمت بتسجيله.
  4. المعالجة (Processing): الخادم الخاص بك يستقبل هذا الطلب، يتأكد من صحته، ويقوم بمعالجة البيانات الموجودة فيه (الـ Payload) لتنفيذ الإجراء المطلوب (مثلاً: تحديث قاعدة البيانات، إرسال إيميل، تسجيل طلبية).

كيف نبني خطاف الويب الخاص بنا؟ (مثال عملي)

الكلام النظري جميل، لكن “الخواجة” الحقيقي هو اللي بيعرف يطبق. خلينا نشوف كيف ممكن نبني نقطة نهاية (Endpoint) بسيطة لاستقبال Webhooks باستخدام Node.js و Express.js.

الخطوة الأولى: تجهيز نقطة النهاية (Endpoint)

أولاً، نحتاج إلى خادم ويب يستمع لطلبات POST القادمة. سنستخدم Express لسهولته.


// server.js
const express = require('express');
const crypto = require('crypto'); // موديول مدمج في Node.js للتشفير

const app = express();
const PORT = 3000;

// هذا السيكرت يجب أن يكون نفسه الموجود في إعدادات الخدمة (مثل GitHub)
const WEBHOOK_SECRET = 'MySuperSecretKey123';

// استخدم middleware لاستقبال الـ JSON body الخام للتحقق من التوقيع
// verify a function that will be called for each request
const verifySignature = (req, res, buf, encoding) => {
    if (buf && buf.length) {
        const signature = req.headers['x-hub-signature-256']; // مثال لتوقيع GitHub
        if (!signature) {
            throw new Error('No signature found on the request');
        }

        const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
        const digest = 'sha256=' + hmac.update(buf).digest('hex');

        if (!crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature))) {
            throw new Error('Invalid signature');
        }
    }
};

// استقبل الطلبات على هذا المسار
// استخدم express.raw() بدلاً من express.json() في البداية للسماح بالتحقق
app.post('/api/github-webhook', express.raw({ type: 'application/json' }), (req, res) => {
    // التحقق من التوقيع (مهم جداً للأمان!)
    try {
        // نمرر الـ body الخام (req.rawBody غير موجود افتراضياً, لذا نستخدم buf من verify)
        // لكن بما أننا استخدمنا express.raw, الـ body الخام موجود في req.body
        verifySignature(req, res, req.body);
    } catch (e) {
        console.error('Signature verification failed!', e.message);
        return res.status(401).send('Unauthorized');
    }

    // الآن بعد التحقق، يمكننا تحويل الـ Buffer إلى JSON
    const payload = JSON.parse(req.body.toString());

    console.log('Webhook payload received!');
    // console.log(JSON.stringify(payload, null, 2));

    // هنا تبدأ معالجة البيانات
    // مثال: التحقق من نوع الحدث
    const eventType = req.headers['x-github-event'];
    if (eventType === 'push') {
        console.log(`New push to branch "${payload.ref}" by ${payload.pusher.name}`);
        // يمكنك هنا تشغيل سكربت لتحديث الخادم (CI/CD)
    } else if (eventType === 'issues') {
        if (payload.action === 'opened') {
            console.log(`New issue opened: "${payload.issue.title}"`);
            // يمكنك إرسال إشعار إلى Slack أو Discord
        }
    }

    // الأهم: أرسل رداً ناجحاً بسرعة!
    res.status(200).send('Webhook Received');
});

app.listen(PORT, () => {
    console.log(`Server is listening for webhooks on http://localhost:${PORT}`);
});

ملاحظة أمان مهمة: كما رأيت في الكود، التحقق من توقيع الـ Webhook (Signature Verification) ليس خياراً، بل هو ضرورة قصوى. أي شخص يعرف عنوان الـ Endpoint الخاص بك يمكنه إرسال بيانات مزيفة إليه. التوقيع يضمن أن الطلب جاء فعلاً من الخدمة الأصلية ولم يتم التلاعب به.

الخطوة الثانية: تسجيل خطاف الويب

هذه الخطوة تتم خارج الكود، في لوحة تحكم الخدمة التي تريد استقبال الأحداث منها. على سبيل المثال، في GitHub، تذهب إلى إعدادات المستودع (Repository settings) -> Webhooks -> Add webhook. هناك ستضع:

  • Payload URL: عنوان الـ Endpoint الذي أنشأته. يجب أن يكون عنواناً عاماً (Public URL). (سنرى كيف نفعل ذلك محلياً بعد قليل).
  • Content type: عادةً `application/json`.
  • Secret: المفتاح السري (في مثالنا `MySuperSecretKey123`) الذي سيستخدم لتوقيع الطلبات.
  • Events: تختار الأحداث التي تهمك (مثل `pushes`, `issues`, etc.).

نصائح من خبرة أبو عمر 🧔

هذه شوية نصائح من القلب، تعلمتها بالطريقة الصعبة. خذوها بعين الاعتبار لتجنب المشاكل:

1. اجعل معالجتك سريعة وغير متزامنة (Asynchronous)

الخدمة التي ترسل الـ Webhook (مثل Stripe أو GitHub) لا تنتظر طويلاً. تتوقع منك رداً سريعاً (عادةً `200 OK`) خلال ثوانٍ قليلة. إذا تأخرت في الرد، قد تعتبر الخدمة أن الـ Webhook فشل وتحاول إرساله مرة أخرى، مما قد يسبب تكراراً في البيانات.

الحل: لا تقم بمعالجة طويلة ومعقدة (مثل إرسال إيميلات، معالجة صور، عمليات قاعدة بيانات ثقيلة) مباشرةً في دالة الـ Webhook. استلم الطلب، تحقق منه، ضعه في طابور مهام (Message Queue) مثل RabbitMQ أو Redis, أو حتى في جدول في قاعدة البيانات، ثم أرسل رداً `200 OK` فوراً. بعد ذلك، دع عاملاً منفصلاً (Background Worker) يأخذ المهام من الطابور وينفذها براحته.

2. استعد للفشل وتكرار المحاولات

ماذا لو كان الخادم الخاص بك معطلاً للحظات عند وصول الـ Webhook؟ معظم الخدمات المحترمة لديها آلية إعادة محاولة (Retry Mechanism). ستحاول إرسال نفس الـ Webhook عدة مرات على فترات متباعدة. هذا يعني أن عليك بناء نظامك ليكون قادراً على التعامل مع الطلبات المكررة. هذه الخاصية تسمى “التعاودية” أو Idempotency.

الحل: قبل معالجة أي Webhook، تحقق إذا كنت قد قمت بمعالجته من قبل. يمكنك تخزين معرف فريد لكل طلب (عادةً يكون موجوداً في headers الطلب) في قاعدة البيانات. إذا وصل طلب بنفس المعرف مرة أخرى، تجاهله ببساطة.

3. الاختبار والتطوير محلياً باستخدام Ngrok

أكبر تحدٍ في تطوير الـ Webhooks هو أنها تتطلب عنوان URL عام يمكن الوصول إليه من الإنترنت، بينما خادم التطوير الخاص بك يعمل على جهازك المحلي (`localhost`).

الحل السحري: استخدم أداة مثل Ngrok. هذه الأداة الصغيرة تنشئ نفقاً آمناً بين جهازك المحلي والإنترنت، وتعطيك عنوان URL عام مؤقت (مثل `https://random-string.ngrok.io`). يمكنك استخدام هذا العنوان في إعدادات الـ Webhook في GitHub أو Stripe، وستصلك الطلبات مباشرة إلى `localhost` الخاص بك. إنها أداة لا غنى عنها لكل مطور يتعامل مع الـ Webhooks.

الخلاصة: من الاستجداء إلى الاستجابة 🚀

بالعودة إلى قصتنا، بعد أن حولنا نظام تحديث المخزون من Polling إلى Webhooks، كانت النتائج مذهلة وفورية. استهلاك المعالج على خوادمنا انخفض بنسبة تزيد عن 80%. لم نعد نرى تلك الطلبات اللانهائية التي تسد الشبكة. والأهم من ذلك، أصبح تحديث المخزون فورياً وحقيقياً. انتقلنا من نموذج “الاستجداء” البطيء والمكلف، إلى نموذج “الاستجابة” الفعال والأنيق.

نصيحتي الأخيرة لكل مطور: في المرة القادمة التي تحتاج فيها إلى بيانات من خدمة خارجية، قبل أن تكتب `setInterval`، توقف لحظة واسأل نفسك: “هل هذه الخدمة تدعم Webhooks؟”. إذا كانت الإجابة “نعم”، فاستخدمها دون تردد. دع خوادمك ترتاح وتتفاعل مع الأحداث عند وقوعها، ولا تجعلها تستجدي المعلومات طوال الوقت.

أتمنى أن تكون هذه المقالة قد أفادتكم. بالتوفيق في مشاريعكم القادمة!

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

برمجة وقواعد بيانات

كانت استعلاماتنا تزحف كالسلحفاة: كيف أنقذنا ‘فهرس قاعدة البيانات’ من جحيم البحث الكامل في الجداول (Full Table Scan)؟

أشارككم قصة حقيقية عن يوم كادت فيه استعلاماتنا البطيئة أن تدمر مشروعًا بالكامل، وكيف كان "الفهرس" البسيط في قاعدة البيانات هو طوق النجاة. سنتعلم معًا...

15 مايو، 2026 قراءة المزيد
الحوسبة السحابية

كانت بيئاتنا تتغير كالحرباء: كيف أنقذتنا ‘البنية التحتية كشيفرة’ (IaC) من جحيم التكوين اليدوي؟

أروي لكم حكايتي كـ "أبو عمر"، مبرمج فلسطيني، مع الفوضى التي كنا نعيشها في إدارة الخوادم يدوياً. سأشارككم كيف كانت 'البنية التحتية كشيفرة' (IaC) وأداة...

15 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كانت مقابلاتي التقنية فشلاً ذريعاً: كيف أنقذتني ‘المقابلات الوهمية’ من جحيم الرفض المتكرر؟

بعد سلسلة من المقابلات الفاشلة التي كادت أن تدمر ثقتي بنفسي، اكتشفت سلاحاً سرياً غيّر قواعد اللعبة. هذه قصتي مع "المقابلات الوهمية" (Mock Interviews)، وكيف...

15 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

كانت خوادمنا تموت بالتناوب: كيف أنقذنا ‘موازنة الأحمال’ من جحيم النقاط الفردية للفشل؟

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

15 مايو، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

كان فريقنا يخشى الفشل: كيف أنقذتنا ‘ثقافة ما بعد الوفاة بلا لوم’ من جحيم إخفاء الأخطاء؟

بتذكر ليلة ما انهار نظامنا بالكامل، وكيف تحولت غرفة الدردشة لساحة محاكمة. في هذه المقالة، أسرد لكم يا جماعة كيف انتقلنا من ثقافة اللوم المدمرة...

15 مايو، 2026 قراءة المزيد
أدوات وانتاجية

كانت ذاكرتي هي عنق الزجاجة: كيف أنقذتني أدوات تدوين الملاحظات للمبرمجين (Obsidian) من جحيم البحث في Google؟

كمبرمج، كنت أعيش في حلقة مفرغة من البحث عن نفس المعلومات مرارًا وتكرارًا. في هذه المقالة، أشارككم قصتي وكيف ساعدتني أداة مثل Obsidian في بناء...

15 مايو، 2026 قراءة المزيد
البودكاست