Skip to content

5. برنامه IMPOTS

5.1. مقدمه

در اینجا بار دیگر به برنامه IMPOTS می‌پردازیم که در جزوهٔ جاوا توسط همان نویسنده در موارد متعددی استفاده شده است. بیایید مسئلهٔ مورد نظر را یادآوری کنیم. وظیفه نوشتن برنامه‌ای است که قادر به محاسبهٔ بدهی مالیاتی یک مؤدی باشد. ما در حال بررسی حالت ساده‌شده‌ای هستیم که مؤدی تنها حقوق خود را برای اظهار دارد:

  • تعداد طبقات مالیاتی کارمند را به صورت nbParts = nbEnfants/2 + 1 در صورتی که مجرد باشد، محاسبه می‌کنیم، nbEnfants/2 + 2 اگر متأهل باشند، که در آن nbEnfants تعداد فرزندانشان است.
  • اگر او حداقل سه فرزند داشته باشد، مستحق دریافت نیم‌پرداخت اضافی است.
  • درآمد مشمول مالیات او به صورت R = 0.72 × S محاسبه می‌شود، که در آن S حقوق سالانه او است.
  • ضریب خانوادگی او به صورت QF = R / nbParts محاسبه می‌شود
  • مالیات I او محاسبه می‌شود. جدول زیر را در نظر بگیرید:
۱۲٬۶۲۰٫۰
0
0
۱۳٬۱۹۰
0.05
۶۳۱
۱۵٬۶۴۰
0.1
۱٬۲۹۰٫۵
۲۴٬۷۴۰
0.15
۲٬۰۷۲٫۵
۳۱٬۸۱۰
0.2
۳٬۳۰۹٫۵
۳۹٬۹۷۰
0.25
۴٬۹۰۰
۴۸٬۳۶۰
0.3
۶۸۹۸.۵
۵۵٬۷۹۰
0.35
۹٬۳۱۶٫۵
۹۲٬۹۷۰
0.4
۱۲٬۱۰۶
۱۲۷٬۸۶۰
0.45
۱۶٬۷۵۴٫۵
۱۵۱٬۲۵۰
0.50
۲۳۱۴۷.۵
۱۷۲٬۰۴۰
0.55
۳۰٬۷۱۰
۱۹۵٬۰۰۰
0.60
۳۹٬۳۱۲
0
0.65
۴۹٬۰۶۲

هر سطر دارای ۳ فیلد است. برای محاسبه مالیات I، به دنبال اولین سطری می‌گردیم که QFfield1 باشد. برای مثال، اگر QF = 23000 باشد، سطر را پیدا خواهیم کرد

    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 یک SGBD در دامنهٔ عمومی است که می‌توان از آن روی پلتفرم‌های مختلف، از جمله ویندوز و لینوکس، استفاده کرد. با این SGBD، یک پایگاه داده به نام 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 راه‌اندازی می‌شود

Image

  • از دکمه [Add] برای افزودن منبع داده جدید ODBC استفاده کنید

Image

  • درایور MySQL را انتخاب کرده و آن را روی [Terminer] تنظیم کنید

54321

Image

  • راننده MySQL به اطلاعات خاصی نیاز دارد:
1
نام DSN که باید به منبع داده ODBC داده شود – این می‌تواند هر چیزی باشد
۲
ماشینی که SGBD و MySQL روی آن در حال اجرا هستند – در این مورد، localhost. شایان ذکر است که پایگاه داده می‌تواند یک پایگاه دادهٔ راه دور باشد. برنامه‌های محلی که از منبع داده ODBC استفاده می‌کنند، از این موضوع مطلع نخواهند بود. این موضوع به‌ویژه برای برنامهٔ جاوا ما صدق می‌کند.
۳
پایگاه داده MySQL برای استفاده. MySQL یک SGBD است که پایگاه‌های داده رابطه‌ای را مدیریت می‌کند، که مجموعه‌ای از جداول مرتبط با یکدیگر از طریق روابط هستند. در اینجا، ما نام پایگاه داده تحت مدیریت را مشخص می‌کنیم.
۴
نام کاربری با حقوق دسترسی به این پایگاه داده
۵
رمز عبور آنها

دو کلاس برای محاسبه مالیات تعریف شده‌اند: impots و impotsJDBC**. یک نمونه از کلاس impots** با استفاده از داده‌های جدول مالیات که به صورت آرایه به عنوان پارامتر ارسال شده‌اند، ایجاد می‌شود:

// ایجاد یک کلاس مالیاتی

public class impots{

  //داده‌های مورد نیاز برای محاسبه مالیات
   // از یک منبع خارجی می‌آید

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

   // سازنده خالی
  protected impots(){}

   // سازنده
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
     // ما بررسی می‌کنیم که سه جدول اندازه یکسانی داشته باشند
    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+")");
    // همه چیز درست است
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//سازنده

   //محاسبه مالیات
  public long calculer(boolean marié, int nbEnfants, int salaire){
     //محاسبه تعداد سهام
    double nbParts;
    if (marié) nbParts=(double)nbEnfants/2+2;
    else nbParts=(double)nbEnfants/2+1;
    if (nbEnfants>=3) nbParts+=0.5;
         //محاسبه درآمد مشمول مالیات و ضریب خانوادگی
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // محاسبه مالیات
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // نتیجه اظهارنامه
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//محاسبه
}// محدوده

