Skip to content

10. تطبيق الويب MVC [personne] – الإصدار 5

10.1. مقدمة

في هذا الإصدار، قمنا بإجراء تعديلين:

الأول يتعلق بالطريقة التي يستخدمها العميل لإبلاغ الخادم بالإجراء الذي يرغب في القيام به. حتى الآن، كان يتم تحديد ذلك باستخدام معلمة تسمى [action] في طلب GET أو POST من العميل. هنا، سيتم تحديد الإجراء من خلال العنصر الأخير في عنوان URL الذي يطلبه العميل كما هو موضح في التسلسل التالي:

Image

في [1]، عنوان URL الذي تم إرسال النموذج إليه هو [/personne5/do/validationFormulaire]. العنصر الأخير [validationFormulaire] من عنوان URL هو الذي سمح للمتحكم بالتعرف على الإجراء المطلوب. في [2]، تم تنفيذ POST الناتج عن الرابط [Retour au formulaire] في عنوان URL [/personne5/do/retourFormulaire]. ومرة أخرى، يشير العنصر الأخير [retourFormulaire] في عنوان URL إلى وحدة التحكم بالإجراء المطلوب.

نقوم بإدخال هذا التعديل لأن هذه هي الطريقة المستخدمة من قبل أطر عمل تطوير الويب الأكثر انتشارًا مثل Struts أو Spring MVC.

