Skip to content

5. تطبيق حساب الضرائب

5.1. مقدمة

نعود هنا إلى تطبيق IMPOTS، الذي يستخدمه المؤلف نفسه مرارًا وتكرارًا في نشرة Java. دعونا نستعرض المشكلة. الهدف هو كتابة تطبيق يحسب الالتزام الضريبي للمكلف. سننظر في الحالة المبسطة للمكلف الذي لديه راتب واحد فقط للإبلاغ عنه:

  • نحسب عدد شرائح الضريبة للموظف على النحو التالي: nbParts = nbEnfants / 2 + 1 إذا كان غير متزوج، و nbEnfants / 2 + 2 إذا كان متزوجًا، حيث nbEnfants هو عدد الأطفال.
  • إذا كان لديه ثلاثة أطفال على الأقل، يحصل على نصف حصة إضافية
  • نحسب دخله الخاضع للضريبة R = 0.72 * S، حيث S هو راتبه السنوي
  • نحسب معامل الأسرة QF = R / nbParts
  • نحسب ضريبته I. انظر الجدول التالي:
12620.0
0
0
13,190
0.05
631
15,640
0.1
1,290.5
24,740
0.15
2,072.5
31,810
0.2
3,309.5
39,970
0.25
4,900
48,360
0.3
6,898.5
55,790
0.35
9,316.5
92,970
0.4
12,106
127,860
0.45
16,754.5
151,250
0.50
23,147.5
172,040
0.55
30,710
195,000
0.60
39312
0
0.65
49062

يحتوي كل صف على 3 حقول. لحساب الضريبة I، ابحث عن أول صف حيث QF <= الحقل 1. على سبيل المثال، إذا كان QF = 23,000، فسيكون الصف الذي تم العثور عليه هو

    24740        0.15        2072.5

الضريبة I تساوي إذن 0.15*R - 2072.5*nbParts. إذا كان QF بحيث لا تتحقق الشرط QF<=field1 أبدًا، يتم استخدام المعاملات من الصف الأخير. هنا:

    0                0.65        49062

مما يعطي الضريبة I = 0.65*R - 49062*nbParts.

يتم تخزين البيانات التي تحدد شرائح الضريبة المختلفة في قاعدة بيانات ODBC-MySQL. MySQL هو نظام إدارة قواعد البيانات (DBMS) مفتوح المصدر يمكن استخدامه على منصات مختلفة، بما في ذلك Windows وLinux. باستخدام نظام إدارة قواعد البيانات هذا، تم إنشاء قاعدة بيانات باسم dbimpots، تحتوي على جدول واحد يسمى impots. يتم التحكم في الوصول إلى قاعدة البيانات بواسطة اسم مستخدم/كلمة مرور، وفي هذه الحالة admimpots/mdpimpots. توضح لقطة الشاشة التالية كيفية استخدام قاعدة بيانات dbimpots مع MySQL:


C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
 
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
 
mysql> use dbimpots;
Database changed
 
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots             |
+--------------------+
1 row in set (0.00 sec)
 
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES  |     | NULL    |       |
| coeffR  | double | YES  |     | NULL    |       |
| coeffN  | double | YES  |     | NULL    |       |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
 
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24740 |   0.15 |  2072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55790 |   0.35 |  9316.5 |
|   92970 |    0.4 |   12106 |
|  127860 |   0.45 |   16754 |
|  151250 |    0.5 | 23147.5 |
|  172040 |   0.55 |   30710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
 
mysql>quit

يتم تحويل قاعدة بيانات dbimpots إلى مصدر بيانات ODBC على النحو التالي:

  • قم بتشغيل "مدير مصدر بيانات ODBC" 32 بت

Image

  • استخدم الزر [إضافة] لإضافة مصدر بيانات ODBC جديد

Image

  • حدد برنامج تشغيل MySQL وانقر على [إنهاء]

54321

Image

  • يطلب برنامج تشغيل MySQL قدرًا معينًا من المعلومات:
1
اسم DSN الذي سيتم إعطاؤه لمصدر بيانات ODBC — يمكن أن يكون أي شيء
2
الجهاز الذي يعمل عليه نظام إدارة قواعد البيانات MySQL — هنا، localhost. تجدر الإشارة إلى أن قاعدة البيانات قد تكون قاعدة بيانات بعيدة. ولن تدرك التطبيقات المحلية التي تستخدم مصدر بيانات ODBC ذلك. وينطبق هذا بشكل خاص على تطبيق Java الخاص بنا.
3
قاعدة بيانات MySQL المراد استخدامها. MySQL هو نظام إدارة قواعد البيانات (DBMS) الذي يدير قواعد البيانات العلائقية، وهي مجموعات من الجداول المرتبطة ببعضها البعض من خلال علاقات. هنا، نحدد اسم قاعدة البيانات التي يتم إدارتها.
4
اسم المستخدم الذي لديه حقوق الوصول إلى قاعدة البيانات هذه
5
كلمة المرور الخاصة به

تم تعريف فئتين لحساب الضريبة: impots و impotsJDBC. يتم إنشاء مثيل لفئة impots باستخدام بيانات شرائح الضريبة التي يتم تمريرها كمعلمات في المصفوفات:

// creation of an impots class

public class impots{

  // data required for tax calculation
   // come from an external source

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

   // empty builder
  protected impots(){}