کلاس impotsJDBC از کلاس مالیاتی قبلی مشتق شده است. یک نمونه از کلاس impotsJDBC با استفاده از داده‌های بازه مالیاتی که در یک پایگاه داده ذخیره شده است، ایجاد می‌شود. اطلاعات مورد نیاز برای دسترسی به این پایگاه داده به عنوان پارامتر به سازنده ارسال می‌شود:

// پکیجهای وارداتی
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // افزودن یک سازنده برای ایجاد
   // جدولهای 'limites'، 'coeffr' و 'coeffn' از
   // مالیاتها از یک پایگاه داده
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: نام پایگاه داده DSN
     //userIMPOTS, mdpIMPOTS: نام کاربری/رمز عبور برای دسترسی به پایگاه داده

     // جدولهای داده
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    //اتصال به پایگاه داده
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // ایجاد یک شیء Statement
    Statement S=connect.createStatement();
    // پرسوجوی SELECT
    String select="select limites, coeffr, coeffn from impots";
    //اجرای پرسوجو
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      //پردازش سطر فعلی
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// ردیف بعدی
     // بستن منابع
    RS.close();
    S.close();
    connect.close();
     //انتقال دادهها به آرایههای محدود
    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));
    }//برای
  }//سازنده
}//کلاس

پس از ایجاد یک نمونه از کلاس impotsJDBC، می‌توان متد calculer آن را برای محاسبه مالیات بارها فراخوانی کرد:

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

سه مورد دادهٔ مورد نیاز را می‌توان به روش‌های مختلفی به‌دست آورد. مزیت کلاس impotsJDBC این است که تنها کافی است به دریافت این داده‌ها توجه شود. پس از به‌دست‌آوردن سه مورد اطلاعات (وضعیت تأهل، تعداد فرزندان، حقوق سالانه)، فراخوانی متد calculer از کلاس impotsJDBC، مالیات قابل پرداخت را به ما می‌دهد.

5.2. نسخه ۱

ما در حال بررسی یک برنامه وب هستیم که یک رابط HTML را به کاربر ارائه می‌دهد تا سه پارامتر مورد نیاز برای محاسبه مالیات را به دست آورد:

  • وضعیت تأهل (متأهل یا مجرد)
  • تعداد فرزندان
  • حقوق سالانه

Image

این فرم توسط صفحهٔ زیر نمایش داده می‌شود، JSP:

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

<%
     //ویژگی‌های ارسال‌شده توسط سرولت اصلی را بازیابی می‌کند
  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(){
           //فرم را پاک می‌کند
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//با
      }//پاک کردن
        </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>
    <%
         //آیا خطایی وجود دارد
      if(erreurs!=null){
           //نمایش خطاها
        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 صرفاً اطلاعاتی را که توسط سرولت اصلی برنامه به آن ارسال شده است، نمایش می‌دهد:

     // بازیابی ویژگی‌های ارسال‌شده توسط سرولت اصلی
  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
ویژگی‌های دکمه رادیویی «yes» و «no» می‌توانند مقادیر "checked" یا "" را برای فعال یا غیرفعال کردن دکمه رادیویی مربوطه بگیرند.
txtEnfants
تعداد فرزندان مؤدی
txtSalaire
حقوق سالانه آنها
txtImpots
مبلغ مالیات پرداختنی
erreurs
فهرستی از هرگونه خطا اگر errors!=null

صفحه ارسال‌شده به کلاینت شامل یک اسکریپت جاوااسکریپت است که یک تابع effacer را در بر می‌گیرد که با دکمه "Effacer" مرتبط است و هدف آن بازنشانی فرم به وضعیت اولیه است: دکمه بدون تیک و فیلدهای ورودی خالی. شایان ذکر است که این نتیجه را نمی‌توان با دکمه HTML از نوع «reset» به دست آورد. دلیل آن این است که هنگام استفاده از این نوع دکمه، مرورگر فرم را به حالتی که دریافت کرده بود، ریست می‌کند. با این حال، در برنامه ما، مرورگر فرم‌هایی را دریافت می‌کند که ممکن است خالی نباشند.

سرولیت اصلی برنامه main.java نام دارد و کد آن به شرح زیر است:

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

public class main extends HttpServlet{

    // متغیرهای نمونه
    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{

        // آیا فرایند راهاندازی با موفقیت به پایان رسید؟
        if(msgErreur!=null){
             //کنترل به صفحهٔ خطا واگذار میشود
            request.setAttribute("msgErreur",msgErreur);
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // ویژگیهای درخواست
        String chkoui=null;
        String chknon=null;
        String txtImpots=null;

        // بازیابی پارامترهای درخواست
        String optMarie=request.getParameter("optMarie");              //وضعیت تأهل
        String txtEnfants=request.getParameter("txtEnfants");       // تعداد فرزندان
        if(txtEnfants==null) txtEnfants="";
        String txtSalaire=request.getParameter("txtSalaire");       // حقوق سالانه
        if(txtSalaire==null) txtSalaire="";

         //آیا همه پارامترهای مورد انتظار را داریم؟
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // پارامترها مفقود هستند
            request.setAttribute("chkoui","");
            request.setAttribute("chknon","checked");
            request.setAttribute("txtEnfants","");
            request.setAttribute("txtSalaire","");
            request.setAttribute("txtImpots","");
             // کنترل را به URL نمایش مالیات بسپارید
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // تمام پارامترها موجود هستند  در حال بررسی آنها
        ArrayList erreurs=new ArrayList();
         //وضعیت تأهل
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // خطا
            erreurs.add("Etat marital incorrect");
            optMarie="non";
        }
         // تعداد فرزندان
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            //خطا
            erreurs.add("Nombre d'enfants incorrect");
        }
         // حقوق
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // خطا
            erreurs.add("Salaire incorrect");
        }

         // اگر خطایی وجود داشته باشد، بهعنوان ویژگیهای پرسوجو ارسال میشوند
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
            txtImpots="";
        }else{
             // ما میتوانیم مالیات قابل پرداخت را محاسبه کنیم
            try{
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
            }catch(Exception ex){}
        }

         // سایر ویژگیهای پرسوجو
        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);

         //کنترل به 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(){
         // پارامترهای راهاندازی را بازیابی کنید
        ServletConfig config=getServletConfig();
        urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         //پارامترها OK هستند؟
        if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
            msgErreur="Configuration incorrecte";
            return;
        }

         //ایجاد یک نمونه از impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }
}
  • متد init سرولت دو کار انجام می‌دهد:
    • پارامترهای راه‌اندازی خود را بازیابی می‌کند. این‌ها به آن امکان می‌دهند تا به پایگاه داده ODBC متصل شود، که شامل داده‌هایی برای طبقات مختلف مالیاتی (DSNimpots, admimpots, mdpimpots) و فایل‌های URL برای صفحات مرتبط با برنامه است: urlAffichageImpots برای فرم و urlErreur برای صفحهٔ خطا.
    • یک نمونه از کلاس impotsJDBC ایجاد می‌کند.

