أول Servlet لك
أول Servlet لك
الـ Servlet هو فئة Java تعيش داخل حاوية ويب (كـ Tomcat أو Jetty)، تستقبل طلبات HTTP وتكتب استجابات HTTP. أنت تعرف بالفعل كيف تصمم الفئات وتتعامل مع الاستثناءات وتُهيكل برامج Java — الـ Servlet مجرد فئة متخصصة تتوصّل بالبنية التحتية للحاوية. يشرح هذا الدرس بالضبط ما يعنيه ذلك، بدءًا من البنية الدنيا للفئة وصولًا إلى التعليقات التوضيحية (annotations) التي تُسجّل الـ Servlet مع الحاوية.
الفئة الأساسية HttpServlet
كل Servlet يتعامل مع HTTP يمتد من jakarta.servlet.http.HttpServlet. هذه الفئة المجردة هي جزء من مواصفة Jakarta Servlet (التي كانت تُعرف سابقًا بـ javax.servlet) وتوفّر الإطار الذي تُعيد تعريفه بدلًا من بنائه من الصفر. تبدو هيكلية الفئات كما يلي:
تتولى GenericServlet الربط بالحاوية: تخزّن ServletConfig، وتُنفّذ getServletName()، وتوفر تطبيقات افتراضية فارغة لتوابع دورة الحياة. أما HttpServlet فتُضيف الإرسال الخاص بـ HTTP: يفحص تابعها service() طريقة الطلب (GET، POST، PUT، إلخ) ويستدعي التابع doXxx() المناسب في فئتك الفرعية. لا تستدعي service() أبدًا بشكل مباشر — الحاوية هي من تفعل ذلك.
javax.servlet إلى jakarta.servlet. يستخدم Tomcat 10+ وأي خادم Jakarta EE 10/11 فضاء الأسماء jakarta.*. أما خوادم Tomcat 9 / Java EE 8 الأقدم فلا تزال تستخدم javax.*. تحقق دائمًا من الإصدار الذي تستهدفه حاويتك واستورد وفقًا لذلك.
التعليق التوضيحي @WebServlet
قبل وجود التعليقات التوضيحية، كان عليك الإعلان عن كل Servlet في web.xml بـ XML مطوّل. منذ Servlet 3.0، أصبح التعليق التوضيحي @WebServlet على الفئة نفسها هو الأسلوب المُفضَّل — إذ يُبقي ربط URL بجانب الكود الذي يتعامل معه.
لنفكّك خصائص التعليق التوضيحي:
name— معرّف منطقي تستخدمه الحاوية داخليًا (ويُستخدم فيgetServletName()). اختياري؛ يُعيَّن افتراضيًا إلى الاسم الكامل للفئة.urlPatterns— نمط URL واحد أو أكثر تتعامل معه هذه الـ Servlet. يُطابق النمط/helloالمسارhttp://host:port/yourApp/helloبالضبط. يمكنك أيضًا استخدام أنماط البادئة (/api/*) أو ربط الامتدادات (*.do).- الصيغة المختصرة
@WebServlet("/hello")(سلسلة واحدة) تعادلurlPatterns = "/hello"وهي الشكل الأكثر شيوعًا في الممارسة.
@WebServlet نسبي إلى جذر سياق التطبيق، لا إلى جذر الخادم. إذا نشرت myapp.war، فإن النمط /hello يُحلَّل إلى /myapp/hello. أثناء التطوير مع IDE أو إضافة Maven، غالبًا ما تضبط جذر السياق على / للإبقاء على عناوين URL قصيرة.
تابع doGet بالتفصيل
تستدعي الحاوية doGet عند استقبال طلب HTTP GET المطابق لنمط URL الخاص بالـ Servlet. يمنحك المعاملان كل ما تحتاجه للتعامل مع الطلب:
HttpServletRequest request— يُغلّف رسالة HTTP الواردة. يمنحك الوصول إلى معاملات URL والترويسات وجسم الطلب والكوكيز والجلسة والإعداد الإقليمي وعنوان IP للعميل.HttpServletResponse response— يُغلّف رسالة HTTP الصادرة. يتيح لك ضبط رمز الحالة وترويسات الاستجابة وكتابة جسم الاستجابة إما عبرPrintWriter(نص) أوServletOutputStream(بيانات ثنائية).
يُعلن توقيع التابع عن throws IOException. تُعلن HttpServlet.doGet أيضًا عن throws ServletException — يمكنك إضافتها أيضًا لكن يجب استيراد jakarta.servlet.ServletException. يبدو التعريف الواقعي كما يلي:
لاحظ النمط المؤلف من ثلاث خطوات: قراءة المدخلات → تطبيق المنطق → كتابة المخرجات. إن الحفاظ على هذا الفصل يجعل الـ Servlet سهل الاختبار: منطق الأعمال ينتمي إلى فئات خدمة Java بسيطة، لا داخل الـ Servlet نفسه. المهمة الوحيدة للـ Servlet هي ترجمة HTTP إلى استدعاءات توابع، وترجمة نتائج التوابع مجددًا إلى HTTP.
قراءة كائن الطلب
تعرض واجهة HttpServletRequest طلب HTTP الكامل. أكثر التوابع استخدامًا في معالج doGet:
request.getParameter("key")— يُعيد القيمة الأولى لمعامل سلسلة الاستعلام أو النموذج، أوnullإذا كان غائبًا.request.getParameterValues("key")— يُعيد جميع قيم معامل متعدد القيم (كمربعات الاختيار) كـString[].request.getHeader("Accept-Language")— يُعيد ترويسة طلب مُسمّاة.request.getMethod()— يُعيد"GET"أو"POST"إلخ.request.getRequestURI()— الجزء المسار من URL، مثلًا/myapp/hello.request.getAttribute("key")/request.setAttribute("key", value)— تخزين نطاق الطلب المُستخدم عند التوجيه إلى JSP أو Servlet آخر.
getParameter() سلاسل خام من المتصفح. تحقق دائمًا من صحة أي قيمة تُضمّنها في الاستجابة وعقّمها وترمّز HTML لها. إن إهمال ذلك هو السبب الجذري لثغرات البرمجة النصية عبر المواقع (XSS). على الأقل، اهرب من < و> و& قبل كتابة السلاسل التي يوفرها المستخدم في مخرجات HTML.
كتابة كائن الاستجابة
قبل كتابة جسم الاستجابة يجب استدعاء response.setContentType(). يضبط هذا ترويسة HTTP Content-Type حتى يعرف المتصفح كيف يفسّر البايتات. القيم الشائعة:
"text/html;charset=UTF-8"— صفحة HTML."application/json;charset=UTF-8"— استجابة واجهة برمجية JSON."application/octet-stream"— تنزيل ملف ثنائي.
يجب ضبط نوع المحتوى قبل استدعاء getWriter() أو getOutputStream(). بمجرد بدء كتابة الجسم، تُلتزم الترويسات (تُرسل إلى العميل) ولا يمكن تغييرها بعد ذلك.
للاستجابات النصية استخدم response.getWriter() الذي يُعيد PrintWriter. للاستجابات الثنائية (الصور وملفات PDF وملفات ZIP) استخدم response.getOutputStream() الذي يُعيد ServletOutputStream. يمكنك الحصول على أحدهما فقط لكل طلب — استدعاء الاثنين يرمي IllegalStateException.
مثال كامل قابل للتشغيل
إليك Servlet مكتفية بذاتها تُحيّي زائرًا بالاسم وتُوضح جميع المفاهيم أعلاه بطريقة واقعية:
يُنتج طلب GET إلى /greet?name=Alice صفحة HTML بـ 200 OK تقول "مرحبًا، Alice!". يستقبل طلب بدون المعامل استجابة خطأ 400 Bad Request — يتولّاها المحرّف الافتراضي للأخطاء في الحاوية.
ما تفعله الحاوية نيابةً عنك
من المفيد أن نكون واضحين بشأن العمل الذي تؤدّيه الحاوية (Tomcat أو Jetty أو WildFly إلخ) تلقائيًا:
- تُحلّل بايتات TCP الخام إلى كائن
HttpServletRequest. - تُنشئ فئة الـ Servlet الخاصة بك مرة واحدة وتستدعي
init(). - تُخصص خيطًا من تجمّعها لكل طلب وارد وتستدعي
doGetالخاصة بك على ذلك الخيط. - تبني ترويسات استجابة HTTP مما ضبطته على
HttpServletResponseوتُسلسل الجسم إلى المقبس. - تستدعي
destroy()عند إلغاء نشر التطبيق.
هذا تقسيم العمل — الحاوية تتعامل مع سباكة البروتوكول، وكودك يتعامل مع منطق التطبيق — هو العقد الأساسي لمواصفة Servlet وأساس كل إطار عمل ويب Java مبني فوقها.
الخلاصة
الـ Servlet هي فئة تمتد من HttpServlet وتُعيد تعريف توابع doXxx المطابقة لأفعال HTTP التي تتعامل معها. يُسجّلها التعليق التوضيحي @WebServlet مع الحاوية ويُعلن نمط URL. يعرض المعامل HttpServletRequest كل تفاصيل الطلب الوارد؛ بينما يُتيح لك المعامل HttpServletResponse التحكم في كل تفاصيل الرد. أبقِ كود الـ Servlet ضئيلًا — اقرأ، وفوّض، وردّ — وضع المنطق الحقيقي في فئات Java بسيطة. في الدرس القادم ستطّلع على دورة الحياة الكاملة: كيف تُنشئ الحاوية الـ Servlet وتُهيّئها وتدمّرها في نهاية المطاف.