أساسيات جافا للويب والـ Servlets

معالجة النماذج: GET مقابل POST

18 دقيقة الدرس 7 من 13

معالجة النماذج: GET مقابل POST

النماذج هي الطريقة الرئيسية التي يرسل بها المستخدمون البيانات إلى الخادم. سواء أكان صندوق تسجيل الدخول أم حقل البحث أم صفحة الدفع متعددة الخطوات، فإن كل نموذج HTML يوصل بياناته في النهاية على شكل طلب HTTP. يتناول هذا الدرس بالتفصيل كيفية عمل هذه الآلية، ولماذا يهم الاختيار بين GET وPOST، وكيف تقرأ خادمتك (servlet) قيم الحقول المُرسلة وتتحقق منها وتستخدمها بأمان.

كيف ترسل نماذج HTML البيانات

لكل نموذج HTML سمتان أساسيتان: method وaction. السمة action هي عنوان URL الذي يستقبل عملية الإرسال؛ أما method فتكون إما get أو post. عندما ينقر المستخدم على زر الإرسال، يحزّم المتصفح جميع حقول الإدخال المسمّاة في طلب HTTP ويرسله إلى ذلك العنوان.

<!-- نموذج تسجيل موجَّه إلى /app/register --> <form method="post" action="${pageContext.request.contextPath}/register"> <label>اسم المستخدم: <input type="text" name="username" required /> </label> <label>البريد الإلكتروني: <input type="email" name="email" required /> </label> <label>كلمة المرور: <input type="password" name="password" required /> </label> <button type="submit">تسجيل</button> </form>

يصبح كل <input name="..."> معاملًا في الطلب. سمة name هي المفتاح وقيمة الحقل هي القيمة. حقول الإدخال المتعددة التي تحمل الاسم ذاته تنتج قيمًا متعددة لذلك المفتاح — وهو ما تستخدمه خانات الاختيار (checkboxes) والقوائم متعددة التحديد تمامًا.

GET مقابل POST: الفرق الحقيقي

كلا الأسلوبين يوصلان المعاملات، لكنهما يختلفان في موضع البيانات وظهورها ودلالتهما.

  • GET يُلحق المعاملات بعنوان URL كسلسلة استعلام: /search?q=servlet&page=2. البيانات مرئية في شريط العنوان، ومحفوظة في سجل المتصفح، وقابلة للإشارة المرجعية (bookmark)، وقابلة للتخزين المؤقت. طلبات GET لا تغيّر الحالة — تكرارها لا يسبب أي تأثير جانبي.
  • POST يضع المعاملات في جسم طلب HTTP بعيدًا عن العنوان. لا إشارات مرجعية ولا تخزين مؤقت افتراضيًا. طلبات POST مخصصة لتغيير الحالة — إنشاء سجل، تقديم طلب، تسجيل الدخول.
القاعدة الدلالية: استخدم GET للاستعلامات والتنقل (البحث، التصفية، التصفح بالصفحات). استخدم POST للتعديلات (الإنشاء، التحديث، الحذف، المصادقة). هذا ليس مجرد أسلوب — فالمتصفحات تُعيد إرسال نماذج POST بنافذة تأكيد عند الضغط على زر "رجوع" أو "تحديث"، تحديدًا لأن POST يُفهم على أنه ذو تأثيرات جانبية.

doGet و doPost في خادمتك

يستدعي حاوي الخوادم (servlet container) التابعَ doGet لطلبات GET والتابعَ doPost لطلبات POST. تجاوز (override) أي من التابعين الذي يستخدمه نموذجك. خادمة تتعامل مع نموذج بحث (GET) ونموذج إرسال (POST) على عنوان URL ذاته تتجاوز كليهما:

import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/register") public class RegisterServlet extends HttpServlet { /** عرض النموذج الفارغ */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher("/WEB-INF/views/register.jsp").forward(req, resp); } /** معالجة النموذج المُرسَل */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String email = req.getParameter("email"); String password = req.getParameter("password"); // التحقق ثم التوجيه if (username == null || username.isBlank()) { req.setAttribute("error", "اسم المستخدم مطلوب."); req.getRequestDispatcher("/WEB-INF/views/register.jsp").forward(req, resp); return; } // ... حفظ في قاعدة البيانات ... // نمط PRG: إعادة التوجيه بعد نجاح POST resp.sendRedirect(req.getContextPath() + "/dashboard"); } }

قراءة معاملات الطلب

