Skip to content

3. مقدمة إلى سيرفلتات Java وصفحات JSP

يحتوي هذا الفصل على أمثلة متنوعة لـ servlets وصفحات JSP. وقد تم اختبارها باستخدام خادم Tomcat، الذي يعمل على المنفذ 8080. من خلال اتباع الروابط الموجودة على الصفحة الرئيسية، يمكنك الوصول إلى أمثلة لـ servlets وصفحات JSP. معظم الأمثلة أدناه مأخوذة من أمثلة Tomcat. لاختبارها، ما عليك سوى تشغيل Tomcat، وإدخال عنوان URL http://localhost:8080 في متصفح، واتباع رابط السيرفلت.

3.1. برمجيات الخدمة (Java Servlets)

3.1.1. إرسال محتوى HTML إلى عميل ويب

دعونا ندرس مثال "Hello World" أعلاه. فيما يلي نص السيرفلت:


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class HelloWorld extends HttpServlet {
 
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

عند تنفيذ هذا السيرفلت، يتم عرض ما يلي:

Image

لاحظ النقاط التالية:

  • يجب استيراد فئات خاصة للبرمجيات الخادمة:

import javax.servlet.*;
import javax.servlet.http.*;

لا يتم تضمين مكتبة javax.servlet دائمًا بشكل افتراضي مع JDK. في هذه الحالة، يمكنك تنزيلها مباشرةً من موقع Sun على الويب.

  • تقوم السيرفلت بتوسيع فئة HttpServlet

public class HelloWorld extends HttpServlet {
  • يتم معالجة طلب GET الموجه إلى السيرفلت بواسطة طريقة doGet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException

  • وبالمثل، يتم التعامل مع طلب POST الموجه إلى السيرفلت بواسطة طريقة doPost

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • كائن الطلب HttpServletRequest هو الكائن الذي يتيح لنا الوصول إلى الطلب المقدم من عميل الويب. سيتم إرسال استجابة السيرفلت عبر كائن الاستجابة HttpServletResponse
  • يسمح لنا كائن الاستجابة بتعيين رؤوس HTTP التي سيتم إرسالها إلى العميل. على سبيل المثال، يتم تعيين رأس Content-Type: text/html هنا بواسطة:
        response.setContentType("text/html");
  • لإرسال الاستجابة إلى العميل، يستخدم السيرفلت دفق إخراج يوفره كائن الاستجابة:
        PrintWriter out = response.getWriter();
  • بمجرد الحصول على تيار الإخراج هذا، يتم كتابة كود HTML إليه ومن ثم إرساله إلى العميل:
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");

3.1.2. استرداد المعلمات المرسلة من عميل الويب

يوضح المثال التالي كيف يمكن لـ servlet استرداد المعلمات المرسلة من عميل الويب. نموذج إدخال:

Image

الاستجابة المرسلة من قبل السيرفلت:

Image

فيما يلي شفرة مصدر السيرفلت:


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class myRequestParamExample extends HttpServlet {
 
    String title="Récupération des paramètres d'un formulaire";
 
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }
 
    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }
 
}

لاحظ التغييرات التالية مقارنة بالمثال السابق:

  • يتم استرداد المعلمات المرسلة من المتصفح على النحو التالي:

        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");

تُرجع الطريقة request.getParameter("parameterName") مؤشرًا فارغًا إذا لم تكن المعلمة "parameterName" من بين المعلمات المرسلة من قبل عميل الويب.

  • يحدد النموذج أن المتصفح يجب أن يرسل المعلمات باستخدام طريقة POST
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
  • سيتم معالجة المعلمات المستلمة بواسطة طريقة doPost الخاصة بالسيرفلت. هنا، تقوم هذه الطريقة ببساطة باستدعاء طريقة doGet. وبالتالي، يقوم هذا السيرفلت بمعالجة قيم النموذج بغض النظر عما إذا تم إرسالها عبر GET أو POST.

    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }

3.1.3. استرداد رؤوس HTTP المرسلة من قبل عميل الويب

يوضح السيرفلت التالي كيفية استرداد رؤوس HTTP المرسلة من عميل الويب:

Image

فيما يلي شفرة المصدر للبرنامج الخادم:


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class RequestHeaderExample extends HttpServlet {
 
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println(name + " = " + value);
        }
    }
}

نقاط يجب ملاحظتها:

  • إن كائن `request` وطريقة `getHeaderNames` الخاصة به هما اللذان يتيحان لنا الوصول إلى رؤوس HTTP المرسلة من المتصفح في شكل قائمة:
        Enumeration e = request.getHeaderNames();
  • تسمح لك طريقة request.getHeader("header") باسترداد رأس HTTP محدد. يقدم المثال أعلاه بعضًا منها. ضع في اعتبارك أن الرؤوس المعروضة هنا يتم إرسالها بواسطة المتصفح. كما أن للخادم رؤوس HTTP خاصة به، والتي تعكس أحيانًا تلك الخاصة بالمتصفح. تهدف رؤوس HTTP التي يرسلها المتصفح إلى إعلام الخادم بقدرات المتصفح.
رأس
المعنى
User-Agent
هوية المتصفح
Accept
أنواع MIME التي يقبلها المتصفح. على سبيل المثال، image/gif تعني أن المتصفح يمكنه التعامل مع الصور بتنسيق GIF
المضيف
بالتنسيق host:port. يشير إلى الجهاز والمنفذ اللذين يريد المتصفح الاتصال بهما.
Accept-Encoding
تنسيق الترميز الذي يقبله المتصفح للوثائق المرسلة من الخادم. وبالتالي، إذا كان لدى الخادم وثيقة بتنسيق عادي غير مضغوط وأخرى بتنسيق gzip، وأشار المتصفح إلى أنه يمكنه التعامل مع تنسيق gzip، فيمكن للخادم إرسال الوثيقة بتنسيق gzip لتوفير النطاق الترددي.
Accept-language
اللغات التي يقبلها المتصفح. إذا كان لدى الخادم نفس المستند بعدة لغات، فسيرسل النسخة باللغة التي يقبلها المتصفح.
المُحيل
عنوان URL الذي طلبه المتصفح
Connection
وضع الاتصال الذي يطلبه المتصفح. تعني عبارة "Keep-alive" أن الخادم يجب ألا يغلق الاتصال بعد تقديم الصفحة المطلوبة إلى المتصفح. إذا اكتشف المتصفح أن الصفحة المستلمة تحتوي على روابط لصور، على سبيل المثال، فيمكنه إرسال طلبات جديدة إلى الخادم لاستردادها دون الحاجة إلى إنشاء اتصال جديد. سيقوم المتصفح بعد ذلك بمبادرة إغلاق الاتصال بمجرد استلامه لجميع عناصر الصفحة.

3.1.4. استرداد معلومات البيئة

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

Image

فيما يلي كود السيرفلت:


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class RequestInfo extends HttpServlet {
 
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Request Information Example</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h3>Request Information Example</h3>");
        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());
        out.println("</body>");
        out.println("</html>");
    }
 
 
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        doGet(request, response);
    }
}

يتم الحصول على المعلومات هنا باستخدام طرق مختلفة:

        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());

فيما يلي قائمة ببعض الطرق المتاحة ومعانيها:

الطريقة
المعنى
getServerName()
اسم خادم الويب
getServerPort()
منفذ عمل خادم الويب
getMethod()
طريقة GET أو POST التي يستخدمها المتصفح لإرسال طلبه
getRemoteHost()
اسم جهاز العميل الذي أرسل المتصفح طلبه منه
getRemoteAddr()
عنوان IP لنفس الجهاز
getContentType()
نوع المحتوى الذي أرسله المتصفح (رأس HTTP Content-Type)
getContentLength()
عدد الأحرف التي أرسلها المتصفح (رأس HTTP Content-Length)
getProtocol()
إصدار بروتوكول HTTP الذي طلبه المتصفح
getRequestURI()
عنوان URI الذي طلبه المتصفح. يتوافق مع جزء عنوان URL الذي يلي معرف المضيف:المنفذ في http://hote:port/URI

3.1.5. إنشاء سيرفلت باستخدام JBuilder، ونشره باستخدام Tomcat

سنشرح الآن كيفية إنشاء وتشغيل سيرفلت Java. سنستخدم أداتين: JBuilder لتجميع السيرفلت وTomcat لتشغيله. قد يكفي استخدام Tomcat وحده. ومع ذلك، فإنه يوفر إمكانيات محدودة لتصحيح الأخطاء. سنعيد النظر في المثال الذي تم تطويره سابقًا والذي يعرض المعلمات التي يستقبلها الخادم. يرسل السيرفلت أولاً نموذج الإدخال التالي:

Image

الاستجابة المرسلة من قبل السيرفلت:

Image

فيما يلي شفرة المصدر لبرنامج الخدمة:


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class myRequestParamExample extends HttpServlet {
 
    String title="Récupération des paramètres d'un formulaire";
 
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }
 
    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }
 
}
  • أنشئ مشروعًا باسم myRequestParamExample باستخدام JBuilder وأدرج فيه البرنامج myRequestParamExample.java السابق.
  • أثناء التحويل البرمجي، قد تواجه المشكلة التالية: قد لا يحتوي JBuilder على مكتبة javax.servlet المطلوبة لتحويل برامج servlet. في هذه الحالة، تحتاج إلى تكوين JBuilder لاستخدام مكتبات فئات إضافية. يرد وصف الإجراء في ملاحق هذا المستند الخاص بـ JBuilder 7. وقد قمنا بإعادة إنتاجه جزئيًا هنا:
  • قم بتمكين الخيار Tools/Configure JDKs

Image

في قسم "إعدادات JDK" أعلاه، يُظهر حقل "الاسم" عادةً "JDK 1.3.1". إذا كان لديك إصدار أحدث من JDK، فاستخدم زر "تغيير" لتحديد دليل التثبيت الخاص به. في المثال أعلاه، حددنا الدليل E:\Program Files\jdk14 حيث تم تثبيت JDK 1.4. من الآن فصاعدًا، سيستخدم JBuilder هذا الإصدار من JDK في عمليات التحويل البرمجي والتشغيل. في قسم (Class, Source, Documentation)، سترى قائمة بجميع مكتبات الفئات التي سيقوم JBuilder بمسحها — في هذه الحالة، الفئات من JDK 1.4. الفئات المضمنة في هذا JDK ليست كافية لتطوير الويب باستخدام Java. لإضافة مكتبات فئات إضافية، استخدم زر Add وحدد ملفات .jar الإضافية التي تريد استخدامها. ملفات .jar هي مكتبات فئات. يتضمن Tomcat 4.x جميع مكتبات الفئات اللازمة لتطوير الويب. وهي موجودة في <tomcat>\common\lib، حيث <tomcat> هو دليل تثبيت Tomcat:

Image

باستخدام الزر "إضافة"، سنقوم بإضافة هذه المكتبات، واحدة تلو الأخرى، إلى قائمة المكتبات التي يقوم JBuilder بمسحها:

Image

من الآن فصاعدًا، يمكنك ترجمة برامج Java المتوافقة مع معيار J2EE، بما في ذلك Java servlets. يُستخدم JBuilder للترجمة فقط؛ بينما يتولى Tomcat التنفيذ لاحقًا.

  • يمكنك الآن ترجمة برنامج myRequestParamExample.java وإنشاء خادم myRequestParamExample.class. أين يجب وضع هذا الخادم؟ إذا لم يتم تغيير التكوين الأولي لـ Tomcat، فيجب وضع ملفات .class الخاصة بالخادم في <tomcat>\webapps\examples\WEB-INF\classes (Tomcat 4.x).
  • تأكد من تشغيل Tomcat واستخدم متصفحًا لطلب عنوان URL http://localhost:8080/examples/servlet/myRequestParamExample:

Image

3.1.6. أمثلة

في الأمثلة التالية، استخدمنا الطريقة الموضحة أعلاه:

  • ترجمة ملف المصدر XX.java الخاص بالسيرفلت باستخدام JBuilder
  • نشر السيرفلت XX.class إلى <tomcat>\webapps\examples\WEB-INF\classes
  • أثناء تشغيل Tomcat، أدخل عنوان URL http://localhost:8080/examples/servlet/XX في متصفح

3.1.6.1. إنشاء النماذج الديناميكية - 1

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

Image

إذا نقرت على "إرسال" في المثال أعلاه، فستحصل على الرد التالي:

Image

لاحظ أن عنوان URL الذي يعرض الرد هو نفسه الذي يعرض النموذج. هنا، لدينا سيرفلت يعالج الرد على النموذج الذي أرسله. هذا سيناريو شائع. كود HTML الخاص بالنموذج هو كما يلي:

<html>
    <head><title>Génération de formulaire</title></head>
    <body>
    <h3>Choississez un nombre</h3><hr>
    <form method="POST">
      <select name="cmbValeurs" size="1">
        <option>zéro</option>
        <option>un</option>
        <option>deux</option>
        <option>trois</option>
        <option>quatre</option>
        <option>cinq</option>
        <option>six</option>
        <option>sept</option>
        <option>huit</option>
        <option>neuf</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>
    </body>
