5. Додаток IMPOTS
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 |
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,5 |
55790 | 0,35 | 9316,5 |
92970 | 0,4 | 12106 |
127860 | 0,45 | 16754,5 |
151250 | 0,50 | 23147,5 |
172040 | 0,55 | 30710 |
195000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Кожен рядок має 3 поля. Щоб обчислити податок I, шукаємо перший рядок, у якому QF <= поле1. Наприклад, якщо QF = 23000, то знайдемо рядок
Тоді податок I дорівнює 0,15*R - 2072,5*nbParts. Якщо QF є таким, що співвідношення QF<=поле1 ніколи не виконується, то використовуються коефіцієнти останнього рядка. У даному випадку:
що дає податок I=0,65*R - 49062*nbParts.
Дані, що визначають різні податкові шкали, містяться в базі даних ODBC-MySQL. MySQL — це SGBD, що є загальнодоступним і може використовуватися на різних платформах, зокрема Windows та Linux. За допомогою цього 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 наступним чином:
- запускається 32-бітний менеджер джерел даних ODBC

- за допомогою кнопки [Add] додають нове джерело даних ODBC

- вибираємо драйвер MySQL і виконуємо [Terminer]
54321

- драйвер MySQL вимагає певної інформації:
1 | ім’я DSN, яке слід присвоїти джерелу даних ODBC — можливо, будь-яке |
2 | машина, на якій виконується SGBD MySQL — у даному випадку localhost. Цікаво зазначити, що база даних може бути віддаленою. Локальні додатки, що використовують джерело даних ODBC, цього навіть не помітять. Це стосується, зокрема, нашого Java-додатка. |
3 | база даних MySQL, яку слід використовувати. MySQL — це SGBD, що керує реляційними базами даних, які є наборами таблиць, пов’язаних між собою відношеннями. Тут вказується назва бази даних, що керується. |
4 | ім’я користувача, який має право доступу до цієї бази даних |
5 | його пароль |
Для розрахунку податку було визначено два класи: 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{
// перевіряється, чи всі 3 таблиці мають однаковий розмір
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 походить від попереднього класу impots. Екземпляр класу impotsJDBC створюється на основі даних про податкові шкали, що зберігаються в базі даних. Інформація, необхідна для доступу до цієї бази даних, передається як параметри конструктору:
// імпортовані пакети
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// додавання конструктора, що дозволяє створювати
// таблиці limit, 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));
}//for
}//конструктор
}//клас
Після створення екземпляра класу impotsJDBC можна неодноразово викликати його метод calculer для розрахунку податку:
Отримати три необхідні дані можна різними способами. Перевага класу impotsJDBC полягає в тому, що потрібно лише подбати про це отримання. Після отримання трьох даних (сімейний стан, кількість дітей, річний оклад) виклик методу calculer класу impotsJDBC дає нам суму податку до сплати.
5.2. Версія 1
Розглянемо веб-додаток, який надає користувачеві інтерфейс HTML для отримання трьох параметрів, необхідних для розрахунку податку:
- сімейний стан (одружений чи ні)
- кількість дітей
- річний дохід

Форма відображається на наступній сторінці 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");
атрибути перемикачів «так», «ні» — можуть мати значення "checked" або "", щоб увімкнути або вимкнути відповідний перемикач | |
кількість дітей платника податків | |
його річний заробіток | |
сума податку до сплати | |
можливий перелік помилок, якщо errors!=null |
Сторінка, що надсилається клієнту, містить скрипт JavaScript із функцією 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");
// параметри в порядку?
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». Однак це означає забути, що ніщо не заважає програмі безпосередньо звертатися до сервлету, надсилаючи йому потрібні параметри. Ніколи не можна бути впевненим, що насправді відбувається взаємодія саме з браузером. Ігнорування цього моменту може призвести до вразливостей у безпеці додатка, і, до речі, такі вразливості часто зустрічаються навіть у комерційних додатках.
- Перевіряється правильність кожного з трьох отриманих параметрів. Кожна виявлена помилка додається до списку помилок (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» і налаштований у файлі server.xml сервера Tomcat наступним чином:
Папка додатка містить такі папки та файли:




Файл конфігурації програми 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 недоступна:

Сторінка, що з’являється у разі введення неправильних даних, виглядає так:

Якщо дані введені правильно, податок розраховується:

5.3. Версія 2
У попередньому прикладі сервер перевіряє правильність параметрів txtEnfants, txtSalaire у формі. Тут пропонується перевіряти їх за допомогою скрипта JavaScript, вбудованого в сторінку форми. У цьому випадку перевірку параметрів здійснює браузер. До сервера звертаються лише в тому випадку, якщо параметри є дійсними. Таким чином економиться «пропускна здатність». Сторінка відображення 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;
}//if
//зарплата
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// модель не перевірена
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//якщо
// все гаразд — форму надсилаємо на сервер
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. Якщо вони правильні, значення з форми будуть надіслані на сервер (submit), інакше з’явиться повідомлення про помилку.
Ось приклад відображення у разі помилки:

