بناء الاستجابات
بناء الاستجابات
مهمة السيرفليت هي استقبال طلب HTTP وإنشاء استجابة HTTP مُشكَّلة بشكل صحيح. ركّز الدرس السابق على تشريح الطلب؛ أما هذا الدرس فيركّز على الجانب الآخر من التبادل: كتابة جسم الاستجابة، وتعيين نوع المحتوى الصحيح، والتحكم في رموز الحالة، وإضافة رؤوس الاستجابة. الحصول على هذه الأمور الأربعة صحيحةً هو الفارق بين سيرفليت يعمل بشكل موثوق عبر المتصفحات والبروكسيات وعملاء API، وبين سيرفليت هشّ في بيئة الإنتاج.
كائن HttpServletResponse
يُسلّم الحاوي كل تابع خدمة كائن jakarta.servlet.http.HttpServletResponse. من خلاله تتحكم في كل جانب من جوانب استجابة HTTP. أهم قرارين يجب اتخاذهما قبل كتابة بايت واحد من الجسم هما: نوع المحتوى (الذي يُعيّن أيضًا ترميز المحارف) ورمز الحالة. كلاهما يجب أن يُثبَّت قبل الجسم وإلا سيُسقَط بصمت أو يُسبّب IllegalStateException.
تعيين نوع المحتوى
استدع دائمًا response.setContentType() قبل الحصول على كاتب أو دفق. يُخبر نوع المحتوى المتصفح (أو عميل API) بكيفية تفسير البايتات التي ستُرسلها.
أشيع قيم نوع المحتوى التي ستستخدمها عمليًا:
text/html; charset=UTF-8— استجابات HTML القياسيةapplication/json— استجابات REST API (يكون charset افتراضيًا UTF-8 وفق RFC 8259)text/plain; charset=UTF-8— نص عادي، مفيد للتشخيصapplication/xml— حمولات XMLapplication/octet-stream— تنزيلات الملفات الثنائية (معContent-Disposition)
setContentType("text/html; charset=UTF-8") اختصار لاستدعاء setContentType("text/html") وsetCharacterEncoding("UTF-8") معًا. إن أغفلت charset، قد تُعيّن الحاوية ISO-8859-1 افتراضيًا، مما يُفسد أي محارف غير ASCII تكتبها بصمت.
كتابة جسم الاستجابة
لديك خياران مُتبادَلان لكتابة الجسم: PrintWriter القائم على المحارف (للنصوص) أو ServletOutputStream القائم على البايتات (للثنائيات). محاولة الحصول على كليهما من الاستجابة ذاتها تُلقي IllegalStateException — تفرض الحاوية هذا بصرامة.
close() على الكاتب أو الدفق. إغلاق دفق الاستجابة داخل سيرفليتك قد يمنع الحاوية من إلحاق أي بيانات معلّقة (مثل رؤوس ملفات تعريف الارتباط للجلسة المُعيَّنة بعد إعادة الكود) ويتداخل مع إدارة اتصالات Keep-Alive. دع الحاوية تدير دورة حياة الدفق.
تعيين رمز حالة HTTP
الحالة الافتراضية هي 200 OK. لأي حالة أخرى، استدع response.setStatus(int) قبل كتابة الجسم. استخدم الثوابت المُسمَّاة في HttpServletResponse بدلًا من الأرقام الخام — فهي توثّق النية وتُقلل الأخطاء المطبعية.
الثوابت الأكثر استخدامًا — مع مقابلاتها الرقمية للمرجعية:
SC_OK(200) — استجابة ناجحة مع جسمSC_CREATED(201) — أنشأ طلب POST موردًا؛ اقرنه برأسLocationSC_NO_CONTENT(204) — نجاح بلا جسم (مثل DELETE)SC_MOVED_PERMANENTLY(301) — إعادة توجيه دائمة؛ استخدمsendRedirectللحالة 302SC_BAD_REQUEST(400) — أرسل العميل بيانات مشوّهةSC_UNAUTHORIZED(401) — مطلوب مصادقةSC_FORBIDDEN(403) — تمت المصادقة لكن لا توجد صلاحيةSC_NOT_FOUND(404) — المورد غير موجودSC_INTERNAL_SERVER_ERROR(500) — خطأ غير معالَج في الخادم
resp.sendError(404, "Not found") رمز الحالة ويُشغّل آلية صفحة الخطأ للحاوية (أي تعيينات error-page في web.xml أو @WebServlet). أما setStatus فيضبط الرمز فحسب ويتيح لك كتابة جسمك. بالنسبة لواجهات API، فضّل setStatus مع جسم خطأ JSON. بالنسبة لتطبيقات HTML، فضّل sendError ليُقدّم الحاوي صفحات الخطأ المُهيَّئة.
إضافة رؤوس الاستجابة
إلى جانب سطر الحالة ونوع المحتوى، تحمل استجابات HTTP رؤوسًا تتحكم في التخزين المؤقت والأمان وإعادة التوجيه وغير ذلك. استخدم response.setHeader(name, value) لقيمة واحدة أو addHeader(name, value) لإلحاق قيم إضافية لرأس يسمح بالتعدد.
النمط العملي: بناء استجابة JSON لواجهة API
بجمع كل شيء معًا، إليك تابع مساعد واقعي تستخرجه كثير من الفرق في فئة سيرفليت أساسية:
يُقلّل تمركُز هذه الأسطر الأربعة خطر نسيان ترميز المحارف أو رأس Cache-Control في أي نقطة نهاية.
التخزين المؤقت وتثبيت الاستجابة
تُخزّن الحاوية مخرجات الاستجابة مؤقتًا قبل إرسالها. طالما لم يُفرَّغ المخزن، يمكنك تعديل الرؤوس والحالة. بمجرد أن يُفرَّغ المخزن — سواء امتلأ، أو استدعيت flushBuffer()، أو ثُبّتت الاستجابة — تُقفَل الرؤوس. أي استدعاء لاحق لـ setStatus أو setHeader سيُتجاهل بصمت.
يمكنك الاستعلام عن حجم المخزن وتعديله باستخدام resp.getBufferSize() وresp.setBufferSize(int). زيادته قليلًا (مثلًا إلى 16 كيلوبايت) مفيدة حين تحتاج إلى تحديد الحالة النهائية بعد بعض العمل قبل التثبيت.
الخلاصة
بناء استجابة HTTP صحيحة يتطلب أربعة أشياء تعمل معًا: نوع المحتوى الصحيح (يُعيَّن قبل الكتابة)، وترميز المحارف الصحيح (UTF-8 ما لم يكن لديك سبب محدد خلاف ذلك)، ورمز الحالة الصحيح (استخدم ثوابت SC_*)، وأي رؤوس يحتاجها العميل لتفسير الاستجابة أو تخزينها مؤقتًا. عيّن دائمًا الحالة والرؤوس قبل كتابة الجسم، ولا تُغلق الكاتب بنفسك أبدًا، وفضّل المساعدات المُمركَزة على تشتيت هذه الأسطر الأربعة عبر كل معالج. يجمع الدرس التالي كل شيء من الدرسين الخامس والسادس لمعالجة نماذج HTML بكل من GET وPOST.