در هر دو حالت، هرگونه خطا رسیدگی می‌شود و پیام خطا در متغیر msgErreur ذخیره می‌شود.

  • متد doGET
    • ابتدا بررسی می‌کند که سرویس‌لت به درستی راه‌اندازی شده باشد. اگر اینطور نباشد، صفحه خطا نمایش داده می‌شود
    • پارامترهای مورد انتظار را از فرم مالیاتی بازیابی می‌کند: optMarie، txtEnfants، txtSalaire. اگر هر یک از این‌ها (==null) وجود نداشته باشد، یک فرم مالیات خالی ارسال می‌شود. ممکن است تصور شود که بررسی اعتبار پارامتر optMarie غیرضروری است. این مقدار یک دکمه رادیویی است و در اینجا فقط می‌تواند یکی از مقادیر «oui» یا «non» را داشته باشد. با این حال، این امر این واقعیت را نادیده می‌گیرد که هیچ چیز مانع از آن نمی‌شود که یک برنامه با ارسال پارامترهای دلخواه خود، مستقیماً از سرولت (servlet) استعلام کند. هرگز نمی‌توان مطمئن بود که واقعاً به یک مرورگر متصل هستید. نادیده گرفتن این نکته می‌تواند به آسیب‌پذیری‌های امنیتی در برنامه منجر شود و در واقع، یافتن چنین آسیب‌پذیری‌هایی حتی در برنامه‌های تجاری نیز رایج است.
    • اعتبار هر یک از سه پارامتر بازیابی‌شده بررسی می‌شود. هرگونه خطای یافت‌شده به فهرست خطاها (خطاهای ArrayList) اضافه می‌شود. اگر خطایی وجود نداشته باشد، مبلغ مالیات محاسبه می‌شود؛ در غیر این صورت، محاسبه نمی‌شود.
    • اطلاعات مورد نیاز برای نمایش صفحه به عنوان ویژگی‌های درخواست تنظیم می‌شود و سپس فرم مالیات نمایش داده می‌شود

صفحه خطای JSP به شرح زیر است:

<%
     //jspService
   // یک خطا رخ داده است
  String msgErreur= (String)request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- شروع صفحه HTML -->
<html>
  <head>
      <title>impots</title>
  </head>
  <body>
      <h3>calcul d'impots</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

برنامهٔ وب «impots» نام دارد و در فایل Tomcat با شناسهٔ server.xml به شرح زیر پیکربندی شده است:

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

پوشهٔ برنامه شامل پوشه‌ها و فایل‌های زیر است:

Image

Image

Image

Image

فایل پیکربندی برنامه برای برنامه «impots»، 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>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. نسخه ۲

در مثال قبلی، اعتبار پارامترهای فرم txtEnfants و txtSalaire توسط سرور بررسی می‌شود. در اینجا پیشنهاد می‌کنیم آن‌ها را با استفاده از یک اسکریپت جاوااسکریپت که در صفحه فرم گنجانده شده است، بررسی کنیم. سپس مرورگر اعتبارسنجی پارامترها را انجام می‌دهد. اگر پارامترها معتبر باشند، تنها در آن صورت با سرور تماس گرفته می‌شود. این کار باعث صرفه‌جویی در «پهنای باند» می‌شود. صفحه نمایش JSP به شکل زیر درمی‌آید:

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