ستكون جميع عناوين URL للتطبيق بالشكل [/personne5/do/action]. سيشير الملف [web.xml] الخاص بالتطبيق [/personne5] إلى أن هذا التطبيق يقبل عناوين URL بالشكل [/do/*]:


    <servlet-mapping>
        <servlet-name>personne</servlet-name>
        <url-pattern>/do/*</url-pattern>
</servlet-mapping>

سيسترد وحدة التحكم اسم الإجراء المطلوب تنفيذه بالطريقة التالية:

        // استرداد الإجراء المطلوب تنفيذه
String action=request.getPathInfo();

تقدم الطريقة [getPathInfo] الخاصة بالكائن [request] العنصر الأخير من عنوان URL للطلب.

التعديل الثاني يتعلق بطريقة تخزين المدخلات التي يقوم بها المستخدم بين دورتين من الطلب/الاستجابة. في الوقت الحالي، يتم تخزين هذه المعلومات في جلسة عمل. قد تنطوي هذه الطريقة على عيوب إذا كان هناك عدد كبير من المستخدمين وكمية كبيرة من البيانات التي يجب تخزينها لكل منهم. في الواقع، لكل مستخدم جلسة عمل خاصة به. بالإضافة إلى ذلك، تظل هذه الجلسة نشطة لفترة معينة بعد مغادرة المستخدم ما لم يتم توفير خيار تسجيل الخروج له. وبالتالي، ستشغل 1000 جلسة بحجم 1000 بايت 1 ميغابايت من الذاكرة. ويظل هذا متطلبًا معتدلًا، وهناك القليل من التطبيقات التي لديها 1000 جلسة نشطة في وقت واحد.

ومع ذلك، هناك بدائل للجلسة، أقل تكلفة من حيث الذاكرة، ومن الجيد معرفتها. سنستخدم هنا طريقة ملفات تعريف الارتباط. لنوضح ذلك بمثال.


الخطوة 1: يقوم المستخدم بتأكيد نموذج:


يؤدي دورة الطلب/الاستجابة هذه إلى التبادلات التالية HTTP بين العميل والخادم:

[1] : [demande du client]

POST /personne5/do/validationFormulaire HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/personne5/do/formulaire
Cookie: JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

txtNom=pauline&txtAge=18

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

[2]: [réponse du serveur]

1
2
3
4
5
6
7
HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: nom=pauline
Set-Cookie: age=18
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 547
Date: Mon, 22 May 2006 08:03:51 GMT

نرى في السطرين 3 و 4 أن الرؤوس HTTP [Set-Cookie] قد أُرسلت إلى متصفح العميل، واحدة للاسم (السطر 3) والأخرى للعمر (السطر 4). قيم ملفات تعريف الارتباط هذه هي القيم التي تم إرسالها في السطر 14 من POST [1] أعلاه.


الخطوة 2: العودة إلى النموذج


Image

يؤدي دورة الطلب/الاستجابة هذه إلى التبادلات التالية HTTP بين العميل والخادم:

[1]: [demande du client]

POST /personne5/do/retourFormulaire HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/personne5/do/validationFormulaire
Cookie: nom=pauline; age=18; JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

نشهد هنا POST الناتج عن النقر على الرابط [Retour au formulaire]. في السطر 11، نرى أن المتصفح يعيد إلى الخادم ملفات تعريف الارتباط التي تلقاها [nom, age, JSESSIONID] عن طريق رأس Http [Cookie]. هذا هو مبدأ ملفات تعريف الارتباط. يعيد العميل إلى المتصفح ملفات تعريف الارتباط التي أرسلها إليه. في هذا المثال، سيتلقى وحدة التحكم القيم [pauline, 18] التي يجب أن يضعها في الحقول [txtNom, txtAge] في العرض [formulaire] المعروض في [2].

[2]: [réponse du serveur]

1
2
3
4
5
HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2341
Date: Mon, 22 May 2006 08:16:47 GMT

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

10.2. مشروع Eclipse

لإنشاء مشروع Eclipse [mvc-personne-05] الخاص بتطبيق الويب [/personne5]، سنقوم بنسخ مشروع [mvc-personne-04] باتباع الإجراء الموضح في الفقرة 6.2.

10.3. تكوين تطبيق الويب [personne5]

الملف web.xml لتطبيق /personne5 هو التالي:


<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>mvc-personne-05</display-name>
    <!--  ServletPersonne -->
    <servlet>
        <servlet-name>personne</servlet-name>
        <servlet-class>
            istia.st.servlets.personne.ServletPersonne
        </servlet-class>
...
    </servlet>
    <!--  تعيين ServletPersonne-->
    <servlet-mapping>
        <servlet-name>personne</servlet-name>
        <url-pattern>/do/*</url-pattern>
    </servlet-mapping>
    <!--  ملفات الصفحة الرئيسية -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

هذا الملف مطابق لملف الإصدار السابق باستثناء بعض التفاصيل:

  • السطر 6: تغير اسم عرض تطبيق الويب إلى [mvc-personne-05]
  • السطر 18: عناوين URL التي يعالجها التطبيق هي من النوع [/do/*]. في السابق، كان يتم معالجة عنوان URL [/main] فقط. الآن، لدينا عدد من عناوين URL يساوي عدد الإجراءات المطلوب معالجتها.

تتغير الصفحة الرئيسية [index.jsp]:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/do/formulaire"/>
  • السطر 5: الصفحة [index.jsp] تعيد توجيه العميل إلى عنوان URL [/personne5/do/formulaire]، وهو ما يعادل مطالبة وحدة التحكم بتنفيذ الإجراء [formulaire].

10.4. كود العروض

لا تتغير طرق العرض [formulaire, réponse, erreurs] كثيرًا. التغيير الوحيد هو أن الإجراء المطلوب لم يعد محددًا بنفس الطريقة السابقة، حيث كان يُعرَّف في حقل مخفي باسم [action] في النماذج المرسلة. أما الآن، فقد تم تعريفها في عنوان URL الهدف للنماذج المرسلة، c.a.d، في السمة [action] لعلامة <form>:

[formulaire.jsp]:


...
<html>
  <head>
    <title>Personne - formulaire</title>
    <script language="javascript">
...
    </script>
  </head>
  <body>
    <center>
      <h2>Personne - formulaire</h2>
      <hr>
      <form name="frmPersonne" action="validationFormulaire" method="post">
...
      </form>
    </center>
  </body>
</html>
  • السطر [13]: يعود المعلم [action] الخاص بالنموذج للظهور بعد أن اختفى لفترة في الإصدارات السابقة. لفهم قيمة هذا السمة هنا، يجب تذكر أن جميع عناوين URL التي يعالجها التطبيق تكون على شكل [/do/action]. السطر [13]، قيمة السمة [action] هي عنوان URL نسبي (لا يبدأ بـ /). لذلك سيقوم المتصفح باستكمالها بعنوان URL للصفحة المعروضة حاليًا، وبالتالي سيكون عنوان URL بالضرورة على الشكل [/do/action]. سيتم استبدال العنصر الأخير بعنوان URL النسبي للسمة [action] في العلامة <form> للحصول على عنوان URL [/do/validationFormulaire] كهدف لـ POST.
  • اختفى الحقل المخفي [action]

[réponse.jsp]:


...

<html>
...
  <body>
      ...
    <form name="frmPersonne" action="retourFormulaire" method="post">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      ${lienRetourFormulaire}
    </a>
  </body>
</html>

  • السطر [7]: سيكون الهدف لـ POST هو [/do/retourFormulaire]
  • اختفى الحقل المخفي [action] في النموذج في السطرين 7-8.

[erreurs.jsp]:


...
<html>
...
  <body>
...
    <form name="frmPersonne" action="retourFormulaire" method="post">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      ${lienRetourFormulaire}
    </a>
  </body>
</html>

  • السطر [6]: سيكون هدف POST هو [/do/retourFormulaire]
  • اختفى الحقل المخفي [action] من النموذج في السطرين 6-7.

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

10.5. وحدة التحكم [ServletPersonne]

سيعالج وحدة التحكم [ServletPersonne] الخاصة بتطبيق الويب [/personne5] الإجراءات التالية:

رقم
الطلب
الأصل
المعالجة
1
[GET /personne5/do/formulaire]
رابط URL الذي أدخله المستخدم
- إرسال عرض [formulaire] فارغ
2
[POST
/personne5/do/validationFormulaire]
مع المعلمات [txtNom, txtAge]
المنشورة
النقر على الزر
[Envoyer] في العرض
[formulaire]
- التحقق من قيم المعلمات [txtNom, txtAge]
- إذا كانت غير صحيحة، أرسل العرض [erreurs(erreurs)]
- إذا كانت صحيحة، أرسل العرض [reponse(nom,age)]
3
[POST
/personne5/do/retourFormulaire]
بدون معلمات مرسلة
انقر على الرابط [العودة إلى
النموذج] من المشاهدات
[réponse] و [erreurs].
- إرسال العرض [formulaire] مملوءًا مسبقًا بأحدث القيم التي تم إدخالها

هيكل وحدة التحكم [ServletPersonne] مطابق للنسخة السابقة. نستعرض التعديلات التي تم إجراؤها على الطرق [doValidationFormulaire, doRetourFormulaire, doGet]، حيث لم تتغير الطرق [init, doInit, doPost].

10.5.1. الطريقة [doGet]

لا تسترد الطريقة [doGet] الإجراء المطلوب تنفيذه بنفس الطريقة المتبعة في الإصدارات السابقة:

        @SuppressWarnings("unchecked")
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        // نتحقق من كيفية إجراء تهيئة السيرفلت
        if (erreursInitialisation.size() != 0) {
...
        }
        // استرداد طريقة إرسال الطلب
        String méthode=request.getMethod().toLowerCase();
        // استرداد الإجراء المطلوب تنفيذه
        String action=request.getPathInfo();
        // الإجراء؟
        if(action==null){
            action="/formulaire";
        }
        // تنفيذ الإجراء
        if(méthode.equals("get") && action.equals("/formulaire")){
            // بدء تشغيل التطبيق
            doInit(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/validationFormulaire")){
            // التحقق من صحة نموذج الإدخال
            doValidationFormulaire(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/retourFormulaire")){
            // العودة إلى نموذج الإدخال
            doRetourFormulaire(request,response);
            return;
        }
        // حالات أخرى
        doInit(request,response);
    }
  • السطر 12: يتم استرداد الإجراء المطلوب تنفيذه. وهو بالصيغة [/action].
  • الأسطر 18-22: معالجة الإجراء [/formulaire] المطلوب بواسطة طلب GET
  • الأسطر 23-27: معالجة الإجراء [/validationFormulaire] المطلوب بواسطة طلب POST
  • الأسطر 28-32: معالجة الإجراء [/retourFormulaire] المطلوب بواسطة طلب POST

10.5.2. الطريقة [doValidationFormulaire]

تعالج هذه الطريقة الطلب رقم 2 [POST /personne5/do/validationFormulaire] مع [txtNom, txtAge] في العناصر المرسلة. رمزها هو التالي:

// التحقق من صحة النموذج
    void doValidationFormulaire(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
        // استرداد المعلمات
        String nom = request.getParameter("txtNom");
        String age = request.getParameter("txtAge");
        // التي يتم تخزينها في ملف تعريف ارتباط
        response.addCookie(new Cookie("nom",nom));
        response.addCookie(new Cookie("age",age));
        // التحقق من المعلمات
        ...
    }

المستجدات:

  • ترسل الطريقة [doValidationFormulaire] كاستجابة إحدى طرق العرض [réponse, erreurs]. بغض النظر عن هذا الرد، يضع المتحكم ملفين من ملفات تعريف الارتباط فيه، السطران 8-9. يتم تمثيل ملف تعريف الارتباط بواسطة كائن [Cookie] الذي يقبل المنشئ له معلمتين، مفتاح ملف تعريف الارتباط والقيمة المرتبطة به.
  • السطر 8: يتم وضع القيمة التي تم إدخالها للاسم في ملف تعريف ارتباط بمفتاح "name"
  • السطر 9: يتم وضع القيمة التي تم إدخالها للعمر في ملف تعريف ارتباط بمفتاح "age"
  • يتم إضافة ملف تعريف ارتباط إلى الاستجابة HTTP المرسلة إلى العميل باستخدام الطريقة [response.addCookie]. هذه الاستجابة هي مجرد استجابة مُعدة مسبقًا في هذه المرحلة. ولن يتم إرسالها فعليًا إلا عند تنفيذ الصفحة JSP الخاصة بالعرض المرسلة إلى العميل.

10.5.3. الطريقة [doRetourFormulaire]

تعالج هذه الطريقة الطلب رقم 2 [POST /personne5/do/retourFormulaire] بدون عناصر مرسلة. ورمزها هو التالي:

        // عرض النموذج المملوء مسبقًا
    void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // يتم استرداد ملفات تعريف الارتباط الخاصة بالمستخدم
        Cookie[] cookies=request.getCookies();
        String nom=null;
        String age=null;
        int nbCookies=0;
        for(int i=0;i<cookies.length && nbCookies<2;i++){
            if(cookies[i].getName().equals("nom")){
                nom=cookies[i].getValue();
                nbCookies++;
            }else{
                if(cookies[i].getName().equals("age")){
                    age=cookies[i].getValue();
                    nbCookies++;
                }
            }
        }
        // إعداد نموذج النموذج
        request.setAttribute("nom",nom);
        request.setAttribute("age",age);
        // عرض النموذج
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

المستجدات:

يجب أن تعرض الطريقة [doRetourFormulaire] نموذجًا مملوءًا مسبقًا بآخر الإدخالات التي تمت. في الإصدار السابق، كانت هذه الإدخالات موجودة في الجلسة. في هذا الإصدار، لم نعد نستخدم الجلسة بل نستخدم ملفات تعريف الارتباط لتخزين العناصر بين تبادلين بين العميل والخادم. عندما طلب العميل التحقق من صحة النموذج، تلقى ردًا في شكل عرض [réponse] أو [erreurs] حسب الحالة، مصحوبًا بملفين من ملفات تعريف الارتباط (cookies) بعنوان "name" و"age". عند النقر على الرابط [Retour au formulaire] في هاتين العرضتين، مما يؤدي إلى ظهور POST على عنوان URL [/do/retourFormulaire]، يقوم المتصفح بإعادة إرسال ملفات تعريف الارتباط التي تلقّاها إلى الخادم.

  • الأسطر 4-18: يتم استرداد قيم ملفات تعريف الارتباط المسماة "name" و"age". ومن الغريب أنه لا توجد طريقة تسمح بالحصول على قيمة ملف تعريف الارتباط من مفتاحه. لذلك، نضطر إلى مراجعة كل ملف من ملفات تعريف الارتباط المستلمة.
  • وبعد ذلك، يتم وضع القيمتين اللتين تم الحصول عليهما في نموذج العرض [formulaire] (السطور 20-21) حتى يتم عرضهما.

10.6. الاختبارات

قم بتشغيل أو إعادة تشغيل Tomcat بعد دمج مشروع Eclipse [personne-mvc-05] فيه، ثم اطلب عنوان URL [http://localhost:8080/personne5].