3. مقدمة إلى سيرفلتات Java وصفحات JSP
يحتوي هذا الفصل على أمثلة متنوعة لـ servlets وصفحات JSP. وقد تم اختبارها باستخدام خادم Tomcat الذي يعمل على المنفذ 8080. ومن خلال اتباع الروابط الموجودة في الصفحة الرئيسية، يمكن الوصول إلى أمثلة لـ servlets وصفحات JSP. معظم الأمثلة الواردة أدناه مأخوذة من أمثلة Tomcat. لاختبارها، يكفي تشغيل Tomcat، وطلب URL http://localhost:8080 باستخدام متصفح، ثم اتباع رابط السيرفلتات.
![]() | ![]() |
3.1. سيرفلتات Java
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>");
}
}
عند تشغيل هذه الخدمة، يظهر العرض التالي:

تجدر الإشارة إلى النقاط التالية:
- يجب استيراد فئات خاصة للسيرفلتات:
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 request هو الكائن الذي يتيح لنا الوصول إلى الطلب المقدم من عميل الويب. وسيتم إرسال استجابة السيرفلت عبر الكائن HttpServletResponse response
- يتيح لنا الكائن response تعيين رؤوس HTTP التي سيتم إرسالها إلى العميل. على سبيل المثال، يتم تعيين رأس Content-type: text/html هنا بواسطة:
- لإرسال الرد إلى العميل، تستخدم السيرفلت تدفق إخراج يزودها به الكائن response:
- بمجرد الحصول على تدفق الإخراج هذا، يتم كتابة الرمز 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 استرداد المعلمات المرسلة من عميل الويب. نموذج إدخال:

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

كود مصدر السيرفلت هو كما يلي:
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("nomParamètre") المؤشر null، إذا لم يكن المعلمة nomParamètre ضمن المعلمات المرسلة من قبل عميل الويب.
- يحدد النموذج أن المتصفح يجب أن يرسل المعلمات عبر الطريقة POST
- سيتم معالجة المعلمات المستلمة بواسطة الطريقة doPost الخاصة بالسيرفلت. وهنا، تكتفي هذه الطريقة باستدعاء الطريقة doGet. وبالتالي، فإن هذه الخدمة (servlet) تعالج قيم النموذج سواء أُرسلت عبر GET أو POST.
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
doGet(request, response);
}
3.1.3. استرداد رؤوس HTTP المرسلة من قبل عميل الويب
توضح السيرفلت التالية كيفية استرداد رؤوس HTTP المرسلة من عميل الويب:

فيما يلي شفرة المصدر للـ servlet:
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 المرسلة من المتصفح في شكل قائمة:
- تسمح الطريقة request.getHeader("entête") بالحصول على رأس HTTP محدد. يقدم المثال أعلاه بعضًا منها. تجدر الإشارة إلى أن الرؤوس المعروضة هنا يتم إرسالها من المتصفح. أما الخادم، فلديه أيضًا رؤوس HTTP خاصة به والتي تتطابق أحيانًا مع تلك الخاصة بالمتصفح. وتهدف رؤوس HTTP المرسلة من المتصفح إلى إعلام الخادم بقدرات المتصفح.
رأس الرسالة | المعنى |
| هوية المتصفح |
| التنسيقات MIME التي يدعمها المتصفح. وبالتالي، فإن image/gif تعني أن المتصفح قادر على معالجة الصور بتنسيق GIF |
| بصيغة hote:port. يشير إلى الجهاز والمنفذ اللذين يرغب المتصفح في الاتصال بهما. |
| تنسيق الترميز الذي يقبله المتصفح للمستندات المرسلة من الخادم. وهكذا، إذا كان لدى الخادم مستند بصيغة عادية غير مضغوطة وآخر بصيغة gzip المضغوطة، وأشار المتصفح إلى أنه قادر على معالجة تنسيق gzip، فسيتمكن الخادم من إرسال المستند بتنسيق gzip لتوفير النطاق الترددي. |
| اللغات التي يدعمها المتصفح. إذا كان لدى الخادم نفس المستند بعدة لغات، فسيرسل المستند باللغة التي يدعمها المتصفح. |
| URL الذي طلبه المتصفح |
| طريقة الاتصال التي طلبها المتصفح. Keep-alive تعني أن الخادم يجب ألا يقطع الاتصال بعد تقديم الصفحة المطلوبة إلى المتصفح. إذا اكتشف المتصفح أن الصفحة المستلمة تحتوي على روابط لصور على سبيل المثال، فيمكنه إرسال طلبات جديدة إلى الخادم للحصول عليها دون الحاجة إلى إنشاء اتصال جديد. وسيكون المتصفح هو الذي يتخذ المبادرة لإغلاق الاتصال بمجرد استلامه لجميع عناصر الصفحة. |
3.1.4. استرداد معلومات البيئة
توضح السيرفلت التالية كيفية الوصول إلى معلومات بيئة تشغيل السيرفلت. يتم إرسال بعض هذه المعلومات في شكل رؤوس HTTP من المتصفح، وبالتالي يمكن استردادها باستخدام الطريقة السابقة.

فيما يلي كود السيرفلت:
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());
فيما يلي قائمة ببعض الطرق المتاحة ومعانيها:
الطريقة | المعنى |
| اسم خادم الويب |
| منفذ عمل خادم الويب |
| الطريقة GET أو POST التي يستخدمها المتصفح لإرسال طلبه |
| اسم جهاز العميل الذي أرسل المتصفح الطلب منه |
| عنوان IP لهذا الجهاز نفسه |
| نوع المحتوى الذي أرسله المتصفح (رأس HTTP Content-type) |
| عدد الأحرف التي أرسلها المتصفح (رأس HTTP Content-length) |
| إصدار بروتوكول http المطلوب من قبل المتصفح |
| القيمة التي طلبها المتصفح. تتوافق مع الجزء من القيمة الموجود بعد المعرف في http://hote:port/URI |
3.1.5. إنشاء سيرفلت باستخدام JBuilder، ونشره باستخدام Tomcat
نصف الآن كيفية إنشاء وتشغيل سيرفلت Java. سنستخدم أداتين: Jbuilder لتجميع السيرفلت وTomcat لتشغيله. قد يكفي استخدام Tomcat وحده. ومع ذلك، فإنه يوفر إمكانيات تصحيح أخطاء محدودة. نستأنف المثال الذي تم تطويره سابقًا والذي يعرض المعلمات التي يتلقاها الخادم. ترسل السيرفلت أولاً نموذج الإدخال التالي:

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

فيما يلي شفرة المصدر لـ servlet:
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 اللازمة لتحويل برمجيات السيرفلت. في هذه الحالة، يتعين عليك تهيئة Jbuilder لاستخدام مكتبات فئات إضافية. يتم وصف هذه الطريقة في ملاحق هذا المستند الخاص بـ JBuilder 7. ونوردها هنا جزئيًا:
- قم بتفعيل الخيار «Tools/Configure JDKs» أو (Options/Configure JDK)

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

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

من الآن فصاعدًا، يمكننا ترجمة برامج Java المتوافقة مع معيار J2EE، ولا سيما سيرفلتات Java. يستخدم 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 باستخدام متصفح:

3.1.6. أمثلة
في الأمثلة التالية، استخدمنا الطريقة الموضحة سابقًا:
- تجميع المصدر XX.java للـservlet باستخدام Jbuilder
- نشر السيرفلت XX.class في <tomcat>\webapps\examples\WEB-INF\classes
- تشغيل Tomcat، ثم طلب صفحة URL http://localhost:8080/examples/servlet/XX عبر متصفح
3.1.6.1. التوليد الديناميكي للنماذج - 1
نأخذ كمثال إنشاء نموذج يحتوي على عنصر تحكم واحد فقط: قائمة. يتم إنشاء محتوى هذه القائمة ديناميكيًا باستخدام قيم مأخوذة من مصفوفة. في الواقع، غالبًا ما يتم أخذها من قاعدة بيانات. النموذج هو كما يلي:

إذا قمنا في المثال أعلاه بإجراء Envoyer، فسنحصل على النتيجة التالية:

نلاحظ أن الرمز 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{
// متغيرات المثيل
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{
// يتم إعلام العميل بنوع المستند المرسل
response.setContentType("text/html");
// يتم إرسال النموذج
PrintWriter out=response.getWriter();
// البداية
out.println(HTML1);
// قائمة منسدلة
out.println("<select name=\"cmbValeurs\" size=\"1\">");
for (int i=0;i<valeurs.length;i++){
out.println("<option>"+valeurs[i]+"</option>");
}//لـ
out.println("</select>");
// نهاية النموذج
out.println(HTML2+HTML3);
}//GET
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// يتم استرداد اختيار المستخدم
String choix=request.getParameter("cmbValeurs");
if(choix==null) doGet(request,response);
// يتم إعداد الرد
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>";
// يتم إعلام العميل بنوع المستند المرسل
response.setContentType("text/html");
// إرسال النموذج
PrintWriter out=response.getWriter();
out.println(réponse);
}//POST
}//التصنيف
تُستخدم الطريقة doGet لإنشاء النموذج. هناك جزء ديناميكي وهو محتوى القائمة، الذي يأتي هنا من جدول. تُستخدم الطريقة doPost لإنشاء الرد. هنا، الجزء الديناميكي الوحيد هو قيمة الاختيار الذي قام به المستخدم في قائمة النموذج. يتم الحصول على هذه القيمة من خلال request.getParameter("cmbValeurs") حيث cmbValeurs هو اسم القائمة HTML:
وأخيرًا، تجدر الإشارة إلى النقاط التالية:
- يرسل المتصفح قيم النموذج إلى السيرفلت الذي أنشأ النموذج لأن العلامة <form> لا تحتوي على السمة <action>. في هذه الحالة، يرسل المتصفح البيانات التي تم إدخالها في النموذج إلى URL الذي قدمه.
- تحدد العلامة <form> أن بيانات النموذج يجب إرسالها عبر الطريقة POST. ولهذا السبب يتم استرداد هذه القيم عبر الطريقة doPost الخاصة بـ servlet.
3.1.6.2. التوليد الديناميكي للنموذج - 2
نستأنف المثال السابق مع تعديله على النحو التالي. النموذج المقترح لا يزال هو نفسه:

هل تختلف الاستجابة؟:

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

ثم يقوم بإجراء Envoyer. فيحصل على الرد التالي:

فيما يلي كود السيرفلت المسمى gener2.java:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class gener2 extends HttpServlet{
// متغيرات المثيل
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{
// يتم استرداد الاختيار المحتمل للمستخدم
String choix=request.getParameter("cmbValeurs");
if(choix==null) choix="";
// يتم إعلام العميل بنوع المستند المرسل
response.setContentType("text/html");
// إرسال النموذج
PrintWriter out=response.getWriter();
// البداية
out.println(HTML1);
// قائمة منسدلة
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>");
}//لـ
out.println("</select>");
// استمرار النموذج
out.println(HTML2);
if(! choix.equals("")){
// عرض اختيار المستخدم
out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
}//إذا
// نهاية النموذج
out.println(HTML3);
}//GET
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// يتم إعادة التوجيه إلى GET
doGet(request,response);
}//POST
}//فئة
تقوم الطريقة doGet بكل شيء: فهي تُعد النموذج الذي ترسله إلى العميل وتعالج القيم التي يرسلها العميل. النقاط التي يجب ملاحظتها هي التالية:
- يتم التحقق مما إذا كان المعلمة cmbValeurs لها قيمة أم لا.
- وإذا كان الأمر كذلك، فعند إنشاء محتوى القائمة، يتم مقارنة كل عنصر فيها باختيار المستخدم لتعيين السمة selected على العنصر الذي اختاره المستخدم: <option selected>العنصر</option>. علاوة على ذلك، يتم عرض قيمة الاختيار أسفل النموذج.
3.1.6.3. التوليد الديناميكي للنموذج - 3
نستعرض نفس المشكلة السابقة، لكن هذه المرة يتم استخراج القيم من قاعدة بيانات. وفي مثالنا هذا، قاعدة البيانات هي MySQL:
- تسمى قاعدة البيانات dbValeurs
- مالكها هو admDbValeurs وكلمة المرور هي mdpDbValeurs
- تحتوي قاعدة البيانات على جدول واحد يسمى tvaleurs
- تحتوي هذه الجدولة على حقل واحد من نوع عدد صحيح يُسمى «قيمة»
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{
// عنوان الصفحة
private final String title="Génération d'un formulaire";
// قاعدة بيانات قيم القائمة
private final String DSNValeurs="odbc-valeurs";
private final String admDbValeurs="admDbValeurs";
private final String mdpDbValeurs="mdpDbValeurs";
// قيم القائمة
private String[] valeurs=null;
// رسالة الخطأ
private String msgErreur=null;
// الرمز 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{
// يتم إعلام العميل بنوع المستند المرسل
response.setContentType("text/html");
// تدفق الإخراج
PrintWriter out=response.getWriter();
// هل تمت تهيئة السيرفلت بنجاح؟
if (msgErreur!=null){
// حدث خطأ - يتم إنشاء صفحة خطأ
out.println("<html><head><title>"+title+"</title></head>");
out.println("<body><h3>Application indisponible ("+msgErreur+
")</h3></body></html>");
return;
}//if
// يتم استرداد الاختيار المحتمل للمستخدم
String choix=request.getParameter("cmbValeurs");
if(choix==null) choix="";
// يتم إرسال النموذج
// البداية
out.println(HTML1);
// قائمة منسدلة
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>");
// متابعة النموذج
out.println(HTML2);
if(! choix.equals("")){
// يتم عرض اختيار المستخدم
out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
}//إذا
// نهاية النموذج
out.println(HTML3);
}//GET
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// يتم إعادة التوجيه إلى GET
doGet(request,response);
}//POST
// تهيئة السيرفلت
public void init(){
// ملء المصفوفة بالقيم من قاعدة البيانات ODBC
// الاسم DSN: DSNvaleurs
Connection connexion=null;
Statement st=null;
ResultSet rs=null;
try{
// الاتصال بقاعدة البيانات ODBC
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
// كائن Statement
st=connexion.createStatement();
// تنفيذ استعلام SELECT لاسترداد القيم
rs=st.executeQuery("select valeur from Tvaleurs");
// يتم استرداد القيم ووضعها في مصفوفة ديناميكية
ArrayList lstValeurs=new ArrayList();
while(rs.next()){
// يتم تسجيل القيمة في القائمة
lstValeurs.add(rs.getString("valeur"));
}//while
// تحويل القائمة إلى مصفوفة
valeurs=new String[lstValeurs.size()];
for (int i=0;i<lstValeurs.size();i++){
valeurs[i]=(String)lstValeurs.get(i);
}
}catch(Exception ex){
// مشكلة
msgErreur=ex.getMessage();
}finally{
try{rs.close();}catch(Exception ex){}
try{st.close();}catch(Exception ex){}
try{connexion.close();}catch(Exception ex){}
}//محاولة
}//التهيئة
}//فئة
النقاط المهمة التي يجب ملاحظتها هي التالية:
- يمكن تهيئة السيرفلت بواسطة دالة يجب أن تكون صيغتها public void init(). ولا يتم تنفيذ هذه الدالة إلا عند التحميل الأولي للسيرفلت
- بمجرد تحميلها، تظل السيرفلت في الذاكرة طوال الوقت. وهذا يعني أنها لا تُفرغ من الذاكرة بعد أن تخدم عميلاً ما. وبالتالي، فإنها تستجيب بشكل أسرع لطلبات العملاء.
- في السيرفلت الخاص بنا، يجب البحث عن قائمة من القيم في قاعدة البيانات. ونظرًا لأن هذه القائمة لا تتغير بمرور الوقت، فإن طريقة init هي اللحظة المثالية لاستردادها. وبالتالي، لا يتم الوصول إلى قاعدة البيانات إلا مرة واحدة من قبل السيرفلت، عند التحميل الأولي لها، وليس عند كل طلب من العملاء.
- قد يفشل الوصول إلى قاعدة البيانات. تقوم الطريقة init في السيرفلت الخاص بنا بوضع رسالة خطأ msgErreur في حالة الفشل. يتم فحص هذه الرسالة في الطريقة doGet، وفي حالة وجود خطأ، تقوم الطريقة doGet بإنشاء صفحة تشير إلى ذلك.
- تستخدم طريقة init الوصول التقليدي إلى قاعدة البيانات باستخدام برامج تشغيل Odbc-Jdbc. إذا لزم الأمر، يُرجى من القارئ مراجعة طرق الوصول إلى قواعد البيانات JDBC.
عند تشغيل السيرفلت (servlet) دون تشغيل الخادم MySQL، تظهر صفحة الخطأ التالية:
![]()
وإذا قمنا الآن بتشغيل الخادم MySQL، فسنحصل على الصفحة التالية:

إذا اخترنا الرقم 6 و«أرسلنا»:

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