</html>

لاحظ أن القيم المرسلة بواسطة النموذج يتم إرسالها باستخدام طريقة POST. كود HTML للاستجابة:

<html>
    <head><title>Voici ma réponse</title></head>
  <body>
      Vous avez choisi le nombre<h2>neuf</h2>
  </body>
</html>

فيما يلي كود السيرفلت الذي يولد هذا النموذج وهذا الرد:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class gener1 extends HttpServlet{
    // variables d'instance
    private String title="Génération d'un formulaire";
    private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
      "sept","huit","neuf"};
    private final String HTML1=
                "<html>" +
                  "<head>" +
                    "<title>Génération de formulaire</title>"+
                    "</head>" +
                  "<body>" +
                     "<h3>Choississez un nombre</h3>"+
                     "<hr>" +
                     "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\">";
        private final String HTML3="</form>\n</body>\n</html>";

    // GET
    public void doGet(HttpServletRequest request,HttpServletResponse response)
          throws IOException, ServletException{

      // on indique au client le type de document envoyé
      response.setContentType("text/html");
      // on envoie le formulaire
      PrintWriter out=response.getWriter();
            // début
      out.println(HTML1);
            // combo
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            for (int i=0;i<valeurs.length;i++){
                out.println("<option>"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // fin formulaire
            out.println(HTML2+HTML3);
    }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
          throws IOException, ServletException{

            // on récupère le choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) doGet(request,response);

            // on prépare la réponse
            String réponse="<html><head><title>Voici ma réponse</title></head>";
            réponse+="<body>Vous avez choisi le nombre <h2>"+choix+"</h2></body></html>";
            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // on envoie le formulaire
            PrintWriter out=response.getWriter();
            out.println(réponse);
    }//POST
    }//classe

تُستخدم طريقة doGet لإنشاء النموذج. هناك جزء ديناميكي، وهو محتوى القائمة، الذي يتم الحصول عليه هنا من مصفوفة. تُستخدم طريقة doPost لإنشاء الاستجابة. هنا، الجزء الديناميكي الوحيد هو قيمة اختيار المستخدم في قائمة النموذج. يتم الحصول على هذه القيمة عبر request.getParameter("cmbValeurs")، حيث cmbValeurs هو الاسم HTML للقائمة:

      <select name="cmbValeurs" size="1">

أخيرًا، لاحظ النقاط التالية:

  • يرسل المتصفح قيم النموذج إلى السيرفلت الذي أنشأ النموذج لأن العلامة <form> لا تحتوي على سمة <action>. في هذه الحالة، يرسل المتصفح البيانات التي تم إدخالها في النموذج إلى عنوان URL الذي قدمه.
  • تحدد العلامة <form> أنه يجب إرسال بيانات النموذج باستخدام طريقة POST. ولهذا السبب يتم استرداد هذه القيم بواسطة طريقة doPost الخاصة بالبرنامج الخادم.

3.1.6.2. إنشاء النماذج ديناميكيًا - 2

سنعود إلى المثال السابق ونعدله على النحو التالي. يظل النموذج كما هو:

Image

لكن الاستجابة مختلفة:

Image

في الاستجابة، يتم إرجاع النموذج، مع الإشارة إلى الرقم الذي اختاره المستخدم أسفله. علاوة على ذلك، هذا الرقم هو الذي يظهر على أنه محدد عند عرض القائمة. يمكن للمستخدم بعد ذلك اختيار رقم آخر:

Image

ثم النقر على "إرسال". ويتلقى المستخدم الرد التالي:

Image

فيما يلي كود السيرفلت المسمى gener2.java:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class gener2 extends HttpServlet{
        // variables d'instance
        private String title="Génération d'un formulaire";
        private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
            "sept","huit","neuf"};
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on récupère l'éventuel choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // on envoie le formulaire
            PrintWriter out=response.getWriter();
            // début
            out.println(HTML1);
            // combo
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // suite formulaire
            out.println(HTML2);
            if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
            // fin du formulaire
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on renvoie sur GET
            doGet(request,response);
        }//POST
    }//classe

تقوم طريقة doGet بكل شيء: فهي تبني النموذج الذي ترسله إلى العميل وتعالج القيم التي يعيدها العميل. تجدر الإشارة إلى النقاط التالية:

  • نتحقق مما إذا كان المعلمة cmbValeurs تحتوي على قيمة.
  • إذا كان الأمر كذلك، عند إنشاء محتوى القائمة، تتم مقارنة كل عنصر باختيار المستخدم لتعيين السمة selected على العنصر الذي اختاره المستخدم: <option selected>item</option>. بالإضافة إلى ذلك، يتم عرض القيمة المحددة أسفل النموذج.

3.1.6.3. إنشاء النموذج الديناميكي - 3

نحن نتعامل مع نفس المشكلة كما في السابق، ولكن هذه المرة يتم استرداد القيم من قاعدة بيانات. في مثالنا، هذه قاعدة بيانات MySQL:

  • اسم قاعدة البيانات هو dbValues
  • مالكها هو admDbValeurs وكلمة المرور هي mdpDbValeurs
  • تحتوي قاعدة البيانات على جدول واحد باسم tvaleurs
  • يحتوي هذا الجدول على حقل واحد فقط من نوع عدد صحيح يسمى value
E:\Program Files\EasyPHP\mysql\bin>mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbVa
leurs

mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs            |
+---------------------+
1 row in set (0.00 sec)

mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field  | Type    | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) |      |     | 0       |       |
+--------+---------+------+-----+---------+-------+

mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
|      0 |
|      1 |
|      2 |
|      3 |
|      4 |
|      6 |
|      5 |
|      7 |
|      8 |
|      9 |
+--------+
10 rows in set (0.00 sec)

أصبحت قاعدة بيانات MySQL dbValeurs متاحة عبر برنامج تشغيل ODBC لـ MySQL. اسم مصدر البيانات (DSN) الخاص بها هو odbc-valeurs. فيما يلي كود السيرفلت:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;

public class gener3 extends HttpServlet{
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;
        // code HTML
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // flux de sortie
            PrintWriter out=response.getWriter();

            // l'initialisation de la servlet s'est-elle bien passée ?
            if (msgErreur!=null){
                // il y a eu une erreur - on génère une page d'erreur
                out.println("<html><head><title>"+title+"</title></head>");
                out.println("<body><h3>Application indisponible ("+msgErreur+
                                        ")</h3></body></html>");
                return;
            }//if

            // on récupère l'éventuel choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

            // on envoie le formulaire
            // début
            out.println(HTML1);
            // combo
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // suite formulaire
            out.println(HTML2);
            if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
            // fin du formulaire
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on renvoie sur GET
            doGet(request,response);
        }//POST

        // initialisation de la servlet
        public void init(){
            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                // connexion à la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                // objet Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                rs=st.executeQuery("select valeur from Tvaleurs");
                // les valeurs sont récupérées et mises dans un tableau dynamique
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // on enregistre la valeur dans la liste
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                // transformation liste --> tableau
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                // problème
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
    }//classe

النقاط الرئيسية التي يجب ملاحظتها هي كما يلي:

  1. يمكن تهيئة السيرفلت بواسطة طريقة يجب أن يكون توقيعها public void init(). لا يتم تنفيذ هذه الطريقة إلا عند تحميل السيرفلت لأول مرة
  2. بمجرد التحميل، يبقى السيرفلت في الذاكرة طوال الوقت. وهذا يعني أنه بعد خدمة العميل، لا يتم إلغاء تحميله. وبالتالي يستجيب بسرعة أكبر لطلبات العميل.
  3. في السيرفلت الخاص بنا، يجب استرداد قائمة من القيم من قاعدة البيانات. ونظرًا لأن هذه القائمة لا تتغير بمرور الوقت، فإن طريقة init هي الوقت المثالي لاستردادها. وبالتالي، لا يتم الوصول إلى قاعدة البيانات إلا مرة واحدة بواسطة السيرفلت، عند تحميله لأول مرة، بدلاً من الوصول إليها مع كل طلب من العميل.
  4. قد يفشل الوصول إلى قاعدة البيانات. تحدد طريقة init في السيرفلت الخاص بنا رسالة خطأ، msgErreur، في حالة الفشل. يتم التحقق من هذه الرسالة في طريقة doGet، وإذا حدث خطأ، تقوم doGet بإنشاء صفحة تشير إلى ذلك.
  5. يستخدم تنفيذ طريقة init الوصول القياسي إلى قاعدة البيانات باستخدام برامج تشغيل ODBC-JDBC. إذا لزم الأمر، يُنصح القارئ بمراجعة طرق الوصول إلى قواعد بيانات JDBC.

عند تنفيذ السيرفلت ولم يتم تشغيل خادم MySQL، تظهر صفحة الخطأ التالية:

Image

إذا قمت الآن بتشغيل خادم MySQL، فسترى الصفحة:

Image

إذا حددت الرقم 6 ونقرت على "إرسال":

Image

3.1.6.4. استرداد القيم من نموذج

سنعود إلى مثال سبق أن رأيناه، وهو النموذج التالي على الويب:

Image

فيما يلي كود HTML للنموذج balises2.htm:

<html>

  <head>
      <title>balises</title>
    <script language="JavaScript">
        function effacer(){
          alert("Vous avez cliqué sur le bouton Effacer");
      }//effacer
        </script>
  </head>

  <body background="/images/standard.jpg">
...

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

      <table border="0">
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
              <input type="radio" value="Oui" name="R1">Oui
              <input type="radio" name="R1" value="non" checked>Non
          </td>
        </tr>
        <tr>
          <td>Cases à cocher</td>
          <td>
              <input type="checkbox" name="C1" value="un">1
              <input type="checkbox" name="C2" value="deux" checked>2
              <input type="checkbox" name="C3" value="trois">3
          </td>
        </tr>
        <tr>
          <td>Champ de saisie</td>
          <td>
              <input type="text" name="txtSaisie" size="20" value="qqs mots">
          </td>
        </tr>
        <tr>
          <td>Mot de passe</td>
          <td>
              <input type="password" name="txtMdp" size="20" value="unMotDePasse">
          </td>
        </tr>
        <tr>
          <td>Boîte de saisie</td>
          <td>
               <textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
          </td>
        </tr>
        <tr>
          <td>combo</td>
          <td>
              <select size="1" name="cmbValeurs">
                <option>choix1</option>
                <option selected>choix2</option>
                <option>choix3</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix simple</td>
          <td>
              <select size="3" name="lst1">
                <option selected>liste1</option>
                <option>liste2</option>
                <option>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix multiple</td>
          <td>
              <select size="3" name="lst2" multiple>
                <option selected>liste1</option>
                <option>liste2</option>
                <option selected>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>bouton</td>
          <td>
              <input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()">
          </td>
        </tr>
        <tr>
          <td>envoyer</td>
          <td>
              <input type="submit" value="Envoyer" name="cmdRenvoyer">
          </td>
        </tr>
        <tr>
          <td>rétablir</td>
          <td>
              <input type="reset" value="Rétablir" name="cmdRétablir">
          </td>
        </tr>
      </table>
      <input type="hidden" name="secret" value="uneValeur">
    </form>
  </body>
</html>

تم تعريف العلامة <form> في النموذج على النحو التالي:

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

سيقوم المتصفح بـ"إرسال" قيم النموذج إلى عنوان URL http://localhost:8080/examples/servlet/parameters، وهو عنوان URL لـservlet يديره Tomcat ويعرض القيم من النموذج السابق. إذا قمنا باستدعاء servlet المعلمات مباشرةً، فسنحصل على النتائج التالية:

Image

إذا كان النموذج balises2.htm الذي تم إدخاله هو هذا:

Image

وإذا نقرت على زر "إرسال"، فسيتم استدعاء خدمة servlet هذه المرة مع المعلمات. ثم تعرض الاستجابة التالية:

Image