<html>

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

      function calculer(){
           // بررسی تنظیمات قبل از ارسال آنها به سرور
        with(document.frmImpots){
          //تعداد فرزندان
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            //قالب اعتبارسنجی نشده است
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//اگر
          //حقوق
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            //قالب تأیید نشده است
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//اگر
          //OK – فرم در حال ارسال به سرور است
          submit();
        }//با
      }//محاسبه        
        </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>

لطفاً تغییرات زیر را توجه فرمایید:

  • دکمه Calculer دیگر از نوع submit نیست، بلکه از نوع button است که با تابع calculer مرتبط است. این تابع است که فیلدهای txtEnfants و txTsalaire را تحلیل می‌کند. اگر آن‌ها صحیح باشند، مقادیر فرم به سرور ارسال (ارسال) می‌شوند؛ در غیر این صورت، یک پیام خطا نمایش داده می‌شود.

در اینجا مثالی از آنچه در صورت بروز خطا نمایش داده می‌شود، آورده شده است:

Image

5.4. نسخه ۳

ما در حال ایجاد یک تغییر جزئی در برنامه برای معرفی مفهوم جلسه هستیم. اکنون این برنامه را به‌عنوان شبیه‌ساز محاسبه مالیات در نظر می‌گیریم. کاربر می‌تواند سناریوهای مختلف مالیات‌دهنده را شبیه‌سازی کند و ببیند که مالیات پرداختنی برای هر یک چه خواهد بود. صفحه WEb زیر نمونه‌ای از آنچه ممکن است نمایش داده شود را نشان می‌دهد:

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 است. این سرولِت در جنبه‌های اصلی زیر با آن تفاوت دارد:

  • سرولِت اصلی مقداری را از پارامترهای optmarie محاسبه می‌کند، txtEnfants و txtSalaire و این مقدار را به صفحه نمایش JSP ارسال می‌کند
  • سرولِت شبیه‌سازی به همین ترتیب مقدار txtImpots را محاسبه می‌کند، پارامترها را ذخیره می‌کند (optMarie, txtEnfants, txtsalaire, txtImpots) در لیستی به نام 'simulations' ذخیره می‌شود. این لیست است که به‌عنوان پارامتر به صفحه نمایش JSP ارسال می‌شود. برای اطمینان از اینکه این لیست شامل تمام شبیه‌سازی‌های انجام‌شده توسط کاربر باشد، به‌عنوان یک ویژگی از جلسهٔ جاری ذخیره می‌شود.

سرولِت «simulations» به شرح زیر است (فقط خطوط کدی که با نسخه قبلی برنامه متفاوت هستند، حفظ شده‌اند):

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

public class simulations extends HttpServlet{

    // متغیرهای نمونه
    String msgErreur=null;
    String urlSimulationImpots=null;
    String urlErreur=null;
...........

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

...........

         //شبیهسازیهای قبلی را از جلسه بازیابی میکند
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();
        //شبیهسازیها در پرسوجوی جاری گنجانده میشوند
        request.setAttribute("simulations",simulations);

         // سایر ویژگیهای پرسوجو
...........

         //آیا همه پارامترهای مورد انتظار را داریم
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             //کنترل را به URL برای نمایش شبیهسازیهای محاسبه مالیات واگذار میکنیم
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // تمام پارامترها موجود هستند  آنها را بررسی کنید
...........

         //اگر خطایی وجود داشته باشد، آنها را بهعنوان ویژگیهای پرسوجو ارسال میکنیم
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
        }else{
            try{
                 // میتوانیم مالیات قابل پرداخت را محاسبه کنیم
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // ما نتیجه فعلی را به شبیهسازیهای قبلی اضافه میکنیم
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 //مقدار شبیهسازی جدید در جلسه ذخیره میشود
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // سایر ویژگیهای پرسوجو
..........

         //کنترل به 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(){
         //پارامترهای راهاندازی را بازیابی میکند
        ServletConfig config=getServletConfig();
        urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         //پارامترها OK هستند؟
.........................
    }
}

صفحه نمایش JSP برای simulationsImpots.jsp به شرح زیر تغییر یافته است (فقط کدی که با صفحه نمایش JSP از برنامه قبلی متفاوت است، حفظ شده است).

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

<%
     // بازیابی ویژگی‌های ارسال‌شده توسط سرولت اصلی