الرمز HTML للنموذج balises2.htm هو التالي:
<html>
<head>
<title>balises</title>
<script language="JavaScript">
function effacer(){
alert("Vous avez cliqué sur le bouton 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> في النموذج على النحو التالي:
سيقوم المتصفح بـ"إرسال" قيم النموذج إلى URL http://localhost:8080/examples/servlet/parameters، وهو URL لـservlet تديره Tomcat ويعرض قيم النموذج السابق. إذا تم استدعاء السيرفلت parameters مباشرةً، فسنحصل على النتائج التالية:

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

وعند الضغط على زر «إرسال» (من النوع submit)، يتم استدعاء السيرفلت parameters هذه المرة مع المعلمات. ثم ترسل الاستجابة التالية:

ونلاحظ في هذه الاستجابة القيم التي تم إدخالها في النموذج. وفيما يلي كود السيرفلت:
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class parameters extends HttpServlet{
// متغيرات المثيل
String title="Récupération des paramètres d'un formulaire";
private String getParameter(HttpServletRequest request, String contrôle){
// تُرجع القيمة request.getParameter (التحقق) أو "" إذا لم تكن موجودة
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
{
// نبدأ باسترداد معلمات النموذج
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");
// نحدد محتوى المستند
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>");
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
{
// يعيد التوجيه إلى GET
doGet(request,response);
}
}
نجد في هذا الكود التقنيات التي تم عرضها سابقًا في مثال آخر. تجدر الإشارة إلى نقطتين:
- عنصر التحكم lst2 عبارة عن قائمة متعددة الاختيارات، وبالتالي يمكن تحديد عدة عناصر. وهذا هو الحال في مثالنا حيث تم تحديد العنصرين liste1 و liste3. تم إرسال قيم lst2 من المتصفح إلى الخادم في صيغة lst2=liste1&lst2=liste3. يمكن لبرنامج السيرفلت Java استرداد هذه القيم في صفيفة باستخدام الطريقة getParameterValues: هنا توفر request.getParameterValues("lst2") صفيفة مكونة من سلسلتين من الأحرف ["liste1","liste3"].
- عنصر التحكم areaSaisie هو حقل إدخال متعدد الأسطر. توفر الدالة request.getParameter("areaSaisie") محتوى الحقل في شكل سلسلة أحرف واحدة. وإذا أردنا استخراج الأسطر المختلفة التي يتكون منها هذا المحتوى، فيمكننا استخدام الدالة split من الفئة String. الكود التالي
يستخرج الأسطر من حقل الإدخال. تنتهي هذه الأسطر بالأحرف \r\n (0D0A).
لإجراء الاختبارات، قمنا بما يلي:
- قمنا بإنشاء وتجميع السيرفلت parameters مع JBuilder كما تم شرحه سابقًا
- وضعنا الفئة التي تم إنشاؤها في <tomcat>\webapps\examples\WEB-INF\classes حيث <tomcat> هو دليل تثبيت Tomcat.
- طلبت URL http://localhost:81/html/balises2.htm الذي تم عرض كوده أعلاه
- ملأت النموذج وضغطت على الزر Envoyer.
3.1.6.5. استرداد رؤوس القسمات HTTP من عميل ويب
نستخدم نفس المثال السابق، ولكن ردًا على عميل الويب الذي أرسل قيم النموذج، نرسل إليه الرؤوس HTTP التي أرسلها في نفس الوقت. نجري تغييرًا واحدًا فقط في نموذجنا:
سيتم إرسال قيم النموذج عبر الطريقة GET إلى سيرفلت جافا يُسمى 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
{
// يتم تحديد طبيعة المستند
response.setContentType("text/html");
// يتم الحصول على تدفق الكتابة
PrintWriter out = response.getWriter();
// عرض قائمة الرؤوس 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
}
نطلب URL http://localhost:81/html/balises2.htm ونقوم بـ Envoyer دون تعديل النموذج. نحصل على الرد التالي:

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

يُلاحظ وجود الرؤوس HTTP وcontent-type وcontent-length التي تميز عملية الإرسال عبر POST. من ناحية أخرى، تجدر الإشارة إلى أن قيم النموذج لم تعد تظهر في حقل Address في المتصفح.
3.2. صفحات JSP
تُعد صفحات JSP (Java Server Pages) طريقة أخرى لكتابة تطبيقات خوادم الويب. في الواقع، تُترجم هذه الصفحات JSP إلى سيرفلتات قبل تنفيذها، ومن ثم نجد تقنية السيرفلتات. تسمح صفحات JSP بإبراز بنية الصفحات HTML التي تم إنشاؤها بشكل أفضل. نقدم أدناه أمثلة، يمكن الوصول إلى بعضها من خلال اتباع الرابط JSP الموجود في الصفحة الرئيسية لـ Tomcat:
![]() | ![]() |
3.2.1. استرداد معلومات البيئة
نستعرض هنا مثالًا سبق تناوله باستخدام سيرفلت: عرض متغيرات بيئة سيرفلت. هذا هو المثال snoop من أمثلة JSP:

يوجد كود المصدر للصفحة 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. استرداد المعلمات المرسلة من عميل الويب
نستأنف هنا المثال الذي سبق دراسته باستخدام سيرفلت. يتم عرض نموذج في المتصفح:

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

رمز الصفحة JSP هو كما يلي:
<%
// المتغيرات المحلية في الإجراء الرئيسي
String title="Récupération des paramètres d'un formulaire";
String firstName = request.getParameter("firstname");
String lastName = request.getParameter("lastname");
%>
<!-- رمز 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; %>. العلامة <% تُدخل كود Java. وينتهي هذا الكود عند العلامة الختامية للكود %>.
- سيتم تحويل الكود السابق بأكمله (HTML + JSP) إلى سيرفلت جافا. وسيتم تضمينه في دالة واحدة، تُسمى الدالة الرئيسية للصفحة JSP. ولهذا السبب، فإن متغيرات جافا المُعلنة في بداية الصفحة JSP معروفة في أجزاء الكود الأخرى JSP المنتشرة في الكود HTML: ستكون هذه المتغيرات وأجزاء الكود جزءًا من نفس الأسلوب في جافا. ولكن إذا كان كودنا JSP يحتوي على طرق، فلن تكون المتغيرات title وfirstname وlastname معروفة فيه بسبب العزل بين الطرق. سيكون من الضروري تحويلها إلى متغيرات عامة أو تمريرها كمعلمات إلى الطرق. سنعود إلى هذا الموضوع لاحقًا.
- لإدراج أجزاء ديناميكية في الكود HTML، هناك طريقتان ممكنتان: <%= تعبير %> أو out.println(تعبير). الكائن out هو تدفق إخراج مشابه لتدفق الإخراج الذي يحمل الاسم نفسه الموجود في أمثلة السيرفلت، ولكنه ليس من نفس النوع: فهو كائن JspWriter وليس PrintWriter. وهو يتيح الكتابة في التدفق HTML باستخدام الطريقتين print و println.
- تعكس الصفحة JSP بنية الصفحة HTML التي تم إنشاؤها بشكل أفضل من السيرفلت المكافئ.
3.2.3. علامات JSP
فيما يلي قائمة بعلامات قد تظهر في صفحة JSP ومعانيها.
العلامة | المعنى |
| تعليق HTML. يتم إرساله إلى العميل. |
| تعليق JSP. لا يتم إرساله إلى العميل. |
| يعلن عن متغيرات عامة وطرق. ستكون المتغيرات معروفة في جميع الطرق |
| سيتم تضمين قيمة التعبير في الصفحة HTML بدلاً من العلامة |
| يحتوي على كود Java الذي سيصبح جزءًا من الدالة الرئيسية للصفحة JSP |
| يحدد سمات للصفحة JSP. على سبيل المثال: import="java.util.*,java.sql.*" لتحديد المكتبات اللازمة للصفحة JSP extends="uneClasseParent" لجعل صفحة JSP مشتقة من فئة أخرى |
3.2.4. الكائنات الضمنية JSP
في الأمثلة السابقة، صادفنا كائنين غير مُعلَّنين: request و out. وهذان هما اثنان من الكائنات التي يتم تعريفها تلقائيًا في السيرفلت التي يتم تحويل الصفحة JSP إليها. يُطلق عليها كائنات ضمنية أو محددة مسبقًا. وهناك كائنات أخرى، لكن هذه هي الأكثر استخدامًا مع الكائن response:
الكائن | المعنى |
الكائن الذي يُتيح الوصول إلى طلب العميل على الويب (getParameter، getParameterNames، getParameterValues) | |
الكائن الذي يمكن من خلاله إنشاء استجابة خادم الويب لعميله. يتيح تحديد رؤوس HTTP المراد إرسالها إلى عميل الويب. | |
تدفق الإخراج الذي يسمح لنا بإرسال كود HTML إلى العميل (print، println) |
3.2.5. تحويل صفحة JSP إلى سيرفلت
لنعد إلى الكود JSP من myRequestParamExample.jsp:
<%
// متغيرات محلية في الإجراء الرئيسي
String title="Récupération des paramètres d'un formulaire";
String firstName = request.getParameter("firstname");
String lastName = request.getParameter("lastname");
%>
<!-- الرمز 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:

نجد في هذا الاسم الرمز URL http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp الخاص بالصفحة JSP. ونلاحظ أعلاه أنه يمكننا الوصول إلى كود جافا الخاص بالسيرفلت الذي تم إنشاؤه للصفحة 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();
// متغيرات محلية في الإجراء الرئيسي
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);
}
}
}
الرمز الذي تم إنشاؤه معقد إلى حد ما. سنكتفي بالتركيز على النقاط التالية:
- الطريقة الرئيسية للسيرفلت هي كما يلي:
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
هذه هي الطريقة التي يتم تشغيلها عند بدء تشغيل السيرفلت. نلاحظ أنها تتلقى معلمتين: الطلب request من العميل وكائن response لتوليد استجابتها للعميل على الويب.
- في الطريقة الرئيسية، يتم إعلان كائن JspWriter out ثم تهيئته. وهذا الكائن هو الذي سيسمح بإرسال الكود HTML إلى العميل عبر التعليمات out.print("codeHTML").
- كود Java
<%
// المتغيرات المحلية في الإجراء الرئيسي
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:
<%!
// العلامة السابقة تبدأ قسم المتغيرات والطرق العامة
// سيتم تضمين هذا الجزء دون تعديل في السيرفلت
// متغير عام
String prenom="inconnu";
// دالة
private String sonChien(){
return "milou";
}//sonChien
// دالة أخرى
private void afficheAmi(JspWriter out) throws Exception{
out.println("<p>Son ami s'appelle Haddock</p>");
}//afficheAmi
// نهاية الجزء العام من السيرفلت
%>
<%
// تشير العلامة السابقة إلى أن الكود التالي سيتم تسجيله
// في الطريقة الرئيسية للـ servlet
// متغير محلي في الطريقة الرئيسية
String nom="tintin";
%>
<%-- الكود 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>
<%
// اسم صديقه
afficheAmi(out);
%>
</center>
</body>
</html>
تقوم هذه الصفحة JSP بإنشاء صفحة الويب التالية:

لنلقِ نظرة على كيفية إنشاء الأسطر الأربعة المذكورة أعلاه:
<p>Son nom est <%= nom %></p>
<p>Son prénom est <%= prenom %></p>
<p>Son chien s'appelle <%= sonChien() %></p>
<%
// اسم صديقه
afficheAmi(out);
%>
تقع الأسطر المذكورة أعلاه داخل علامة <%..%>، وبالتالي ستكون جزءًا من الطريقة الرئيسية _jspService للـservlet التي سيتم إنشاؤها. كيف يمكنها الوصول إلى المتغيرات nom و prenom والطرق sonChien و afficheAmi؟
هي متغير محلي في الدالة الرئيسية للصفحة JSP، وبالتالي فهي معروفة فيها | |
هي متغير عام في الصفحة JSP، وبالتالي فهي معروفة في الطريقة الرئيسية | |
هي دالة عامة في الصفحة JSP، وبالتالي يمكن الوصول إليها من الدالة الرئيسية | |
هي طريقة عامة في الصفحة JSP، وبالتالي يمكن الوصول إليها من الطريقة الرئيسية. يُلاحظ أننا نمرر الكائن out كمعلمة إلى الطريقة. وهذا أمر إلزامي هنا. في الواقع، يتم إعلان الكائن out وتهيئته في الطريقة الرئيسية للسيرفلت، وهو ليس متغيرًا عامًا. |
لنلقِ الآن نظرة على كود السيرفلت جافا الذي تم إنشاؤه من هذه الصفحة 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 {
// العلامة السابقة تبدأ قسم المتغيرات والطرق العامة
// سيتم تضمين هذا الجزء دون تعديل في السيرفلت
// متغير عام
String prenom="inconnu";
// دالة
private String sonChien(){
return "milou";
}//sonChien
// دالة أخرى
private void afficheAmi(JspWriter out) throws Exception{
out.println("<p>Son ami s'appelle Haddock</p>");
}//afficheAmi
// نهاية الجزء العام من السيرفلت
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");
// تشير العلامة السابقة إلى أن الكود التالي سيتم تسجيله
// في الطريقة الرئيسية للـ servlet
// متغير محلي في الطريقة الرئيسية
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 ");
// اسم صديقه
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 <%! .. %> قد تم استنساخه بالكامل ولا يشكل جزءًا من الطريقة الرئيسية _jspService للـ servlet. وبالتالي، فإن المتغيرات المُعلنة في هذا الجزء هي متغيرات مثيل، أي أنها متغيرات عامة للطرق، كما يمكن في هذا الجزء تعريف طرق أخرى بخلاف _jspService.
// سيتم تضمين هذا الجزء دون تعديل في السيرفلت
// متغير عام
String prenom="inconnu";
// دالة
private String sonChien(){
return "milou";
}//sonChien
// دالة أخرى
private void afficheAmi(JspWriter out) throws Exception{
out.println("<p>Son ami s'appelle Haddock</p>");
}//afficheAmi
// نهاية الجزء العام من السيرفلت
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 إلى ملف جافا المصدر، ثم يقوم Tomcat بتجميع هذا الملف عندما يطلب متصفح الصفحة JSP (التي تحمل الرقم URL). قد تظهر أخطاء في التجميع. ويقوم Tomcat بالإبلاغ عنها في رده إلى المتصفح. ويشير بشكل خاص إلى الأسطر الخاطئة في ملف .java. قد تكون لهذه الأخطاء أسباب متنوعة:
- الرمز JSP للصفحة خاطئ (أخطاء في علامات jsp المستخدمة على سبيل المثال)
- كود Java المضمن في الصفحة JSP خاطئ
يمكن استبعاد السبب الأول عن طريق التحقق من رمز الصفحة JSP. أما السبب الثاني فيمكن استبعاده عن طريق التحقق من كود Java. ويمكن القيام بذلك عن طريق ترجمة ملف .java الذي تم إنشاؤه للصفحة JSP مباشرةً باستخدام أداة مثل JBuilder التي توفر إمكانيات تصحيح أخطاء أكثر تطورًا من تلك المتوفرة في Tomcat.
3.2.8. أمثلة
نستعرض المثال الذي تناولناه سابقًا باستخدام سيرفلت حيث يختار المستخدم رقمًا من قائمة ويخبره الخادم بالرقم الذي اختاره مع إعادة إرسال نفس القائمة إليه مع تحديد العنصر الذي اختاره المستخدم كعنصر محدد:

لإنشاء هذه الصفحة، تم استرداد كود السيرفلت وتعديله على النحو التالي:
- احتفظنا برمز Java كما هو، حيث إنه لم ينتج رمز HTML
- تم تحويل كود Java الذي كان ينتج كود HTML إلى مزيج من كود HTML وكود JSP
وبذلك نحصل على الصفحة التالية JSP:
<%@ page import="java.sql.*, java.util.*" %>
<%!
// المتغيرات العامة للتطبيق
// عنوان الصفحة
private final String title="Génération d'un formulaire";
// قاعدة بيانات قيم القائمة
private final String DSNValeurs="odbc-valeurs";
private final String admDbValeurs="admDbValeurs";
private final String mdpDbValeurs="mdpDbValeurs";
// قيم القائمة
private String[] valeurs=null;
// رسالة الخطأ
private String msgErreur=null;
// تهيئة الصفحة JSP - يتم تنفيذها مرة واحدة فقط
public void jspInit(){
// تملأ جدول القيم من قاعدة بيانات ODBC
// الاسم DSN: DSNvaleurs
Connection connexion=null;
Statement st=null;
ResultSet rs=null;
try{
// الاتصال بقاعدة البيانات ODBC
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
// كائن Statement
st=connexion.createStatement();
// تنفيذ استعلام SELECT لاسترداد القيم
rs=st.executeQuery("select valeur from Tvaleurs");
// يتم استرداد القيم ووضعها في مصفوفة ديناميكية
ArrayList lstValeurs=new ArrayList();
while(rs.next()){
// يتم تسجيل القيمة في القائمة
lstValeurs.add(rs.getString("valeur"));
}//while
// تحويل القائمة إلى مصفوفة
valeurs=new String[lstValeurs.size()];
for (int i=0;i<lstValeurs.size();i++){
valeurs[i]=(String)lstValeurs.get(i);
}
}catch(Exception ex){
// مشكلة
msgErreur=ex.getMessage();
}finally{
try{rs.close();}catch(Exception ex){}
try{st.close();}catch(Exception ex){}
try{connexion.close();}catch(Exception ex){}
}//محاولة
}//التهيئة
%>
<%
// رمز _jspService الذي يتم تنفيذه عند كل طلب من العميل
// هل حدث خطأ أثناء تهيئة الصفحة JSP؟
if(msgErreur!=null){
%>
<!-- رمز HTML -->
<html>
<head>
<title>Erreur</title>
</head>
<body>
<h3>Application indisponible (<%= msgErreur %></h3>
</body>
</html>
<%
// نهاية jspService
return;
}//إذا
// يتم استرداد الاختيار المحتمل للمستخدم
String choix=request.getParameter("cmbValeurs");
if(choix==null) choix="";
%>
<%-- لا توجد أخطاء - رمز HTML للصفحة العادية --%>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h3>Choisissez une valeur</h3>
<form method="POST">
<select name="cmbValeurs">
<%
// العرض الديناميكي للقيم
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>");
}//لـ
%>
</select>
<input type="submit" value="Envoyer">
</form>
<%
// هل تم اختيار أي قيمة؟
if(! choix.equals("")){
// يتم عرض اختيار المستخدم
%>
<hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%
}//if
%>
</body>
</html>
تجدر الإشارة إلى النقاط التالية:
- تم تضمين تعليمات import الخاصة بالسيرفلت ضمن توجيه <% page import="..." %>
- تحيط العلامة <%! ... %> بالمتغيرات العامة وأساليب جافا الخاصة بالتطبيق
- يتم استدعاء الدالة init الخاصة بالسيرفلت، والتي يتم تنفيذها مرة واحدة فقط عند تحميل السيرفلت، من أجل الصفحة JSP: jspInit. وتؤدي هاتان الدالتان نفس الدور. لذلك تم هنا استنساخ كود الطريقة init الخاصة بالسيرفلت بالكامل.
- أما متغيرات مثيل السيرفلت، أي تلك التي يجب أن تكون معروفة في عدة طرق، فقد تم استنساخها بالضبط كما هي. وهي تتألف أساسًا من المتغيرات title و valeurs و msgErreur التي تُستخدم لاحقًا في كود JSP.
- تحيط العلامات <% ... %> برمز Java الذي سيتم تضمينه في الأسلوب _jspService الذي يتم تنفيذه عند تلقي طلب من العميل.
- وكما هو الحال مع السيرفلت، ستبدأ الطريقة _jspService بالتحقق من قيمة المتغير msgErreur لمعرفة ما إذا كان عليها إنشاء صفحة خطأ. في حالة وجود خطأ، تقوم بإنشاء صفحة الخطأ وتتوقف (return).
- وإذا لم يكن هناك خطأ، فإنها تقوم بإنشاء النموذج مع قائمة القيم
- وبعد ذلك، تتحقق مما إذا كان المستخدم قد اختار رقمًا، وفي هذه الحالة تعرض هذا الرقم في الصفحة التي تم إنشاؤها
ما الذي كسبناه مقارنةً بالسيرفلت؟ بلا شك رؤية أفضل للكود HTML الذي تم إنشاؤه. لكن لا يزال هناك الكثير من كود جافا الذي «يُشوش» على هذه الرؤية. سنرى لاحقًا طريقة أخرى تُسمى «التفويض»، حيث يمكننا وضع الجزء الأكبر من كود Java في سيرفلت، بينما تحتفظ الصفحة JSP فقط بكود HTML وJSP. وبذلك يتم الفصل بوضوح بين جزء المعالجة وجزء العرض.
3.3. نشر تطبيق ويب على خادم Tomcat
نقدم الآن طريقة نشر تطبيقات الويب التي تعمل بلغة Java باستخدام خادم Tomcat. ورغم أن ما سيُذكر هنا يخص هذا الخادم تحديدًا، فإن نشر تطبيق ويب يعمل بلغة Java داخل حاوية أخرى J2EE سيتسم بخصائص مشابهة لتلك التي سيتم وصفها الآن.
3.3.1. ملفات التكوين server.xml و web.xml
حتى الآن، لاختبار سيرفلتاتنا وصفحاتنا JSP، قمنا بوضع
- السيرفلتات في المجلد <tomcat>\webapps\examples\WEB-INF\classes. وكان من الممكن الوصول إليها عبر http://localhost:8080/examples/servlet/nomServlet
- الصفحات JSP في الشجرة <tomcat>\webapps\examples\jsp. وكان يمكن الوصول إليها آنذاك عبر URL http://localhost:8080/examples/jsp/nomPageJSP
لم نوضح أبدًا سبب ذلك. تتم تهيئة خادم Tomcat في ملف نصي يُسمى server.xml موجود في المجلد <tomcat>\conf:

هذا الملف النصي هو في الواقع ملف XML (لغة ترميز eXtended). مستند XML هو مستند نصي يحتوي على علامات، تمامًا مثل مستند HTML. ومع ذلك، في حين أن علامات لغة HTML محددة بوضوح، فإن علامات لغة XML ليست كذلك. وبالتالي، فإن المستند التالي هو مستند XML:
المستند XML هو ببساطة مستند "مُعلم" ويتبع قواعد معينة للتعليم:
- نص مُعلَّم بالصيغة <xx att1="val1" att2="val2" ....>نص</xx>
- يمكن أن تكون العلامة منفردة وتكون على الشكل <xx att1="val1" att2="val2" ..../>
تُسمى الحقول «atti» بسمات العلامة «xx»، أما الحقول «vali» فهي القيم المرتبطة بهذه السمات. بعض المستندات HTML ليست مستندات XML صالحة. على سبيل المثال، العلامة <br> ليست علامة صالحة. يجب كتابتها على النحو التالي: <br/> لتكون صالحة، وذلك امتثالًا للقاعدة التي تنص على أن كل علامة يجب أن تُغلق. تم إنشاء نسخة معدلة من HTML تُسمى XHTML بهدف تحويل أي مستند XHTML إلى مستند XML صالح. بعض المتصفحات الحديثة قادرة على عرض ملفات XML. وبالتالي، إذا أطلقنا على المستند XML المذكور في المثال أعلاه اسم personne.xml وقمنا بعرضه باستخدام IE6، فسنحصل على العرض التالي:

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

فقد تعرّف IE6 بشكل صحيح على بنية ومحتوى المستند. وتكمن أهمية المستند XML في هذه الخاصية: فمن السهل استرجاع بنية ومحتوى مستند XML. ويتم ذلك باستخدام برنامج يُسمى محلل XML. تميل المستندات XML إلى أن تصبح المعيار السائد في تبادل المستندات عبر الويب. لنفترض أن الجهاز «أ» عليه إرسال مستند DOC إلى الجهاز «ب». يتم إنشاء المستند DOC استنادًا إلى المعلومات الموجودة في قاعدة بيانات DB-A. أما الجهاز B، فيجب عليه تخزين المستند DOC في قاعدة بيانات DB-B. ويمكن أن يتم التبادل على النحو التالي:
- تسترد الآلة «أ» البيانات من قاعدة البيانات «DB-A» وتغلفها في مستند نصي «XML»
- يتم إرسال المستند XML إلى الجهاز B عبر الشبكة
- تقوم الآلة B بتحليل المستند المستلم باستخدام محلل XML وتستخرج منه البنية والبيانات (كما فعلت IE6 في مثالنا). ويمكنها بعد ذلك تخزين البيانات المستلمة في قاعدة البيانات DB-B
لن نذكر المزيد عن لغة XML التي تستحق كتابًا خاصًا بها.
هنا، إذن، يتم تكوين Tomcat بواسطة الملف XML server.xml. إذا قمنا بعرض هذا الملف باستخدام IE6، فسنحصل على مستند معقد. سنكتفي بالتركيز على الأسطر التالية:

العلامة <Context ...> هي ما يهمنا هنا. وهي تُستخدم لتعريف تطبيقات الويب. تجدر الإشارة إلى اثنين من سماتها:
- path: هو اسم تطبيق الويب
- docBase: هو المجلد الذي يوجد فيه التطبيق. هنا هو اسم نسبي: examples. نسبي بالنسبة إلى أي مجلد؟ توجد الإجابة أيضًا في الملف server.xml في السطر التالي:

يحدد السطر أعلاه خادم الويب:
- name: اسم خادم الويب
- appBase: جذر شجرة المستندات التي يوزعها. مرة أخرى، لدينا اسم نسبي: webapps. وهو نسبي بالنسبة لمجلد تثبيت خادم Tomcat <tomcat>. وبالتالي، فإن المقصود هو المجلد <tomcat>\webapps.
تحتوي تطبيق الويب examples على مستنداتها في المجلد examples (انظر docBase أعلاه). هذا الاسم مرتبط بجذر شجرة الويب الخاصة بالخادم، c.a.d. <tomcat>\webapps. وبالتالي، فإن المجلد المقصود هو <tomcat>\webapps\examples. دعونا نلقي نظرة عن قرب على هذا المجلد:

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

يُستخدم هذا الملف لتكوين تطبيق الويب examples. لن نخوض في تفاصيل هذا الملف المعقد جدًّا في الوقت الحالي. سنكتفي بالتركيز على الأسطر القليلة التالية:
<servlet>
<servlet-name>
servletToJsp
</servlet-name>
<servlet-class>
servletToJsp
</servlet-class>
</servlet>
تُستخدم العلامة <servlet> لتعريف سيرفلت داخل تطبيق ويب. تجدر الإشارة هنا إلى أن تطبيق الويب المعني هو examples. تحتوي العلامة servlet هنا على علامتين أخريين:
- <servlet-name>servletToJsp</servlet-name>: تحدد اسم السيرفلت
- <servlet-name>servletToJsp</servlet-name>: يحدد اسم الفئة التي سيتم تنفيذها عند طلب السيرفلت. في هذا المثال، يحمل السيرفلت وفئته نفس الاسم. وهذا ليس إلزامياً.
كيف يطلب المتصفح من خادم Tomcat السيرفلت servletToJsp؟
- يطلب المتصفح الـ 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، c.a.d. في <tomcat>\webapps\examples\WEB-INF\classes ويقوم بتنفيذها.

3.3.2. مثال: نشر تطبيق الويب «القائمة»
نستعيد سيرفلت سبق دراسته وكان يعرض للمستخدم قائمة بالأرقام ليختار منها رقمًا واحدًا. ثم كانت السيرفلت تؤكد له الرقم الذي اختاره:

كما يظهر في الحقل Address في المتصفح أعلاه، كان اسم ملف فئة السيرفلت هو gener3. وفقًا للتفسيرات التي تم تقديمها سابقًا:
- يُظهر URL /examples/servlet/gener3 أن الأمر يتعلق بـ«سيرفلت» يُسمى gener3 تابع لتطبيق الويب examples
- في الملف web.xml الخاص بالتطبيق examples، لن نجد أي شيء يشير إلى سيرفلت gener3. إذن، كيف عثر عليها Tomcat؟ بعد أن قمت بمراجعة ملف web.xml بالكامل، لا يمكنني الإجابة على هذا السؤال بثقة... يبقى السؤال مطروحًا...
نختار نشر السيرفلت gener3.class تحت الاسم lstValeurs في تطبيق ويب يُسمى «liste» موجود في المجلد E:\data\serge\Servlets\lstValeurs:

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

نقوم بتكوين تطبيق الويب «liste» بإضافة الأسطر التالية في الملف server.xml فوق الأسطر التي تحدد تطبيق الويب manager:
<!-- شخصي: lstValeurs -->
<Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />
<!-- سياق Tomcat Manager -->
<Context path="/manager" docBase="manager" debug="0" privileged="true" />
<!-- سياق أمثلة Tomcat -->
<Context path="/examples" docBase="examples" debug="0" reloadable="true" crossContext="true">
........
تشير السطر الذي يحدد قائمة التطبيق إلى أنه موجود في المجلد 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 الخاص بتطبيق «القائمة»:

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

نلاحظ أن ملف URL السابق يحتوي على الكلمة الرئيسية servlet مثل جميع ملفات URL الخاصة بـ servlets المستخدمة حتى الآن. يمكن الاستغناء عنها من خلال ربط السيرفلت lstValeurs بنموذج URL (url-pattern) في ملف web.xml الخاص بتطبيق «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-mapping>، نربط المسار /valeurs بالسيرفلت lstValeurs المحدد في الأسطر السابقة. نحفظ الملف الجديد web.xml ونطلب URL وhttp://localhost:8080/liste/valeurs:

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

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

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

لقد حصلنا بالفعل على الصفحة المتوقعة JSP.
3.3.4. معلمات تهيئة السيرفلت
لقد رأينا أن السيرفلت يتم تكوينه بواسطة الملف <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>
الفئة المرتبطة بالسيرفلت هي الفئة gener3. ويوجد في شفرة المصدر الخاصة بها تعريف لبعض الثوابت:
public class gener3 extends HttpServlet{
// عنوان الصفحة
private final String title="Génération d'un formulaire";
// قاعدة بيانات قيم القائمة
private final String DSNValeurs="odbc-valeurs";
private final String admDbValeurs="admDbValeurs";
private final String mdpDbValeurs="mdpDbValeurs";
دعونا نستذكر معنى الثوابت الأربعة المحددة أعلاه:
عنوان المستند HTML الذي تم إنشاؤه بواسطة السيرفلت | |
اسم قاعدة البيانات DSN التي ستستخرج منها الخدمة (servlet) البيانات | |
اسم مستخدم لديه حق القراءة في قاعدة البيانات السابقة | |
كلمة مروره |
إذا قام مسؤول قاعدة البيانات DSNValeurs بتغيير كلمة مرور المستخدم admDbValeurs، فيجب تعديل شفرة مصدر السيرفلت وإعادة تجميعها. وهذا الأمر غير عملي إلى حد كبير. يوفر لنا ملف تكوين السيرفلت web.xml بديلاً عن طريق السماح بتعريف معلمات تهيئة السيرفلت باستخدام العلامة <init-param>:
يسمح بتحديد اسم المعلمة | |
يحدد القيمة المرتبطة بالمعلمة السابقة |
يمكن لـ servlet الوصول إلى معلمات التهيئة الخاصة بها من خلال الطرق التالية:
طريقة تابعة لفئة Servlet التي تنحدر منها فئة HttpServlet المستخدمة في برمجة الويب. تُرجع كائنًا من نوع ServletConfig يتيح الوصول إلى معلمات تكوين السيرفلت. | |
طريقة تابعة للفئة ServletConfig تُرجع قيمة معلمة التهيئة "paramètre" |
نقوم بتكوين التطبيق 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>
في التطبيق liste، نقوم بتعريف سيرفلت ثانية تسمى lstValeurs2 مرتبطة بملف الفئة gener5. وقد تم وضع هذا الملف في <application>\WEB-INF\classes:

يحتوي السيرفلت lstValeurs2 على أربعة معلمات تهيئة: title، DSNValeurs، admDbValeurs، mdpDbValeurs. بالإضافة إلى ذلك، تم تعريف الاسم المستعار /valeurs2 للـservlet باستخدام العلامة <servlet-mapping>. وبالتالي، سيكون من الممكن الوصول إلى السيرفلت lstValeurs2 التابع للتطبيق liste عبر URL وhttp://localhost:8080/liste/valeurs2.
تم تعديل كود مصدر السيرفلت على النحو التالي لاسترداد معلمات التهيئة الخاصة بالسيرفلت:
public class gener5 extends HttpServlet{
// عنوان الصفحة
private String title=null;
// قاعدة بيانات قيم القائمة
private String DSNValeurs=null;
private String admDbValeurs=null;
private String mdpDbValeurs=null;
...............
// تهيئة السيرفلت
public void init(){
// استرداد معلمات تهيئة السيرفلت
ServletConfig config=getServletConfig();
title=config.getInitParameter("title");
DSNValeurs=config.getInitParameter("DSNValeurs");
admDbValeurs=config.getInitParameter("admDbValeurs");
mdpDbValeurs=config.getInitParameter("mdpDbValeurs");
//هل تم استرداد جميع المعلمات؟
if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null){
msgErreur="Configuration incorrecte";
return;
}
// ملء جدول القيم من قاعدة البيانات ODBC
// الذي يحمل الاسم DSN: DSNvaleurs
...............
لاختبار السيرفلت، يجب إعادة تشغيل Tomcat حتى يأخذ في الاعتبار ملف التكوين الجديد web.xml الخاص بالتطبيق liste. باستخدام متصفح، نطلب URL من السيرفلت http://localhost:8080/liste/valeurs2:

إذا كان أحد معلمات التهيئة الضرورية للـservlet غير موجود في الملف web.xml، فسيتم عرض الصفحة التالية:

3.3.5. معلمات التهيئة لتطبيق ويب
في المثال السابق، لا يمكن إلا لـ servlet lstValeurs2 الوصول إلى المعلمات title، DSNValeurs، admDbValeurs، mdpDbValeurs. قد نتصور أن سيرفلتًا آخر من نفس التطبيق، وهو liste، يحتاج إلى البيانات الموجودة في نفس قاعدة البيانات التي يستخدمها السيرفلت lstValeurs2. في هذه الحالة، سيتعين إعادة تعريف المعلمات DSNValeurs، admDbValeurs، mdpDbValeurs في قسم التكوين بملف web.xml الخاص بالسيرفلت الجديد. وهناك حل آخر يتمثل في تعريف المعلمات المشتركة بين عدة سيرفلتات على مستوى التطبيق وليس على مستوى السيرفلتات. ويصبح الملف الجديد 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>. تُستخدم هذه العلامة لتعريف المعلومات الخاصة بالتطبيق وليس بـ servlet أو صفحة JSP معينة. كيف يمكن لـ servlet Java الوصول إلى هذه المعلمات التي غالبًا ما تُسمى معلمات السياق؟ الطرق المتاحة للحصول على معلومات السياق مشابهة جدًّا لتلك المستخدمة للحصول على معلمات التهيئة الخاصة بـ servlet:
طريقة من فئة Servlet التي تنحدر منها فئة HttpServlet المستخدمة في برمجة الويب. تُرجع كائن ServletContext يتيح الوصول إلى معلمات تكوين التطبيق | |
طريقة من فئة ServletContext تُرجع قيمة معلمة التهيئة "paramètre" |
تُحدث الفئة gener6.java التعديلات التالية فقط على كود Java الخاص بـ gener5.java المستخدم سابقًا:
// يتم استرداد معلمات التهيئة الخاصة بالسيرفلت
ServletConfig config=getServletConfig();
title=config.getInitParameter("title");
ServletContext context=getServletContext();
DSNValeurs=context.getInitParameter("DSNValeurs");
admDbValeurs=context.getInitParameter("admDbValeurs");
mdpDbValeurs=context.getInitParameter("mdpDbValeurs");
//هل تم استرداد جميع المعلمات؟
if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null){
msgErreur="Configuration incorrecte";
return;
}
// يتم ملء مصفوفة القيم من قاعدة بيانات ODBC
// التي تحمل الاسم DSN: DSNvaleurs
...............
يتم الحصول على المعلمة title الخاصة بـ servlet عبر كائن ServletConfig. أما المعلمات الثلاث الأخرى المحددة على مستوى التطبيق، فيتم الحصول عليها عبر كائن ServletContext. نقوم بتجميع هذه الفئة ووضعها مثل الفئات الأخرى في <application>\WEB-INF\classes:

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

3.3.6. معلمات التهيئة لصفحة JSP
لقد رأينا كيفية تعريف معلمات التهيئة لـ servlet أو تطبيق ويب. هل يمكننا فعل الشيء نفسه لصفحة JSP؟ لنعد إلى بداية كود الصفحة listvaleurs.jsp التي درسناها سابقًا:
<%@ page import="java.sql.*, java.util.*" %>
<%!
// المتغيرات العامة للتطبيق
// عنوان الصفحة
private final String title="Génération d'un formulaire";
// قاعدة بيانات قيم القائمة
private final String DSNValeurs="odbc-valeurs";
private final String admDbValeurs="admDbValeurs";
private final String mdpDbValeurs="mdpDbValeurs";
.........
توجد الثوابت الأربعة title و DSNValeurs و admDbValeurs و mdpDbValeurs محددة في ملف web.xml الخاص بالتطبيق. أما الثوابت DSNValeurs و admDbValeurs و mdpDbValeurs فقد تم تعريفها الآن على مستوى التطبيق، وبالتالي يمكننا افتراض أن الصفحة 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-name>JSPlstValeurs</servlet-name>
<jsp-file>/listvaleurs2.jsp</jsp-file>
...
</servlet>
في الواقع، تُعتبر الصفحة JSP بمثابة سيرفلت يُعطى اسمًا (servlet-name). وبدلاً من ربط ملف فئة بهذا السيرفلت، يتم ربط ملف المصدر للصفحة JSP المراد تنفيذه (jsp-file). وبالتالي، فإن الأسطر القليلة السابقة تُعرِّف سيرفلتًا يُسمى JSPlstvaleurs مرتبطًا بالصفحة JSP /listvaleurs2.jsp. يُحسب المسار /listvaleurs2.jsp بالنسبة إلى جذر التطبيق. وبالتالي، في حالة تطبيقنا liste، سيكون الملف listvaleurs2.jsp موجودًا في المجلد docBase (انظر server.xml) التابع للتطبيق liste:

ستكون تهيئة الصفحة 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():
<%!
// المتغيرات العامة للتطبيق
// عنوان الصفحة
private String title=null;
// قاعدة بيانات قيم القائمة
private String DSNValeurs=null;
private String admDbValeurs=null;
private String mdpDbValeurs=null;
// قيم القائمة
private String[] valeurs=null;
// رسالة الخطأ
private String msgErreur=null;
// تهيئة الصفحة JSP - يتم تنفيذها مرة واحدة فقط
public void jspInit(){
// يتم استرداد معلمات تهيئة السيرفلت
ServletConfig config=getServletConfig();
title=config.getInitParameter("JSPtitle");
ServletContext context=getServletContext();
DSNValeurs=context.getInitParameter("DSNValeurs");
admDbValeurs=context.getInitParameter("admDbValeurs");
mdpDbValeurs=context.getInitParameter("mdpDbValeurs");
//هل تم استرداد جميع المعلمات؟
if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null){
msgErreur="Configuration incorrecte";
return;
}
// ملء المصفوفة بالقيم من قاعدة البيانات ODBC
// التي تحمل الاسم DSN: DSNvaleurs
..............
تسترد الصفحة JSP معلمات التهيئة الخاصة بها بنفس الطريقة التي تسترد بها السيرفلتات معلماتها. يتم حفظ الملف السابق في جذر تطبيق الويب liste:

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

3.3.7. التعاون بين السيرفلتات/الصفحات JSP داخل تطبيق ويب
عندما يرسل عميل طلبًا إلى خادم ويب، يمكن أن يتم إعداد الاستجابة بواسطة عدة سيرفلتات وصفحات JSP. حتى الآن، كانت الاستجابة تُعد بواسطة سيرفلت أو صفحة واحدة JSP. لقد لاحظنا أن الصفحة JSP توفر قابلية أفضل لقراءة بنية المستند HTML الذي تم إنشاؤه. ومع ذلك، فهي تحتوي عمومًا على الكثير من كود Java. يمكن تحسين الوضع عن طريق وضع
- كود جافا الذي لا يُنشئ كود HTML الخاص بالاستجابة
- في صفحات JSP، ووضع كود إنشاء المستندات المختلفة HTML المرسلة كاستجابة للعميل
وبذلك يمكننا أن نأمل في تحسين الفصل بين كود Java وكود HTML. سنطبق هذه البنية الجديدة على تطبيقنا liste: ستتولى خدمة Java (servlet) lstValeurs4 قراءة القيم من قاعدة البيانات عند بدء التشغيل، ثم تحليل طلبات العملاء. وبناءً على نتيجة هذا التحليل، سيتم توجيه طلب العميل إما إلى صفحة خطأ erreur.jsp أو إلى صفحة عرض قائمة الأرقام liste.jsp. وبالتالي، سيتألف التطبيق liste من سيرفلت وصفحتين JSP.
كيف يمكن لـ«سيرفلت» أن تمرر الطلب الذي تلقتْه من عميل إلى «سيرفلت» آخر أو إلى صفحة JSP؟ سنستخدم الطرق التالية:
طريقة من فئة ServletContext التي تُرجع كائن RequestDispatcher. المعلمة url هي اسم URL الذي نريد توجيه طلب العميل إليه. لا يمكن تمرير هذا الطلب إلا داخل نفس التطبيق. كما أن المعلمة url هي مسار نسبي في شجرة الويب لهذا التطبيق. | |
طريقة واجهة RequestDispatcher التي تنقل إلى الطريقة السابقة URL طلب العميل request والكائن response الذي يجب استخدامه لإنشاء الرد. | |
عندما تقوم خدمة أو صفحة JSP بتمرير طلب إلى خدمة أو صفحة أخرى JSP، فإنها تحتاج عادةً إلى تمرير معلومات أخرى إلى تلك الخدمة أو الصفحة، بخلاف طلب العميل وحده، وهي معلومات ناتجة عن عملها الخاص على الطلب. تسمح الطريقة setAttribute الخاصة بالفئة ServletRequest بإضافة سمات إلى كائن request الخاص بالعميل في شكل يشبه قاموسًا من أزواج (السمة، القيمة)، حيث attribut هو اسم السمة وvaleur هو كائن ما يمثل قيمتها. | |
تسمح باسترداد قيم سمات طلب ما. ستستخدم هذه الطريقة من قبل السيرفلت أو الصفحة 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 المراد إنشاؤه | |
رقم خطأ الصفحة JSP | |
URL الخاص بصفحة JSP التي تعرض قائمة الأرقام | |
URL المرتبطة بسمة action للنموذج المعروض في الصفحة JSPliste. وستكون هذه الصفحة URL هي صفحة السيرفلت lstValeurs4 |
وستحمل السيرفلت الاسم المستعار /valeurs4 (تعيين السيرفلت) وبالتالي سيكون الوصول إليها ممكنًا عبر 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{
// عنوان الصفحة
private String title=null;
// قاعدة بيانات قيم القائمة
private String DSNValeurs=null;
private String admDbValeurs=null;
private String mdpDbValeurs=null;
// صفحات العرض JSP
private String JSPerreur=null;
private String JSPliste=null;
// الـ URL الخاص بالسيرفلت
private String URLservlet=null;
// قيم القائمة
private String[] valeurs=null;
// رسالة الخطأ
private String msgErreur=null;
// -----------------------------------------------------------------
// GET
public void doGet(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// يتم وضع msgErreur، العنوان في سمات الطلب
request.setAttribute("msgErreur",msgErreur);
request.setAttribute("title",title);
request.setAttribute("URLservlet",URLservlet);
// هل حدث خطأ أثناء تحميل السيرفلت؟
if(msgErreur!=null){
// يتم التوجيه إلى صفحة خطأ JSP
getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
// النهاية
return;
}
// لم تحدث أي أخطاء
// يتم وضع قائمة القيم في سمات الطلب
request.setAttribute("valeurs",valeurs);
// يتم استرداد الاختيار المحتمل للمستخدم
String choix=request.getParameter("cmbValeurs");
if(choix==null) choix="";
request.setAttribute("choix",choix);
// ننتقل إلى الصفحة JSP لعرض القائمة
getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
// النهاية
return;
}//GET
// -----------------------------------------------------------------
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// يتم إعادة التوجيه إلى GET
doGet(request,response);
}//POST
// -----------------------------------------------------------------
// تهيئة السيرفلت
public void init(){
// استرداد معلمات تهيئة السيرفلت
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");
//هل تم استرداد جميع المعلمات؟
if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
msgErreur="Configuration incorrecte";
return;
}
// ملء المصفوفة بالقيم من قاعدة البيانات ODBC
// التي تحمل الاسم DSN: DSNvaleurs
Connection connexion=null;
Statement st=null;
ResultSet rs=null;
try{
// الاتصال بقاعدة البيانات ODBC
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
// كائن Statement
st=connexion.createStatement();
// تنفيذ استعلام SELECT لاسترداد القيم
rs=st.executeQuery("select valeur from Tvaleurs");
// يتم استرداد القيم ووضعها في مصفوفة ديناميكية
ArrayList lstValeurs=new ArrayList();
while(rs.next()){
// يتم تسجيل القيمة في القائمة
lstValeurs.add(rs.getString("valeur"));
}//while
// تحويل القائمة إلى مصفوفة
valeurs=new String[lstValeurs.size()];
for (int i=0;i<lstValeurs.size();i++){
valeurs[i]=(String)lstValeurs.get(i);
}
}catch(Exception ex){
// مشكلة
msgErreur=ex.getMessage();
}finally{
try{rs.close();}catch(Exception ex){}
try{st.close();}catch(Exception ex){}
try{connexion.close();}catch(Exception ex){}
}//محاولة
}//تهيئة
}//فئة
الجديد في هذه الفئة هو توجيه طلب العميل إلى الصفحة JSPerreur في حالة حدوث خطأ، وإلى الصفحة JSPliste في الحالات الأخرى. ولا تقوم الفئة نفسها بإعداد الرد. بل تتولى الصفحات JSP وJSPerreur وJSPliste هذه المهمة. في السابق، كانت السيرفلت تضيف سمات (setAttribute) إلى طلب العميل:
- رسالة خطأ msgErreur في حالة حدوث خطأ في الصفحة JSPerreur
- القيم (valeurs)) المراد عرضها، والقيمة (choix) التي اختارها المستخدم، وعنوان (title) النموذج، وURL (URLservlet) لخاصية action في النموذج للصفحة JSPliste
يتم ترجمة هذه الفئة وإضافتها إلى فئات التطبيق:

يتم تكوين الصفحة 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 المرتبط بصفحة الخطأ يُسمى erreur.jsp ويوجد في الجذر الخاص بالتطبيق:

ويُعرف باسم مستعار /JSPerreur، مما يجعل الوصول إليه ممكنًا عبر http://localhost:8080/liste/JSPerreur. ولديها معلمة تهيئة تسمى mainServlet، وقيمتها هي الاسم المستعار للسيرفلت الرئيسي الموصوف سابقًا. تجدر الإشارة إلى أن هذا الاسم المستعار مرتبط بجذر التطبيق liste,، وإلا لكانت القيمة /liste/valeurs4.. وفيما يلي كود الصفحة erreur.jsp:
<%
// رمز _jspService
// يتم استرداد معلمة التهيئة mainServlet
String servletListValeurs=config.getInitParameter("mainServlet");
// يتم استرداد السمة msgErreur
String msgErreur=(String)request.getAttribute("msgErreur");
// هل السمة صالحة؟
if(msgErreur!=null){
%>
<!-- الرمز HTML -->
<html>
<head>
<title>Erreur</title>
</head>
<body>
<h3>Application indisponible (<%= msgErreur %>)</h3>
</body>
</html>
<%
} else { // السمة msgErreur غير صالحة - العودة إلى السيرفلت الرئيسي
%>
<jsp:forward page="<%= servletListValeurs %>" />
<%
}
%>
من المفترض أن يتم استدعاء هذه الصفحة بواسطة السيرفلت السابق الذي يجب أن يمرر إليها السمة msgErreur. ومع ذلك، لا يوجد ما يمنع من استدعائها مباشرةً إذا كنا نعرف السمة URL الخاصة بها. وكذلك، إذا تبين أن السمة msgErreur غير موجودة، يتم تمرير الطلب إلى السيرفلت الرئيسي. هنا، نستخدم علامة خاصة بصفحات JSP، وصيغتها هي:
حيث يمثل URL السيرفلت الذي يتم تحويل طلب العميل إليه. إذا كان السمة msgErreur موجودة، يتم عرض صفحة الخطأ.
يتم تكوين الصفحة 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 ويوجد في الجذر الخاص بالتطبيق:

الاسم المستعار للـservlet هو /JSPliste، مما يجعل الوصول إليها ممكنًا عبر http://localhost:8080/liste/JSPliste. وتحتوي على معلمة تهيئة تسمى mainServlet، وقيمتها هي الاسم المستعار للـservlet الرئيسي. وفيما يلي كود الصفحة liste.jsp:
<%-- صفحة عرض قائمة القيم --%>
<%
// رمز jspService
// يتم استرداد معلمة التهيئة
String servletListValeurs=config.getInitParameter("mainServlet");
// يتم استرداد سمات الطلب الواردة من السيرفلت الرئيسي
String title=(String) request.getAttribute("title");
String[] valeurs=(String[]) request.getAttribute("valeurs");
String choix=(String) request.getAttribute("choix");
String URLservlet=(String) request.getAttribute("URLservlet");
// هل السمات صالحة؟
if(title==null || valeurs==null || choix==null){
// هناك سمة غير صالحة - يتم تمرير الأمر إلى السيرفلت
%>
<jsp:forward page="<%= servletListValeurs %>" />
<%
}//if
%>
<%-- الرمز HTML --%>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h3>Choisissez une valeur</h3>
<form method="POST" action="<%= URLservlet %>">
<select name="cmbValeurs">
<%
// العرض الديناميكي للقيم
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>
<%
// هل تم اختيار قيمة ما؟
if(! choix.equals("")){
// يتم عرض اختيار المستخدم
%>
<hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%
}//if
%>
</body>
</html>
تعمل هذه الصفحة بنفس طريقة الصفحة erreur.jsp. ويجب عادةً أن يتم استدعاؤها بواسطة السيرفلت /liste/valeurs4 وأن تتلقى السمات title و valeurs و choix. إذا كان أحد هذه المعلمات مفقودًا، يتم تمرير المهمة إلى السيرفلت URLservlet (/liste/valeurs4). إذا كانت جميع المعلمات موجودة، يتم عرض قائمة الأرقام بالإضافة إلى الرقم الذي اختاره المستخدم في حال كان قد اختار رقمًا.
إذا تم استدعاء URL من السيرفلت الرئيسي، فسنحصل على النتيجة التالية:

مع شفرة المصدر التالية (View/Source):
<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. ونلاحظ أن السمات title وvaleurs وURLservlet قد تم استردادها بالفعل.
وختامًا بشأن التفاعل بين السيرفلتات والصفحات JSP، نلاحظ أن الصفحات JSP قصيرة جدًّا هنا وتخلو من كود جافا الذي لا يساهم في إنشاء الاستجابة HTML بشكل مباشر. وبذلك تصبح بنية المستندات التي تم إنشاؤها أكثر وضوحًا.
3.4. دورة حياة السيرفلتات والصفحات JSP
3.4.1. دورة الحياة
نركز هنا على دورة حياة السيرفلت. أما دورة حياة الصفحات JSP فهي مستمدة منها. لنفترض أن سيرفلت ما تم استدعاؤه للمرة الأولى. عندئذٍ يقوم خادم الويب بإنشاء مثيل للفئة وتحميله في الذاكرة. ثم يقوم هذا المثيل بتلبية الطلب. وبعد الانتهاء من ذلك، لا يتم تفريغ السيرفلت من الذاكرة. بل تبقى فيها لخدمة طلبات أخرى بهدف تحسين أوقات استجابة الخادم. وسيتم إزالتها من الذاكرة عندما يمر وقت كافٍ دون أن تكون قد خدمت أي طلبات جديدة. وعادةً ما يكون هذا الوقت قابلاً للتكوين داخل خادم الويب.
وعندما تكون السيرفلت موجودة في الذاكرة، يمكنها تلبية عدة طلبات في وقت واحد. يقوم خادم الويب بإنشاء مؤشر ترابط لكل طلب، وتستخدم جميعها نفس مثيل السيرفلت:
![]() |
تشترك جميع الخيوط المذكورة أعلاه في متغيرات مثيل السيرفلت. قد يكون من الضروري مزامنة الخيوط لتجنب تلف بيانات السيرفلت. سنعود إلى هذا الموضوع لاحقًا.
عند تحميل السيرفلت، يتم تنفيذ طريقة معينة من طرق السيرفلت:
بالنسبة لصفحة JSP، فإن هذه الطريقة هي
public void jspInit(){
}
. وفيما يلي مثال لصفحة JSP تستخدم الطريقة jspInit:
<html>
<head>
<title>Compteur synchronisé</title>
</head>
<body>
Compteur= <%= getCompteur() %>
</body>
</html>
<%!
// المتغيرات والطرق العامة للصفحة JSP
// متغير المثيل
int compteur;
// طريقة لزيادة العداد
public int getCompteur(){
// يتم زيادة العداد
int myCompteur=compteur;
myCompteur++;
compteur=myCompteur;
// إعادته
return compteur;
}
// الطريقة التي يتم تنفيذها عند التحميل الأولي للصفحة
public void jspInit(){
// تهيئة العداد
compteur=100;
}
%>
تقوم الصفحة السابقة JSP بتهيئة عداد في jspInit بقيمة 100. أي استعلام لاحق إلى السيرفلت يزيد قيمة هذا العداد ثم يعرضها:
في المرة الأولى:

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