5.4. Версія 3
Ми дещо модифікуємо додаток, щоб впровадити в нього поняття сесії. Тепер ми розглядаємо додаток як програму для моделювання розрахунку податків. Таким чином, користувач може моделювати різні «конфігурації» платника податків і бачити, який податок доведеться сплатити за кожною з них. На сторінці WEb, наведеній нижче, наведено приклад того, що можна отримати:

Головний сервлет змінено, і тепер він називається 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. Вона має ті самі параметри ініціалізації, що й розглянутий раніше сервлет main, що стосується доступу до бази даних. З’являється новий параметр — urlSimulationsImpots, який відповідає URL на сторінці симуляції JSP (яка щойно була представлена трохи вище).
Сервлет simulations.java схожий на сервлет main.java. Він відрізняється від нього такими основними моментами:
- сервлет main обчислює значення txtImpots на основі параметрів optmarie, txtEnfants та txtSalaire і передає це значення на сторінку відображення JSP
- сервлет «simulations» аналогічним чином обчислює значення 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");
// параметри в порядку?
.........................
}
}
Сторінка 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. Версія 4
Тепер ми створимо автономний додаток, який буде веб-клієнтом попереднього додатка /impots/simulations. Цей додаток матиме такий графічний інтерфейс:
![]() |
№ | тип | назва | роль |
1 | JTextField | txtUrlServiceImpots | URL служби моделювання розрахунку податків |
2 | JRadioButton | rdOui | позначено, якщо одружений |
3 | JRadioButton | rdNon | позначте, якщо не одружений |
4 | JSpinner | spinEnfants | кількість дітей платника податків (мінімум = 0, максимум = 20, крок = 1) |
5 | JTextField | txtSalaire | річний дохід платника податків у франках |
6 | JList у JScrollPane | lstSimulations | список моделювань |
Меню «Податки» складається з таких опцій:
опція головна | опція додаткова | назва | роль |
Податки | |||
Обчислити | mnuCalculer | розраховує суму податку до сплати, якщо всі дані, необхідні для розрахунку, є та є правильними | |
Скинути | mnuEffacer | повертає форму до початкового стану | |
Вийти | mnuQuitter | завершує роботу програми |
Правила роботи
- пункт меню Calculer залишається неактивним, якщо одне з полів 1 або 5 порожнє
- виявлено синтаксично некоректний URL у полі 1

- виявлено неправильну суму заробітної плати

- повідомляється про будь-яку помилку підключення до сервера (у прикладі 1 нижче вказано неправильний порт — у прикладі 2 запитуване URL не існує — у прикладі 3 база даних MySQL не була запущена)



- Якщо все правильно, відображаються симуляції