...........
  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>
    <%
         //آیا خطایی وجود دارد؟
      if(erreurs!=null){
..................
      }else if(simulations.size()!=0){
          //نتایج شبیه‌سازی
        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. نسخه ۴

اکنون یک برنامه مستقل ایجاد خواهیم کرد که به‌عنوان کلاینت وب برای برنامه قبلی /impots/simulations عمل خواهد کرد. این برنامه دارای رابط کاربری گرافیکی زیر خواهد بود:

خیر.
نوع
نام
نقش
۱
JTextField
txtUrlServiceImpots
URL از سرویس شبیه‌سازی محاسبه مالیات
2
JRadioButton
rdOui
در صورت متأهل بودن تیک خورده است
۳
JRadioButton
rdNon
در صورت مجرد بودن تیک بزنید
4
JSpinner
spinEnfants
تعداد فرزندان مؤدی (حداقل=0، حداکثر=20، گام=1)
5
JTextField
txtSalaire
حقوق سالانه مالیات‌دهنده به F
6
JList در JScrollPane
lstSimulations
فهرست شبیه‌سازی‌ها

منوی مالیات شامل گزینه‌های زیر است:

گزینه
اصلی
ثانویه
ثانویه
نام
نقش
مالیات‌ها
   
 
حساب کردن
mnuCalculer
مالیات قابل پرداخت را زمانی محاسبه می‌کند که تمام داده‌های مورد نیاز برای محاسبه موجود و صحیح باشند
 
پاک کردن
mnuEffacer
فرم را به وضعیت اولیه خود بازمی‌گرداند
 
خروج
mnuQuitter
برنامه را می‌بندد

قوانین عملیاتی

  • گزینه منوی Calculer در صورتی که هر یک از فیلدهای ۱ یا ۵ خالی باشد، غیرفعال باقی می‌ماند
  • یک URL نحوی نادرست در فیلد 1 شناسایی شده است

Image

  • یک حقوق نادرست شناسایی شد

Image

  • هرگونه خطای اتصال به سرور گزارش می‌شود (در مثال ۱ زیر، پورت نادرست است – در مثال ۲، URL درخواستی وجود ندارد – در مثال ۳، پایگاه داده MySQL در حال اجرا نبود)

Image

Image

Image

  • اگر همه چیز درست باشد، شبیه‌سازی‌ها نمایش داده می‌شوند

Image

هنگام نوشتن یک کلاینت وب سفارشی، دانستن دقیق اینکه سرور در پاسخ به درخواست‌های مختلف ممکن از سوی کلاینت چه چیزی ارسال می‌کند، ضروری است. سرور مجموعه‌ای از خطوط HTML را ارسال می‌کند که حاوی اطلاعات مفید و سایر عناصری هستند که صرفاً برای اهداف چیدمان HTML به کار می‌روند. بیان‌های منظم جاوایی می‌توانند به ما در شناسایی اطلاعات مفید در میان جریان خطوط ارسال‌شده توسط سرور کمک کنند. برای این کار، باید فرمت دقیق پاسخ‌های مختلف سرور را بدانیم. در اینجا، از کلاینت وبی که پیش از این با آن مواجه شده‌ایم استفاده خواهیم کرد، کلاینتی که به ما امکان می‌دهد پاسخ سرور به درخواست یک URL را روی صفحه نمایش دهیم. درخواست URL مربوط به سرویس شبیه‌سازی محاسبه مالیات ما، http://localhost:8080/impots/simulations، خواهد بود که می‌توانیم پارامترها را به شکل http://localhost:8080/impots/simulations?param1=vam1&param2=val2&... به آن ارسال کنیم.

بیایید URL را درخواست کنیم در حالی که پایه MySQL، که امکان ایجاد یک شیء impots را فراهم می‌کند، در حال اجرا نیست:


Dos>java clientweb http://localhost:8080/taxes/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 از صفحه 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>

برای بازیابی خطا، کلاینت وب باید در پاسخ وب‌سرور به دنبال خطی بگردد که عبارت «Application unavailable» را در خود داشته باشد. اکنون پایگاه داده MySQL را راه‌اندازی کرده و همان URL را درخواست می‌کنیم و مقادیری را که انتظار دارد به آن ارسال می‌کنیم (این مقادیر را به یکسان به صورت یک GET یا یک POST می‌پذیرد – به کد جاوا سرور مراجعه کنید):


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(){
...................................................
      }//حذف

      function 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 ارسال‌شده توسط سرور را داریم. نتایج شبیه‌سازی‌های مختلف را می‌توان در یک جدول موجود در این سند یافت. عبارت منظم (regular expression) که به ما امکان استخراج اطلاعات مرتبط از سند را می‌دهد می‌تواند به شکل زیر باشد:

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

که در آن چهار عبارت داخل پرانتز نمایانگر چهار بخش اطلاعات مورد نظر برای استخراج هستند.

اکنون دستورالعمل‌هایی برای اقدام در صورت درخواست کاربر برای محاسبه مالیات از طریق رابط کاربری گرافیکی قبلی داریم:

  • بررسی کنید که تمام داده‌ها در رابط معتبر هستند و در صورت لزوم هرگونه خطا را علامت‌گذاری کنید.
  • به URL مشخص‌شده در فیلد 1 متصل شوید. برای این کار، از مدل کلاینت وب عمومی که قبلاً ارائه و مورد بحث قرار گرفته است، پیروی خواهیم کرد.
  • در جریان پاسخ سرور، از عبارت‌های منظم برای انجام یکی از موارد زیر استفاده کنید:
    • پیام خطا را پیدا کنید، در صورت وجود
    • در صورت عدم وجود خطا، نتایج شبیه‌سازی را استخراج کنید

کد مربوط به منوی calculer به شرح زیر است:

  void mnuCalculer_actionPerformed(ActionEvent e) {
    //محاسبه مالیات
         //تأیید URL سرویس
        URL urlImpots=null;
        try{
            urlImpots=new URL(txtURLServiceImpots.getText().trim());
            String query=urlImpots.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
             // پیام خطا
            JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
             // تمرکز بر فیلد نادرست
            txtURLServiceImpots.requestFocus();
             //بازگشت به رابط کاربری
            return;
        }
     // تأیید حقوق
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // پیام خطا
            JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
       //تمرکز روی فیلد اشتباه
      txtSalaire.requestFocus();
       //بازگشت به رابط کاربری
      return;
    }
     // تعداد فرزندان
    Integer nbEnfants=(Integer)spinEnfants.getValue();

    try{
             //مالیات را محاسبه می‌کند
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             //خطا نمایش داده می‌شود
            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{
         //محاسبه مالیات
         //urlImpots: URL از اداره مالیات
         // متأهل: true اگر متأهل باشد، false در غیر این صورت
         // nbEnfants: تعداد فرزندان
         // حقوق: حقوق سالانه

         // اطلاعات مورد نیاز برای اتصال به سرور مالیاتی از urlImpots استخراج می‌شود
        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();

         //داده‌های محلی
        Socket  client=null;                        // کلاینت
        BufferedReader IN=null;                    //جریان خواندن مشتری
        PrintWriter OUT=null;                        //جریان نوشتن کلاینت
        String réponse=null;                        // پاسخ سرور
         // قالبی که در سربرگ‌ها جستجو می‌شود HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        //قالب پاسخ صحیح
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // نتیجهٔ مقایسه با الگو
        Matcher résultat=null;

        try{
             //اتصال به سرور
            client=new Socket(host,port);

            // جریان‌های ورودی و خروجی کلاینت ایجاد می‌شوند TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // درخواست URL  ارسال سربرگ‌ها HTTP
            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("");

             // خط اول پاسخ را می‌خواند
            réponse=IN.readLine();
             //خط HTTP را با قالب پاسخ صحیح مقایسه کنید
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // مشکلاتی با URL وجود دارد
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(result)

             // پاسخ را تا انتهای سربرگ‌ها می‌خوانیم و به دنبال هرگونه کوکی می‌گردیم
            while((réponse=IN.readLine())!=null){
                 // خط خالی؟
                if(réponse.equals("")) break;
                //خط غیرخالی HTTP
                 // اگر توکن جلسه موجود نباشد، آن را جستجو کنید
                if (JSESSIONID.equals("")){
                    // خط HTTP را با قالب کوکی مقایسه کنید
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        //کوکی توکن پیدا شد
                        JSESSIONID=résultat.group(1);
                    }//if(result)
                }//if(JSESSIONID)
            }//در حالی که

             // برای سربرگ‌ها همین بود HTTP  حالا می‌رویم سراغ کد HTML
             //برای بازیابی شبیه‌سازی‌ها
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            //همین
            client.close();
        }catch (Exception ex){
            throw new Exception(ex.getMessage());
        }
    }//calculerImpots

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

        //یک ردیف نمونه از جدول شبیه‌سازی
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        //یک سطر نمونه از فهرست خطاها
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
         // نتیجه مقایسه با مدل
        Matcher résultat=null;
         //شبیه‌سازی‌ها
        ArrayList listeSimulations=new ArrayList();

         // تمام سطرها تا انتها خوانده می‌شوند
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             //پیگیری
             // خط در مقابل الگوی خطا مقایسه می‌شود اگر بخش «شبیه‌سازی‌ها» هنوز مشاهده نشده باشد
            if(! simulationRéussie){
                résultat=ptnErreur.matcher(ligne);
                if(résultat.find()){
                    // پیام خطا
                    JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
                    // پایان
                    return listeSimulations;
                }//اگر
            }//اگر
             // ردیف با مدل شبیه‌سازی مقایسه می‌شود
            résultat=ptnSimulation.matcher(ligne);
            if(résultat.find()){
                // یک سطر در جدول یافت شد
                listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
                                                ":"+résultat.group(4));
                 // شبیه‌سازی موفقیت‌آمیز بود
                simulationRéussie=true;
            }//اگر
        }//در حالی که
        //پایان
        return listeSimulations;
    }