   // manufacturer
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
     // check that the 3 arrays have the same size
    boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
    if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
      LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
    // it's good
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//manufacturer

   // tAX CALCULATION
  public long calculer(boolean marié, int nbEnfants, int salaire){
     // calculating the number of shares
    double nbParts;
    if (marié) nbParts=(double)nbEnfants/2+2;
    else nbParts=(double)nbEnfants/2+1;
    if (nbEnfants>=3) nbParts+=0.5;
         // calculation of taxable income & family quota
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // tAX CALCULATION
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // return result
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calculate
}//class

تستمد فئة impotsJDBC من فئة impots السابقة. يتم إنشاء مثيل لفئة impotsJDBC باستخدام بيانات الشرائح الضريبية المخزنة في قاعدة البيانات. يتم تمرير المعلومات اللازمة للوصول إلى قاعدة البيانات هذه كمعلمات إلى المنشئ:

// imported packages
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // addition of a constructor for building
   // limit tables, coeffr, coeffn from table
   // database taxes
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: DSN database name
     // userIMPOTS, mdpIMPOTS: database login/password

     // data tables
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // connection to base
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // creation of a Statement object
    Statement S=connect.createStatement();
    // select request
    String select="select limites, coeffr, coeffn from impots";
    // query execution
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // running line operation
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// next line
     // closing resources
    RS.close();
    S.close();
    connect.close();
     // data transfer to bounded arrays
    int n=aLimites.size();
    limites=new double[n];
    coeffR=new double[n];
    coeffN=new double[n];
    for(int i=0;i<n;i++){
      limites[i]=Double.parseDouble((String)aLimites.get(i));
      coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
      coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
    }//for
  }//manufacturer
}//class

بمجرد إنشاء مثيل لفئة impotsJDBC، يمكن استدعاء طريقة calculate الخاصة بها بشكل متكرر لحساب الضريبة:

  public long calculer(boolean marié, int nbEnfants, int salaire){

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

5.2. الإصدار 1

نحن في سياق تطبيق ويب يقدم واجهة HTML للمستخدم من أجل الحصول على المعلمات الثلاثة اللازمة لحساب الضريبة:

  • الحالة الاجتماعية (متزوج أم لا)
  • عدد الأبناء
  • الدخل السنوي

Image

يتم عرض النموذج من خلال صفحة JSP التالية:

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

<%
    // on récupère les attributs passés par la servlet principale
  String chkoui=(String)request.getAttribute("chkoui");
  String chknon=(String)request.getAttribute("chknon");
  String txtEnfants=(String)request.getAttribute("txtEnfants");
  String txtSalaire=(String)request.getAttribute("txtSalaire");
  String txtImpots=(String)request.getAttribute("txtImpots");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
          // raz du formulaire
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//with
      }//effacer
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" action="/impots/main" method="POST">
          <table>
            <tr>
              <td>Etes-vous marié(e)</td>
            <td>
                    <input type="radio" name="optMarie" value="oui" <%= chkoui %>>oui
              <input type="radio" name="optMarie" value="non" <%= chknon %>>non
            </td>
          </tr>
          <tr>
              <td>Nombre d'enfants</td>
            <td><input type="text" size="5" name="txtEnfants" value="<%= txtEnfants %>"></td>
          </tr>
          <tr>
              <td>Salaire annuel</td>
            <td><input type="text" size="10" name="txtSalaire" value="<%= txtSalaire %>"></td>
          </tr>
          <tr>
              <td><font color="green">Impôt</font></td>
            <td><input type="text" size="10" name="txtImpots" value="<%= txtImpots %>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="submit" value="Calculer"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
          </tr>
        </table>
      </form>
    </center>
    <%
        // y-a-t-il des erreurs
      if(erreurs!=null){
          // affichage des erreurs
        out.println("<hr>");
        out.println("<font color=\"red\">");
        out.println("Les erreurs suivantes se sont produites<br>");
        out.println("<ul>");
        for(int i=0;i<erreurs.size();i++){
            out.println("<li>"+(String)erreurs.get(i));
        }
        out.println("</ul>");
        out.println("</font>");
      } 
   %>
 </body>
</html>

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

     // retrieve attributes passed by the main servlet
  String chkoui=(String)request.getAttribute("chkoui");
  String chknon=(String)request.getAttribute("chknon");
  String txtEnfants=(String)request.getAttribute("txtEnfants");
  String txtSalaire=(String)request.getAttribute("txtSalaire");
  String txtImpots=(String)request.getAttribute("txtImpots");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
chkoui، chknon
يمكن أن تحتوي سمات "checked" و"unchecked" الخاصة بأزرار الاختيار على القيمتين "checked" أو "unchecked" للإشارة إلى ما إذا كان زر الاختيار المقابل محددًا أم لا
txtChildren
عدد أطفال دافع الضرائب
txtSalary
راتبهم السنوي
txtTaxes
مبلغ الضريبة المستحقة
الأخطاء
قائمة بالأخطاء، إن وجدت، إذا كانت errors!=null

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

يُسمى السيرفلت الرئيسي للتطبيق main.java، ورمزه كما يلي:

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

public class main extends HttpServlet{

    // instance variables
    String msgErreur=null;
    String urlAffichageImpots=null;
    String urlErreur=null;
    String DSNimpots=null;
    String admimpots=null;
    String mdpimpots=null;
    impotsJDBC impots=null;

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

