مقالات معمّقة حول التكنولوجيا التي تشكّل المستقبل.

IRCv3: دروس في تطوير البروتوكولات مع الحفاظ على التوافق

كيف طوّر IRCv3 بروتوكولاً عمره أكثر من 35 عاماً دون أن يكسره: التفاوض على القدرات، ووسوم الرسائل، والتطور المتوافق مع الإصدارات السابقة.

جهاز تلغراف قديم متصل بهاتف ذكي حديث على مكتب عبر كابل متوهج
خدعة IRCv3: ما زال تنسيق السلك القديم من 1988 يعمل، لكن العملاء الحديثين يحصلون على تجربة حديثة.

في عام 1988، كتب طالب جامعي فنلندي بروتوكول دردشة لا يتجاوز بضع صفحات. وبعد قرابة أربعة عقود، ما زال هذا البروتوكول نفسه، أي IRC، ينقل ملايين الرسائل يومياً، بينما يعلّمنا فريق عمل IRCv3 بهدوء كيف نطوّر بروتوكولاً دون أن نحرق قاعدة المستخدمين المثبتة. إذا سبق لك أن حاولت إصدار نسخ من واجهة برمجة تطبيقات يعتمد عليها غرباء، فإن IRCv3 دراسة حالة تستحق أن تستفيد منها.

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

الخطيئة الأصلية: قدرات لا يمكنك الاستعلام عنها

لم يكن في بروتوكول IRC الأساسي، كما ورد في RFC 1459 ثم RFC 2812 لاحقاً، أي آلية تتيح للعميل أن يسأل الخادم عمّا يدعمه. لا شيء على الإطلاق. تتصل، ترسل NICK وUSER، ثم تخمّن. هل تريد معرفة ما إذا كان الخادم يدعم وضعاً معيناً أو امتداداً ما؟ أرسله وانتظر لترى إن كان سيعود بخطأ. هذا النوع من الأمور يبدو طريفاً حتى تصون مكتبة عملاء عليها أن تعالج سلوكيات خاصة لخمس تطبيقات خادم مختلفة، كل منها يفسّر أجزاء الموصفة الغامضة بطريقة مختلفة قليلاً.

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

كان حل IRCv3 هو التفاوض على القدرات، وهو بسيط لدرجة تكاد تكون محرجة. أثناء تسجيل الاتصال، يرسل العميل CAP LS فيرد الخادم بقائمة من القدرات المسماة التي يدعمها، مثل sasl وserver-time وmessage-tags. ثم يرسل العميل CAP REQ بالمجموعة الجزئية التي يريدها بالضبط، فيؤكد الخادم، ويمضي الطرفان بفهم مشترك وصريح لما تدعمه الجلسة. العميل القديم الذي لا يرسل CAP يحصل ببساطة على السلوك القديم. والخادم القديم الذي لا يفهم CAP يعيد خطأً يمكن للعميل تجاهله بأمان والرجوع إلى الوضع الافتراضي.

-> CAP LS 302
<- CAP * LS :sasl=PLAIN,EXTERNAL server-time message-tags echo-message
-> CAP REQ :sasl server-time
<- CAP * ACK :sasl server-time
-> AUTHENTICATE PLAIN
<- AUTHENTICATE +
-> AUTHENTICATE dXNlcgB1c2VyAHBhc3N3b3Jk
<- 900 jane jane!jane@host jane :You are now logged in

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

الدرس: قابلية التوسع أفضل من الأوامر الجديدة

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

الحل كان إضافة قسم جديد واختياري في بداية الرسالة: @key=value;key2=value2 قبل البادئة. والأهم أن الخوادم ترسل الوسوم فقط للعملاء الذين تفاوضوا على قدرة message-tags، فالعملاء القدامى لا يرون بايتاً واحداً لا يستطيعون تحليله. هذا ما تبدو عليه رسالة بطابع زمني فعلياً على السلك:

@time=2024-03-14T09:26:00.000Z;account=jane :jane!jane@host PRIVMSG #rust :borrow checker finally clicked for me

هذه هي الحركة المعمارية نفسها التي نجدها في ترويسات HTTP أو بيانات gRPC الوصفية أو تسامح protobuf مع الحقول غير المعروفة: تحجز نقطة امتداد عامة، فتتوقف الميزات عن الحاجة إلى جراحة في البروتوكول. امتداد server-time، الذي يحل مشكلة الـ bouncer القديمة حيث تبدو كل الرسائل المعادة وكأنها وصلت للتو، ليس سوى وسم واحد. ومسودة +reply، التي تتيح للعملاء ربط الردود برسائل محددة، وسم آخر. لا أوامر جديدة. لا أرقام خطأ جديدة. لا يوم إلغاء للإصدارات.

ارتكبت الخطأ المعاكس بنفسي. قبل سنوات، في نظام pub/sub داخلي، أضفنا ميزة بتحميل حقل JSON ببادئة سحرية، لأن تغيير تنسيق المغلف «كان أكثر من اللازم». بعد ثمانية أشهر، كنا نصون تعبيراً نمطياً بثلاث لغات لفك تلك البادئة، وتعامل محلل أحد الفرق مع النص السحري كبيانات حرفية، مما أنتج حادثة لا تُنسى. حقل بيانات وصفية بسيط وصريح كان سيكلفنا يوماً واحداً. النص السحري كلّفنا أرباعاً من السنة. وسوم الرسائل في IRCv3 هي ما كان ينبغي أن يملكه ذلك النظام منذ البداية.

مغلفات على خط ناقل، بعضها يحمل ملصقات بيانات وصفية صغيرة وملونة
وسوم الرسائل: بيانات وصفية اختيارية تُرفق بكل رسالة، ولا يراها العملاء الذين لم يطلبوها.

الدرس: التخلص من حل كلمة المرور المكشوفة

اسأل أي شخص شغّل عميل IRC في الألفينيات كيف كان يتم تسجيل الدخول، وستحصل على الجواب نفسه: تكتب /msg NickServ IDENTIFY password. كانت بيانات اعتمادك تُرسل كرسالة دردشة عادية، ظاهرة في السجلات، وتُلصق أحياناً في قناة عامة عندما يخطئ العميل في الأمر، وهي طقس عبور مألوف لدرجة أنه صار نكتة. كانت المصادقة مُلصقة على طبقة التطبيق لأن البروتوكول لم يكن يعرف مفهومها أصلاً.

أصلح IRCv3 هذا بشكل صحيح عبر توحيد مصادقة SASL أثناء التسجيل. بدلاً من الدردشة مع روبوت بعد الاتصال، يتفاوض العميل على قدرة sasl ويتحقق من هويته ضمن المصافحة، قبل أن يكون له اسم مستعار يراه أحد. يعرف الخادم هويتك من أول رسالة، وهذا يؤثر في كل شيء بعد ذلك: يستطيع وسم account-tag إرفاق اسم حسابك بكل رسالة، ويستطيع الروبوتات اتخاذ قرارات الصلاحيات دون اللجوء إلى استدلالات خاصة لتحليل NickServ، ويستطيع العملاء بناء تدفقات تسجيل لا تتطلب قراءة جدار من نصوص NOTICE.

الدرس الأوسع: عندما يصبح الحل البديل شائعاً عالمياً، يتوقف عن كونه حلاً بديلاً ويصير بروتوكولك الفعلي، دون أي عناية تصميمية. لم يكن NickServ موجوداً في أي RFC، لكنه شكّل سلوك العملاء لعقدين، بما في ذلك أنماط فشله. تحويل ما كان الناس يفعلونه أصلاً (المصادقة عند الاتصال) إلى آلية حقيقية (SASL، التي تتضمن PLAIN وEXTERNAL لشهادات العميل ومساحة لغيرها) حوّل الموروث الشفهي إلى بنية تحتية. إذا كان مستخدموك جميعاً يؤدون الرقصة المحرجة نفسها لإنجاز شيء ما، فهذا بند مواصفات قادم، وليس سؤالاً شائعاً في الدعم.