يحتوي هذا الرد بوضوح على القيم التي تم إدخالها في النموذج. فيما يلي كود السيرفلت:

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class parameters extends HttpServlet{
    // variables d'instance
    String title="Récupération des paramètres d'un formulaire";

    private String getParameter(HttpServletRequest request, String contrôle){
      // rend la valeur request.getParameter(contrôle) ou "" si elle n'existe pas
      String valeur=request.getParameter(contrôle);
      if(valeur==null) return ""; else return valeur;
    }//getParameter

    // GET
    public void doGet(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
      // on commence par récupérer les paramètres du formulaire
      String R1=getParameter(request,"R1");
      String C1=getParameter(request,"C1");
      String C2=getParameter(request,"C2");
      String C3=getParameter(request,"C3");
      String txtSaisie=getParameter(request,"txtSaisie");
      String txtMdp=getParameter(request,"txtMdp");
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");
      String cmbValeurs=getParameter(request,"cmbValeurs");
      String lst1=getParameter(request,"lst1");
      String[] lst2=request.getParameterValues("lst2");
      String secret=getParameter(request,"secret");

      // on indique le contenu du document
        response.setContentType("text/html");
      // on envoie le document
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        out.println("<hr>");
        out.println("<table border=\"1\">");
        out.println("<tr><td>R1</td><td>"+R1+"</td></tr>");
        out.println("<tr><td>C1</td><td>"+C1+"</td></tr>");
        out.println("<tr><td>C2</td><td>"+C2+"</td></tr>");
        out.println("<tr><td>C3</td><td>"+C3+"</td></tr>");
        out.println("<tr><td>txtSaisie</td><td>"+txtSaisie+"</td></tr>");
        out.println("<tr><td>txtMdp</td><td>"+txtMdp+"</td></tr>");
        for(int i=0;i<lignes.length;i++)
          out.println("<tr><td>areaSaisie["+i+"]</td><td>"+lignes[i]+"</td></tr>");
        out.println("<tr><td>cmbValeurs</td><td>"+cmbValeurs+"</td></tr>");
        out.println("<tr><td>lst1</td><td>"+lst1+"</td></tr>");
        if(lst2==null)
          out.println("<tr><td>lst2</td><td></td></tr>");
        else
          for(int i=0;i<lst2.length;i++)
            out.println("<tr><td>lst2</td><td>"+lst2[i]+"</td></tr>");
        out.println("<tr><td>secret</td><td>"+secret+"</td></tr>");
        out.println("</body>");
        out.println("</html>");
    }

    // POST
    public void doPost(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
      // renvoie sur GET
      doGet(request,response);
    }
}

يستخدم هذا الكود التقنيات التي تم عرضها سابقًا في مثال آخر. لاحظ النقطتين التاليتين:

  1. عنصر التحكم lst2 هو قائمة متعددة الاختيارات، لذا يمكن تحديد عناصر متعددة. وهذا هو الحال في مثالنا، حيث تم تحديد العنصرين list1 و list3. تم إرسال قيم lst2 من المتصفح إلى الخادم في النموذج lst2=liste1&lst2=liste3. يمكن لبرنامج Java servlet استرداد هذه القيم في صفيف باستخدام طريقة getParameterValues: هنا، تعرض request.getParameterValues("lst2") صفيفًا من سلسلتين ["liste1", "liste3"].
  2. عنصر التحكم areaSaisie هو حقل إدخال متعدد الأسطر. تعرض request.getParameter("areaSaisie") محتوى الحقل كسلسلة واحدة. إذا كنت ترغب في استخراج الأسطر الفردية بداخله، يمكنك استخدام طريقة split الخاصة بفئة String. الكود التالي
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");

يسترد الأسطر من حقل الإدخال. تنتهي هذه الأسطر بالأحرف \r\n (0D0A).

لإجراء الاختبارات، لدينا:

  • قمنا بإنشاء وتجميع معلمات السيرفلت باستخدام JBuilder، كما هو موضح سابقًا
  • وضعنا الفئة التي تم إنشاؤها في <tomcat>\webapps\examples\WEB-INF\classes، حيث <tomcat> هو دليل تثبيت Tomcat.
  • طلبنا عنوان URL http://localhost:81/html/balises2.htm، الذي تم عرض الكود الخاص به أعلاه
  • ملأنا النموذج وانقرنا على زر "إرسال".

3.1.6.5. استرداد رؤوس HTTP من عميل ويب

سنستخدم نفس المثال السابق، ولكن ردًا على عميل الويب الذي أرسل قيم النموذج، سنرسل له رؤوس HTTP التي أرسلها في نفس الوقت. سنقوم بإجراء تغيير واحد على نموذجنا:

    <form method="GET" action="http://localhost:8080/examples/servlet/headers">

سيتم إرسال قيم النموذج باستخدام طريقة GET إلى خادم Java صغير يسمى headers موجود في <tomcat>\webapps\examples\WEB-INF\classes. تم إنشاء خادم headers الصغير وتجميعه باستخدام JBuilder:

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class headers extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
       // determine the nature of the document
        response.setContentType("text/html");
       // we obtain a writing flow
        PrintWriter out = response.getWriter();
      // display header list HTTP
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println("<b>"+name + "</b> = " + value + "<br>");
        }
    }//GET

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
      //GET
      doGet(request,response);
    }//POST
}

نقوم بالوصول إلى الرابط http://localhost:81/html/balises2.htm ونضغط على «إرسال» دون تعديل النموذج. ونحصل على الرد التالي:

Image

لاحظ عنوان URL الذي يحتوي على المعلمات في شريط العناوين بالمتصفح، والذي يوضح الطريقة (GET) المستخدمة لنقل المعلمات. سنستخدم نفس المثال ولكن سنغير الطريقة المستخدمة لإرسال المعلمات (POST):

    <form method="POST" action="http://localhost:8080/examples/servlet/headers">

نحصل على الاستجابة الجديدة التالية:

Image

لاحظ رؤوس HTTP content-type وcontent-length، وهما من سمات طلب POST. بالإضافة إلى ذلك، لاحظ أن قيم النموذج لم تعد تظهر في حقل العنوان بالمتصفح.

3.2. صفحات JSP

تعد JSP (صفحات خادم Java) طريقة أخرى لكتابة تطبيقات خادم الويب. في الواقع، تُترجم صفحات JSP هذه إلى سيرفلتات قبل تنفيذها، لذا فإننا نتعامل بشكل أساسي مع تقنية السيرفلت. تسمح صفحات JSP بتسليط الضوء بشكل أوضح على بنية صفحات HTML التي تم إنشاؤها. فيما يلي بعض الأمثلة، يمكن الوصول إلى بعضها من خلال اتباع رابط JSP على الصفحة الرئيسية لـ Tomcat:

3.2.1. استرداد معلومات البيئة

نعود هنا إلى مثال سبق تناوله باستخدام سيرفلت: عرض متغيرات بيئة السيرفلت. هذا هو مثال "snoop" من أمثلة JSP:

Image

يوجد كود المصدر لصفحة JSP في <tomcat>\jakarta-tomcat\examples\jsp\snp\snoop.jsp (Tomcat 3.x) أو <tomcat>\examples\jsp\snp\snoop.jsp (Tomcat 4.x)


<html>
<!--
  Copyright (c) 1999 The Apache Software Foundation.  All rights 
  reserved.
-->
 
  <body bgcolor="white">
    <h1> Request Information </h1>
    <font size="4">
      JSP Request Method: <%= request.getMethod() %>
      <br>
      Request URI: <%= request.getRequestURI() %>
      <br>
      Request Protocol: <%= request.getProtocol() %>
      <br>
      Servlet path: <%= request.getServletPath() %>
      <br>
      Path info: <%= request.getPathInfo() %>
      <br>
      Path translated: <%= request.getPathTranslated() %>
      <br>
      Query string: <%= request.getQueryString() %>
      <br>
      Content length: <%= request.getContentLength() %>
      <br>
      Content type: <%= request.getContentType() %>
      <br>
      Server name: <%= request.getServerName() %>
      <br>
      Server port: <%= request.getServerPort() %>
      <br>
      Remote user: <%= request.getRemoteUser() %>
      <br>
      Remote address: <%= request.getRemoteAddr() %>
      <br>
      Remote host: <%= request.getRemoteHost() %>
      <br>
      Authorization scheme: <%= request.getAuthType() %> 
      <hr>
      The browser you are using is <%= request.getHeader("User-Agent") %>
      <hr>
    </font>
  </body>
</html>

يرجى ملاحظة النقاط التالية:

  • يشبه هذا الكود لغة HTML إلى حد كبير. ومع ذلك، فإنه يحتوي على علامات <%= expression %> الخاصة بلغة JSP. يقوم مُركب JSP باستبدال العلامة بأكملها في نص HTML بقيمة expression.
  • يستخدم هذا المثال أساليب كائن Java `request`، وهو كائن `request` الذي سبق أن تناولناه في دراسة السيرفلت. وبالتالي فهو كائن `HttpServletRequest`. وبذلك، سيتم استبدال العلامة `<%= request.getRemoteHost() %>` في كود HTML باسم جهاز عميل الويب الذي أرسل الطلب.
  • يمكنك تحقيق نفس النتيجة باستخدام سيرفلت، ولكن هنا تكون بنية صفحة الويب أكثر وضوحًا.

3.2.2. استرداد المعلمات المرسلة من عميل الويب

هنا نعيد النظر في المثال الذي درسناه سابقًا باستخدام سيرفلت. يتم عرض نموذج على المتصفح:

Image

استجابةً للطلب أعلاه، يتلقى المتصفح الصفحة التالية:

Image

فيما يلي كود صفحة JSP:


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>
 
<!-- code HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>
  • بينما نرى علامة <%= expression %> مرة أخرى، كما في المثال السابق، تظهر علامة جديدة: <% Java instructions; %>. تقدم علامة <% كود Java. ينتهي هذا الكود عندما يواجه علامة إغلاق الكود %>.
  • سيتم تحويل الكود السابق بأكمله (HTML + JSP) إلى خادم Java. وسيتم تضمينه في طريقة واحدة، تُعرف بالطريقة الرئيسية لصفحة JSP. وهذا هو السبب في أن متغيرات Java المُعلنة في بداية صفحة JSP يمكن الوصول إليها في الأجزاء الأخرى من كود JSP المنتشرة في جميع أنحاء HTML: ستكون هذه المتغيرات ومقاطع الكود جزءًا من نفس طريقة Java. ومع ذلك، إذا كان كود JSP الخاص بنا يحتوي على طرق، فلن يكون من الممكن الوصول إلى المتغيرات `title` و`firstname` و`lastname` داخلها بسبب نطاق الطريقة. سنحتاج إلى جعلها متغيرات عامة أو تمريرها كمعلمات إلى الطرق. سنعود إلى هذا لاحقًا.
  • لإدراج أجزاء ديناميكية في كود HTML، هناك طريقتان ممكنتان: <%= expression %> أو out.println(expression). كائن out هو دفق إخراج مشابه لذلك الذي يحمل الاسم نفسه في أمثلة السيرفلت، ولكنه ليس من نفس النوع: فهو كائن JspWriter، وليس PrintWriter. وهو يسمح بالكتابة إلى دفق HTML باستخدام طريقتي print و println.
  • تعكس صفحة JSP بنية صفحة HTML التي تم إنشاؤها بشكل أفضل من السيرفلت المكافئ.

3.2.3. علامات JSP

فيما يلي قائمة بالعلامات التي قد تصادفها في صفحة JSP ومعانيها.

العلامة
المعنى
<!-- تعليق -->
تعليق HTML. تم إرساله إلى العميل.
<%-- تعليق --%>
تعليق JSP. لا يتم إرساله إلى العميل.
<%! إعلانات، طرق %>
يعلن المتغيرات والأساليب العالمية. ستكون المتغيرات متاحة في جميع الأساليب
<%= تعبير %>
سيتم إدراج قيمة التعبير في صفحة HTML بدلاً من العلامة
<% كود Java %>
يحتوي على كود Java الذي سيكون جزءًا من الطريقة الرئيسية لصفحة JSP
<%@ page attribute1=value1
السمة2=القيمة2 … %>
يحدد سمات لصفحة JSP. على سبيل المثال:
import="java.util.*,java.sql.*" لتحديد المكتبات المطلوبة لصفحة JSP
extends="aParentClass" لجعل صفحة JSP ترث من فئة أخرى

3.2.4. الكائنات الضمنية في JSP

في الأمثلة السابقة، صادفنا كائنين غير مُعلَّنين: request و out. هذان هما اثنان من الكائنات التي يتم تعريفها تلقائيًا في السيرفلت الذي يتم تحويل صفحة JSP إليه. ويُطلق عليهما كائنات ضمنية أو محددة مسبقًا. وهناك كائنات أخرى، لكن هذين هما الأكثر استخدامًا إلى جانب كائن response:

object
المعنى
HttpServletRequest request
الكائن الذي يمكنك من خلاله الوصول إلى طلب عميل الويب (getParameter، getParameterNames، getParameterValues)
HttpServletResponse response
الكائن المستخدم لإنشاء استجابة خادم الويب للعميل. يسمح لك بتعيين رؤوس HTTP المراد إرسالها إلى عميل الويب.
JspWriter out
دفق الإخراج الذي يسمح لنا بإرسال كود HTML إلى العميل (print، println)

3.2.5. تحويل صفحة JSP إلى سيرفلت

دعونا نراجع كود JSP من myRequestParamExample.jsp:


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>
 
<!-- code HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>

عندما يطلب المتصفح صفحة JSP هذه من خادم Tomcat، سيقوم الخادم بتحويلها إلى سيرفلت. إذا كان عنوان URL المطلوب هو