        // was the initialization successful?
        if(msgErreur!=null){
             // we hand over to the error page
            request.setAttribute("msgErreur",msgErreur);
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // query attributes
        String chkoui=null;
        String chknon=null;
        String txtImpots=null;

        // retrieve query parameters
        String optMarie=request.getParameter("optMarie");              // marital status
        String txtEnfants=request.getParameter("txtEnfants");       // no. of children
        if(txtEnfants==null) txtEnfants="";
        String txtSalaire=request.getParameter("txtSalaire");       // annual salary
        if(txtSalaire==null) txtSalaire="";

         // do we have all the expected parameters
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // missing parameters
            request.setAttribute("chkoui","");
            request.setAttribute("chknon","checked");
            request.setAttribute("txtEnfants","");
            request.setAttribute("txtSalaire","");
            request.setAttribute("txtImpots","");
             // we hand over to the tax display url
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // we have all the parameters - we check them
        ArrayList erreurs=new ArrayList();
         // marital status
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // error
            erreurs.add("Etat marital incorrect");
            optMarie="non";
        }
         // number of children
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // error
            erreurs.add("Nombre d'enfants incorrect");
        }
         // salary
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // error
            erreurs.add("Salaire incorrect");
        }

         // if there are errors, they are passed as query attributes
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
            txtImpots="";
        }else{
             // you can calculate the tax payable
            try{
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
            }catch(Exception ex){}
        }

         // other query attributes
        if(optMarie.equals("oui")){
            request.setAttribute("chkoui","checked");
            request.setAttribute("chknon","");
        }else{
            request.setAttribute("chknon","checked");
            request.setAttribute("chkoui","");
        }
        request.setAttribute("txtEnfants",txtEnfants);
        request.setAttribute("txtSalaire",txtSalaire);
        request.setAttribute("txtImpots",txtImpots);

         // we hand over to the tax display url
        getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // retrieve initialization parameters
        ServletConfig config=getServletConfig();
        urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parameters ok?
        if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
            msgErreur="Configuration incorrecte";
            return;
        }

         // create an instance of impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }
}
  • تقوم طريقة init الخاصة بالـ servlet بأمرين:
    • تسترد معلمات التهيئة الخاصة بها. تسمح هذه المعلمات لها بالاتصال بقاعدة بيانات ODBC التي تحتوي على البيانات الخاصة بشرائح الضرائب المختلفة (DSNimpots، admimpots، mdpimpots) وعناوين URL للصفحات المرتبطة بالتطبيق: urlAffichageImpots للنموذج، urlErreur لصفحة الخطأ.
    • تقوم بإنشاء مثيل لفئة impotsJDBC

في كلتا الحالتين، يتم التعامل مع الأخطاء المحتملة، ويتم تخزين رسالة الخطأ في المتغير msgErreur.

  • تقوم طريقة doGET
    • تتحقق أولاً من أن السيرفلت قد تم تهيئته بشكل صحيح. إذا لم يكن الأمر كذلك، يتم عرض صفحة الخطأ
    • يسترد المعلمات المتوقعة من نموذج الضرائب: optMarie، txtEnfants، txtSalaire. إذا كان أي منها مفقودًا (==null)، يتم إرسال نموذج ضرائب فارغ. قد يظن المرء أن التحقق من صحة المعلمة optMarie غير ضروري. فهذه المعلمة تمثل قيمة زر اختيار، ولا يمكن أن تأخذ هنا سوى إحدى القيمتين "نعم" أو "لا". ومع ذلك، فإن هذا يتجاهل حقيقة أنه لا يوجد ما يمنع البرنامج من الاستعلام مباشرة عن السيرفلت عن طريق إرسال المعلمات التي يريدها إليه. لا يمكنك أبدًا التأكد من أنك متصل فعليًا بمتصفح. قد يؤدي تجاهل هذه النقطة إلى ثغرات أمنية في التطبيق، وفي الواقع، من الشائع العثور على مثل هذه المشكلات حتى في التطبيقات التجارية.
    • يتم التحقق من صحة كل من المعلمات الثلاثة المستردة. تتم إضافة أي أخطاء يتم العثور عليها إلى قائمة الأخطاء (ArrayList errors). إذا لم تكن هناك أخطاء، يتم حساب مبلغ الضريبة؛ وإلا، لا يتم حسابه.
    • يتم تعيين المعلومات اللازمة لعرض الصفحة كسمات طلب، ثم يتم عرض نموذج الضريبة

صفحة JSP الخاصة بالأخطاء هي كما يلي:

<%
    // jspService
  // une erreur s'est produite
  String msgErreur= (String)request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- top of page HTML -->
<html>
  <head>
      <title>impots</title>
  </head>
  <body>
      <h3>calcul d'impots</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

يُسمى التطبيق الويب impots ويتم تكوينه في ملف server.xml الخاص بـ Tomcat على النحو التالي:

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

يحتوي دليل التطبيق على الدلائل والملفات التالية:

Image

Image

Image

Image

فيما يلي ملف التكوين web.xml لتطبيق impots:

<?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>main</servlet-name>
    <servlet-class>main</servlet-class>
    <init-param>
          <param-name>urlAffichageImpots</param-name>
        <param-value>/impots.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>DSNimpots</param-name>
        <param-value>mysql-dbimpots</param-value>
    </init-param>
    <init-param>
          <param-name>admimpots</param-name>
        <param-value>admimpots</param-value>
    </init-param>
    <init-param>
          <param-name>mdpimpots</param-name>
        <param-value>mdpimpots</param-value>
    </init-param>    
    <init-param>
          <param-name>urlErreur</param-name>
        <param-value>/erreur.jsp</param-value>
    </init-param>    
  </servlet>
  <servlet-mapping>
      <servlet-name>main</servlet-name>
    <url-pattern>/main</url-pattern>
  </servlet-mapping>