توفر واجهة HttpServletRequest عدة توابع للوصول إلى البيانات المُرسلة:

  • getParameter(name) — يُرجع القيمة الأولى (أو الوحيدة) لاسم الحقل كـ String، أو null إن لم يُرسَل المعامل أصلًا.
  • getParameterValues(name) — يُرجع String[] بجميع القيم لذلك الاسم (خانات الاختيار والقوائم متعددة التحديد).
  • getParameterMap() — يُرجع Map<String, String[]> غير قابل للتعديل يحتوي على كل معامل في الطلب، مفيد للتكرار والتسجيل.
// قيمة واحدة String city = req.getParameter("city"); // قيم متعددة من مجموعة خانات اختيار String[] interests = req.getParameterValues("interests"); if (interests != null) { for (String interest : interests) { System.out.println("اهتمام: " + interest); } } // كل المعاملات (للتسجيل/التصحيح) req.getParameterMap().forEach((key, values) -> System.out.println(key + " = " + String.join(", ", values)) );
لا تثق أبدًا في المدخلات الخام. يُرجع getParameter() بالضبط ما أرسله العميل. تحقق دائمًا من الطول والصيغة والنطاق قبل استخدام أي قيمة. واهرب دائمًا من السلاسل التي يوفرها المستخدم بترميز HTML قبل كتابتها في الاستجابة — فعكس المدخلات غير المُهرَّبة مباشرةً يُعدّ ثغرة Cross-Site Scripting (XSS).

معالجة الحقول الرقمية والاختيارية

يصل كل معامل كـ String. التحويل مسؤوليتك، وقد يرمي استثناءات:

String ageParam = req.getParameter("age"); int age; try { age = Integer.parseInt(ageParam); // NumberFormatException إن كان فارغًا أو غير رقمي if (age < 0 || age > 150) throw new IllegalArgumentException("العمر خارج النطاق"); } catch (NumberFormatException | IllegalArgumentException e) { req.setAttribute("error", "يرجى إدخال عمر صحيح."); req.getRequestDispatcher("/WEB-INF/views/form.jsp").forward(req, resp); return; }

نمط Post-Redirect-Get (PRG)

بعد نجاح عملية POST، أعد التوجيه دائمًا — ولا تُوجّه (forward) إلى صفحة نجاح. إن وجّهت، يظل عنوان URL في المتصفح يشير إلى نقطة نهاية POST. وعندما يُحدّث المستخدم الصفحة، يُعيد المتصفح إرسال النموذج مما قد ينشئ سجلات مكررة أو يُكرر عملية دفع. إعادة التوجيه تغير عنوان URL في المتصفح إلى GET آمن، فتحديث الصفحة يُعيد تحميل صفحة النتيجة فحسب.

// في نهاية doPost الناجح: resp.sendRedirect(req.getContextPath() + "/orders/confirmation?id=" + newOrderId);
نمط PRG ليس اختياريًا في الإنتاج. كل موقع تجارة إلكترونية، وكل تدفق تسجيل دخول، وكل نموذج يكتب بيانات يجب أن يستخدمه. ستراه أيضًا تحت مسمى "redirect after post". طبّقه افتراضيًا؛ لا تتجاهله إلا لسبب مقصود (مثل صفحة تقدم رفع ملف يجب أن تبقى على نفس العنوان).

ترميز المحارف

إذا احتوى نموذجك على محارف غير ASCII (عربية أو صينية أو حروف لاتينية منقوطة) فيجب إخبار الحاوي بالترميز المستخدم في جسم الطلب قبل استدعاء getParameter للمرة الأولى:

@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); // يجب أن تكون السطر الأول قبل أي getParameter resp.setContentType("text/html; charset=UTF-8"); String name = req.getParameter("name"); // مُفكَّك الترميز بشكل صحيح الآن // ... }

في التطبيق الحقيقي، اضبط الترميز مرة واحدة في فلتر خادمة (servlet filter) بدلًا من تكراره في كل doPost. الحاويات الحديثة (Tomcat 10+ وJakarta EE 10) تستخدم UTF-8 افتراضيًا، لذا قد تكون هذه الخطوة مُعالَجة مسبقًا — لكن تحقق بدلًا من الافتراض.

الخلاصة

نماذج HTML ترسل البيانات كمعاملات HTTP. استخدم GET للعمليات الآمنة غير المغيِّرة للحالة، وPOST للعمليات التي تغيّر الحالة. تجاوز doGet وdoPost في خادمتك للتعامل مع كل أسلوب. اقرأ الحقول بـgetParameter وgetParameterValues؛ تحقق دائمًا من المدخلات وعقّمها قبل الاستخدام. أنهِ كل POST ناجح بإعادة توجيه (نمط PRG) لمنع الإرسال المزدوج. اضبط ترميز UTF-8 قبل قراءة المعاملات حين يقبل نموذجك مدخلات دولية.