بیایید این کد را کمی توضیح دهیم:

  • روند mnuCalculer_actionPerformed بررسی می‌کند که داده‌های رابط معتبر هستند. اگر معتبر نباشند، یک پیام خطا نمایش داده می‌شود و روند متوقف می‌شود. اگر معتبر باشند، روند calculerImpots اجرا می‌شود.
  • روال calculerImpots با ساخت URL که برای درخواست نیاز دارد، آغاز می‌شود
         //بازیابی اطلاعات مورد نیاز برای اتصال به سرور مالیاتی از urlImpots
        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 به این URL متصل می‌شود:
             //اتصال به سرور
            client=new Socket(host,port);

            //جریان‌های تراکنش ورودی و خروجی مشتری را ایجاد می‌کند TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            //درخواست URL  ارسال سربرگ‌ها HTTP
            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 نادرست را درخواست کرده است و این موضوع به او اطلاع داده می‌شود.
         //الگوی مورد نظر در هدرها HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        //الگوی یک پاسخ صحیح
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........

             // خط اول پاسخ خوانده می‌شود
            réponse=IN.readLine();
             // خط HTTP با قالب پاسخ صحیح مقایسه می‌شود
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // مشکلی با URL وجود دارد
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(result)

             // پاسخ را تا انتهای سربرگ‌ها می‌خوانیم و به دنبال هرگونه کوکی می‌گردیم
            while((réponse=IN.readLine())!=null){
                 // خط خالی؟
                if(réponse.equals("")) break;
                // خط غیرخالی HTTP
                 // اگر توکن جلسه موجود نباشد، آن را جستجو کن
                if (JSESSIONID.equals("")){
                    // خط HTTP را با قالب کوکی مقایسه کنید
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // کوکی توکن پیدا شده است
                        JSESSIONID=résultat.group(1);
                    }//if(result)
                }//if(JSESSIONID)
            }//در حالی که
  • پس از پردازش سربرگ‌های HTTP، به بخش HTML پاسخ می‌رویم
             // برای سربرگ‌ها همین بود HTTP  حالا می‌رویم سراغ کد HTML
             //برای بازیابی شبیه‌سازی‌ها
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }
  • روال getSimulations فهرست شبیه‌سازی‌ها را در صورت وجود بازمی‌گرداند؛ این فهرست در صورتی که سرور پیغام خطا بازگرداند خالی خواهد بود. در این صورت، پیغام خطا در یک کادر پیغام نمایش داده می‌شود. اگر فهرست خالی نباشد، در فهرست کشویی رابط کاربری گرافیکی نمایش داده می‌شود.
  • روند getSimulations هر خط از پاسخ دریافتی از HTML را با عبارت منظم نماینده پیام خطا (Application unavailable...) و با عبارت منظم نماینده یک شبیه‌سازی مقایسه می‌کند. اگر پیام خطا یافت شود، نمایش داده می‌شود و روند متوقف می‌شود. اگر نتیجه یک شبیه‌سازی یافت شود، به فهرست شبیه‌سازی‌ها اضافه می‌شود. در پایان رویه، این فهرست به عنوان نتیجه بازگردانده می‌شود.
    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        //قالب یک سطر در جدول شبیه‌سازی
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        //قالب یک سطر در لیست خطا
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");