http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp، فسيقوم Tomcat 4.x بوضع السيرفلت الذي تم إنشاؤه في الدليل <tomcat>\work\localhost\examples\jsp\perso\intro:

Image

يعكس هذا الاسم عنوان URL لصفحة JSP، http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp. كما هو موضح أعلاه، يمكننا الوصول إلى كود Java للـ servlet الذي تم إنشاؤه لصفحة JSP. في مثالنا، يكون كما يلي:

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class myRequestParamExample$jsp extends HttpJspBase {


    static {
    }
    public myRequestParamExample$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();


                   // variables local to the main procedure
                  String title="Récupération des paramètres d'un formulaire";
                  String firstName = request.getParameter("firstname");
                  String lastName = request.getParameter("lastname");

                out.write("\r\n\r\n<!-- code HTML -->\r\n<html>\r\n  <head>\r\n    <title>");
                out.print( title );
                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
                out.print( title );
                out.write("</h3>\r\n    ");

                      if (firstName != null || lastName != null) {
                        out.println("firstname= " + firstName + "<br>");
                        out.println("lastname= " + lastName);
                      } else {
                        out.println("pas de paramètres");
                      }

                out.write("\r\n    <P>\r\n    <form method=\"POST\">\r\n      firstname= <input type=\"text\" size=\"20\" name=\"firstname\">\r\n      <br>\r\n      lastname= <input type=\"text\" size=\"20\" name=\"lastname\">\r\n      <br>\r\n      <input type=\"submit\">\r\n    </form>\r\n  </body>\r\n</html>\r\n");

        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

الرمز الذي تم إنشاؤه معقد للغاية. سنركز فقط على النقاط التالية:

  • الطريقة الرئيسية للـ servlet هي كما يلي:
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

يتم استدعاء هذه الطريقة عند بدء تشغيل السيرفلت. يمكننا أن نرى أنها تأخذ معلمتين: طلب العميل وكائن استجابة لتوليد استجابتها للعميل على الويب.

  • في الطريقة الرئيسية، يتم إعلان كائن JspWriter باسم out ثم تهيئته. يستخدم هذا الكائن لإرسال كود HTML إلى العميل عبر العبارة out.print("HTML code").
        JspWriter out = null;
...
            out = pageContext.getOut();
  • كود جافا


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

تم تضمينه بالكامل في الطريقة الرئيسية _jspService للـ servlet. وينطبق الأمر نفسه على أي كود موجود داخل العلامات <%… %>

  • يتم إخراج كود HTML لصفحة JSP باستخدام عبارات out.print("codeHTML") أو out.write(...). على سبيل المثال

                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
  • في هذا المثال، لا توجد طرق أخرى غير الطريقة الرئيسية _jspService.

3.2.6. الطرق والمتغيرات العالمية لصفحة JSP

انظر إلى صفحة JSP التالية:


<%!
  // la balise précédente démarre la partie variables et méthodes globales
  // cette partie sera reprise sans modification dans la servlet
 
  // une variabe globale
  String prenom="inconnu";
 
  // une méthode  
  private String sonChien(){
    return "milou";
  }//sonChien
 
  // une autre méthode
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi
 
  // fin de la partie globale de la servlet
%>  
 
<%
  // la balise précédente indique que le code qui suit sera enregistré
  // dans la méthode principale de la servlet
 
  // variable locale à la méthode principale
  String nom="tintin";
%>
 
 
<%-- code HTML --%>
<html>
  <head>
    <title>Page JSP</title>
  </head>
  <body>
    <center>
      <h2>Page JSP</h2>
      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
        // le nom de son ami
        afficheAmi(out);
      %>
    </center>
  </body>
</html>

تقوم صفحة JSP هذه بإنشاء صفحة الويب التالية:

Image

دعونا نلقي نظرة على كيفية إنشاء الأسطر الأربعة أعلاه:


      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
        // le nom de son ami
        afficheAmi(out);
      %>

تقع الأسطر أعلاه داخل علامة <%..%>، وبالتالي ستكون جزءًا من الطريقة الرئيسية _jspService للـservlet التي سيتم إنشاؤها. كيف تصل إلى المتغيرات lastName و firstName والطريقتين hisDog و displayFriend؟

name (tintin)
هي متغير محلي للطريقة الرئيسية لصفحة JSP، وبالتالي فهي معروفة داخلها
firstName (غير معروف)
هو متغير عام لصفحة JSP، وبالتالي فهو معروف داخل الطريقة الرئيسية
sonChien (milou)
هي طريقة عامة لصفحة JSP، وبالتالي يمكن الوصول إليها من الطريقة الرئيسية
displayFriend (Haddock)
هي طريقة عامة لصفحة JSP وبالتالي يمكن الوصول إليها من الطريقة الرئيسية. لاحظ أن الكائن out يتم تمريره كمعلمة إلى الطريقة. وهذا أمر إلزامي هنا. في الواقع، يتم إعلان الكائن out وتهيئته في الطريقة الرئيسية للـ servlet وهو ليس متغيرًا عامًا.

لنلقِ نظرة الآن على كود خادم Java الذي تم إنشاؤه من صفحة JSP هذه، بعد إزالة الكود غير الضروري:

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class tintin$jsp extends HttpJspBase {

          // the preceding tag starts the global variables and methods section
           // this part will be adopted unchanged in the servlet

           // global variability
          String prenom="inconnu";

           // a method  
          private String sonChien(){
            return "milou";
          }//sonChien

           // another method
          private void afficheAmi(JspWriter out) throws Exception{
            out.println("<p>Son ami s'appelle Haddock</p>");
          }//afficheAmi

           // end of the global part of the servlet

    static {
    }
    public tintin$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();

                out.write("  \r\n\r\n");
                  // the preceding tag indicates that the following code will be saved
                   // in the servlet's main method

                   // variable local to the main method
                  String nom="tintin";

                out.write("\r\n\r\n\r\n");
                out.write("\r\n<html>\r\n  <head>\r\n    <title>Page JSP</title>\r\n  </head>\r\n  <body>\r\n    <center>\r\n      <h2>Page JSP</h2>\r\n      <p>Son nom est ");
                out.print( nom );
                out.write("</p>\r\n      <p>Son prénom est ");
                out.print( prenom );
                out.write("</p>\r\n      <p>Son chien s'appelle ");
                out.print( sonChien() );
                out.write("</p>\r\n      ");

                        // his friend's name
                        afficheAmi(out);

                out.write("\r\n    </center>\r\n  </body>\r\n</html>\r\n");
        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

كما هو موضح أعلاه، تم تضمين كود Java الذي كان موجودًا بين علامات JSP <%! .. %> بالكامل، وهو ليس جزءًا من الطريقة الرئيسية للـ servlet، _jspService. المتغيرات المُعلنة في هذا القسم هي متغيرات مثيل، وبالتالي فهي عالمية بالنسبة للطرق؛ وهذا هو المكان الذي يمكن فيه تعريف طرق أخرى غير _jspService.


  // this part will be adopted unchanged in the servlet
 
   // global variability
  String prenom="inconnu";
 
   // a method
  private String sonChien(){
    return "milou";
  }//sonChien
 
   // another method
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi
 
   // end of the global part of the servlet

3.2.7. نشر وتصحيح صفحات JSP على خادم Tomcat

عندما ترغب في إنشاء صفحة JSP واستخدامها مع خادم Tomcat، يطرح السؤال حول مكان وضع الصفحة في بنية دليل الخادم. هناك طرق مختلفة للقيام بذلك، وسنعود إليها لاحقًا. في الوقت الحالي، تتمثل أبسط طريقة في وضع صفحة JSP في مجلد داخل شجرة الدليل <tomcat>\webapps\examples\jsp (Tomcat 4.x)، حيث يمثل <tomcat> دليل تثبيت Tomcat. وبالتالي، كان عنوان URL في المثال السابق هو http://localhost:8080/examples/jsp/perso/tintin/tintin.jsp. وهذا يعني أن صفحة tintin.jsp كانت موجودة في المجلد <tomcat>\webapps\examples\jsp\perso\tintin.

يتم ترجمة صفحة JSP إلى ملف مصدر Java، والذي يتم تجميعه بعد ذلك بواسطة Tomcat عندما يطلب المتصفح عنوان URL لصفحة JSP. قد تحدث أخطاء في التجميع. يقوم Tomcat 4.x بالإبلاغ عنها في استجابته للمتصفح. ويشير بشكل محدد إلى الأسطر التي تحتوي على أخطاء في ملف .java. يمكن أن يكون للأخطاء أسباب مختلفة:

  1. كود JSP على الصفحة غير صحيح (على سبيل المثال، أخطاء في علامات JSP المستخدمة)
  2. كود Java المضمن في صفحة JSP غير صحيح

يمكن التخلص من السبب الأول عن طريق فحص كود JSP للصفحة. ويمكن التخلص من السبب الثاني عن طريق فحص كود Java. ويمكن القيام بذلك عن طريق ترجمة ملف .java الذي تم إنشاؤه لصفحة JSP مباشرةً باستخدام أداة مثل JBuilder، التي توفر إمكانيات تصحيح أخطاء أكثر تقدمًا من Tomcat.

3.2.8. أمثلة

سنعود إلى المثال الذي تناولناه سابقًا والذي يتضمن «سيرفلت» يختار فيه المستخدم رقمًا من قائمة، ويقوم الخادم بإبلاغ المستخدم بالرقم الذي اختاره مع إعادة عرض القائمة نفسها مع تمييز العنصر الذي اختاره المستخدم:

Image

لإنشاء هذه الصفحة، استرجعنا كود السيرفلت وقمنا بتعديله على النحو التالي:

  • احتفظنا برمز Java الذي لم يولد رمز HTML كما هو
  • تم تحويل كود Java الذي أنشأ كود HTML إلى مزيج من كود HTML و JSP

ينتج عن ذلك صفحة JSP التالية:


 
<%@ page import="java.sql.*, java.util.*" %>
 
<%!
 
        // variables globales de l'application
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;
 
        // initialisation de la page JSP - n'est exécutée qu'une seule fois
        public void jspInit(){
            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                // connexion à la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                // objet Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                rs=st.executeQuery("select valeur from Tvaleurs");
                // les valeurs sont récupérées et mises dans un tableau dynamique
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // on enregistre la valeur dans la liste
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                // transformation liste --> tableau
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                // problème
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
 
%>
 
<%
    // code de _jspService exécuté à chaque requête cliente
  // y-at-il eu une erreur lors de l'initialisation de la page JSP ?
  if(msgErreur!=null){
%>
       <!-- code HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %></h3>
      </body>
    </html>
<%
      // fin de jspService
    return;
  }//if
 
    // on récupère l'éventuel choix de l'utilisateur
    String choix=request.getParameter("cmbValeurs");
    if(choix==null) choix="";
%>
 
  <%-- pas d'erreur - code HTML de la page normale --%>
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST">
            <select name="cmbValeurs">
              <%
                // affichage dynamique des valeurs
                          String selected="";
                          for (int i=0;i<valeurs.length;i++){
                              if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                              out.println("<option "+selected+">"+valeurs[i]+"</option>");
                          }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
<%
        // y-avait-il une valeur choisie ?
                if(! choix.equals("")){
        // on affiche le choix de l'utilisateur
%>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%        
                }//if
%>        
      </body>
    </html>

يرجى ملاحظة النقاط التالية:

  • يتم تضمين عبارات الاستيراد الخاصة بـ servlet في توجيه <% page import="..." %>
  • تحيط العلامة <%! ... %> بالمتغيرات العامة للتطبيق وأساليب Java
  • تسمى طريقة init الخاصة بالسيرفلت، والتي يتم تنفيذها مرة واحدة فقط عند تحميل السيرفلت، jspInit بالنسبة لصفحة JSP. تخدم هاتان الطريقتان نفس الغرض. لذلك، تم تضمين كود طريقة init الخاصة بالسيرفلت هنا بالكامل.
  • تم تضمين متغيرات مثيل السيرفلت — تلك التي يجب أن تكون متاحة عبر طرق متعددة — حرفياً. وهذه هي في المقام الأول متغيرات title و values و msgError، والتي تُستخدم لاحقاً في كود JSP.
  • تحيط علامات <% ... %> برمز Java الذي سيتم تضمينه في طريقة _jspService التي يتم تنفيذها عندما يقوم العميل بإرسال طلب.
  • كما هو الحال مع السيرفلت، ستتحقق طريقة _jspService أولاً من قيمة متغير msgError لتحديد ما إذا كانت بحاجة إلى إنشاء صفحة خطأ. إذا كان هناك خطأ، فإنها تنشئ صفحة الخطأ وتخرج (return).
  • إذا لم يكن هناك خطأ، فإنها تنشئ النموذج مع قائمة القيم
  • بمجرد الانتهاء من ذلك، تتحقق مما إذا كان المستخدم قد حدد رقمًا؛ وإذا كان الأمر كذلك، فإنها تعرض هذا الرقم على الصفحة التي تم إنشاؤها

ما هي المزايا مقارنة بـ servlet؟ بلا شك، عرض أوضح لرمز HTML الذي تم إنشاؤه. ولكن لا يزال هناك الكثير من رموز Java التي "تشوش" هذا العرض. سنلقي نظرة لاحقًا على طريقة أخرى تسمى التفويض، حيث يمكننا وضع معظم رموز Java في servlet، مع احتفاظ صفحة JSP برموز HTML و JSP فقط. وهذا يفصل بوضوح جزء المعالجة عن جزء العرض.

3.3. نشر تطبيق ويب على خادم Tomcat

سنشرح الآن كيفية نشر تطبيقات الويب Java باستخدام خادم Tomcat. في حين أن الإرشادات التالية خاصة بهذا الخادم، فإن نشر تطبيق ويب Java داخل حاوية J2EE أخرى سيتضمن خطوات مشابهة لتلك الموضحة هنا.

3.3.1. ملفات التكوين server.xml و web.xml

حتى الآن، من أجل اختبار سيرفلتاتنا وصفحات JSP، كنا نضع

  • البرمجيات الخادمة في المجلد <tomcat>\webapps\examples\WEB-INF\classes. وكان من الممكن الوصول إليها بعد ذلك عبر عنوان URL http://localhost:8080/examples/servlet/nomServlet
  • صفحات JSP في شجرة الدليل <tomcat>\webapps\examples\jsp. ومن ثم كان يمكن الوصول إليها عبر عنوان URL http://localhost:8080/examples/jsp/nomPageJSP

لم نوضح أبدًا سبب ذلك. يتم تكوين خادم Tomcat في ملف نصي يسمى server.xml، موجود في الدليل <tomcat>\conf:

Image

هذا الملف النصي هو في الواقع ملف XML (لغة الترميز الموسعة). مستند XML هو مستند نصي يحتوي على علامات، تمامًا مثل مستند HTML. ومع ذلك، في حين أن علامات HTML محددة جيدًا، فإن علامات XML ليست كذلك. وبالتالي، فإن المستند التالي هو مستند XML:

<personne>
    <prenom>Pierre</prenom>
  <nom>Lucas</nom>
  <age>28</age>
</personne>

مستند XML هو ببساطة مستند "موسوم" يتبع قواعد معينة لوضع العلامات:

  • نص مُعلم بالشكل <xx att1="val1" att2="val2" ....>text</xx>
  • يمكن أن تكون العلامة قائمة بذاتها وتأخذ الشكل <xx att1="val1" att2="val2" ..../>

تسمى الحقول "att" بسمات العلامة "xx"، والحقول "val" هي القيم المرتبطة بهذه السمات. بعض مستندات HTML ليست مستندات XML صالحة. على سبيل المثال، العلامة HTML <br> ليست علامة XML صالحة. يجب كتابتها على النحو <br/> لتكون صالحة، من أجل الامتثال للقاعدة التي تنص على أن جميع علامات XML يجب أن تكون مغلقة. تم إنشاء نسخة معدلة من HTML تسمى XHTML لضمان أن تكون كل مستندات XHTML مستندات XML صالحة. بعض المتصفحات الحديثة قادرة على عرض ملفات XML. لذا، إذا أطلقنا على مستند XML الموضح في المثال أعلاه اسم "person.xml" وعرضناه باستخدام IE6، فسنحصل على العرض التالي:

Image

يتعرف IE6 على العلامات ويبرزها. كما يتعرف على بنية المستند بناءً على العلامات. لذا، إذا أطلقنا على المستند التالي اسم "person2.xml":

<personne><prenom>Pierre</prenom><nom>Lucas</nom><age>28</age></personne>

وعرضناه باستخدام IE6، نحصل على نفس العرض:

Image

تعرف IE6 بشكل صحيح على بنية ومحتوى المستند. تكمن أهمية مستند XML في هذه الخاصية: من السهل استرجاع بنية ومحتوى مستند XML. ويتم ذلك باستخدام برنامج يسمى محلل XML. أصبحت مستندات XML هي المعيار لتبادل المستندات على الويب. لنأخذ على سبيل المثال الجهاز A، الذي يحتاج إلى إرسال مستند DOC إلى الجهاز B. يتم إنشاء مستند DOC من المعلومات الموجودة في قاعدة البيانات DB-A. يجب أن يقوم الجهاز B بتخزين مستند DOC في قاعدة البيانات DB-B. يمكن أن يتم التبادل على النحو التالي:

  • تسترد الآلة A البيانات من قاعدة البيانات DB-A وتغلفها في مستند نصي XML
  • يتم إرسال مستند XML إلى الجهاز B عبر الشبكة
  • يقوم الجهاز B بتحليل المستند المستلم باستخدام محلل XML واستخراج كل من البنية والبيانات (كما فعل IE6 في مثالنا). يمكنه بعد ذلك تخزين البيانات المستلمة في قاعدة البيانات DB-B

لن نتطرق أكثر إلى لغة XML، التي تستحق كتابًا خاصًا بها.

هنا، يتم تكوين Tomcat بواسطة ملف XML server.xml. إذا قمنا بعرض هذا الملف باستخدام IE6، فسنحصل على مستند معقد. سنركز ببساطة على الأسطر التالية:

Image

إن العلامة <Context ...> هي ما يهمنا هنا. تُستخدم لتعريف تطبيقات الويب. ومن الجدير بالذكر اثنتان من سماتها:

  • path: هذا هو اسم تطبيق الويب
  • docBase: هذا هو المجلد الذي يوجد فيه التطبيق. وهو هنا اسم نسبي: examples. نسبي بالنسبة إلى أي مجلد؟ توجد الإجابة أيضًا في ملف server.xml في السطر التالي:

Image

يحدد السطر أعلاه خادم الويب:

  • name: اسم خادم الويب
  • appBase: جذر شجرة المستندات التي يخدمها. مرة أخرى، لدينا اسم نسبي: webapps. وهو نسبي إلى دليل تثبيت خادم Tomcat <tomcat>. وبالتالي، يشير هذا إلى المجلد <tomcat>\webapps.

يخزن تطبيق الويب «examples» ملفاته في المجلد «examples» (انظر docBase أعلاه). هذا الاسم نسبي بالنسبة إلى جذر شجرة دليل الويب الخاص بالخادم، أي <tomcat>\webapps. وبالتالي، فإنه يقع في المجلد <tomcat>\webapps\examples. دعونا نلقي نظرة عن قرب على هذا المجلد:

Image

هناك نجد المجلد WEB-INF\classes حيث قمنا بتخزين سيرفلتاتنا للاختبار. يحتوي المجلد WEB-INF على ملف يسمى web.xml:

Image

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

    <servlet>
      <servlet-name>
          servletToJsp
      </servlet-name>
      <servlet-class>
          servletToJsp
      </servlet-class>
    </servlet>

تُستخدم علامة <servlet> لتعريف سيرفلت داخل تطبيق ويب. لاحظ أن تطبيق الويب المعني هو examples. تحتوي علامة السيرفلت على علامتين أخريين:

  • <servlet-name>servletToJsp</servlet-name>: تحدد اسم السيرفلت
  • <servlet-name>servletToJsp</servlet-name>: تحدد اسم الفئة التي سيتم تنفيذها عند طلب السيرفلت. في هذا المثال، يحمل السيرفلت وفئته نفس الاسم. وهذا ليس إلزامياً.

كيف يطلب المتصفح سيرفلت servletToJsp من خادم Tomcat؟

  • يطلب المتصفح عنوان URL http://localhost:8080/examples/servlet/servletToJsp
  • يقوم Tomcat بتحليل مسار السيرفلت /examples/servlet/servletToJsp. ويقوم بتفسير الجزء الأول من المسار /examples على أنه اسم تطبيق ويب ويبحث في ملف التكوين server.xml الخاص به لمعرفة مكان تخزين المستندات الخاصة بهذا التطبيق. كما رأينا سابقًا، يوجد هذا في المجلد <tomcat>\webapps\examples.
  • يستخدم Tomcat بقية مسار السيرفلت لتحديد موقعه داخل تطبيق الويب examples. يشير هذا المسار /servlet/servletToJsp إلى أنه يجب عليه تنفيذ السيرفلت المسمى servletToJsp. سيقوم Tomcat بعد ذلك بقراءة ملف التكوين web.xml لتطبيق examples، والذي سيجده في <tomcat>\webapps\examples\WEB-INF. وسيجد في هذا الملف أن السيرفلت servletToJsp مرتبط بفئة Java servletToJsp (انظر ملف web.xml أعلاه). ثم سيبحث عن هذه الفئة في مجلد WEB-INF\classes لتطبيق الويب examples، أي في <tomcat>\webapps\examples\WEB-INF\classes، ويقوم بتنفيذها.

Image

3.3.2. مثال: نشر تطبيق الويب "list"

سنعود إلى سيرفلت درسناه سابقًا، والذي كان يعرض للمستخدم قائمة بالأرقام ليختار منها رقمًا واحدًا. ثم كان السيرفلت يؤكد الرقم الذي اختاره المستخدم:

Image

كما هو موضح في حقل العنوان بالمتصفح أعلاه، تم تسمية ملف فئة السيرفلت بـ gener3. وبناءً على التفسيرات الواردة سابقًا:

  • يشير عنوان URL /examples/servlet/gener3 إلى أن هذه سيرفلت باسم gener3 من تطبيق الويب examples
  • في ملف web.xml الخاص بتطبيق الأمثلة، لا يوجد ما يشير إلى سيرفلت باسم gener3. كيف عثر عليه Tomcat إذن؟ بعد مراجعة ملف web.xml بالكامل، لا يمكنني الإجابة بثقة... يبقى السؤال مطروحًا...

اخترنا نشر سيرفلت gener3.class تحت اسم lstValeurs في تطبيق ويب يسمى liste موجود في المجلد E:\data\serge\Servlets\lstValeurs:

Image

نضع ملف gener3.class في المجلد WEB-INF\classes المذكور أعلاه:

Image

نقوم بتكوين تطبيق الويب liste بإضافة الأسطر التالية إلى ملف server.xml فوق تلك التي تحدد تطبيق الويب manager:

                 <!-- Perso: lstValeurs -->
                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

                <!-- Tomcat Manager Context -->
                <Context path="/manager" docBase="manager" debug="0" privileged="true" />
                <!-- Tomcat Examples Context -->
                <Context path="/examples" docBase="examples" debug="0" reloadable="true" crossContext="true">
........

يشير السطر الذي يحدد تطبيق lstValeurs إلى أنه موجود في المجلد e:/data/serge/servlets/lstValeurs. يجب علينا الآن تحديد ملف web.xml لهذا التطبيق. سيحدد هذا الملف السيرفلت الوحيد للتطبيق:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
</web-app>

يشير الملف أعلاه إلى أن السيرفلت المسمى lstValeurs مرتبط بملف الفئة gener3.class. يجب إنشاء ملف web.xml هذا وحفظه في مجلد WEB-INF لتطبيق liste:

Image

تُظهر لقطة الشاشة أعلاه مجلدًا باسم «src» يحتوي على ملف المصدر «gener3.java». قد لا يكون هذا المجلد موجودًا، فهو لا يخدم أي غرض في هذا العرض التوضيحي. نحن جاهزون الآن لتشغيل الاختبارات:

  • أوقف Tomcat وأعد تشغيله حتى يعيد قراءة ملف التكوين server.xml الخاص به. نحن نستخدم هنا نظام Windows. في نظام Unix، يمكنك إجبار Tomcat على إعادة قراءة ملف التكوين الخاص به دون إيقافه.
  • باستخدام متصفح، اطلب عنوان URL http://localhost:8080/liste/servlet/lstValeurs

Image

يمكننا أن نرى أن عنوان URL السابق يحتوي على الكلمة الرئيسية *servlet*، تمامًا مثل جميع عناوين URL الخاصة بـ servlet المستخدمة حتى الآن. يمكننا التخلص من هذا عن طريق ربط servlet lstValeurs بنمط URL (*url-pattern*) في ملف web.xml الخاص بالتطبيق:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

في العلامة <servlet-mapping>، نربط المسار /values بـ servlet lstValeurs المحدد في الأسطر السابقة. نحفظ ملف web.xml الجديد ونطلب عنوان URL http://localhost:8080/liste/valeurs:

Image

3.3.3. نشر الصفحات العامة لتطبيق ويب

لقد رأينا للتو كيفية نشر تطبيق ويب يتكون من سيرفلت واحد. يمكن أن يحتوي تطبيق الويب على العديد من المكونات: سيرفلتات، صفحات JSP، ملفات HTML، تطبيقات Java الصغيرة، إلخ. أين نضع عناصر التطبيق هذه؟ إذا كان <application> هو دليل تطبيق الويب المحدد بواسطة سمة docBase للتطبيق في ملف server.xml الخاص بتكوين Tomcat ()، فقد رأينا أن السيرفلتات توضع في <application>\WEB-INF\classes. يمكن وضع مكونات التطبيق الأخرى في أي مكان داخل شجرة دليل <application> باستثناء دليل WEB-INF. لنأخذ تطبيق JSP listvaleurs.jsp الذي درسناه سابقًا:

Image

تم تخزين صفحة JSP هذه في المجلد <tomcat>\webapps\examples\jsp\perso\listvaleurs. يمكن أن تكون هذه الصفحة مكونًا من مكونات تطبيق list الذي تم نشره سابقًا. دعونا نضع ملف listvaleurs.jsp مباشرةً في مجلد هذا التطبيق:

Image

تذكر تكوين تطبيق liste في ملف server.xml:

                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

يُعتبر أي عنوان URL يبدأ بمسار /liste جزءًا من تطبيق liste، وسيتم البحث عنه في المجلد المحدد. دعونا نطلب عنوان URL http://localhost:8080/liste/listvaleurs.jsp باستخدام متصفح:

Image

لقد حصلنا بالفعل على صفحة JSP المتوقعة.

3.3.4. معلمات تهيئة Servlet

لقد رأينا أن السيرفلت يتم تكوينه بواسطة الملف <application>\WEB-INF\web.xml، حيث <application> هو مجلد تطبيق الويب الذي ينتمي إليه. من الممكن تضمين معلمات تهيئة السيرفلت في هذا الملف. لنعد إلى سيرفلت lstValeurs الخاص بنا من تطبيق الويب liste، الذي كان ملف تكوينه كما يلي:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

الفئة المرتبطة بالـservlet هي فئة gener3. يحتوي كود المصدر لهذه الفئة على تعريف لعدد من الثوابت:

public class gener3 extends HttpServlet{
        // page title
        private final String title="Génération d'un formulaire";
         // the list values database
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";

دعونا نستعرض معنى الثوابت الأربعة المحددة أعلاه:

title
عنوان مستند HTML الذي تم إنشاؤه بواسطة السيرفلت
DSNValues
اسم DSN لقاعدة بيانات ODBC التي يسترد منها السيرفلت البيانات
admDbValues
اسم المستخدم الذي يتمتع بحق الوصول للقراءة إلى قاعدة البيانات المذكورة أعلاه
dbPasswordValues
كلمة المرور الخاصة به

إذا قام مسؤول قاعدة بيانات DSNValeurs بتغيير كلمة مرور المستخدم admDbValeurs، فيجب تعديل شفرة مصدر السيرفلت وإعادة تجميعها. وهذا ليس عمليًا للغاية. يقدم ملف تكوين web.xml الخاص بالسيرفلت بديلاً عن ذلك من خلال السماح بتعريف معلمات تهيئة السيرفلت باستخدام العلامة <init-param>:

    <init-param>
        <param-name>...</param-name>
        <param-value>...</param-value>
    </init-param>
<اسم-المعلمة>
يتيح لك تحديد اسم المعلمة
<param-value>
يحدد القيمة المرتبطة بالمعلمة السابقة

يمكن لبرنامج الخدمة الوصول إلى معلمات التهيئة الخاصة به باستخدام الطرق التالية:

[Servlet].getServletConfig()
طريقة من فئة Servlet، التي اشتقت منها فئة HttpServlet المستخدمة في برمجة الويب. تُرجع كائن ServletConfig الذي يوفر الوصول إلى معلمات تكوين السيرفلت.
[ServletConfig].getInitParameter("parameter")
طريقة من فئة ServletConfig تُرجع قيمة معلمة التهيئة "parameter"

نقوم بتكوين تطبيق "liste" باستخدام ملف web.xml الجديد التالي:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
      <servlet-name>lstValeurs2</servlet-name>
    <servlet-class>gener5</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>
        <param-name>DSNValeurs</param-name>
      <param-value>odbc-valeurs</param-value>
    </init-param>
    <init-param>
        <param-name>admDbValeurs</param-name>
      <param-value>admDbValeurs</param-value>
    </init-param>
    <init-param>
        <param-name>mdpDbValeurs</param-name>
      <param-value>mdpDbValeurs</param-value>
    </init-param>   
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
      <servlet-name>lstValeurs2</servlet-name>
    <url-pattern>/valeurs2</url-pattern>
  </servlet-mapping>
</web-app>

في تطبيق القائمة، نحدد سيرفلت ثانٍ يسمى lstValeurs2 مرتبط بملف فئة gener5. تم وضع هذا الملف في <application>\WEB-INF\classes:

Image

يحتوي سيرفلت lstValeurs2 على أربعة معلمات تهيئة: title، وDSNValeurs، وadmDbValeurs، وmdpDbValeurs. بالإضافة إلى ذلك، تم تعريف الاسم المستعار /values2 لهذا السيرفلت باستخدام علامة <servlet-mapping>. وبالتالي، يمكن الوصول إلى سيرفلت lstValeurs2 في تطبيق القائمة عبر عنوان URL http://localhost:8080/liste/valeurs2.

تم تعديل كود مصدر السيرفلت على النحو التالي لاسترداد معلمات تهيئة السيرفلت:

public class gener5 extends HttpServlet{
    // page title
    private String title=null;
    // the list values database
    private String DSNValeurs=null;
    private String admDbValeurs=null;
    private String mdpDbValeurs=null;
...............

         // servlet initialization
        public void init(){
             // retrieve servlet initialization parameters
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            DSNValeurs=config.getInitParameter("DSNValeurs");
            admDbValeurs=config.getInitParameter("admDbValeurs");
            mdpDbValeurs=config.getInitParameter("mdpDbValeurs");

             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // table of values is filled from a ODBC database
             // name DSN : DSNvaleurs
...............

لاختبار السيرفلت، يجب إعادة تشغيل Tomcat حتى يتمكن من تحميل ملف التكوين web.xml الجديد لتطبيق "liste". باستخدام متصفح، اطلب عنوان URL الخاص بالسيرفلت: http://localhost:8080/liste/valeurs2:

Image

إذا كانت أي من معلمات التهيئة المطلوبة من قبل السيرفلت مفقودة من ملف web.xml، فسيتم عرض الصفحة التالية:

Image

3.3.5. معلمات التهيئة لتطبيق ويب

في المثال السابق، لا يملك سوى سيرفلت lstValeurs2 حق الوصول إلى المعلمات title و DSNValeurs و admDbValeurs و mdpDbValeurs. ومن الممكن أن يحتاج سيرفلت آخر في تطبيق liste نفسه إلى بيانات من قاعدة البيانات نفسها التي يستخدمها سيرفلت lstValeurs2. في هذه الحالة، سيتعين إعادة تعريف المعلمات DSNValeurs و admDbValeurs و mdpDbValeurs في قسم التكوين بملف web.xml الخاص بـ servlet الجديد. هناك حل آخر يتمثل في تعريف المعلمات المشتركة بين عدة servlets على مستوى التطبيق بدلاً من مستوى servlet. يصبح ملف web.xml الجديد للتطبيق كما يلي:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
      <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
      <servlet-name>lstValeurs3</servlet-name>
    <url-pattern>/valeurs3</url-pattern>
  </servlet-mapping>
</web-app>

يُسمى السيرفلت الجديد lstValeurs3، وهو مرتبط بملف فئة gener6، وقد تم ربطه بالاسم المستعار /valeurs3 (تعيين السيرفلت). المعلمة title هي المعلمة الوحيدة التي تم الاحتفاظ بها في تعريف السيرفلت. أما المعلمات الأخرى فقد تم وضعها في تكوين التطبيق داخل علامات <context-param>. تُستخدم هذه العلامة لتعريف المعلومات الخاصة بالتطبيق، وليس بسيرفلت معين أو صفحة JSP معينة. كيف يصل السيرفلت في Java إلى هذه المعلمات، التي غالبًا ما تسمى معلمات السياق؟ تشبه الطرق المتاحة للحصول على معلومات السياق إلى حد كبير تلك المستخدمة للحصول على معلمات التهيئة الخاصة بالسيرفلت:

[Servlet].getServletContext()
طريقة من فئة Servlet التي اشتقت منها فئة HttpServlet المستخدمة في برمجة الويب. تُرجع كائن ServletContext الذي يوفر الوصول إلى معلمات تكوين التطبيق
[ServletContext].getInitParameter("parameter")
طريقة من فئة ServletContext تعرض قيمة معلمة التهيئة "parameter"

تقوم فئة gener6.java بإجراء التغييرات التالية فقط على كود Java من gener5.java المستخدم سابقًا:

             // retrieve servlet initialization parameters
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // table of values is filled from a ODBC database
             // name DSN : DSNvaleurs
...............

يتم الحصول على المعلمة title الخاصة بالسيرفلت عبر كائن ServletConfig. أما المعلمات الثلاث الأخرى المحددة على مستوى التطبيق فيتم الحصول عليها عبر كائن ServletContext. نقوم بتجميع هذه الفئة ووضعها، مثل الفئات الأخرى، في <application>\WEB-INF\classes:

Image

نقوم بإعادة تشغيل Tomcat حتى يأخذ ملف web.xml الجديد للتطبيق في الاعتبار ونطلب عنوان URL http://localhost:8080/liste/valeurs3:

Image

3.3.6. معلمات التهيئة لصفحة JSP

لقد رأينا كيفية تعريف معلمات التهيئة لـ servlet أو تطبيق ويب. هل يمكننا فعل الشيء نفسه لصفحة JSP؟ لنعد إلى بداية كود صفحة listvaleurs.jsp التي درسناها سابقًا:

<%@ page import="java.sql.*, java.util.*" %>

<%!
        // variables globales de l'application
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
.........

نجد الثوابت الأربعة title و DSNValues و admDbValues و mdpDbValues محددة في ملف web.xml الخاص بالتطبيق. تم الآن تحديد الثوابت DSNValues و admDbValues و mdpDbValues على مستوى التطبيق، لذا يمكننا أن نفترض أن صفحة JSP تابعة لهذا التطبيق ستتمكن من الوصول إليها. وهذا هو الحال بالفعل. نعلم أن صفحة JSP ستُترجم إلى سيرفلت. سيتمكن السيرفلت من الوصول إلى السياق عبر طريقة getServletContext(). أما حالة الثابت title فهي أكثر تعقيدًا. في الواقع، قمنا بتعريفه على مستوى السيرفلت بدلاً من مستوى التطبيق على النحو التالي:

    <servlet>
      <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>

بالنسبة لصفحة JSP، لم تعد الصيغة السابقة مناسبة لأن مفهوم ملف الفئة لم يعد ينطبق. ومع ذلك، فإن صيغة التكوين لصفحة JSP تشبه إلى حد كبير صيغة التكوين الخاصة بـ servlet. وهي كما يلي:

    <servlet>
      <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
...
  </servlet>

في الواقع، يتم التعامل مع صفحة JSP على أنها سيرفلت يتم تعيين اسم لها (servlet-name). بدلاً من ربط ملف فئة بهذا السيرفلت، نقوم بربط ملف المصدر لصفحة JSP المراد تنفيذها (jsp-file). وبالتالي، تحدد الأسطر السابقة سيرفلتًا باسم JSPlstValues مرتبطًا بصفحة JSP /listvalues2.jsp. المسار /listvalues2.jsp نسبي بالنسبة إلى جذر التطبيق. وبالتالي، في حالة تطبيق القائمة الخاص بنا، سيكون ملف listvalues2.jsp موجودًا في مجلد docBase (انظر server.xml) لتطبيق القائمة:

Image

سيكون تكوين صفحة JSP كما يلي في ملف web.xml الخاص بالتطبيق:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   
.......      
    <servlet>
      <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
    <init-param>
        <param-name>JSPtitle</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
..........
  <servlet-mapping>
      <servlet-name>JSPlstValeurs</servlet-name>
    <url-pattern>/jspvaleurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
........
</web-app>

تقع صفحة JSP listvaleurs2.jsp في الدليل الجذري لتطبيق liste وهي مرتبطة باسم سيرفلت JSPlstValeurs (servlet-name)، والذي هو بدوره مرتبط بالاسم المستعار /jspvaleurs (servlet-mapping). وبالتالي، سيكون من الممكن الوصول إلى صفحة JSP الخاصة بنا عبر عنوان URL http://localhost:8080/liste/jspvaleurs.

تمت إعادة تسمية صفحة JSP الأصلية listvaleurs.jsp إلى listvaleurs2.jsp وتسترد معلمات التهيئة الأربعة الخاصة بها في طريقة jspInit():

<%!
         // application global variables
         // page title
        private String title=null;
        // the list values database
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // list values
        private String[] valeurs=null;
         // error msg
        private String msgErreur=null;

         // initialization of page JSP - executed only once
        public void jspInit(){

             // retrieve servlet initialization parameters
      ServletConfig config=getServletConfig();
            title=config.getInitParameter("JSPtitle");
      ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // fills the table of values from a ODBC database
             // name DSN : DSNvaleurs
..............

تسترد صفحة JSP معلمات التهيئة الخاصة بها بنفس الطريقة التي تستخدمها السيرفلتات. يتم حفظ الملف السابق في الدليل الجذري لتطبيق الويب "liste":

Image

يتم إعادة تشغيل خادم Tomcat لإجباره على إعادة تحميل ملف التكوين web.xml الجديد للتطبيق. يمكنك بعد ذلك طلب عنوان URL http://localhost:8080/liste/jspvaleurs:

Image

3.3.7. التعاون بين السيرفلت وJSP داخل تطبيق ويب

عندما يرسل عميل طلبًا إلى خادم ويب، يمكن إنشاء الاستجابة بواسطة عدة سيرفلتات وصفحات JSP. حتى الآن، كانت الاستجابة تُنشأ بواسطة سيرفلت واحد أو صفحة JSP واحدة. وقد رأينا أن صفحة JSP توفر قابلية قراءة أفضل لهيكل مستند HTML الذي تم إنشاؤه. ومع ذلك، فهي تحتوي عمومًا على الكثير من كود Java. يمكننا تحسين هذا عن طريق وضع

  • كود Java الذي لا يولد كود HTML للاستجابة في واحد أو أكثر من السيرفلتات
  • في صفحات JSP، ووضع الكود الخاص بإنشاء مستندات HTML المختلفة المرسلة استجابةً للعميل

من المفترض أن يساعد ذلك في تحسين الفصل بين كود Java وكود HTML. سنطبق هذه البنية الجديدة على تطبيق القائمة الخاص بنا: سيكون خادم Java باسم lstValeurs4 مسؤولاً عن قراءة القيم من قاعدة البيانات عند بدء التشغيل ثم معالجة طلبات العميل عند . اعتماداً على نتيجة هذا التحليل، سيتم توجيه طلب العميل إلى صفحة خطأ (erreur.jsp) أو إلى الصفحة التي تعرض قائمة الأرقام (liste.jsp). وبالتالي، سيتألف تطبيق القائمة من سيرفلت وصفحتين JSP.

كيف يمكن لـ servlet تمرير الطلب الذي تلقاه من عميل إلى servlet آخر أو إلى صفحة JSP؟ سنستخدم الطرق التالية:

[ServletContext].getRequestDispatcher(
String url)
الخاصة بفئة ServletContext والتي تُرجع كائن RequestDispatcher. المعلمة url هي اسم عنوان URL الذي نريد إعادة توجيه طلب العميل إليه. لا يمكن أن تحدث إعادة توجيه الطلب هذه إلا داخل نفس التطبيق. لذلك، فإن المعلمة url هي مسار نسبي بالنسبة لهيكل دليل الويب الخاص بذلك التطبيق.
[RequestDispatcher].forward
(ServletRequest request,
 ServletResponse response)
طريقة لواجهة RequestDispatcher تقوم بإعادة توجيه طلب العميل وكائن الاستجابة — الذي يجب استخدامه لإنشاء الاستجابة — إلى عنوان URL السابق.
[ServletRequest].setAttribute(String name, Object obj)
عندما تقوم صفحة سيرفلت أو JSP بتمرير طلب إلى صفحة سيرفلت أو JSP أخرى، فإنها تحتاج عمومًا إلى تمرير معلومات أخرى بخلاف طلب العميل فحسب — وهي معلومات مستمدة من معالجتها الخاصة للطلب. تسمح لك طريقة setAttribute الخاصة بفئة ServletRequest بإضافة سمات إلى كائن طلب العميل بتنسيق يشبه قاموسًا من أزواج (السمة، القيمة)، حيث السمة هي اسم السمة والقيمة هي أي كائن يمثل قيمتها.
[ServletRequest].getAttribute(
String attribute)
تسمح لك باسترداد قيم سمات الطلب. سيتم استخدام هذه الطريقة من قبل صفحة servlet أو JSP التي تم توجيه الطلب إليها للحصول على المعلومات المضافة إليها.

سيتم تكوين السيرفلت المسؤول عن معالجة النموذج على النحو التالي في ملف web.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

............
    <servlet>
      <servlet-name>lstValeurs4</servlet-name>
    <servlet-class>gener7</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>    
        <param-name>JSPerreur</param-name>
      <param-value>/erreur.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>JSPliste</param-name>
      <param-value>/liste.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>URLservlet</param-name>
      <param-value>/liste/valeurs4</param-value>
    </init-param>      
  </servlet>
...........
  <servlet-mapping>
      <servlet-name>lstValeurs4</servlet-name>
    <url-pattern>/valeurs4</url-pattern>
  </servlet-mapping>
.......
</web-app>

سيكون لـ servlet lstValeurs4 أربعة معلمات تهيئة خاصة به:

العنوان
عنوان مستند HTML المراد إنشاؤه
JSPerreur
عنوان URL لصفحة JSP الخاصة بالخطأ
JSPlist
عنوان URL لصفحة JSP التي تعرض قائمة الأرقام
عنوان URL لـ Servlet
عنوان URL المرتبط بسمة action للنموذج الذي تعرضه صفحة JSPlist. سيكون عنوان URL هذا هو عنوان servlet lstValeurs4

سيكون للـ servlet الاسم المستعار /valeurs4 (servlet-mapping) وبالتالي سيكون من الممكن الوصول إليه عبر عنوان URL http://localhost:8080/liste/valeurs4. وهو مرتبط بملف الفئة gener7.java، الذي يكون كود المصدر الكامل له كما يلي:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;

public class gener7 extends HttpServlet{
        // page title
        private String title=null;
        // the list values database
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // JSP display pages
        private String JSPerreur=null;
        private String JSPliste=null;
        // the URL of the servlet
        private String URLservlet=null;
        // list values
        private String[] valeurs=null;
         // error msg
        private String msgErreur=null;

        // -----------------------------------------------------------------
        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // put msgErreur,title in the query attributes
            request.setAttribute("msgErreur",msgErreur);
            request.setAttribute("title",title);
            request.setAttribute("URLservlet",URLservlet);

             // was there an error loading the servlet?
            if(msgErreur!=null){
                 // we hand over to a JSP error page
                getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
                 // end
                return;
            }

             // there was no error
             // put the list of values in the query attributes
            request.setAttribute("valeurs",valeurs);

             // we retrieve the user's possible choice
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";
            request.setAttribute("choix",choix);

             // hand over to the JSP list presentation page
            getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
            // end
            return;
        }//GET

        // -----------------------------------------------------------------
        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

             // returns to GET
            doGet(request,response);
        }//POST

        // -----------------------------------------------------------------
         // servlet initialization
        public void init(){

             // retrieve servlet initialization parameters
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            JSPerreur=config.getInitParameter("JSPerreur");
            JSPliste=config.getInitParameter("JSPliste");
            URLservlet=config.getInitParameter("URLservlet");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");


             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // fills the table of values from a ODBC database
             // name DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // connection to the ODBC database
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // statement object
                st=connexion.createStatement();
                 // execute select query to retrieve values
                rs=st.executeQuery("select valeur from Tvaleurs");
                // values are retrieved and put into a dynamic table
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // the value is saved in the
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // transformation list --> table
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problem
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
    }//class

الميزة الجديدة في هذه الفئة هي أنها تقوم بإعادة توجيه طلب العميل إلى صفحة JSPerreur في حالة حدوث خطأ، وإلى صفحة JSPliste في الحالات الأخرى. لا تقوم الفئة بإنشاء الاستجابة بنفسها. تتولى صفحتا JSP JSPerreur و JSPliste هذه المهمة. في السابق، كان السيرفلت يضيف سمات (setAttribute) إلى طلب العميل:

  • رسالة خطأ msgError في حالة حدوث خطأ لصفحة JSPerreur
  • القيم (values) المراد عرضها، والقيمة المحددة (choice) من قبل المستخدم، وعنوان النموذج (title)، وعنوان URL (URLservlet) الخاص بسمة action للنموذج لصفحة JSPliste

يتم ترجمة هذه الفئة ووضعها في فئات التطبيق:

Image

يتم تكوين صفحة JSP التي تعرض رسالة خطأ على النحو التالي:

    <servlet>
      <servlet-name>JSPerreur</servlet-name>
    <jsp-file>/erreur.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
  </servlet>
.........
<servlet-mapping>
      <servlet-name>JSPerreur</servlet-name>
    <url-pattern>/JSPerreur</url-pattern>
  </servlet-mapping>

يُسمى ملف JSP المرتبط بصفحة الخطأ error.jsp ويقع في جذر التطبيق:

Image

له الاسم المستعار /JSPerreur، مما يجعله قابلاً للوصول عبر عنوان URL http://localhost:8080/liste/JSPerreur. له معلمة تهيئة تسمى mainServlet، وقيمتها هي الاسم المستعار للـservlet الرئيسي الموصوف أعلاه. لاحظ أن هذا الاسم المستعار نسبي بالنسبة لجذر تطبيق liste؛ وإلا لكان /liste/valeurs4. كود صفحة erreur.jsp هو كما يلي:

<%
    // code de _jspService
  // on récupère le paramètre d'initialisation mainServlet
  String servletListValeurs=config.getInitParameter("mainServlet");
  // on récupère l'attribut msgErreur
  String msgErreur=(String)request.getAttribute("msgErreur");
  // attribut valide ?
  if(msgErreur!=null){
%>
       <!-- code HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %>)</h3>
      </body>
    </html>
<%
    } else { // attribut msgErreur invalide - retour à la servlet principale
%>
    <jsp:forward page="<%= servletListValeurs %>" />  
<%    
  }
%>  

عادةً ما يتم استدعاء هذه الصفحة بواسطة السيرفلت السابق، الذي يجب أن يمرر لها السمة msgError. ومع ذلك، لا يوجد ما يمنعك من استدعائها مباشرةً إذا كنت تعرف عنوان URL الخاص بها. أيضًا، إذا وجدت أن السمة msgError مفقودة، فقم بتمرير الطلب إلى السيرفلت الرئيسي. هنا، نستخدم علامة خاصة بصفحات JSP، وصيغتها هي:

<jsp:forward page="URL" />

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

يتم تكوين صفحة JSP التي تعرض قائمة الأرقام على النحو التالي:

    <servlet>
      <servlet-name>JSPliste</servlet-name>
    <jsp-file>/liste.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
.........
  <servlet-mapping>
      <servlet-name>JSPliste</servlet-name>
    <url-pattern>/JSPliste</url-pattern>
  </servlet-mapping>

يُسمى ملف JSP المرتبط بصفحة الخطأ liste.jsp ويقع في جذر التطبيق:

Image

يحتوي السيرفلت على الاسم المستعار /JSPliste، مما يجعله قابلاً للوصول عبر عنوان URL http://localhost:8080/liste/JSPliste. ويحتوي على معلمة تهيئة تسمى mainServlet، وقيمتها هي الاسم المستعار للسيرفلت الرئيسي. وفيما يلي كود صفحة liste.jsp:

  <%-- page d'affichage de la liste des valeurs --%>
  <%
      // code de jspService
      // on récupère le paramètre d'initialisation
      String servletListValeurs=config.getInitParameter("mainServlet");

    // on récupère les attributs de la requête venant de la servlet principale
    String title=(String) request.getAttribute("title");
    String[] valeurs=(String[]) request.getAttribute("valeurs");
    String choix=(String) request.getAttribute("choix");
    String URLservlet=(String) request.getAttribute("URLservlet");

    // attributs valides ?
    if(title==null || valeurs==null || choix==null){
        // il y a un attribut invalide - on passe la main à la servlet
   %>
   <jsp:forward page="<%= servletListValeurs %>" />
    <%
    }//if
  %>

  <%-- code HTML --%>  
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST" action="<%= URLservlet %>">
            <select name="cmbValeurs">
              <%
                // affichage dynamique des valeurs
                          String selected="";
                          for (int i=0;i<valeurs.length;i++){
                              if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                              out.println("<option "+selected+">"+valeurs[i]+"</option>");
                          }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
                <%
            // y-avait-il une valeur choisie ?
                    if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                %>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
                <%        
                    }//if
                %>        
      </body>
    </html>

تعمل هذه الصفحة مثل صفحة error.jsp. عادةً ما يتم استدعاؤها بواسطة سيرفلت /list/values4 وتستقبل سمات العنوان والقيم والاختيار. إذا كان أي من هذه المعلمات مفقودًا، يتم تمرير التحكم إلى سيرفلت URLservlet (/liste/valeurs4). إذا كانت جميع المعلمات موجودة، يتم عرض قائمة الأرقام جنبًا إلى جنب مع الرقم الذي اختاره المستخدم، إن وجد.

إذا طلبت عنوان URL لبرنامج الخدمة الرئيسي، فستحصل على النتيجة التالية:

Image

مع شفرة المصدر التالية (عرض/المصدر):

<html>
    <head>
      <title>Génération d'un formulaire</title>
  </head>
  <body>
      <h3>Choisissez une valeur</h3>
      <form method="POST" action="/liste/valeurs4">
        <select name="cmbValeurs">
          <option >0</option>
        <option >1</option>
        <option >2</option>
        <option >3</option>
        <option >4</option>
        <option >6</option>
        <option >5</option>
        <option >7</option>
        <option >8</option>
        <option >9</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>

  </body>
</html>

تم إنشاء مستند HTML هذا بواسطة صفحة JSP liste.jsp. يمكننا أن نرى أنه تم استرداد سمات العنوان والقيم وURLservlet بنجاح.

وختامًا للتعاون بين السيرفلتات وصفحات JSP، نلاحظ أن صفحات JSP هنا قصيرة جدًا وتفتقر إلى كود Java الذي لا يساهم بشكل مباشر في إنشاء استجابة HTML. وبالتالي، فإن بنية المستندات التي تم إنشاؤها تكون أكثر وضوحًا.

3.4. دورة حياة السيرفلتات وصفحات JSP

3.4.1. دورة الحياة

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

أثناء وجوده في الذاكرة، يمكن للبرنامج الخادم معالجة طلبات متعددة في وقت واحد. يقوم خادم الويب بإنشاء مؤشر ترابط واحد لكل طلب، وتستخدم جميعها نفس مثيل البرنامج الخادم:

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

عند تحميل سيرفلت، يتم تنفيذ طريقة محددة من السيرفلت:

public void init() throws ServletException{
}

بالنسبة لصفحة JSP، فإنها هي الطريقة


  public void jspInit(){
  }

هي التي يتم تنفيذها. فيما يلي مثال لصفحة JSP تستخدم طريقة jspInit:

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
  // variables et méthodes globales de la page JSP

  // variable d'instance
  int compteur;

  // méthode pour incrémenter le compteur  
  public int getCompteur(){
    // on incrémente le compteur
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
    // on le rend
    return compteur;
  }

  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

تقوم صفحة JSP السابقة بتهيئة عداد بقيمة 100 في jspInit. أي طلب لاحق إلى السيرفلت يزيد من قيمة هذا العداد ثم يعرضها:

المرة الأولى:

Image

المرة الثانية:

Image

كما هو واضح أعلاه، لم يتم إلغاء تحميل السيرفلت بين الطلبين؛ وإلا لكان العداد قد وصل إلى 101 خلال الطلب الثاني. عند إلغاء تحميل السيرفلت، فإن الطريقة

public void destroy(){
}

إذا كانت موجودة. بالنسبة لصفحات JSP، فإنها هي الطريقة


  public void jspDestroy(){
  }

في هذه الطرق، يمكنك، على سبيل المثال، إغلاق اتصالات قاعدة البيانات التي تم فتحها في طرق init المقابلة.

3.4.2. مزامنة الطلبات مع سيرفلت

لنعد إلى صفحة JSP السابقة التي تزيد العداد وترجع قيمته إلى عميل الويب. لنفترض وجود طلبين متزامنين. عندئذ يتم إنشاء مؤشرين لتنفيذهما؛ وسيستخدم هذان المؤشران نفس مثيل السيرفلت، وبالتالي نفس العداد. تذكر الكود الذي يزيد العداد:


  public int getCompteur(){
    // on incrémente le compteur
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
    // on le rend
    return compteur;
  }

تمت كتابة زيادة العداد بطريقة متعمدة غير دقيقة. لنفترض أن تنفيذ الخيطين يسير على النحو التالي:

 
  1. في الوقت T1، يتم تنفيذ الخيط TH1. يقرأ العداد (=145) من myCounter، ثم يتم مقاطعته ويفقد المعالج. لذلك لم يكن لديه الوقت لزيادة myCounter ونسخ القيمة الجديدة إلى counter.
  2. في الوقت T2، يتم تنفيذ الخيط TH2. يقرأ العداد (=145) من myCounter، ثم يتم مقاطعته ويفقد المعالج. لاحظ أن الخيطين لهما متغيرات myCounter مختلفة. إنهما يشتركان فقط في متغيرات المثيل، تلك التي تعتبر عامة بالنسبة للطرق.
  3. في الوقت T3، يستعيد الخيط TH1 السيطرة وينتهي. وبالتالي، فإنه يعيد 146 إلى عميله.
  4. في الوقت T4، يستعيد الخيط TH2 السيطرة وينتهي. كما يعيد 146 إلى عميله، في حين كان ينبغي أن يعيد 147.

هذه مشكلة تتعلق بمزامنة الخيوط. عندما يرغب الخيط TH1 في زيادة العداد، يجب عليه منع أي خيط آخر من القيام بذلك أيضًا. لتسليط الضوء على هذه المشكلة، نعيد كتابة صفحة JSP على النحو التالي:

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
  // variables et méthodes globales de la page JSP

  // variable d'instance
  int compteur;

  // méthode pour incrémenter le compteur  
  public int getCompteur(){
    // on lit le compteur
    int myCompteur=compteur;
    // on s'arrête 10 secondes
    try{
      Thread.sleep(10000);
    }catch (Exception ignored){}
    // on incrémente le compteur
    compteur=myCompteur+1;
    // on le rend
    return compteur;
  }

  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

هنا، قمنا بإجبار الخيط على التوقف بعد 10 ثوانٍ من قراءة العداد. وبالتالي، من المفترض أن يحرر وحدة المعالجة المركزية (CPU)، مما يسمح لخيط آخر بقراءة عداد لم يتم زيادته. عندما نرسل طلبات باستخدام متصفح، لا نلاحظ أي فرق سوى الانتظار لمدة 10 ثوانٍ قبل الحصول على النتيجة.

Image

الآن، إذا فتحنا نافذتين للمتصفح وأرسلنا طلبين متقاربين زمنياً:

Image

Image

نحصل على نفس قيمة العداد. يمكننا إبراز المشكلة بشكل أفضل باستخدام عميل مبرمج بدلاً من عميل يدوي مثل المتصفح. إليك عميل Perl يتم استدعاؤه على النحو التالي:

*برنامج <bdi dir="ltr" class="odt-ltr-term">URL N</bdi>*

حيث

URL هو عنوان URL لبرنامج الخدمة الخاص بالعد

N هو عدد الطلبات التي يجب إرسالها إلى هذا السيرفلت

فيما يلي النتائج التي تم الحصول عليها لـ 5 طلبات، والتي توضح بوضوح مشكلة ضعف تزامن الخيوط: فجميعها تُرجع نفس قيمة العداد.


DOS>java clientCompteurJSP http://localhost:8080/examples/jsp/perso/compteur/compteur2.jsp 5
Compteur=121
Compteur=121
Compteur=121
Compteur=121
Compteur=121

فيما يلي كود عميل Java.

import java.net.*;
import java.util.regex.*;
import java.io.*;

public class clientCompteurJSP {

    public static void main(String[] params){

         // data
        String syntaxe="Syntaxe : pg URL nbAppels";

         // parameter verification
        if(params.length!=2){
            System.err.println(syntaxe);
            System.exit(1);
        }//if
         // URL
        URL urlCompteur=null;
        try{
            urlCompteur=new URL(params[0]);
            String query=urlCompteur.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
            System.err.println(syntaxe);
            System.err.println("URL ["+params[0]+" incorrecte");
            System.exit(2);
        }//try-catch
         // number of calls
        int nbAppels=0;
        try{
            nbAppels=Integer.parseInt(params[1]);
            if(nbAppels<=0) throw new Exception();
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("Nombre d'appels ["+params[1]+" incorrect");
            System.exit(3);
        }//try-catch

         // parameters are correct - connections can be made to the URL
        try{
            getCompteurs(urlCompteur,nbAppels);
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("L'erreur suivante s'est produite : "+ex.getMessage());
            System.exit(4);
        }//try-catch
    }//hand

    private static void getCompteurs (URL urlCompteur, int nbAppels)
            throws Exception {
         // does nbAppels at URL urlCompteur
         // displays the counter value returned by the web server each time


         // remove from urlCompteur the info needed to connect to the tax server
        String path=urlCompteur.getPath();
        if(path.equals("")) path="/";
        String host=urlCompteur.getHost();
        int port=urlCompteur.getPort();
        if(port==-1) port=urlCompteur.getDefaultPort();

         // calls are made to the URL
        Socket[] clients=new Socket[nbAppels];
        for(int i=0;i<nbAppels;i++){
             // connect to the server
            clients[i]=new Socket(host,port);
             // create a write stream to the server
            PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
             // request URL - send HTTP headers
            OUT.println("GET " + path + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println("");
        }//for

         // local data
        String réponse=null;                        // server response
         // the model searched for in the HTML server response
        Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
         // the model for a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // the result of the model comparison
        Matcher résultat=null;

        for(int i=0;i<nbAppels;i++){
             // each client reads the response sent by the server

             // create customer input/output flows TCP
            BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));

             // read the 1st line of the answer
            réponse=IN.readLine();
             // compare the HTTP line with the model of the correct answer
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                 // we have a URL problem
                throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
            }//if

             // the response is read through to the end of the headers
            while((réponse=IN.readLine())!=null && ! réponse.equals("")){
            }//while

             // that's it for HTTP headers - move on to HTML code
             // to retrieve the counter value
            boolean compteurTrouvé=false;
            while((réponse=IN.readLine())!=null){
                 // compare the line with the counter model
                if(! compteurTrouvé){
                    résultat=modèleCompteur.matcher(réponse);
                    if(résultat.find()){
                         // meter found
                        System.out.println("Compteur="+résultat.group(1));
                        compteurTrouvé=true;
                    }//if
                }//if
            }//while

             // it's over
            clients[i].close();
        }//for
    }//getCompteurs

}//class

دعونا نوضح الكود السابق:

  • يقبل البرنامج معلمتين:
    • عنوان URL لصفحة JSP الخاصة بالعداد
    • عدد العملاء المراد إنشاؤهم لهذا الرابط
  • لذلك يبدأ البرنامج بالتحقق من صحة المعلمات: أن يكون هناك معلمتان بالفعل، وأن تكون الأولى تشبه عنوان URL من الناحية النحوية، وأن تكون الثانية عددًا صحيحًا أكبر من 0. للتحقق من صحة عنوان URL من الناحية النحوية، نستخدم فئة URL ومنشئها URL(String)، الذي ينشئ كائن URL من سلسلة مثل http://istia.univ-angers.fr. يتم إلقاء استثناء إذا لم تكن السلسلة عنوان URL صحيحًا من الناحية النحوية. وهذا يسمح لنا بالتحقق من صحة المعلمة الأولى.
  • بمجرد التحقق من المعلمات، يتم تمرير التحكم إلى الإجراء getCompteurs. سيقوم هذا الإجراء بإنشاء nbAppels عملاء، سيتصلون جميعًا في وقت واحد (أو تقريبًا) بـ URL urlCompteur.
  • يتم اشتقاق المنفذ والجهاز الذي يجب أن يتصل به العملاء من عنوان URL urlCompteur: [URL].getHost() ترجع اسم الجهاز، و[URL].getPort() ترجع المنفذ.
  • تسمح الحلقة الأولى لكل عميل بما يلي:
    • الاتصال بخادم الويب
    • طلب عنوان URL urlCompteur

في هذه الحلقة، لا ينتظر العميل استجابة الخادم. وذلك لأننا نريد أن يتلقى الخادم طلبات متزامنة تقريبًا.

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

لحل المشكلة التي تم تسليط الضوء عليها سابقًا (إرسال العداد نفسه إلى جميع العملاء الخمسة)، نحتاج إلى مزامنة مؤشرات ترابط خدمة العد على كائن واحد قبل الدخول إلى القسم الحرج لقراءة العداد وتحديثه. صفحة JSP الجديدة هي كما يلي:


<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>
 
<%!
  // variables et méthodes globales de la page JSP
 
  // variables d'instance
  int compteur;
  Object verrou=new Object();
 
  // méthode pour incrémenter le compteur  
  public int getCompteur(){
 
    // on syncronise la section critique
    synchronized(verrou){
      // on lit le compteur
      int myCompteur=compteur;
      // on s'arrête 10 secondes
      try{
        Thread.sleep(10000);
      }catch (Exception ignored){}
      // on incrémente le compteur
      compteur=myCompteur+1;
    }//synchronized
    // on le rend
    return compteur;
  }//getCompteur
 
  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

عند التنفيذ، يتم الحصول على النتائج التالية:

dos>c:\perl\bin\perl.exe client2.pl http://localhost:8080/examples/jsp/perso/compteur/compteur3.jsp 5
    Compteur= 104
    Compteur= 106
    Compteur= 105
    Compteur= 107
    Compteur= 108

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


// variable de classe
  static int compteur;
  static Object verrou=new Object();

بقيت بقية الشفرة دون تغيير.