Під час написання веб-клієнта необхідно точно знати, що саме надсилає сервер у відповідь на різні можливі запити клієнта. Сервер надсилає набір рядків HTML, що містять корисну інформацію та інші дані, які призначені лише для верстки HTML. Регулярні вирази в Java можуть допомогти нам знайти корисну інформацію у потоці рядків, що надсилаються сервером. Для цього нам потрібно знати точний формат різних відповідей сервера. Тут ми використаємо вже знайомий веб-клієнт, який дозволяє відображати на екрані відповідь сервера на запит URL. Запитаний URL буде належати нашому сервісу моделювання розрахунків податків http://localhost:8080/impots/simulations, якому ми зможемо передавати параметри у формі http://localhost:8080/impots/simulations?param1=vam1¶m2=val2&...
Запустимо URL, хоча база MySQL, що дозволяє створити об’єкт типу impots, не запущена:
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 зі сторінки 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(){
...................................................
}//видалити
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, надісланого сервером. Результати різних симуляцій наведено в єдиній таблиці, що міститься в цьому документі. Регулярний вираз, який дозволяє витягти необхідну інформацію з документа, може мати такий вигляд:
де чотири вирази в дужках представляють чотири елементи інформації, які потрібно витягти.
Тепер ми маємо інструкції щодо того, що потрібно робити, коли користувач запитує розрахунок податку через попередній графічний інтерфейс:
- перевірити, чи всі дані в інтерфейсі є правильними, і, за необхідності, повідомити про помилки;
- підключитися до 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(результат)
// зчитуємо відповідь до кінця заголовків, шукаючи можливий файл cookie
while((réponse=IN.readLine())!=null){
// порожній рядок?
if(réponse.equals("")) break;
// рядок HTTP не порожній
// якщо немає токена сеансу, його шукають
if (JSESSIONID.equals("")){
// порівнюємо рядок HTTP із шаблоном файлу cookie
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// знайдено файл cookie з токеном
JSESSIONID=résultat.group(1);
}//if(результат)
}//if(JSESSIONID)
}//while
// завершено обробку заголовків 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;
}//if
}//поки
// кінець
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();
........................
- потім підключається до цього URL, надсилаючи відповідні заголовки HTTP:
// здійснюється підключення до сервера
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/версія 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(результат)
// зчитуємо відповідь до кінця заголовків, шукаючи можливий файл cookie
while((réponse=IN.readLine())!=null){
// порожній рядок?
if(réponse.equals("")) break;
// рядок HTTP не порожній
// якщо немає токена сеансу, його шукають
if (JSESSIONID.equals("")){
// порівнюємо рядок HTTP із шаблоном файлу cookie
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// знайдено файл cookie з токеном
JSESSIONID=résultat.group(1);
}//if(результат)
}//if(JSESSIONID)
}//while
- після обробки заголовків 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 із регулярним виразом, що відповідає повідомленню про помилку (Додаток недоступний...), та з регулярним виразом, що відповідає симуляції. Якщо зустрічається повідомлення про помилку, воно відображається, і процедура завершується. Якщо знайдено результат симуляції, його додають до списку симуляцій. Наприкінці процедури цей список повертається як результат.
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. Версія 5
Тут ми перетворюємо попередню автономну графічну програму на Java-аплет. Графічний інтерфейс дещо відрізняється. У автономній програмі користувач самостійно вводив URL сервісу моделювання розрахунку податків, після чого програма підключалася до цього URL. Тут клієнтською програмою є веб-браузер, і користувач буде запитувати URL документа HTML, що містить аплет. Тепер слід пам’ятати, що Java-аплет може встановити мережеве з’єднання лише з тим сервером, з якого він був завантажений. Отже, URL служби моделювання буде на тому самому сервері, що й документ HTML, який містить аплет. У нашому прикладі в поле txtUrlServiceImpots, яке не можна редагувати користувачем, буде вказано параметр ініціалізації аплету. Таким чином, ми отримаємо такий клієнт:

Документ 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);
// спиннер «Діти» — від 0 до 20 дітей
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, є ідентичним. Ось приклад виконання:

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, і наш клієнт використовує цю особливість для їх отримання. Таким чином, він порівнює кожен рядок відповіді сервера з регулярним виразом:
// шаблон одного рядка таблиці моделювання
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
Припустимо тепер, що розробник додатка змінює візуальний вигляд відповіді, розміщуючи симуляції не в таблиці, а у списку у такому форматі:
У цьому випадку наш веб-клієнт потребуватиме переробки. Саме в цьому полягає постійна загроза для веб-клієнтів додатків, які ми не контролюємо самостійно. 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 від сервера