</web-app>

يُسمى السيرفلت الرئيسي main وله الاسم المستعار /main. وبالتالي، يمكن الوصول إليه عبر عنوان URL http://localhost:8080/impots/main.

فيما يلي بعض أمثلة التطبيقات:

للتشغيل بشكل صحيح، يجب أن يكون لدى السيرفلت حق الوصول إلى قاعدة بيانات mysql-dbimpots. فيما يلي الصفحة التي يتم عرضها إذا كان خادم MySQL، على سبيل المثال، غير قيد التشغيل وبالتالي لا يمكن الوصول إلى قاعدة بيانات mysql-dbimpots:

Image

الصفحة التي تظهر في حالة الإدخال غير الصحيح هي كما يلي:

Image

إذا كانت البيانات صحيحة، يتم حساب الضريبة:

Image

5.3. الإصدار 2

في المثال السابق، يقوم الخادم بالتحقق من صحة المعلمات txtEnfants وtxtSalaire في النموذج. هنا، نقترح التحقق من صحتها باستخدام برنامج نصي JavaScript مضمن في صفحة النموذج. ثم يقوم المتصفح بالتحقق من صحة المعلمات. ولا يتم الاتصال بالخادم إلا إذا كانت المعلمات صحيحة. وهذا يوفر "عرض النطاق الترددي". تصبح صفحة العرض JSP كما يلي:

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

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
.......
      }//effacer

      function calculer(){
          // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//if
          // c'est bon - on envoie le formulaire au serveur
          submit();
        }//with
      }//calculer        
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
........
              <td><input type="button" value="Calculer" onclick="calculer()"></td>
          <td><input type="button" value="Effacer" onclick="effacer()"></td>
.....
 </body>
</html>

لاحظ التغييرات التالية:

  • لم يعد زر "Calculate" زر إرسال، بل أصبح زرًا عاديًا مرتبطًا بوظيفة تسمى "calculate". ستقوم هذه الوظيفة بالتحقق من صحة الحقول "txtEnfants" و"txtSalaire". إذا كانت صحيحة، فسيتم إرسال بيانات النموذج إلى الخادم؛ وإلا، فسيتم عرض رسالة خطأ.

فيما يلي مثال على ما يتم عرضه في حالة حدوث خطأ:

Image

5.4. الإصدار 3

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

Image

تم تعديل السيرفلت الرئيسي وأصبح يُسمى الآن simulations. تم تكوينه على النحو التالي داخل تطبيق impots:

<?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>
      <servlet-name>simulations</servlet-name>
    <servlet-class>simulations</servlet-class>
    <init-param>
          <param-name>urlSimulationImpots</param-name>
        <param-value>/simulationsImpots.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>DSNimpots</param-name>
        <param-value>mysql-dbimpots</param-value>
    </init-param>
    <init-param>
          <param-name>admimpots</param-name>
        <param-value>admimpots</param-value>
    </init-param>
    <init-param>
          <param-name>mdpimpots</param-name>
        <param-value>mdpimpots</param-value>
    </init-param>    
    <init-param>
          <param-name>urlErreur</param-name>
        <param-value>/erreur.jsp</param-value>
    </init-param>    
  </servlet>
......
  <servlet-mapping>
      <servlet-name>simulations</servlet-name>
    <url-pattern>/simulations</url-pattern>
  </servlet-mapping>
</web-app>

يُسمى السيرفلت الرئيسي simulations ويستند إلى ملف simulations.class. وله الاسم المستعار /simulations، مما يجعله قابلاً للوصول عبر عنوان URL http://localhost:8080/impots/simulations. وله نفس معلمات التهيئة التي تمت مناقشتها سابقًا فيما يتعلق بالسيرفلت الرئيسي فيما يخص الوصول إلى قاعدة البيانات. تظهر معلمة جديدة، **urlSimulationsImpots،** وهي عنوان URL لصفحة JSP الخاصة بالمحاكاة (التي تم عرضها أعلاه).

يشبه سيرفلت simulations.java سيرفلت main.java. ويختلف عنه في النقاط الرئيسية التالية:

  • يحسب السيرفلت الرئيسي قيمة لـ `txtImpots` استنادًا إلى المعلمات `optmarie` و`txtEnfants` و`txtSalaire`، ويمرر هذه القيمة إلى صفحة JSP للعرض
  • يحسب سيرفلت simulations قيمة txtImpots بنفس الطريقة ويخزن المعلمات (optMarie وtxtEnfants وtxtsalaire وtxtImpots) في قائمة تسمى simulations. يتم تمرير هذه القائمة كمعلمة إلى صفحة JSP للعرض. لضمان احتواء هذه القائمة على جميع عمليات المحاكاة التي أجراها المستخدم، يتم تخزينها كسمة للجلسة الحالية.

فيما يلي خدمة simulations (تم تضمين الأسطر من الكود التي تختلف عن تلك الموجودة في التطبيق السابق فقط):

import java.io.*;
.......

public class simulations extends HttpServlet{