5.6. نسخه ۵

در اینجا، ما در حال تبدیل برنامه گرافیکی مستقل قبلی به یک اپلت جاوا هستیم. رابط کاربری گرافیکی کمی متفاوت است. در برنامه مستقل، کاربر خود فایل URL را برای سرویس شبیه‌سازی محاسبه مالیات وارد می‌کرد و سپس برنامه به آن URL متصل می‌شد. در اینجا، برنامهٔ کلاینت یک مرورگر وب است و کاربر URL را از سند HTML که حاوی اپلت است، درخواست خواهد کرد. به یاد داشتن این نکته مهم است که یک اپلِت جاوا فقط می‌تواند یک اتصال شبکه را با سروری که از آن دانلود شده است برقرار کند. بنابراین، 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 {
  // اجزای پنجره
  JPanel contentPane;
  JMenuBar jMenuBar1 = new JMenuBar();
  JMenu jMenu1 = new JMenu();
  JMenuItem mnuCalculer = new JMenuItem();
.............


   //ساخت فریم
  public void init() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // سایر راه‌اندازی‌ها
    moreInit();
  }

   // ابتدایی‌سازی فرم
  private void moreInit(){
         //پارامتر urlServiceImpots بازیابی شد
        String urlServiceImpots=getParameter("urlServiceImpots");
        if(urlServiceImpots==null){
            //پارامتر وجود ندارد
            JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
             // پایان
            return;
        }
         // ما URL را در فیلد آن وارد می‌کنیم
        String codeBase=""+getCodeBase();
        if(codeBase.endsWith("/"))
           txtURLServiceImpots.setText(codeBase+urlServiceImpots);
      else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
    // منوی «حساب کردن» غیرفعال است
    mnuCalculer.setEnabled(false);
     // چرخ‌گردان فرزندان – بین ۰ و ۲۰ فرزند
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(130,140,50,27));
    contentPane.add(spinEnfants);
  }//moreInit

   //مؤلفه را راه‌اندازی کنید
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(null);
...............
  }

وقتی یک مرورگر یک اپلت را بارگذاری می‌کند، ابتدا رویهٔ init آن را اجرا می‌کند. در این رویه، ما مقدار پارامتر urlServiceImpots اپلِت را بازیابی کردیم، سپس نام کامل سرویس شبیه‌سازی URL را محاسبه کرده و این مقدار را در فیلد txtURLServiceImpots قرار دادیم، گویی که کاربر آن را تایپ کرده است. پس از انجام این کار، دیگر هیچ تفاوتی بین این دو برنامه وجود ندارد. به طور خاص، کد مربوط به منوی Calculer یکسان است. در اینجا نمونه‌ای از خروجی آورده شده است:

Image

5.7. نتیجه‌گیری