الدرس: التاريخ مشكلة بروتوكول لا مشكلة عميل

لمعظم عمر IRC، كان البروتوكول لحظياً بصرامة: إن لم يكن عميلك متصلاً عند إرسال رسالة، فتلك الرسالة لم توجد بالنسبة لك. كانت إجابة المجتمع هي الـ bouncer، وهو وكيل متصل بشكل دائم (ZNC هو المثال الكلاسيكي) يعمل على VPS، يحفظ جلستك ويعيد تشغيل ما فاتك. كانت الـ bouncers تعمل، لكنها كانت قطعة بنية تحتية أخرى تحتاج إلى تشغيل، وبيانات اعتماد أخرى تحتاج إلى إدارة، وتجربة إعادة التشغيل كانت سيئة: كل شيء يصل مختوماً بوقت الإعادة، ومرتباً بشكل خاطئ مقارنة بما رأيته مباشرة.

هاجم IRCv3 هذه المشكلة على طبقات. أولاً جعل server-time تاريخ الرسائل المعادة قابلاً للاستخدام، بحمل الطابع الزمني الحقيقي للاستلام. ثم أعطت مسودة chathistory العملاء طريقة قياسية لطلب نطاقات من التاريخ، وبدأت خوادم مثل Ergo بتخزين تاريخ القنوات مباشرة. والنتيجة، كما اكتشف كثيرون عند تشغيل خادم Ergo صغير لأصدقائهم، مذهلة: تتصل من هاتفك بعد أسبوع من الغياب فيعيد الخادم تشغيل كل شيء بأوقات صحيحة، دون bouncer ولا حاجة لمراقبة VPS. إنها أكبر قفزة في جودة الحياة حققها IRC على الإطلاق، وقد جاءت من البروتوكول، لا من حيلة ذكية في العميل.

هناك حجة مضادة تستحق الجدية، ويطرحها مستخدمو IRC اليوميون: معظم الخشونة المتصورة في IRC تأتي من جانب العميل، ويمكنك اليوم الحصول على تجربة جيدة بعميل مناسب وbouncer. وهذا صحيح، لكنه يثبت النقطة من الاتجاه الآخر. كل مؤلف عميل أعاد تنفيذ تجميع سجل التمرير وتتبع التمييز وإزالة التكرار في الإعادة بأخطاء دقيقة مختلفة. نقل الجزء الصعب إلى البروتوكول والخادم يعني أنه يُنفذ مرة واحدة بشكل صحيح، ويستفيد منه كل عميل. نتخذ القرار نفسه باستمرار في تطوير الويب: ادفع الثوابت إلى المنصة (فكّر في كم من كود التطبيقات اختفى عندما صارت المتصفحات تدعم fetch أصلياً، أو عندما أصبح TLS أمراً أساسياً) بدلاً من أن تترك آلاف قواعد الشيفرة تحلها نصف حلول.

الدرس: الميزات الاختيارية تخلق واقعاً متباعداً

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

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

إذا كنت تبني شيئاً قابلاً للتوسع، فخصص لهذا ميزانية من اليوم الأول. بعض التخفيفات التي استقر عليها IRCv3 والأنظمة المشابهة:

  • صمّم التراجعات بقصد. يجب أن ينزل وسم الرد إلى سطر مقتبس قابل للقراءة، لا أن يختفي. إذا كان البديل غير قابل للاستخدام، فالميزة ليست جاهزة للشحن كخيار اختياري.
  • انشر مصفوفة الدعم وحافظ على صدقها. جداول العملاء والخوادم في IRCv3 تتيح للمستخدمين اختيارات مدروسة، والصمت يولّد لوماً يُوجَّه إلى البرنامج الخطأ.
  • حدد خط أساس وروّجه. الإصدارات المرقمة (3.1 و3.2) تجمع القدرات معاً، فيستطيع العملاء استهداف «دعم 3.2» بدلاً من أحد عشر علماً فردياً. امنح نظامك البيئي أرضية يتقارب إليها الجميع.
  • أنهِ صلاحية مسوداتك. مسودة تُشحن في الخوادم ولا تُنهى أبداً، كما كان حال تاريخ القنوات، أسوأ من عدم وجود مسودة، لأنها تفرّع السلوك دون أن تلتزم به.

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

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