    // instance variables
    String msgErreur=null;
    String urlSimulationImpots=null;
    String urlErreur=null;
...........

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

...........

         // retrieve previous simulations from the session
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();
        // put the simulations in the current query
        request.setAttribute("simulations",simulations);

         // other query attributes
...........

         // do we have all the expected parameters
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // we hand over to the url for displaying tax calculation simulations
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // we have all the parameters - we check them
...........

         // if there are errors, they are passed as query attributes
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
        }else{
            try{
                 // you can calculate the tax payable
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // the current result is added to the previous simulations
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 // the new simulations value is added to the session
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // other query attributes
..........

         // we hand over to the simulations display url
        getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // retrieve initialization parameters
        ServletConfig config=getServletConfig();
        urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parameters ok?
.........................
    }
}

تبدو صفحة JSP للعرض simulationsImpots.jsp الآن كما يلي (تم الاحتفاظ فقط بالكود الذي يختلف عن صفحة JSP للعرض في التطبيق السابق).

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

<%
    // on récupère les attributs passés par la servlet principale
...........
  ArrayList simulations=(ArrayList)request.getAttribute("simulations");  
%>

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
........
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" action="/impots/simulations" method="POST">
.......................
           </form> 
</center>
    <hr>
    <%
        // y-a-t-il des erreurs ?
      if(erreurs!=null){
..................
      }else if(simulations.size()!=0){
          // résultats des simulations
        out.println("<h3>Résultats des simulations<h3>");
        out.println("<table \"border=\"1\">");
        out.println("<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>");
        for(int i=0;i<simulations.size();i++){
            String[] simulation=(String[])simulations.get(i);
            out.println("<tr><td>"+simulation[0]+"</td><td>"+simulation[1]+"</td><td>"+simulation[2]+"</td><td>"+simulation[3]+"</td></tr>");
        }
        out.println("</table>");
      }
   %>
 </body>
</html>

5.5. الإصدار 4

سنقوم الآن بإنشاء تطبيق مستقل سيكون بمثابة عميل ويب لتطبيق /impots/simulations السابق. سيكون لهذا التطبيق واجهة رسومية كما يلي:

رقم
النوع
الاسم
الدور
1
JTextField
txtTaxServiceUrl
عنوان URL لخدمة محاكاة حساب الضرائب
2
JRadioButton
rdYes
تم تحديد ما إذا كان متزوجًا
3
JRadioButton
rdNo
تم تحديد "غير متزوج"
4
JSpinner
spinChildren
عدد أطفال دافع الضرائب (الحد الأدنى = 0، الحد الأقصى = 20، الزيادة = 1)
5
JTextField
txtSalary
الراتب السنوي للمكلف بالضريبة بالفرنك السويسري
6
JList في JScrollPane
lstSimulations
قائمة المحاكاة

تتكون قائمة الضرائب من الخيارات التالية:

الرئيسية
الرئيسية
خيار
ثانوي
الاسم
الدور
الضرائب
   
 
احسب
mnuCalculate
يحسب الضريبة المستحقة عندما تكون جميع البيانات المطلوبة للحساب موجودة وصحيحة
 
مسح
mnuClear
إعادة تعيين النموذج إلى حالته الأولية
 
خروج
mnuExit
يغلق التطبيق

قواعد التشغيل

  • يظل خيار القائمة "حساب" معطلاً إذا كان الحقل 1 أو 5 فارغًا
  • تم الكشف عن عنوان URL غير صحيح من الناحية النحوية في الحقل 1

Image

  • تم الكشف عن راتب غير صحيح

Image

  • تم الإبلاغ عن أي خطأ في اتصال الخادم (في المثال 1 أدناه، المنفذ غير صحيح؛ وفي المثال 2، عنوان URL المطلوب غير موجود؛ وفي المثال 3، قاعدة بيانات MySQL لم تكن قيد التشغيل)

Image

Image

Image

  • إذا كان كل شيء صحيحًا، فسيتم عرض المحاكاة

Image

عند كتابة عميل ويب مبرمج، من الضروري معرفة ما يرسله الخادم بالضبط استجابةً للطلبات المختلفة المحتملة من العميل. يرسل الخادم مجموعة من أسطر HTML تحتوي على معلومات مفيدة وعناصر أخرى موجودة فقط من أجل تخطيط HTML. يمكن أن تساعدنا التعبيرات العادية في Java في العثور على المعلومات المفيدة ضمن تدفق الأسطر التي يرسلها الخادم. للقيام بذلك، نحتاج إلى معرفة التنسيق الدقيق لاستجابات الخادم المختلفة. هنا سنستخدم عميل الويب الذي صادفناه سابقًا، والذي يسمح لنا بعرض استجابة الخادم لطلب URL على الشاشة. سيكون عنوان URL المطلوب هو عنوان خدمة محاكاة حساب الضرائب الخاصة بنا، http://localhost:8080/impots/simulations، والتي يمكننا تمرير المعلمات إليها في النموذج http://localhost:8080/impots/simulations?param1=vam1&param2=val2&...

دعونا نطلب عنوان URL بينما قاعدة بيانات MySQL المستخدمة لإنشاء كائن من نوع الضريبة غير قيد التشغيل:


Dos>java clientweb http://localhost:8080/impots/simulations GET
 
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:31:04 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=9DEC8B27966A1FBE3D4968A7B9DF3331;Path=/impots
 
 
<!-- dÚbut from page HTML -->
<html>
  <head>
        <title>impots</title>
  </head>
  <body>
        <h3>calcul d'impôts</h3>
        <hr>
    Application indisponible([TCX][MyODBC]Can't connect to MySQL server on 'localhost' (10061))
  </body>
</html>

لاسترداد الخطأ، يجب على عميل الويب البحث في استجابة خادم الويب عن السطر الذي يحتوي على النص "التطبيق غير متاح". الآن دعونا نبدأ قاعدة بيانات MySQL ونطلب نفس عنوان URL، ونمرر إليه القيم التي يتوقعها (يقبلها بشكل جيد سواء كطلب GET أو POST — انظر كود Java الخاص بالخادم):


Dos>java clientweb "http://localhost:8080/impots/simulations?ptMarie=oui&txtEnfants=2&txtSalaire=200000" GET
 
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:42:36 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=C2A707600E98A37A343611D80DD5C8A2;Path=/impots
 
 
<html>
 
        <head>
        <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
...................................................
      }//effacer
 
      function calculer(){
...................................................
      }//calculer
                </script>
  </head>
 
  <body background="/impots/images/standard.jpg">
        <center>
        Calcul d'imp¶ts
        <hr>
      <form name="frmImpots" action="/impots/simulations" method="POST">
...................................................
      </form>
    </center>
    <hr>
    <h3>Résultats des simulations<h3>
<table "border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
</table>
 
 </body>
</html>

إليك مستند HTML الكامل الذي أرسله الخادم. توجد نتائج المحاكاة المختلفة في الجدول الوحيد الموجود في هذا المستند. قد يكون التعبير العادي الذي يسمح لنا باستخراج المعلومات ذات الصلة من المستند هو التالي:

"<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>"

حيث تمثل التعبيرات الأربعة الموجودة بين الأقواس المعلومات الأربع المراد استخراجها.

لدينا الآن الإرشادات الخاصة بما يجب فعله عندما يطلب المستخدم حساب الضريبة من الواجهة الرسومية السابقة:

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

فيما يلي الكود المتعلق بقائمة "حساب":

  void mnuCalculer_actionPerformed(ActionEvent e) {
    // tAX CALCULATION
         // verification URL service
        URL urlImpots=null;
        try{
            urlImpots=new URL(txtURLServiceImpots.getText().trim());
            String query=urlImpots.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
             // error msg
            JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
             // focus on wrong field
            txtURLServiceImpots.requestFocus();
             // back to interface
            return;
        }
     // salary verification
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // error msg
            JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
       // focus on wrong field
      txtSalaire.requestFocus();
       // back to interface
      return;
    }
     // no. of children
    Integer nbEnfants=(Integer)spinEnfants.getValue();

    try{
             // tax calculation
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             // error is displayed
            JOptionPane.showMessageDialog(this,"L'erreur suivante s'est produite : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
        }
  }//mnuCalculer


    public void calculerImpots(URL urlImpots,boolean marié, int nbEnfants, int salaire)
          throws Exception{
         // tAX CALCULATION
         // urlImpots : URL of the tax department
         // married: true if married, false otherwise
         // nbEnfants : number of children
         // salary: annual salary

         // remove from urlImpots the info needed to connect to the tax server
        String path=urlImpots.getPath();
        if(path.equals("")) path="/";
        String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
        String host=urlImpots.getHost();
        int port=urlImpots.getPort();
        if(port==-1) port=urlImpots.getDefaultPort();

         // local data
        Socket  client=null;                        // the customer
        BufferedReader IN=null;                    // the customer's reading flow
        PrintWriter OUT=null;                        // the customer's writing flow
        String réponse=null;                        // server response
         // the model searched in HTTP headers
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // the model for a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // the result of the model comparison
        Matcher résultat=null;

        try{
             // connect to the server
            client=new Socket(host,port);

            // create customer input/output flows TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // request URL - send HTTP headers
            OUT.println("GET " + path + query + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            if(! JSESSIONID.equals("")){
                OUT.println("Cookie: JSESSIONID="+JSESSIONID);
            }
            OUT.println("Connection: close");
            OUT.println("");

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

             // we read the response through to the end of the headers, looking for any cookies
            while((réponse=IN.readLine())!=null){
                 // empty line?
                if(réponse.equals("")) break;
                // line HTTP not empty
                 // if you don't have the session token, look for it
                if (JSESSIONID.equals("")){
                    // compare the HTTP line with the cookie template
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // we found the token cookie
                        JSESSIONID=résultat.group(1);
                    }//if(result)
                }//if(JSESSIONID)
            }//while

             // that's it for HTTP headers - move on to HTML code
             // to retrieve simulations
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            // it's over
            client.close();
        }catch (Exception ex){
            throw new Exception(ex.getMessage());
        }
    }//calculerImpots

    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // the template for a line in the error list
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
         // the result of the model comparison
        Matcher résultat=null;
         // simulations
        ArrayList listeSimulations=new ArrayList();

         // read all the lines to the end
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             // follow-up
             // the line is compared with the error model if the simulation part has not yet been encountered
            if(! simulationRéussie){
                résultat=ptnErreur.matcher(ligne);
                if(résultat.find()){
                    // error msg
                    JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
                    // it's over
                    return listeSimulations;
                }//if
            }//if
             // the line is compared with the simulation model
            résultat=ptnSimulation.matcher(ligne);
            if(résultat.find()){
                // we found a row in the
                listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
                                                ":"+résultat.group(4));
                 // the simulation was a success
                simulationRéussie=true;
            }//if
        }//while
        // end
        return listeSimulations;
    }