ما نسخه‌های مختلفی از برنامه محاسبه مالیات مبتنی بر مدل مشتری-سرور خود را نشان داده‌ایم:

  • نسخه ۱: سرویس توسط مجموعه‌ای از سرولت‌ها و صفحات JSP ارائه می‌شود؛ کلاینت یک مرورگر وب است. این نسخه یک شبیه‌سازی انجام می‌دهد و هیچ سوابقی از شبیه‌سازی‌های قبلی را نگه نمی‌دارد.
  • نسخه ۲: ما با جاسازی اسکریپت‌های جاوااسکریپت در سند HTML که توسط مرورگر بارگذاری می‌شود، قابلیت‌های سمت مرورگر را اضافه می‌کنیم. این کار اعتبار پارامترهای فرم را بررسی می‌کند.
  • نسخه ۳: سرویس قادر است با مدیریت یک جلسه (session)، شبیه‌سازی‌های مختلف انجام‌شده توسط کلاینت را به خاطر بسپارد. رابط کاربری HTML نیز بر این اساس اصلاح شده تا این شبیه‌سازی‌ها را نمایش دهد.
  • نسخه ۴: کلاینت اکنون یک برنامه گرافیکی مستقل است. این امر به ما امکان می‌دهد تا از توسعه کلاینت‌های مبتنی بر وب فاصله بگیریم.
  • نسخه ۵: کلاینت به یک اپلِت جاوا تبدیل شده است. اکنون ما یک برنامه کلاینت-سرور داریم که به طور کامل، هم در سمت سرور و هم در سمت کلاینت، با جاوا برنامه‌نویسی شده است.

در این مرحله، چند نکته شایان ذکر است:

  • نسخه‌های ۱ تا ۳ از مرورگرهایی پشتیبانی می‌کنند که قابلیت دیگری به جز اجرای اسکریپت‌های جاوااسکریپت ندارند. شایان ذکر است که کاربر همیشه می‌تواند اجرای این اسکریپت‌ها را غیرفعال کند. در این صورت، برنامه در نسخه ۱ تنها به صورت جزئی کار خواهد کرد (گزینه «پاک کردن» کار نخواهد کرد) و در نسخه‌های ۲ و ۳ اصلاً کار نخواهد کرد (گزینه‌های «پاک کردن» و «محاسبه» کار نخواهند کرد). شاید ارزش داشته باشد که نسخه‌ای از سرویس را توسعه دهیم که از اسکریپت‌های جاوااسکریپت استفاده نکند.
  • نسخهٔ ۴ نیازمند آن است که رایانهٔ مشتری دارای یک ماشین مجازی جاوا ۲ باشد.
  • نسخه ۵ نیازمند آن است که رایانهٔ مشتری دارای مرورگری با ماشین مجازی جاوا ۲ باشد.

هنگام نوشتن یک سرویس وب، باید در نظر گرفت که هدف کدام نوع کلاینت‌ها هستند. اگر هدف دسترسی به گسترده‌ترین مخاطبان ممکن باشد، باید برنامه‌ای نوشت که فقط HTML را به مرورگرها ارسال کند (بدون جاوا اسکریپت یا اپلت‌ها). اگر در یک اینترانت کار می‌کنید و بر پیکربندی ایستگاه‌های کاری آن کنترل دارید، می‌توانید از نظر الزامات کلاینت سخت‌گیرانه‌تر عمل کنید و در این صورت نسخه ۵ قبلی قابل قبول خواهد بود.

نسخه‌های ۴ و ۵ کلاینت‌های وب هستند که اطلاعات مورد نیاز خود را از جریان داده HTML که توسط سرور ارسال می‌شود، بازیابی می‌کنند. اغلب اوقات، ما هیچ کنترلی بر این جریان داده نداریم. این وضعیت زمانی پیش می‌آید که ما یک کلاینت برای یک سرویس وب موجود در شبکه نوشته‌ایم که توسط شخص دیگری مدیریت می‌شود. بیایید یک مثال بزنیم. فرض کنید سرویس شبیه‌سازی محاسبه مالیات ما توسط شرکت X نوشته شده باشد. در حال حاضر، این سرویس شبیه‌سازی‌ها را در یک جدول HTML ارسال می‌کند و کلاینت ما از این موضوع برای بازیابی آن‌ها استفاده می‌کند. بنابراین، هر خط از پاسخ سرور را با عبارت منظم زیر مقایسه می‌کند:

         //قالب یک ردیف در جدول شبیه‌سازی
        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، شیوه‌نامه را اصلاح خواهد کرد. به لطف شیوه‌نامه، مرورگرها طرح‌بندی بصری جدید را نمایش می‌دهند و نیازی به اصلاح کلاینت‌های وب برنامه‌ای نخواهد بود. بنابراین می‌توان نسخه‌های جدید سرویس شبیه‌سازی ما را توسعه داد:

  • نسخهٔ ۶: سرویس یک پاسخ XML را همراه با یک شیوه‌نامه برای مرورگرها ارائه می‌دهد
  • نسخه ۷: کلاینت یک برنامه گرافیکی مستقل است که از پاسخ سرور XML استفاده می‌کند
  • نسخه ۸: کلاینت یک اپلت جاوا است که از پاسخ سرور XML استفاده می‌کند