الحالة فوق بروتوكول عديم الحالة
الحالة فوق بروتوكول عديم الحالة
تعتمد كل تطبيقات الويب الحديثة على فكرة أن الخادم يتذكّر شيئًا ما عن المستخدم عبر طلبات متعددة — سواء كان المستخدم مسجّل الدخول، أو ما يحتويه سلة مشترياته، أو اللغة التي يُفضّلها. غير أن HTTP، البروتوكول الذي يحمل كل تلك الطلبات، صُمِّم بشكل مقصود ليكون عديم الحالة (Stateless). إن فهم التوتر بين هذا الاختيار التصميمي والحاجة الفعلية للاستمرارية هو أساس هذا البرنامج التعليمي كله.
لماذا HTTP عديم الحالة؟
حين يُرسل المتصفح طلب GET إلى /dashboard، لا يضمن مواصفات HTTP/1.1 أي شيء بشأن ما يعرفه الخادم مسبقًا عن ذلك العميل. يصل كل طلب على هيئة رسالة مستقلة بذاتها تحتوي على ترويسات وجسم اختياري — لكن دون هوية مُدمَجة. وما إن يُرسل الخادم استجابته، فهو حرٌّ في نسيان أن هذا التبادل قد وقع.
كان هذا اختيارًا تصميميًا مقصودًا لا خطأً في الإهمال. فاختارت قيود REST لروي فيلدينج (ومواصفة HTTP/1.0 السابقة لها) انعدام الحالة لأنه يُقدّم ثلاثة فوائد هندسية ملموسة:
- قابلية التوسع: يستطيع أي خادم في مجموعة (cluster) معالجة أي طلب لأنه لا توجد ذاكرة خاصة بالعميل داخل عملية الخادم. يمكن لموزّع الحمل أن يُوجّه الطلبات بحرية تامة.
- الموثوقية: إذا تعطّل الخادم، يعمل إعادة المحاولة من العميل إلى نسخة مختلفة بشكل صحيح — فلا توجد جلسة بحاجة إلى إعادة بناء.
- الشفافية: يستطيع وسيط (وكيل، CDN، أداة مراقبة) فهم الطلب بالكامل من الرسالة وحدها، دون الحاجة إلى سياق سابق.
المشكلة: التعرف على الزوار العائدين
تأمّل سير عمل تسجيل دخول بسيط. يُرسل المستخدم بيانات الاعتماد عبر POST إلى /login، يتحقق الخادم منها، والآن يجب أن يُوصّل "هذا العميل مصادَق عليه" في كل طلب لاحق. لكن الطلب التالي — مثلًا GET /orders — يصل دون أي رابط متأصّل بعملية تسجيل الدخول. فكيف يربط الخادم بين الاثنين؟
ثمة أربع استراتيجيات كلاسيكية. لكل منها مفاضلات متمايزة تتعلق بالأمان وقابلية التوسع وتعقيد التنفيذ.
الاستراتيجية الأولى: جلسات HTTP (حالة جانب الخادم)
يُولّد الخادم رمزًا غامضًا يصعب تخمينه — هو معرّف الجلسة (Session ID) — ويخزّن خريطة من معرّفات الجلسات إلى بيانات المستخدم في ذاكرته الخاصة (أو في مخزن مشترك كـ Redis). يُرسَل الرمز إلى المتصفح الذي يُعيد إرساله في كل طلب لاحق.
لا يرى المتصفح معرّف المستخدم الفعلي أبدًا — فقط رمز الجلسة العشوائي (يُسلَّم عادةً عبر كوكي JSESSIONID). يبقي هذا البيانات الحساسة على جانب الخادم، وهي ميزة أمنية كبيرة. الجانب السلبي أن الخادم يجب أن يحتفظ بهذه الحالة وأن يشاركها عبر العقد في النشر المُجمَّع.
الاستراتيجية الثانية: الكوكيز (حالة جانب العميل)
بدلًا من الاحتفاظ بالبيانات على الخادم، رمِّزها مباشرةً في كوكي يخزّنه المتصفح ويُعيد إرساله. ترويسة استجابة Set-Cookie تزرع الكوكي؛ وكل طلب لاحق إلى نفس النطاق يتضمّن تلقائيًا ترويسة Cookie تحتويه.
تُعدّ الكوكيز مثالية للتفضيلات غير الحساسة (اللغة، المظهر) لكن لا ينبغي أبدًا أن تحمل بيانات سرية بنص صريح. تخزين الكوكيز محدود (~4 كيلوبايت لكل كوكي) والعميل يتحكم في قبولها من عدمه.
الاستراتيجية الثالثة: إعادة كتابة عنوان URL
حين تكون الكوكيز معطّلة، يمكن تضمين رمز الجلسة مباشرةً في عناوين URL كمعامل استعلام: /orders?jsessionid=abc123. تتعامل Servlet API مع هذا بشفافية عبر response.encodeURL():
Referer. استخدمه فقط كبديل عند انعدام الكوكيز الفعلي، ولا تستخدمه أبدًا على مسارات HTTPS التي تتعامل مع بيانات حساسة.
الاستراتيجية الرابعة: الرموز (مصادقة عديمة الحالة)
تتجنّب واجهات API الحديثة في أحيان كثيرة الجلسات على جانب الخادم كليًا. بدلًا من ذلك، يُصدر الخادم بعد تسجيل الدخول رمزًا موقَّعًا — في الغالب رمز JSON Web Token (JWT) — يخزّنه العميل (في localStorage أو كوكي آمن) ويُرسله في كل طلب، عادةً كترويسة Bearer.
بما أن الرمز يكتفي بنفسه ويُتحقق منه تشفيريًا، فالخادم لا يحتفظ بأي حالة جلسة على الإطلاق. هذا يُتيح التوسع الأفقي المثالي. المفاضلة: لا يمكن إلغاء الرموز قبل انتهاء صلاحيتها دون إدخال قائمة رفض على جانب الخادم، مما يُعيد الحالة من جديد.
مقارنة الاستراتيجيات
- جلسات HTTP: الأسهل تنفيذًا في Jakarta EE، قابلة للإلغاء فوريًا، لكنها تحتاج لتخزين حالة مشترك في البيئات المُجمَّعة.
- الكوكيز: رائعة للتفضيلات منخفضة الحساسية، يتعامل معها المتصفح تلقائيًا، لكنها محدودة الحجم وقابلة للسرقة إن لم تُؤمَّن.
- إعادة كتابة URL: بديل مستقل عن الكوكيز، لكنه يكشف الرموز في السجلات والسجل — استخدمه بتحفظ ولفترة قصيرة.
- JWT / الرموز: عديم الحالة بطبيعته وصديق للمجموعات، لكن الإلغاء وحجم الرمز تحدّيان عمليان. هو السائد في سياق REST API والخدمات المصغّرة.
HttpSession مدعومًا بمخزن موزّع (Memcached، Redis، Infinispan). أما واجهة REST API النقية التي تخدم تطبيق JavaScript SPA فتميل نحو JWT. كثير من الأنظمة الحقيقية تستخدم الاثنين: جلسة لواجهة الويب، ورموز لطبقة API.
ما توفّره Jakarta Servlet API
تتعامل مواصفة Servlet (Jakarta Servlet 6.x) مع الجلسات والكوكيز على مستوى الحاوي. يدير الحاوي — Tomcat أو WildFly أو GlassFish — ترويسات الكوكيز وانتهاء صلاحية الجلسات وإعادة كتابة URL بشفافية تامة. يستدعي كودك واجهات برمجية نظيفة:
request.getSession()— الحصول علىHttpSessionأو إنشاؤهاsession.setAttribute(String, Object)— تخزين أي كائن قابل للتسلسلresponse.addCookie(Cookie)— إرشاد المتصفح لتخزين كوكيresponse.encodeURL(String)— إلحاق معرّف الجلسة بعنوان URL عند الحاجة
يتعمق الدرس التالي في HttpSession. لكن الفكرة الجوهرية التي يجب أن تحملها معك هي: كل سلوك "يحتفظ بحالة" تراه في تطبيق ويب مبني فوق بروتوكول عديم الحالة بطبيعته، باستخدام أحد هذه الآليات الأربع. معرفة متى تستخدم كل واحدة — وفهم الآثار الأمنية لكل منها — هو ما يُميّز مطوري الويب المحترفين عمّن يكتفون بنسخ كود الجلسة دون تفكير.
الخلاصة
HTTP عديم الحالة بتصميم مقصود، مما يمنحك قابلية التوسع والبساطة على حساب تتبع الهوية المُدمَج. الاستراتيجيات الأربع لسد هذه الفجوة هي: الجلسات على جانب الخادم، الكوكيز، إعادة كتابة URL، والرموز الموقَّعة (JWT). تجعل Servlet API في Jakarta EE تنفيذ الجلسات والكوكيز أمرًا مباشرًا؛ وبقية هذا البرنامج التعليمي يستكشف كل آلية بعمق، بدءًا من HttpSession.