ماذا يعني هذا لواجهة برمجتك أو بروتوكولك القادم

بعيداً عن حنين الدردشة، IRCv3 دليل مضغوط لتطوير أي شيء له تنسيق سلكي. هذه هي القائمة التي أسلّمها لأي فريق يبدأ بروتوكولاً أو مخطط أحداث، أو حتى واجهة REST عامة:

  1. اشحن التفاوض أولاً. تبادل القدرات في النسخة الأولى، حتى لو كانت قائمة القدرات فارغة. إضافة الاكتشاف لاحقاً أصعب بعشر مرات.
  2. احجز نقطة امتداد. وسوم، أو ترويسات، أو خرائط بيانات وصفية، أي فتحة عامة واحدة تستطيع الميزات المستقبلية التعلق بها دون لمس القواعد الأساسية.
  3. اجعل السلوك الجديد اختيارياً لكل اتصال. يجب أن يتلقى العملاء القدامى السلوك القديم مطابقاً بايتاً ببايت. لا يرسل خادم IRCv3 وسماً لعميل لم يطلبه.
  4. وحّد الموروث الشفهي. أي حيلة استقر عليها مستخدموك (مصادقة بأسلوب NickServ، أو إعادة تشغيل بأسلوب الـ bouncer) هي مواصفة تنتظر أن تُكتب بشكل صحيح.
  5. خطط لمصفوفة الدعم. الميزات الاختيارية تتباعد، فقرر كيف تبدو التجارب المتراجعة، وانشر من يدعم ماذا.
  6. اجمع التقدم في معالم مسماة. الأعلام الفردية للمطورين، والإصدارات المرقمة للبشر.

ما يثير إعجابي حقاً هو الاعتدال. لم يسعَ IRCv3 إلى التشفير من طرف لطرف، ولا إلى إعادة تصميم الاتحاد، ولا إلى أي إعادة كتابة كبرى. اختار المشكلات الصغيرة الحادة، وهي الاكتشاف والبيانات الوصفية والمصادقة والوقت، وحلّها بطرق يستطيع عميل من عام 1995 تجاهلها. لهذا يبقى IRC تقريباً الناجي الوحيد من جيله من بروتوكولات الإنترنت، ما زال يعمل إلى حد كبير كما صُمم، ولهذا يستطيع مشروع عطلة نهاية أسبوع اليوم أن يمنح مجموعة من الأصدقاء مزامنة للتاريخ وTLS وحسابات حقيقية على VPS بخمسة دولارات.

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

في المرة القادمة التي تغريك فيها فكرة إصدار واجهة برمجة بتغيير المسار من /v1 إلى /v2 وإجبار كل مستهلك على الترحيل، تذكر البروتوكول الذي سمح لعميل من عصر الاتصال الهاتفي بمشاركة القناة نفسها مع تطبيق جوال حديث. فاوض على القدرات. أرفق البيانات الوصفية بدلاً من اختراع أوامر. أبقِ الأرضية صلبة ودَع السقف يرتفع وفق جدوله الخاص. IRCv3 يثبت أنك تستطيع أن تملك بروتوكولاً يحترم أسلافه ويتعلم حيلاً جديدة، وإن استطاع نظام دردشة من عام 1988 أن يفعل ذلك، فخدمتك المصغرة عمرها ثلاث سنوات لا عذر لها.