يتضح مما سبق أن السيرفلت لم يتم إيقاف تشغيله بين الطلبين، وإلا لكان العداد قد وصل إلى 101 عند الطلب الثاني. وعندما يتم إيقاف تشغيل السيرفلت، فإن الطريقة
يتم تنفيذها إن وجدت. أما بالنسبة للصفحات JSP، فإن الطريقة
public void jspDestroy(){
}
في هذه الطرق، يمكن على سبيل المثال إغلاق اتصالات قواعد البيانات، وهي الاتصالات التي تم فتحها في الطرق المقابلة init.
3.4.2. مزامنة الطلبات مع سيرفلت
لنعد إلى الصفحة السابقة JSP التي تزيد العداد وترسل قيمته إلى عميل الويب. لنفترض وجود طلبين متزامنين. عندئذٍ يتم إنشاء خيطين لتنفيذهما، وهذان الخيطان سيستخدمان نفس مثيل السيرفلت، أي نفس العداد في هذه الحالة. لنستذكر الكود الذي يزيد العداد:
public int getCompteur(){
// يتم زيادة العداد
int myCompteur=compteur;
myCompteur++;
compteur=myCompteur;
// إرجاعه
return compteur;
}
تمت كتابة زيادة العداد بشكل متعمد بطريقة غير دقيقة. لنفترض أن تنفيذ الخيطين يتم على النحو التالي:
![]() |
- في الوقت T1، يتم تنفيذ الخيط TH1. يقرأ العداد (=145) في myCompteur ثم يتم مقاطعته ويفقد وحدة المعالجة المركزية. وبالتالي، لم يتسن له الوقت لزيادة قيمة myCompteur ونسخ القيمة الجديدة إلى compteur.
- في الوقت T2، يتم تنفيذ الخيط TH2. يقرأ العداد (=145) في myCompteur ثم يتم مقاطعته ويفقد وحدة المعالجة المركزية. تجدر الإشارة إلى أن الخيطين لهما متغيرات مختلفة في myCompteur. ولا يشتركان إلا في متغيرات المثيل، وهي المتغيرات الشاملة للطرق.
- في الوقت T3، يستعيد الخيط TH1 السيطرة وينتهي. وبالتالي، فإنه يُرجع القيمة 146 إلى عميله.
- في الوقت T4، يستعيد الخيط TH2 السيطرة وينتهي. وهو أيضًا يُرجع القيمة 146 إلى عميله، في حين كان ينبغي عليه إرجاع 147.
نواجه هنا مشكلة في تزامن الخيوط. عندما يرغب TH1 في زيادة العداد، يجب عندئذٍ منع أي خيط آخر من القيام بذلك أيضًا. لتسليط الضوء على هذه المشكلة، نعيد كتابة الصفحة JSP بالطريقة التالية:
<html>
<head>
<title>Compteur synchronisé</title>
</head>
<body>
Compteur= <%= getCompteur() %>
</body>
</html>
<%!
// المتغيرات والطرق العامة للصفحة JSP
// متغير المثيل
int compteur;
// طريقة لزيادة العداد
public int getCompteur(){
// قراءة العداد
int myCompteur=compteur;
// التوقف لمدة 10 ثوانٍ
try{
Thread.sleep(10000);
}catch (Exception ignored){}
// زيادة العداد
compteur=myCompteur+1;
// إرجاعه
return compteur;
}
// الطريقة التي يتم تنفيذها عند التحميل الأولي للصفحة
public void jspInit(){
// تهيئة العداد
compteur=100;
}
%>
هنا، قمنا بإجبار الخيط على التوقف بعد 10 ثوانٍ من قراءة العداد. وبالتالي، من المفترض أن يفقد الخيط المعالج، ويتمكن خيط آخر بدوره من قراءة عداد لم يتم زيادته. وعندما نقوم بإجراء استعلامات باستخدام متصفح، لا نلاحظ أي فرق سوى الانتظار لمدة 10 ثوانٍ قبل الحصول على النتيجة.

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


سنحصل على نفس قيمة العداد. يمكننا إبراز المشكلة بشكل أفضل باستخدام عميل مبرمج بدلاً من عميل يدوي مثل المتصفح. فيما يلي عميل بلغة Perl يُستدعى بالطريقة التالية:
برنامج URL N
حيث
URL هو عنوان سيرفلت العد URL
N هو عدد الطلبات التي يجب إرسالها إلى هذه الخدمة
فيما يلي النتائج التي تم الحصول عليها لخمس طلبات، والتي توضح جيدًا مشكلة سوء تزامن الخيوط: فجميعها تحصل على نفس قيمة العداد.
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){
// البيانات
String syntaxe="Syntaxe : pg URL nbAppels";
// التحقق من المعلمات
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);
}//محاولة-التعليق
// عدد المكالمات
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
// المعلمات صحيحة - يمكن إجراء الاتصالات بـ 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
}//main
private static void getCompteurs (URL urlCompteur, int nbAppels)
throws Exception {
// يقوم بـ nbAppels إلى URL urlCompteur
// يعرض في كل مرة قيمة العداد التي يرسلها خادم الويب
// يتم استخراج المعلومات اللازمة للاتصال بخادم الضرائب من urlCompteur
String path=urlCompteur.getPath();
if(path.equals("")) path="/";
String host=urlCompteur.getHost();
int port=urlCompteur.getPort();
if(port==-1) port=urlCompteur.getDefaultPort();
// يتم إجراء الاستدعاءات إلى URL
Socket[] clients=new Socket[nbAppels];
for(int i=0;i<nbAppels;i++){
// يتم الاتصال بالخادم
clients[i]=new Socket(host,port);
// يتم إنشاء تدفق كتابة إلى الخادم
PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
// يتم طلب URL - إرسال الرؤوس HTTP
OUT.println("GET " + path + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println("");
}//من أجل
// البيانات المحلية
String réponse=null; // رد الخادم
// النمط المطلوب في استجابة الخادم HTML
Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
// نموذج الرد الصحيح
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
// نتيجة المقارنة بالنموذج
Matcher résultat=null;
for(int i=0;i<nbAppels;i++){
// يقوم كل عميل بقراءة الرد الذي يرسله إليه الخادم
// يتم إنشاء تدفقات الإدخال والإخراج للعميل TCP
BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));
// يتم قراءة السطر الأول من الإجابة
réponse=IN.readLine();
// يتم مقارنة السطر HTTP بنموذج الرد الصحيح
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// هناك مشكلة في URL
throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
}//إذا
// يتم قراءة الإجابة حتى نهاية الرؤوس
while((réponse=IN.readLine())!=null && ! réponse.equals("")){
}//بينما
// انتهت الرؤوس HTTP - ننتقل إلى الكود HTML
// لاسترداد قيمة العداد
boolean compteurTrouvé=false;
while((réponse=IN.readLine())!=null){
// نقوم بمقارنة السطر بنموذج العداد
if(! compteurTrouvé){
résultat=modèleCompteur.matcher(réponse);
if(résultat.find()){
// تم العثور على العداد
System.out.println("Compteur="+résultat.group(1));
compteurTrouvé=true;
}//إذا
}//إذا
}//while
// انتهى الأمر
clients[i].close();
}//for
}//getCompteurs
}//فئة
دعونا نوضح الكود السابق:
- يقبل البرنامج معلمتين:
- القيمة «URL» للصفحة «JSP» الخاصة بعداد
- عدد العملاء المطلوب إنشاؤهم لهذه الصفحة URL
- يبدأ البرنامج إذن بالتحقق من صحة المعلمات: التأكد من وجود معلمتين بالفعل، وأن الأولى تشبه من حيث الصيغة اللغوية معلمة URL، وأن الثانية تشبه عددًا صحيحًا أكبر من الصفر. للتحقق من صحة معلمة 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>
<%!
// المتغيرات والطرق العامة للصفحة JSP
// متغيرات المثيل
int compteur;
Object verrou=new Object();
// طريقة لزيادة العداد
public int getCompteur(){
// تتم مزامنة القسم الحرج
synchronized(verrou){
// قراءة العداد
int myCompteur=compteur;
// التوقف لمدة 10 ثوانٍ
try{
Thread.sleep(10000);
}catch (Exception ignored){}
// يتم زيادة العداد
compteur=myCompteur+1;
}//تمت المزامنة
// إعادته
return compteur;
}//getCompteur
// الطريقة التي يتم تنفيذها عند التحميل الأولي للصفحة
public void jspInit(){
// تهيئة العداد
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
تشير الوثائق إلى أن خادم الويب قد يقوم أحيانًا بإنشاء عدة مثيلات لنفس السيرفلت. في هذه الحالة، لا تعمل المزامنة السابقة لأن المتغير verrou محلي لمثيل واحد، وبالتالي لا تعرفه المثيلات الأخرى. وينطبق الأمر نفسه على المتغير compteur. ولجعلهما عالميتين لجميع المثيلات، نكتب:
// متغير فئة
static int compteur;
static Object verrou=new Object();
لا يتغير باقي الكود.