دعونا نشرح هذا الكود قليلاً:

  • تتحقق الإجراء mnuCalculer_actionPerformed من صحة بيانات الواجهة. إذا لم تكن صحيحة، يتم عرض رسالة خطأ ويتم إنهاء الإجراء. إذا كانت صحيحة، يتم تنفيذ الإجراء calculerImpots.
  • يبدأ الإجراء calculerImpots بإنشاء عنوان URL الذي يحتاجه لطلب
        // on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts
        String path=urlImpots.getPath();
        if(path.equals("")) path="/";
        String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
        String host=urlImpots.getHost();
        int port=urlImpots.getPort();
        if(port==-1) port=urlImpots.getDefaultPort();
........................
  • ثم يتصل بهذا الرابط عن طريق إرسال رؤوس HTTP المناسبة:
             // connect to the server
            client=new Socket(host,port);

            // create customer input/output flows TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // request URL - send HTTP headers
            OUT.println("GET " + path + query + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            if(! JSESSIONID.equals("")){
                OUT.println("Cookie: JSESSIONID="+JSESSIONID);
            }
            OUT.println("Connection: close");
            OUT.println("");
  • بمجرد إرسال الطلب، ينتظر عميل الويب الخاص بنا الرد. يتلقى أولاً رؤوس HTTP من رد الخادم. يقوم بتحليل هذه الرؤوس للعثور على رمز الجلسة. في الواقع، يجب عليه إرسال هذا الرمز إلى الخادم حتى يتمكن الخادم من تتبع المحاكاة المختلفة التي تم إجراؤها. لاحظ هنا أنه كان من الأسهل على العميل تخزين المحاكاة المختلفة التي أجراها المستخدم بنفسه. ومع ذلك، فإننا نتمسك بفكرة إعادة الرمز لتقديم مثال جديد لإدارة الجلسة. يتم التعامل مع السطر الأول من الرد بشكل منفصل. يجب أن يكون في صيغة HTTP/version 200 OK للإشارة إلى أن عنوان URL المطلوب موجود بالفعل. إذا لم يكن في هذه الصيغة، فإننا نستنتج أن المستخدم طلب عنوان URL غير صحيح ونبلغه بذلك.
         // the model searched in HTTP headers
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // the model of a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........

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

             // we read the response through to the end of the headers, looking for any cookies
            while((réponse=IN.readLine())!=null){
                 // empty line?
                if(réponse.equals("")) break;
                // line HTTP not empty
                 // if you don't have the session token, look for it
                if (JSESSIONID.equals("")){
                    // compare the HTTP line with the cookie template
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // we found the token cookie
                        JSESSIONID=résultat.group(1);
                    }//if(result)
                }//if(JSESSIONID)
            }//while
  • بمجرد معالجة رؤوس HTTP، ننتقل إلى الجزء HTML من الاستجابة
             // that's it for HTTP headers - move on to HTML code
             // to retrieve simulations
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }
  • تُرجع الإجراء getSimulations قائمة المحاكاة إن وجدت؛ وستكون هذه القائمة فارغة إذا أرجع الخادم رسالة خطأ. في هذه الحالة، تُعرض رسالة الخطأ في مربع رسالة. وإذا لم تكن القائمة فارغة، تُعرض في القائمة المنسدلة لواجهة المستخدم الرسومية.
  • يقارن الإجراء getSimulations كل سطر من استجابة HTML بالتعبير العادي الذي يمثل رسالة الخطأ (التطبيق غير متاح...) وبالتعبير العادي الذي يمثل محاكاة. إذا تم العثور على رسالة الخطأ، يتم عرضها وينتهي الإجراء. إذا تم العثور على نتيجة محاكاة، يتم إضافتها إلى قائمة المحاكاة. في نهاية الإجراء، يتم إرجاع هذه القائمة كنتيجة.
    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // the template for a line in the error list
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");

5.6. الإصدار 5

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

Image

يُسمى مستند HTML الذي يحتوي على التطبيق الصغير simulations.htm وهو كما يلي:

<html>
    <head>
      <title>Simulations de calculs d'impôts</title>
  </head>
  <body background="/impots/images/standard.jpg">
      <center>
          <h3>Simulations de calculs d'impôts</h3>
        <hr>
        <applet code="appletImpots.class" width="400" height="360">
            <param name="urlServiceImpots" value="simulations">
            </applet>
    </center>
  </body>
</html>  

يحتوي التطبيق الصغير على معلمة تسمى *urlServiceImpots، وهي عنوان URL لخدمة محاكاة حساب الضرائب. هذا العنوان URL نسبي بالنسبة لعنوان URL لمستند HTML simulations.htm. وبالتالي، إذا استرد المتصفح هذا المستند عبر عنوان URL http://localhost:8080/impots/simulations.htm، فسيكون عنوان URL لخدمة المحاكاة هو http://localhost:8080/impots/simulations. وإذا كان عنوان URL هذا هو http://stahe:8080/impots/simulations.htm، فسيكون عنوان URL لخدمة المحاكاة هو http://stahe:8080/impots/simulations*.

تدمج التطبيق الصغير appletImpots.java بالكامل الكود من التطبيق الرسومي المستقل السابق مع الالتزام بقواعد تحويل التطبيق الرسومي إلى تطبيق صغير.

public class appletImpots extends JApplet {
  // window components
  JPanel contentPane;
  JMenuBar jMenuBar1 = new JMenuBar();
  JMenu jMenu1 = new JMenu();
  JMenuItem mnuCalculer = new JMenuItem();
.............


   //Building the frame
  public void init() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // other initializations
    moreInit();
  }

   // form initialization
  private void moreInit(){
         // retrieve the urlServiceImpots parameter
        String urlServiceImpots=getParameter("urlServiceImpots");
        if(urlServiceImpots==null){
            // missing parameter
            JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
             // end
            return;
        }
         // put the URL in its field
        String codeBase=""+getCodeBase();
        if(codeBase.endsWith("/"))
           txtURLServiceImpots.setText(codeBase+urlServiceImpots);
      else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
    // calculate menu disabled
    mnuCalculer.setEnabled(false);
     // spinner Children - between 0 and 20 children
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(130,140,50,27));
    contentPane.add(spinEnfants);
  }//moreInit

   //Initialize component
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(null);
...............
  }

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

Image

5.7. الخلاصة

لقد عرضنا إصدارات مختلفة من تطبيق حساب الضرائب الخاص بنا الذي يعمل بنظام العميل-الخادم:

  • الإصدار 1: يتم توفير الخدمة من خلال مجموعة من السيرفلتات وصفحات JSP؛ والعميل هو متصفح. ويقوم بإجراء محاكاة واحدة ولا يحتفظ بأي سجل للمحاكاة السابقة.
  • الإصدار 2: نضيف بعض الوظائف من جانب المتصفح عن طريق تضمين نصوص JavaScript في مستند HTML الذي يتم تحميله بواسطة المتصفح. ويقوم هذا الإصدار بالتحقق من صحة معلمات النموذج.
  • الإصدار 3: نُمكّن الخدمة من تذكر المحاكاة المختلفة التي أجراها العميل من خلال إدارة جلسة العمل. يتم تعديل واجهة HTML وفقًا لذلك لعرض هذه المحاكاة.
  • الإصدار 4: أصبح العميل الآن تطبيقًا رسوميًا مستقلًا. وهذا يسمح لنا بإعادة النظر في تطوير عملاء الويب المبرمجين.
  • الإصدار 5: يصبح العميل تطبيق Java صغير. لدينا الآن تطبيق خادم-عميل مبرمج بالكامل بلغة Java، سواء على جانب الخادم أو جانب العميل.

في هذه المرحلة، يمكن إبداء بعض الملاحظات:

  • تدعم الإصدارات من 1 إلى 3 المتصفحات التي لا تمتلك أي قدرات سوى القدرة على تنفيذ نصوص JavaScript. لاحظ أن المستخدم لديه دائمًا خيار تعطيل تنفيذ هذه النصوص. عندئذٍ سيعمل التطبيق بشكل جزئي فقط في الإصدار 1 (لن يعمل خيار "مسح") ولن يعمل على الإطلاق في الإصدارين 2 و3 (لن يعمل خيارا "مسح" و"حساب"). قد يكون من المفيد تطوير إصدار من الخدمة لا يستخدم نصوص JavaScript.
  • يتطلب الإصدار 4 أن يكون لدى جهاز الكمبيوتر العميل آلة افتراضية Java 2.
  • يتطلب الإصدار 5 أن يكون لدى جهاز العميل متصفح مزود بجهاز افتراضي Java 2.

عند كتابة خدمة ويب، يجب أن تراعي أنواع العملاء الذين تستهدفهم. إذا كنت ترغب في الوصول إلى أكبر عدد ممكن من العملاء، فيجب عليك كتابة تطبيق يرسل لغة HTML فقط إلى المتصفحات (بدون JavaScript أو تطبيقات صغيرة). أما إذا كنت تعمل ضمن شبكة داخلية (إنترانت) وتتمتع بالسيطرة على تكوين محطات العمل، فيمكنك عندئذٍ أن تكون أكثر صرامة فيما يتعلق بجانب العميل، وقد تكون النسخة 5 السابقة مقبولة في هذه الحالة.

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

         // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");

الآن، لنفترض أن مطور التطبيق يغير المظهر المرئي للاستجابة عن طريق وضع عمليات المحاكاة ليس في جدول بل في قائمة بالصيغة التالية:

Résultat des simulations
<ul>
    <li>oui,2,200000,22504
  <li>non,2,200000,33388
</ul>  

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

  • بدلاً من إنشاء HTML، ستقوم خدمة المحاكاة بإنشاء XML. في مثالنا، يمكن أن يكون هذا
<simulations>
    <entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
  <simulation marie="non" enfants="2" salaire="200000" impot="33388" />
</simulations>
  • يمكن ربط ورقة أنماط بهذا الرد، لتوجيه المتصفحات بشأن العرض المرئي لهذا الرد XML
  • ستتجاهل برامج الويب هذه ورقة الأنماط وتسترد المعلومات مباشرة من تدفق XML للاستجابة

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

  • الإصدار 6: توفر الخدمة استجابة XML مصحوبة بورقة أنماط مخصصة للمتصفحات
  • الإصدار 7: العميل هو تطبيق رسومي مستقل يعالج استجابة XML للخادم
  • الإصدار 8: العميل هو تطبيق Java صغير يعالج استجابة XML من الخادم