Java

         

Использование функции для расчета параметров в интерактивном режиме


<HTML> <HEAD> <TITLE> Использование функции для расчета параметров в интерактивном режиме.</TITLE> </HEAD> <BODY> <CENTER><FONT size=+4>Угадай число ;-)</FONT> <HR width="100%"></CENTER> <CENTER><TABLE> <TR> <TD>Загаданное программой число - целое,</TD> </TR> <TR> <TD>находится в интервале от 10 до 1000000.</TD> </TR> <TR> <TD>В случае угадывания программа сообщит об этом ;-)</TD> </TR> <TR> <TD>Одновременно программа вычисляет и отображает квадрат и куб числа, которое вы ввели.</TD> </TR> </TABLE> <I>Copyright © 2002 by Oleg Nikitenko.</I> </CENTER> <CENTER> <HR width="100%"></CENTER> <SCRIPT language="Javascript"> <!-- function calc_my_val() { // Расчет квадрата и куба числа var kilos=document.form.my_val.value with (Math) { // убрать, если планируется использовать Opera 3.x! jj = kilos; sqrt_val_ = jj*jj; kub_val = jj*jj*jj } // убрать, если планируется использовать Opera 3.x! document.form.sqrt_val.value=sqrt_val_; // document.form.sqrt_val.value=sqrt_val is INCORRECT for Opera 3.x! document.form.kub.value=kub_val } function check_val() { // Проверка введенного числа var kilos=document.form.my_val.value if (kilos==50) { document.form.sqrt_val.value='Угадал!'; document.form.kub.value='Угадал!' } else { document.form.sqrt_val.value='Не угадал!'; document.form.kub.value='Не угадал!' } } // --> </SCRIPT> <FORM name="form"> <TABLE> <TR> <TD>Введите число:</TD> <TD><INPUT type="text" name="my_val"></TD> <TD><INPUT type="button" OnClick="check_val()" value="Предлагаю такой вариант!"></TD> </TR> <TR> <TD><FONT size=2><U>Для расчета щелкни кнопку справа:</U></FONT</TD> <TD><FONT size=2><U>Для угадывания - другую кнопку ;-)</U></FONT></TD> <TD><INPUT type="button" OnClick="calc_my_val()" value="Расчитать квадрат и куб числа!"></TD> </TR> <TR> <TD><B>Квадрат числа:</B></TD> <TD><INPUT type="text" name="sqrt_val"></TD> </TR> <TR> <TD><B>Куб числа:</B></TD> <TD><INPUT TYPE="text" NAME="kub"></TD> </TR> </TABLE> </FORM> </BODY> </HTML>



Скрипт для определения версии браузера


<HTML> <HEAD> <TITLE> Определение версии браузера</TITLE> <SCRIPT language=JavaScript> function check_browser() { var version = 0; if (navigator.userAgent.indexOf ("MSIE 5") != -1) document.write ("Internet Explorer 5.0"); else if (navigator.userAgent.indexOf("MSIE 4") != -1) document.write ("Internet Explorer 3.0!"); else if (navigator.userAgent.indexOf ("MSIE 4.5") != -1) document.write ("Microsoft Internet Explorer 4.5 for Macintosh"); else if (navigator.userAgent.indexOf ("Opera") != -1) document.write("Opera"); else if (navigator.userAgent.indexOf ("Mozilla/5.0") != -1) document.write ("Navigator 5 или выше (в т.ч. 6.2+)"); else if (navigator.userAgent.indexOf ("Mozilla/4.7") != -1) document.write ("Navigator 4.7"); else if ……… else version = 8; return true; } // --> </SCRIPT> </HEAD> <BODY> <SCRIPT language=JavaScript> document.write("Вы используете браузер: "); check_browser(); </SCRIPT> </BODY> </HTML>



Текущая дата на страничке. Использование массивов


<HTML> <HEAD> <TITLE> Текущая дата на страничке. Оптимизация 2. </TITLE> </HEAD> <SCRIPT LANGUAGE="JavaScript"> function cur_date() { var months = new Array(13); // массив месяцев var days = new Array(8); // массив дней недели // в обоих массивах нулевой элемент не используется months[1]="Январь"; … months[12]="Декабрь"; days[1]="Воскресенье"; … days[7]="Суббота"; var time=new Date(); var lmonth=months[time.getMonth() + 1]; var ldate=days[time.getDay() + 1]; var date=time.getDate(); var year=time.getYear(); if ((navigator.appName == "Microsoft Internet Explorer") && (year < 2000)) year="19" + year; if (navigator.appName == "Navigator") year=1900 + year; return("<FONT face=Verdana size=2 color=BLUE> Сегодня ... " + ldate + "," + lmonth + " " + date + ", " + year); } </SCRIPT> <BODY bgcolor="#FFFFF0" text="#000000"> <H3> С днем рождения, программер!</H3> <SCRIPT language="JavaScript"> document.write(cur_date()); </SCRIPT> </BODY> </HTML>



Ввод телефонного номера с проверкой корректности


<HTML> <HEAD> <TITLE>Проверка значений заполняемых полей.</TITLE> <SCRIPT language="javaScript"> <!-- function valF() { formObj = document.WhatAreRates; if (formObj.txtNPA.value == ""!isNum(formObj.txtNPA.value)) { alert ("Неверный area code! Попытайтесь еще раз."); formObj.txtNPA.focus(); return false; } if (formObj.txtNXX.value == ""!isNum(formObj.txtNXX.value)) { alert ("Неверный номер телефона! Попытайтесь еще раз"); formObj.txtNXX.focus(); return false; } return true; } //EOF valF function isNum(passedVal) { for (i=0; i<passedVal.length; i++) { if (passedVal.charAt(i) < "0") return false if (passedVal.charAt(i) > "9") return false } return true }//EOF isNum //--> </SCRIPT> </HEAD> <BODY bgcolor="#FFFFFF"> <FORM name="WhatAreRates" method="post" action="https://www.secretsite.com/user.asp" onSubmit="return valF();"> <TABLE width="600" border="0" cellspacing="0" cellpadding="0" align="center"> <TR> <TD bgcolor="#006633"> <FONT size="2" face="Arial" color="#FFFF66"><B>Тип телефона:</B></FONT> </TD> </TR> <TR> <TD> <INPUT name=rdoBusOrRes type=radio value=Residential checked> <FONT face="Arial" size="2">Home</FONT> <INPUT name=rdoBusOrRes type=radio value=Business> <FONT face="Arial" size="2">Business</FONT> </TD> </TR> <TR> <TD height="69"> <P>Введите код региона (area code, 3 цифры) и 7 цифр вашего телефона: <BR> Пример:(044) 2233559   ( <INPUT type="text" name="txtNPA" size="3" maxlength="3"> ) <INPUT type="text" name="txtNXX" size="7" maxlength="7">         <INPUT type="button" OnClick="return valF();" value="Подтверждаю!"> <INPUT type="hidden" name="txtLine" size="4" maxlength="4" value=""> </P> </TD> </TR> </TABLE> </FORM> </BODY> <BR><BR> </TD> </TR> </TABLE> </CENTER> </HTML>



Abstract


Аспектно-ориентированное программирование, сейчас является пожалуй одной из самых обсуждаемых тем, когда речь заходит о крупных информационных системах. Несмотря на то, что сегодня это скорее академические исследования, множество компаний таких как BEA, IBM, Microsoft а так же open source объединения как JBoss, ObjectWeb, Eclipse уже работают в этом направлении с видимыми результатами. В данной статье я постараюсь показать как можно применять АОП и какие приемущества даёт это применение на примере реализации системы разграничения доступа в WEB приложениях.



Аудит




Реализации аудита могут существенно отличаться, например можно фиксировать каждый вызванный «контроллер» с помощью добавления в каждый метод контроллера вызова метода записи в журнал. Или можно отслеживать посетителей и их путь по сайту с помощью сессии и специального фильтра (такой подход реализован в  ) но возникает проблема установления соответствия между выполнением бизнес-логики и URL на сайте. В любом случае вызовы системы аудита распределяются по всему коду системы, и тем самым существенно затрудняют сопровождение системы и её эволюцию.


Аудит, пожалуй, является самым наглядным примером применения АОП, т.к. его реализация обычным способом обычно обязывает разработчиков добавлять один и тот же код занесения в журнал во все части системы. Например, для ведения журнала действий каждого пользователя нам необходимо включить в каждый контроллер вызов метода ответственного за занесение данных в журнал событий, опять же гарантии что такие вызовы включены в каждый контроллер, обеспечить трудно. Или предположим, что первая версия системы поддерживала простой механизм аудита, в то время как для следующей версии, требования усложнились, и теперь необходимо изменить все включения вызовов старого типа на вызовы новой системы журналирования.

Красота АОП решения в том, что сквозная функциональность аудита изолирована в пределах аспекта. Все изменения в типе и детализации событий могут быть с лёгкостью проведены не затрагивая основную функциональность.

Для примера рассмотрим простейший аспект, который будет перехватывать все вызовы контроллеров, определяя пользователя, и записывать информацию в журнал.

Создадим новый аспект, с pointcut перехватывающий все выполнения контроллеров, и захватывающий объект запроса. После чего на основе созданного pointcut, напишем before advice выводящий сообщение на System.out.

Код аспекта:

package aop.example;

import org.aspectj.lang.*; import javax.servlet.http.*;

/** * Аспект обеспечивающий аудит * @author Zubairov Renat */ public aspect AuditAspect {

/** * Все методы которые необходимо
заносить в журнал * Так же собираем запрос
для извлечения от туда сесии */ public pointcut controllerMethods
(HttpServletRequest request) : // Выполнение всех методов
параметрами которых являются HttpServletRequest
и HttpServletResponse // для всех классов наследников
HttpServlet execution(* javax.servlet.http.
HttpServlet+.*(HttpServletRequest,
HttpServletResponse)) && args(request,
HttpServletResponse);

/** * Advice который обеспечивает печать
на стандартный вывод * информации какой метод будет
выполнен и пользователь * который выполняет метод */ before(HttpServletRequest request)
: controllerMethods(request) { // Собираем статическую информацию
о точке вплетения Signature sig = thisJoinPointStaticPart.
getSignature(); // Выводим доступную информацию System.out.println("User " + request.getSession().getAttribute
(EntranceFilter.USER_KEY).toString() + " executing " + sig.
getDeclaringType().getName() + "." + sig.getName()); }

}

Обратите внимание, что в теле advice мы используем статическую информацию о точке вплетения, для того, что бы отобразить имя класса объекта и название метода.

В нашем примере стандартный вывод системы будет отображать строки вида:

User Anonymous executing aop.example.LoginServlet.doGet

User user1 executing aop.example.ViewServlet.doGet



Авторизация


Самая интересная часть системы защиты. В большинстве случаев применения ООП получаются сложные, сильно связные подсистемы, не позволяющие повторное использование в других проектах. Давайте разберемся почему.

Итак, авторизация – разрешение/запрет действий, выполняемых аутентифицированными пользователями. Вполне логично было бы разместить авторизацию в компоненте «Контроллер», но если, например, несколько контроллеров оперируют с одними и теми же объектами модели, то получается дублирование функций, поэтому логично было бы разместить авторизацию непосредственно в объектах модели. Для примера рассмотрим удаление альбома по названию (в нашей простой версии нет ID альбома):

, обрабатываемый сервлетом, который с начала находит альбом, который нужно удалить (вызов номер 2), а потом пытается его удалить из хранилища альбомов (AlbumList – класс модели). Далее, хранилище альбомов проверяет, может ли текущий пользователь удалить выбранный альбом, используя класс системы авторизации (AuthHelper) и если такое разрешение получено, то удаляет альбом. Вызов номер 6 перенаправляет запрос на слой отображения.

На даиграмме взаимодействия видно, что для проверки авторизации нам необходимо знать пользователя (вызов номер 4 имеет 2 параметра – пользователь и объект который он пытается удалить), то есть нам необходимо передать объект пользователь в методе номер 3. Следовательно, в каждый метод модели нам необходимо дополнительно передавать текущего пользователя, а если контроллер не является объектом, непосредственно получающим запрос (а как мы помним, объект User храниться в сессии), то и в каждый метод контроллера нужно добавить такой параметр.

Кроме того, мы получаем связь модели и системы авторизации, что недопустимо, так как при смене системы авторизации модель не должна меняться.

Предположим, что мы рассматриваем реальную систему, в которой десятки, если не сотни объектов модели. В данном случае никто не сможет дать твёрдой гарантии, что все вызовы методов приводящих к изменению модели окружены такими проверками.

Есть один не маловажный нюанс – ссылка «Удалить» будет всё ещё отображаться напротив всех альбомов, в том числе не принадлежащих текущему пользователю. Безусловно, при нажатии на эту ссылку удаления чужых альбомов происходить не будет, но всё же ссылка на удаление не должна отображаться.


Здесь видно, что « Before advice» созданный на основе «Read access pointcut» перехватывает операцию getTitle объекта модели (вызов номер 5 уже производится аспектом) и взывает класс, инкапсулирующий правила авторизации для проверки операции чтения: boolean isAllowedRead(AnonymousUser, Object). Как видно из сигнатуры метода для проверки нам необходим текущий пользователь. Здесь нам на помощь приходит паттерн "червоточина" (Wormhole) (Статья на английском   раздел "Replace argument trickle by wormhole"). В двух словах этот паттерн позволяет нам неявно передавать параметры контекста одного из одной части системы в другую часть, находящуюся, например, в одном потоке выполнения. Реализуется он следующим образом:

В результате, мы получили pointcut, который даёт нам доступ, как к объекту текущего запроса, так и к объекту над которым производится интересующее нас действие. Обратите внимание, что перехват события будет производиться только в потоке выполнения, включающем в себя фильтр, то есть если мы, например, будем запускать unit тесты модели, тестирующие классы напрямую, то никакой проверки происходить не будет (что естественно, т.к. не откуда будет взять пользователя), что сильно упрощает тестирование.

Итак, с перехватом выполнения методов разобрались, теперь необходимо обработать исключительные ситуации, то есть когда пользователь не авторизован выполнить действие. В данном случае before advice выбросит unchecked exception AuthorizationException и тем самым не даст выполниться не разрешённому методу. Для того, что бы отобразить пользователю цивилизованное сообщение об ошибке, или обработать эту ситуацию другим способом, мы перехватываем исключение, точно так же как и в предыдущем аспекте.

Полученный код аспекта приведён ниже:

package aop.example;

import aop.example.model.*; import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException;

/** * Авторизационный аспект * @author Zubairov Renat */ public aspect AuthorizationAspect {



/** * pointcut включающий в себя метод
с ServletRequest для * того что бы потом можно было
бы получить его в объединении * с другим pointcut (паттерн
"червоточина") */ pointcut requestMethod
(ServletRequest request) : execution(* aop.example.EntranceFilter.
doFilter(ServletRequest, ServletResponse,
FilterChain)) && args(request,
ServletResponse, FilterChain);

/** * pointcut определяющий метод фильтра
в котором мы будем отлавливать * исключение */ public pointcut doFilterMethod
(ServletRequest srequest, ServletResponse sresponse
, EntranceFilter filter) : execution(void aop.example.
EntranceFilter.doFilter(ServletRequest,
ServletResponse, FilterChain)) && args(srequest, sresponse,
FilterChain) && this(filter);

// все методы производящие чтение информации
объектов модели pointcut readMethods(Object object)
: execution (public * aop.example.model.*.get*(..))
&& this(object);

// все методы производящие добавление
объектов модели pointcut addMethods(Object object)
: execution (public * aop.example.model.*.add*(..))
&& this(object);

// все методы производящие удаление
объектов модели pointcut deleteMethods(Album album)
: execution (public * aop.example.model.
AlbumList.deleteAlbum(Album)) && args(album);

// Вызов методов чтения произошедшие в
потоке выполнения // следующим за вызовом метода фильтра // мы объеденили два pointcut - реализация
паттерна "червоточина" pointcut readAccess(ServletRequest request,
Object object) : cflow(requestMethod(request)) &&
readMethods(object);

// то же самое только для добавления pointcut addAccess(ServletRequest request,
Object object) : cflow(requestMethod(request)) &&
addMethods(object);

// то же самое только для удаления pointcut deleteAccess(ServletRequest
request, Album album) : cflow(requestMethod(request))
&& deleteMethods(album); /** * Before advice проверки на чтение */ before(ServletRequest request, Object object)
: readAccess(request, object) { if (!AuthHelper.isAbleToRead
(extractUser(request), object)) { throw new AuthorizationException
("Read access not allowed"); } }



/** * Before advice проверки на добавление */ before(ServletRequest request, Object object)
: addAccess(request, object) { if (!AuthHelper.isAbleToAdd(extractUser
(request), object)) { throw new AuthorizationException
("Add access not allowed"); } }

/** * Before advice проверки на удаление */ before(ServletRequest request, Album album) :
deleteAccess(request, album) { if (!AuthHelper.isAbleToDelete
(extractUser(request), album)) { throw new AuthorizationException
("Delete access not allowed"); } }

/** * Around advice отлавливающий исключение * и отправляющий запрос на страницу с ошибкой */ void around(ServletRequest srequest,
ServletResponse sresponse, EntranceFilter filter)
throws IOException, ServletException : doFilterMethod(srequest,
sresponse, filter) { try { // выполняем метод фильтра proceed(srequest, sresponse, filter); } catch (AuthorizationException e) { // ловим исключение srequest.setAttribute("error_message",
e.getMessage()); // вперёд на страницу с сообщением
об ошибке filter.getConfig().getServletContext()
.getRequestDispatcher("error.vm")
.forward(srequest, sresponse); } }

/** * Приватная фунция которая вынимает
пользователя из запроса */ private AnonymousUser extractUser
(ServletRequest request) { return (AnonymousUser)((HttpServletRequest)
request).getSession().getAttribute(EntranceFilter.USER_KEY); }

}

Обратите внимание, что для удаления альбома мы используем немного отличный от остальных pointcut, это связанно с тем, что метод удаляющий альбомы принадлежит не классу Album, а классу AlbumList, кроме того, альбом, подлежащий удалению, передаётся как параметр метода. Как видно из решения язык определения pointcut AspectJ с лёгкостью справился и с такой задачей.

Для примера попробуйте ввести в строку браузера следующий запрос:

http://localhost:8080/view?delete=Picture%20of%20%3Cb%3Euser2%3C/b%3E

Будучи не зарегистрированным в системе (должна отобразиться страница с логином), или под пользователем User1 (должна отобразиться страница с ошибкой, т.к. производится попытка удалить альбом, не принадлежащий текущему пользователю).



Пред-проверка

Для того, что бы реализовать оповещение слоя отображения о действиях которые разрешено производить с объектом модели, мы создадим дополнительный интерфейс который будет содержать 3 метода - boolean isReadable(),boolean isDeletable() и boolean isAddable() после чего создадим в классе Album методы реализующие данный интерфейс и возвращающие всегда true. Слой отображения будет отображать ссылку «удалить» только если метод isDeletable() даст добро. А в нашем авторизационном аспекте создадим pointuct перехватывающие соответствующие методы всех классов реализующих данный интерфейс. После чего создадим around advice, который будет возвращать результат вызова системы авторизации. Захват параметров производится точно так же, как это было сделано при пост-проверке.

Изменённый аспект:

package aop.example;

import aop.example.model.*; import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException;

/** * Авторизационный аспект * @author Zubairov Renat */ public aspect AuthorizationAspect {

/** * pointcut включающий в себя метод
с ServletRequest для * того что бы потом можно было
бы получить его в объединении * с другим pointcut (паттерн "червоточина") */ pointcut requestMethod
(ServletRequest request) : execution(* aop.example.EntranceFilter.
doFilter(ServletRequest, ServletResponse,
FilterChain)) && args(request, ServletResponse,
FilterChain);

/** * pointcut определяющий метод фильтра
в котором мы будем отлавливать * исключение */ public pointcut doFilterMethod
(ServletRequest srequest, ServletResponse
sresponse, EntranceFilter filter) : execution(void aop.example.
EntranceFilter.doFilter(ServletRequest,
ServletResponse, FilterChain)) && args(srequest,
sresponse, FilterChain) && this(filter);

// все методы производящие чтение информации
объектов модели pointcut readMethods(Object object)
: execution (public * aop.example.model.
*.get*(..)) && this(object);

// все методы производящие добавление
объектов модели pointcut addMethods(Object object)
: execution (public * aop.example.model.
*.add*(..)) && this(object);



// все методы производящие удаление
объектов модели pointcut deleteMethods(Album album)
: execution (public * aop.example.
model.AlbumList.deleteAlbum(Album)) && args(album);

// методы проверки на доступность
чтения (пред-проверка) pointcut controlledRead(Object object)
: execution(public boolean aop.example.model.
Controlled+.isReadable()) && this(object);

// методы проверки на доступность
добавления (пред-проверка) pointcut controlledAdd(Object object)
: execution(public boolean aop.example.model.Controlled+.
isAddable()) && this(object);

// методы проверки на доступность
удаления (пред-проверка) pointcut controlledDelete(Object object)
: execution(public boolean aop.example.model.
Controlled+.isDeletable()) && this(object);

// Вызов методов чтения произошедшие в потоке
выполнения // следующим за вызовом метода фильтра // мы объеденили два pointcut - реализация
паттерна "червоточина" pointcut readAccess(ServletRequest request,
Object object) : cflow(requestMethod(request))
&& readMethods(object);

// то же самое только для добавления pointcut addAccess(ServletRequest request,
Object object) : cflow(requestMethod(request)) &&
addMethods(object);

// то же самое только для удаления pointcut deleteAccess(ServletRequest request,
Album album) : cflow(requestMethod(request)) &&
deleteMethods(album);

// пред-проверка на чтение pointcut readCheck(ServletRequest request,
Object object) : cflow(requestMethod(request)) &&
controlledRead(object);

// пред-проверка на добавление pointcut addCheck(ServletRequest request,
Object object) : cflow(requestMethod(request)) &&
controlledAdd(object);

// пред-проверка на удаление pointcut deleteCheck(ServletRequest request,
Object object) : cflow(requestMethod(request)) &&
controlledDelete(object);

/** * Around advice отлавливающий исключение * и отправляющий запрос на страницу с ошибкой */ void around(ServletRequest srequest,
ServletResponse sresponse, EntranceFilter filter)
throws IOException, ServletException :
doFilterMethod(srequest, sresponse, filter) { try { // выполняем метод фильтра proceed(srequest, sresponse, filter); } catch (AuthorizationException e) { // ловим исключение srequest.setAttribute("error_message",
e.getMessage()); // вперёд на страницу с сообщением
об ошибке filter.getConfig().getServletContext()
.getRequestDispatcher("error.vm").
forward(srequest, sresponse); } }



/** * Before advice проверки на чтение */ before(ServletRequest request, Object object)
: readAccess(request, object) { if (!AuthHelper.isAbleToRead
(extractUser(request), object)) { throw new AuthorizationException
("Read access not allowed"); } }

/** * Before advice проверки на добавление */ before(ServletRequest request, Object object)
: addAccess(request, object) { if (!AuthHelper.isAbleToAdd
(extractUser(request), object)) { throw new AuthorizationException
("Add access not allowed"); } }

/** * Before advice проверки на удаление */ before(ServletRequest request, Album album)
: deleteAccess(request, album) { if (!AuthHelper.isAbleToDelete
(extractUser(request), album)) { throw new AuthorizationException
("Delete access not allowed"); } }

/** * Around advice пред-проверки, здесь
мы игнорируем возвращаемое методом * значение, и всё время возвращаем
то которое удовлетворяет правилам * авторизации * Мы не обрабатываем остальные пред-проверки
т.к. по умолчанию любой может * читать, и все аутентифицированные
пользователи могут добавлять */ boolean around(ServletRequest request,
Object object) : deleteCheck(request, object) { return AuthHelper.isAbleToDelete
(extractUser(request), object); }

/** * Приватная фунция которая вынимает
пользователя из запроса */ private AnonymousUser extractUser
(ServletRequest request) { return (AnonymousUser)((HttpServletRequest)
request).getSession().getAttribute
(EntranceFilter.USER_KEY); }

}

Достоинства АОП решения:

Как видно из определения аспектов readMethods, addMethods, controlledRead, controlledAdd и controlledDelete мы не ссылаемся на определённые классы, следовательно, авторизация будет автоматически распространяться на все новые классы модели помещённые в пакет model и реализующие интерфейс Controllable.

Как выбрасывание исключения, так и его обработка реализована в пределах одного аспекта, что упрощает дальнейшую эволюцию и сопровождение системы.

Все вызовы методов классов модели (созданные согласно правилам) будут объектом для применения системы авторизации.

Слой отображения не зависит от системы авторизации.

Система полностью работоспособна без применения аспекта авторизации, что говорит об отсутствии связи между системой в целом и аспектом авторизации (в свою очередь аспект авторизации зависит от системы, но эта связь не существенна т.к. происходит лишь на уровне определения pointcut).

Для наглядного примера можно создать несколько новых альбомов под одним из пользователей, а потом зарегистрироваться под другим. Ссылки «удалить» будут проставлены только у «своих» альбомов, кроме того попытки удаления чужих альбомов через непосредственное редактирование get запросов приведут к странице с ошибкой.


Целостность


Решение проблемы целостности, в общем, более сложно, чем все остальные аспекты защиты, в частности для целостности и не противоречивости данных обычно используются транзакции. Реализация этого механизма ООП методами представляет не тривиальную задачу, а попытки отделить этот механизм от основной функциональности системы могут привести к решению такому же сложному как EJB (под сложностью я понимаю не только сложность в реализации, но так же сложность в тестировании, поддержке, расширении и переносимости).


Проблемы целостности, как было сказано выше, достаточно сложны для реализации, но всё же необходимо отметить, что паттерн "червоточина" прекрасно подходит для распространения контекста транзакции, например.



Идентификация/аутентификация


Обычно WEB приложение запрашивает регистрационное имя и пароль с помощью формы, данные проверяются (сравниваются с базой данных, например) и на основании сравнения принимается решение об аутентификации текущего пользователя. Далее в сессию клиента попадает объект, инкапсулирующий пользователя (а в cookie браузера отправляется идентификатор сессии).

Данный способ представляет разработчику приложения полный контроль над процессом аутентификации.

В нашем приложении аутентификация не реализована в полной мере, так как она не представляет интереса с точки зрения данной статьи. Существуют по умолчанию два пользователя User1 и User2, логин каждого пользователя осуществляется декларативно со страницы login.vm;


Очевидно, что аутентификация нужна не для всех областей нашего сайта, следовательно, нам необходимо сделать следующее – перед вызовом контроллеров, для которых аутентификация необходима, мы будем проверять пользователя, и в случае если он не аутентифицирован, то перенаправляем запрос на страницу с формой аутентификации.

Для того, что бы реализовать это в АОП нам необходимо сделать следующее:

Созданный аспект приведён ниже:

package aop.example;

import aop.example.model.*; import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException;

/** * Аутентификационный аспект * обеспечивает проверку того что
все контроллеры * нуждающиеся в пользователе
его получат :) * @author Zubairov Renat */ public aspect AuthenticationAspect {

// pointcut определяющий метод где мы
будем отлавливать исключение // если пользователь не аутентифицирован // в нашем случае это метод фильтра public pointcut doFilterMethod
(ServletRequest srequest, ServletResponse sresponse,
EntranceFilter filter) : execution(void aop.example.
EntranceFilter.doFilter(ServletRequest,
ServletResponse, FilterChain)) && args(srequest, sresponse,
FilterChain) && this(filter);

// pointcut определяющий методы для которых
необходима аутентификация // в нашем случае это метод сервлета
ViewServlet doGet // так же захватываем контекст а именно
HttpServletRequest public pointcut authenticationNeeded
(HttpServletRequest request) : execution(* aop.example.ViewServlet.doGet
(HttpServletRequest, HttpServletResponse))
&& args(request, HttpServletResponse);

/** * Advice запускающийся перед методами нуждающимися
в аутентификации * и если пользователь не найдён в сесси, то
выбрасывающий исключение */ before(HttpServletRequest request) :
authenticationNeeded(request) { // В нашем случае только тогда когда
есть хоть один параметр // у сервлета, только тогда необходима
аутентификация if (request.getParameterMap().size() > 0) { if (request.getSession().
getAttribute(EntranceFilter.USER_KEY) == null) { throw new
AuthenticationException("User not logged in"); } if (!(request.getSession().
getAttribute(EntranceFilter.USER_KEY) instanceof User)) { throw new
AuthenticationException
("Anonymous user access denided"); } } }



Прецеденты использования системы


Для примера рассмотрим реализацию простейшего фотоальбома с доступом через WEB.

Как видно из рисунка, в системе существуют пользователи различного типа:

Анонимный пользователь - любой посетитель сайта с фотоальбомом.

Зарегистрированный пользователь – аутентифицированный пользователь системы имеющий регистрационное имя и пароль.



Проблемы


В рассмотренной реализации защиты приложения обычными способами явно видны следующие недостатки:

Реализация защиты очень сильно привязана к конкретному приложению.

Так как код проверок внедряется в код системы то никак нельзя надёжно гарантировать, что все места раскрытия секретных данных окружены такими проверками.

Код с внедрёнными вызовами системы авторизации трудно расширять и тестировать.

Очевидно, что все вышеперечисленные проблемы возникают из-за того, что основная функциональность системы переплетается с функциональностью реализующую защиту. Схожие проблемы всегда существуют при создании сложных информационных систем, и происходит это из-за того, что доступные ООП средства не позволяют нам удобно и просто совмещать пересекающиеся функциональности.



Реализация защиты без АОП


Для того, что бы явно показать преимущества АОП подхода, для начала бегло рассмотрим реализацию защиты приложения обычными способами.



Реализация защиты средствами АОП


Термин аспектно-ориентированное программирование впервые был упомянут в работе Gregor Kiczales et al. "Aspect-oriented programming" в 1997 году (  перевод на русский язык). АОП ставит своей целью разработать механизм реализации сквозной функциональности в ООП системах. АОП не в коей мере не заменяет собой ООП, данная техника всего лишь достраивает концепции ООП. Для реализации примеров статьи использовался AspectJ – реализация АОП для Java. В общих словах АОП предоставляет новый механизм композиции отличный от имеющихся в ООП. Для примера рассмотрим термины, в которых работает AspectJ:

JointPoint – определённая точка в выполнении программы, это может быть выполнение метода, изменение атрибута класса, вызов метода, выбрасывание исключения и т.д.

Pointcut – набор (0..N) точек выполнения программы, например выполнение всех методов начинающихся с «get» классов определённого пакета, или вызов методов классов реализующих некоторый интерфейс.

Advice – java код выполняемый до (before advice), после (after advice) или вместо (around advice) каждой точки выполнения входящих в определённый pointcut.

Aspect – модуль в терминах АОП, аналог класса в Java может содержать публичные/приватные pointcut и advice кроме того обычные методы класса, могут наследоваться и быть абстрактными.

Introduction – метод изменения структуры наследовании и реализаций существующей системы, применяется например для того что бы добавить дополнительный интерфейс к существующему классу, или изменить цепь наследников.

В общем, аспекты добавляют дополнительную функциональность в точки выполнения программы (pointcut) через advice. Важно то, что pointcut могут собирать не только точки выполнения, но и контекст в которых эти точки находятся, например, если pointcut определяет все вызовы методов someMethod всех объектов класса SomeClass, то контекст выполнения это:

Объект, метод которого вызван,

Параметры метода,

Объект, вызвавший метод.



Секретность


Предположим, что у нас существует часть параметров объектов модели, которые не должны быть видимы для определённого типа пользователей, но в тоже время видны для остальных. Для примера рассмотрим тот же самый альбом с фотографиями, но некоторые фотографии в каждом альбоме могут быть помечены как приватные, то есть видны только «друзьям» создателя альбома, в этом случае  получаем следующее взаимодействие:

Как видно, здесь мы опять получили зависимость между слоем отображения и слоем авторизации. Кроме того, опять же необходимо контролировать, что все вызовы к методам возвращающим «секретные» данные должны быть окружены проверками пользователями, что очень трудно проконтролировать (представьте себе какой урон безопасности системы может нанести не окружённый проверками метод).


Усовершенствовав приведенный выше авторизационный  аспект, мы с лёгкостью можем точно разграничить доступ даже к отдельным полям объекта данных, тем самым реализовывать такую систему доступа данным, которая подходит именно для нашего приложения. Достоинства АОП очевидны, т.к. обычно никакого изменения в исходной системе не производится.

Кроме того, AspectJ даёт нам ещё один очень интересный инструмент – ограничения на статическую структуру приложения. Так как внедрение аспектов производится во время компиляции специальным компилятором, то существует возможность создать определённые правила, накладывающие ограничения на структуру и внутреннее взаимодействие объектов системы (статическую структуру). Например, мы можем определить правило - вызовы методов объектов модели приводящих к изменению состояния доступны только из «контроллеров». Или, например, что бы классы модели не были связанны с классами уровня контроллеров. В случае не соблюдения правил AspectJ компилятор может либо просто выдать предупреждение, либо прервать компиляцию с фатальной ошибкой (в зависимости от определения правила).



Ссылки


Приложение описанное в статье с AIP документацией и исходным кодом 

AOP по русски  

Статья Валентина Павлова 

Перевод на русский язык части статьи с первым упоминанием термина АОП 

AspectJ 

Несколько параграфов из книги "AspectJ in action" доступных для свободного скачивания 

AOP blog  

Eclipse бесплатное IDE работающее в том числе и с AspectJ  

AspectJ plugin для Eclipse  



Техническая реализация системы


Система представляет собой простейшее веб-приложение реализованное с использованием паттерна проектирования (design pattern) Модель-Вид-Контроллер (название по GOF). В данном приложении:

Объект пользователь (либо User либо AnonymousUser) храниться в сессии.

В сессии сохранён массив объектов Album, доступный для модификации.

Я использую упрощённую процедуру аутентификации, чтобы уменьшить и упростить код приложения.

Итак, описание системы в терминах Модель-Вид-Контроллер:

Модель

В нашей системе используется следующая модель:

Заметим что, класс User расширяет (наследует) класс AnonymousUser. Каждый альбом (класс Album) имеет атрибут title и ссылку на владельца альбома - класс User, следовательно первое ограничение - анонимный пользователь не может быть владельцем альбома. Альбомы хранятся в объекте AlbumList реализующим методы добавления, удаления и поиска альбомов.

Вид

Отображение осуществляется с помощью   шаблонов. Данный подход позволяет нам разделить отображение от остальных частей системы, кроме того, эти шаблоны, достаточно простые для интуитивного понимания. В приложении используется 2 шаблона:

view.vm – отображает логин текущего пользователя (или Anonymous), ссылки на login/logout, список альбомов, форму для добавления нового альбомa

login.vm – отображает 2 ссылки для регистрации под разными пользователями.

Контроллер

В качестве контроллеров выступают 2 сервлета (я намеренно не использую Struts, так как считаю его использование в подавляющем большинстве случаев антипаттерном).

LoginServlet – сервлет осуществляющий логин пользователя.

ViewServlet – сервлет осуществляет подготовку списка альбомов и обрабатывает добавление/удаление альбома по переданному title.

Кроме того, реализован фильтр (EntranceFilter) который запускается перед каждым запросом.

На данный фильтр возложена обязанность установления пользователя в AnonymousUser если пользователь не найден в сессии, и начальной инициализации массива альбомов.



Терминология


Перед тем как рассматривать методы защиты приложений, разберёмся с терминологией:

Идентификация – процесс определения «личности» (во внутрисистемном понимании) пользователя. В данном случае пользователь, предоставляющий правильное (существующее) регистрационное имя, идентифицирует себя.

Аутентификация – процесс подтверждения того, что пользователь является тем, кем, он себя объявляет (с кем он себя идентифицирует). В нашем случае средство аутентификации – уникальный пароль, который известен только конкретному пользователю.

Авторизация – процесс определения прав пользователя по отношению к объекту внутри системы. В данном случае, очевидно, что даже зарегистрированный пользователь не должен иметь возможность удалить фотографии другого зарегистрированного пользователя.

Специфика WEB приложения работающего через HTTP 1.1 такова, что все аспекты защиты достаточно сложны в реализации. Например, самый казалось бы очевидный аспект – идентификация - совсем не так прост, учитывая отсутствие поддержки состояния соединения в HTTP. Каждый последующий запрос пользователя никак не связан с предыдущим его запросом и для сервера представляется как абсолютно независимый. Даже если мы аутентифицировали пользователя, то необходимо обозначить все запросы этого пользователя к системе. Для этого обычно используются так называемые сессии.



Существует множество способов защиты информационных


Существует множество способов защиты информационных систем, но все реализации строятся на следующих базисных принципах:
Идентификация – совместно с системами аутентификации дают ответ на вопрос кто есть пользователь.
Авторизация – аутентифицированный пользователь может выполнять определённые действия.
Целостность – данные могут быть модифицированы только заранее определённым способом.
Секретность – данные доступны только авторизированным пользователям заранее определённым путём (очень часто идёт подмена терминов секретность и защищённость).
Аудит – система сохраняет отчёт о своей деятельности для дальнейшего анализа.
В данной статье приводится пример простейшего, не защищённого WEB приложения реализованного в терминах MVC, и последовательность применения аспектов для реализации защиты данного приложения. Я уверен, что аспектно-ориентированные техники дают реальную возможность реализовать систему защиты, удовлетворяющую всем принципам, изложенным выше, но при этом оставить аспекты защиты слабо связанными с основной частью системы.

ориентированного программирования на примере реализации


В данной статье я попытался показать достоинства аспектно- ориентированного программирования на примере реализации системы защиты WEB приложения и наглядно продемонстрировать, как можно применять AspectJ на практике.
Итак, АОП при правильном использовании может следующее:
Уменьшить объем кода системы (следовательно, снизить вероятность программных ошибок)
Улучшить дизайн системы с точки зрения реализации сквозной функциональности, улучшить модульность.
Упростить код системы, благодаря локализации кода, не относящегося к основной функциональности.
Упростить тестирования системы (можно тестировать различные аспекты отдельно, а только потом вплетённые в систему). Улучшить управляемость кода, как следствие простота эволюции и сопровождения.
Увеличить количество повторно используемых модулей благодаря слабой связности подсистем.
С другой стороны не правильное применение АОП может привести к следующим последствиям:
Затруднения в ходе отладки (Какой из аспектов выполняется сейчас?)
Трудности с пониманием концепции зачастую приводят к грубым ошибкам в дизайне аспектов.
Новая технология всегда риск.

Запуск системы


Приложение можно взять  . Для того что бы запустить приложение необходимо распаковать архив и запустить выполняемый jar архив startme.jar: java -jar startme.jar после чего запуститься Servlet Container jetty. Приложение доступно с помошью любого веб браузера по адресу http://localhost:<port>/ где <port> это 8080 или первый свободный порт после 8080. По умолчанию приложение сконфигурированно со всеми описанными аспектами, но кроме того, в папке configurations располагаются различные версии системы:

allaspects.jar - система со всеми аспектами (уже скопирован в WEB-INF/lib).

auditaspect.jar - только аспект аудита.

authentaspect.jar - только аутентификационный аспект.

authoraspectpart.jar - только пост-проверка в авторизационном аспекте.

authoraspectfull.jar - пост и пред-проверка в авторизационном аспекте.

noaspects.jar - исходная система без аспектов.

Для того что бы запустить одну из версий системы необходимо скопировать один из вышеперечисленных jar файлов в дирректории WEB-INF/lib (по умолчанию в WEB-INF/lib скопирован allaspects.jar)



Application Server & ONE Studio: аппаратные требования


Несмотря на усилия Sun, разработка Java-приложений значительно более требовательна к аппаратуре, чем разработка приложений на, скажем, С. Поэтому, прежде чем даже начать устанавливать у себя на компьютере средства для построения Java-приложений, вам следует убедиться в том, что ваша система удовлетворяет минимальным требованиям к аппаратуре. Следует отметить: указанные величины не относятся к документации Sun, а представляют собой результат наших собственных экспериментов.

Итак, для разработки вам понадобится компьютер примерно со следующими характеристиками: процессор не хуже Celeron 500, памяти не менее 256 Мб и около 500 Мб дискового пространства. Настойчиво рекомендовать можно такие параметры: процессор около P4/Athlon 1800, 512 Мб оперативной памяти, 1 Гб на винчестере.

Для выполнения полученных программ достаточно станции с процессором класса Celeron 300, 64 Мб памяти и для сетевых возможностей, естественно,- сетевое подключение.

Большой экран (19-21") тоже можно отнести к крайне желательным инструментам Java-разработчика.



AWT


(Abstract Windows Toolkit) - устаревшая библиотека классов (использовалась в JDK 1.0 и 1.1) для создания пользовательского интерфейса, содержащая зависимый от платформы код. Используется для создания оконных приложений для определенной платформы. Хотя допускается совместное использование AWT- и Swing-компонент на одной форме - но такое приложение будет занимать больше места, использовать системно-зависимые ресурсы и может вызывать визуальные помехи во время отображения при разработке или выполнении формы.



DART


(s) - технология, лежащая в основе инструментов серии ONE. Сокращение обозначает Data, Applications, Reports, Transactions - те сущности, которыми оперирует разработчик в процессе построения приложений.



EJB


(Enterprise Java Beans) - компоненты, реализующие высокоуровневые сервисы уровня предприятия.



J2EE


(Java 2 Enterprise Edition) - абстрактная спецификация и реализация в отдельных продуктах стандартного набора функций, необходимых для построения приложений. J2EE включает в себя JSP, Java Servlets и EJB. Для проверки совместимости продукта с J2EE служит J2EE CTS.



J2EE CTS


(Compatibility Test Suite) - тест на совместимость с J2EE, проходимый серверами приложений и инструментами разработки. Критериям совместимости, кроме собственно Sun Application Server, удовлетворяют Borland Enterprise Server, IBM WebSphere Application Server, Macromedia JRun, Oracle Application Server 9i и другие известные продукты.



Java 2: второй шанс вавилонской башни


Арсений Чеботарев


Существует не один способ написания универсальной программы, которая работала бы на различных платформах, таких как Windows, BSD, Linux и пр. Но, тем не менее, существует, и уже давно, один совершенно естественный для таких приложений подход - использование языка Java.

Язык Java появился на 10 лет раньше своего времени, и 9 из них уже прошло. Те, кто еще пару лет назад скептически относился к Java, сегодня должны пересмотреть свое мнение. Есть несколько причин, по которым это следует сделать:

мощность компьютеров стала достаточной для эффективного воспроизведения приложений Java. Особенно важным фактором является снижение стоимости оперативной памяти, так как виртуальная машина всегда была требовательна в этом отношении;

доступность "быстрого интернета". Выделенное подключение станет в ближайшее время скорее нормой, чем исключением, так что приложения с характерным для Java размером около 100 Кб могут быть загружены в течение нескольких секунд. Этот же фактор является ключевым для доступа к удаленным базам данных;

Java прогрессирует - Java 2 в версии 1.4 делает успехи в направлении быстродействия. В частности, в ядро виртуальной машины встроены средства компиляции в машинный код на основании профиля использования классов, т. е. компилятор оптимизирует только там, где нужно. Постоянной переработке подвергается также графическая библиотека, традиционно вызывающая перегрузку сетевого трафика;

с появлением Java 2 и расширений Enterprise Edition технология Java приобрела свойства стабильной промышленной платформы, а не просто средства для создания анимационных аплетов. Новые классы позволяют интегрировать Java-приложения и распределенные приложения через интерфейсы JRMI и CORBA и создавать надежные веб-сервисы, совместимые с другими платформами;

появились средства быстрой разработки приложений. Говоря точнее, эти средства, существовавшие уже давно, достигли того уровня, который делает количественные изменения качественными;



Java Applets


Приложения на Java, построенные для выполнения на клиентском браузере. Должны соответствовать определенным правилам относительно запуска и прекращения работы и могут использовать ограниченные ресурсы клиентской машины.



Java SDK, Source Development Kit


(старое название JDK - Java Development Kit). Набор компиляторов командной строки для компиляции исходных текстов Java в файлы классов. Содержит также дополнительные утилиты - например, для автоматической генерации прототипов классов на основании файлов IDL.



Java Servlets


По аналогии с Applets, Java Servlets предназначены для работы на стороне веб-сервера под управлением сервера приложений. Служат главным строительным компонентом для построения веб-приложений. В основе сервлета - два предопределенных метода, DoPost и DoGet, вызываемых сервером приложений для генерации динамических веб-страниц.



Java vs. J#: не впадите в заблуждение


Некоторые начинающие программисты, желающие освоить Java, задают себе вопрос: а не лучше ли изучать J#, входящий в Visual Studio NET? ОСТОРОЖНО! Язык J# не имеет ничего общего с Java, кроме чисто внешнего сходства. В частности:

код J# не работает на виртуальной Java-машине, работая вместо этого на виртуальной машине NET. Это значит, что ваши программы смогут работать только на компьютерах под Windows 2000/XP с установленным пакетом NET. На всех остальных системах ваши программы работать не будут!

код J# не использует стандартные Java-классы, используя вместо этого классы NET. Хотя классы NET достаточно точно воспроизводят функциональность стандартной и дополнительных "хорошо известных" библиотек Java, но это совсем другие библиотеки, абсолютно не совместимые с JVM;

J# работает с базами данных не посредством JDBC, а через интерфейс ADO. Как следствие - вы не сможете легко интегрировать свои приложения с другими приложениями, использующими источники данных JDBC, например написанные на Cold Fusion.

Таким образом, знание J# никак не делает вас экспертом или хотя бы новичком в настоящем Java. Аналогично обстоит дело с C#, perl# и другими живущими в NET языками - они не имеют ничего общего с реальными прототипами, кроме, повторюсь, внешнего сходства.



JB


(Java Beans) - классы Java, по соглашению реализующие несколько предопределенных методов. Цель последних - предоставлять информацию о классе и поддерживаемых интерфейсах на этапе разработки и во время выполнения. Функциональность Java Beans во многом аналогична компонентам ActiveX и компонентам Delphi (JB созданы не без влияния последних) и служит тем же целям - использованию классов Java совместно с инструментами быстрой разработки приложений. Спецификация Java Beans выработана на основании совместных рекомендаций ряда компаний, в частности Sun, IBM, Borland, Microsoft.



JFC


(Java Foundation Classes) - группа библиотек для создания пользовательского интерфейса. Включает Swing, 2D и plug-and-play.



JRE


(Java Runtime Engine) - в основном, то же самое, что и JVM.



JSP


(Java Server Pages) - метод, язык и объектная среда построения веб-приложений, подобная ASP и PHP.



JVM


(Java Virtual Machine) - часть программного кода, обеспечивающего базовую функциональность программ Java. Основные функции: загрузка и интерпретация откомпилированных Java-классов, распределение системных ресурсов, таких как память и процессорный ресурс, между классами. Виртуальная машина также включает базовый набор классов, доступных каждому приложению. Таким образом, пользовательские приложения могут сразу использовать ввод-вывод, сетевые возможности, встроенные коллекции и графический интерфейс, сокращая размер собственного кода.



ONE


(Open Net Environment) - набор соглашений, технологий, инструментов и философия фирмы Sun для построения систем доставки информации и приложений по запросу, технология Services on Demand.



OS Solaris


Основной принцип Java - переносимость, то есть работает он на максимуме платформ. И, тем не менее, основной серверной платформой для серверов Java является OS Solaris. Являясь клоном Unix System V, эта система продолжительное время находится в самостоятельном плавании - так что многие вещи уже значительно усовершенствованы (или, по крайней мере, видоизменены). На этой платформе начали свое существование RPC и NFS - две технологии, получившие впоследствии широкое распространение и в некоторой мере предопределившие лицо Java.

К сожалению, политика Sun в отношении аппаратной платформы Intel не отличается последовательностью. Одно время Solaris for Intel была полноправным продуктом - причем бесплатным. Впоследствии платформа Intel подверглась дискриминации как "недостаточно надежная и производительная", в результате чего версия SFI 9 является "не самым оптимизированным" вариантом.

В любом случае бинарные коды доступны по условной цене, а стоимость настоящих лицензий, зависящая от количестве процессоров, вполне приемлема (на момент подготовки статьи лицензия рабочей группы для 2-процессорного сервера стоила всего $270 - сопоставимо с лицензией Windows 2000 Professional для одного пользователя).

В ближайшее время мы станем свидетелями драматического столкновения Itanium с платформой Sun SPARC. На стороне Itanium такие гранды, как IBM и HP. То, что Sun понесет потери, это ясно, но насколько критическими эти потери станут для платформы - это вопрос. С другой стороны, поскольку IBM является адептом Java, то судьба языка не зависит от того, в какую сторону качнется маятник фортуны в данном случае.



Project Swing


Библиотека классов, обеспечивающая независимый от операционной системы графический интерфейс, с возможностью изменения внешнего вида элементов управления. Swing используется также для написания интерфейса к апплетам. Swing написан полностью на Java с использованием JDK1.1 Lightweight UI Framework. Во всех новых разработках рекомендуется использовать только компоненты Swing и избегать использования AWT.



Опыт использования показал, что Java


Опыт использования показал, что Java ONE Studio и другие компоненты технологии Java являются инструментами повышенной сложности с серьезными требованиями как к компьютеру, так и к разработчику. Но, единожды освоив эти возможности, вы создадите себе мощный плацдарм для успеха в бизнесе и профессионального роста, поскольку время Java уже наступает.

Sun Application Server


Application Server представляет собой центральный программный компонент, работающий как веб-сервер и дополнительно выполняющий код, генерирующий динамические страницы и реализующий сервисы на основе Java.

Существует три уровня Application Server - в зависимости от предполагаемого использования:

Platform Edition - бесплатная версия, полностью совместимая с J2EE, то есть поддерживающая JSP, Java Servlet и EJB. Включает в себя веб-сервер. Кроме того, содержит все необходимое для создания веб-сервисов (в частности, JAXM, JAXP, JAXR, JAX-RPC, SOAP и WSDL). Application Server плотно интегрирован с Sun ONE Studio для разработки и тестирования web приложений;

Standard Edition - в дополнение к Platform Edition содержит инструментарий для удаленного администрирования нескольких серверов;

Enterprise Edition - добавляет Always-On технологию для повышенной устойчивости сервера и дополнительной масштабируемости в критических приложениях. В основе этой запатентованной технологии - хранение состояния сессий отдельно от бизнес-логики в распределенных устойчивых хранилищах и быстрое восстановление сессий в случае сбоя части кластерной системы.



Sun ONE Studio


Несмотря на наличие мощных Java2-совместимых конкурентов, первичным инструментом в мире Java является сочетание Sun Application Server и собственной оболочки Sun Microsystems Sun ONE Studio. Эта оболочка первоначально выпускалась под именем Forte for Java компанией NetBeans.com и была приобретена Sun для бесплатного распространения (siс. приобретена для бесплатного распространения?!).

Сама "Студия" содержит, в основном, все знакомые вам современные возможности, присущие визуальным средам разработки - такие как синхронные текстовый и визуальный редакторы, редактор свойств, палитры компонент, сенситивные автоподстановки и контекстный help.

В отличие от большинства известных средств разработки, IDE ONE Studio работает асинхронно - после сборки и запуска приложения (класса) среда разработки снова доступна для редактирования и запуска других классов. Таким образом можно отлаживать работу целой группы взаимодействующих классов, что, как правило, и требуется.

ONE Studio также использует несколько полезных технологий, распространенных в мире открытых систем. В частности, для сборки используется процессор сборки Ant вместо традиционного make. Сам Ant не является новшеством, но на это придется обратить внимание, поскольку он использует структурированную XML-запись вместо привычного текстового описания правил компиляции.


Другое применение открытых технологий касается поддержки групповой работы над проектами через системы контроля версий, таких как CVS и VCS,- так что уже при создании проекта в него можно сразу заложить возможности синхронизации и контроля версий.



Многозадачная Java: наступление на системном фронте


Арсений Чеботарев.
Компьютеры+Программы

Идея Java OS может стать реальностью уже в ближайшем будущем. Благодаря нескольким новым интерфейсам и изменениям в виртуальной машине, разрабатываемым в рамках проекта Barcelona.

До сих пор Java была изначально отделена от операционной системы с помощью Java Runtime. В результате этот язык программирования обладал многими полезными свойствами — но не теми, которые делали бы его пригодным для системного программирования. В частности, не предусматривалось никаких встроенных средств управления ресурсами. Это препятствовало реализации такой давно вынашиваемой идее, как полностью самостоятельная операционная среда Java OS. Для выполнения системных функций разработчику приходилось пользоваться обычным в таких случаях языком С — и потом возвращаться в код Java. И хотя в Java изначально включена совместимость с C и это взаимодействие давно не вызывает проблем, тем не менее у многих переход в Native Code вызывал ощущение, будто что-то не так, как должно быть. Функции управления заданиями могли б быть реализованы средствами Java — при условии поддержки их со стороны системы времени выполнения.

Вторым мотивом для введения многопоточности стало распространение параллельного выполнения нескольких Java-приложений в нескольких копиях виртуальной машины. При этом виртуальные машины не оптимизировали использование памяти и тактов процессора. В частности, каждая из VM отдельно от остальных разбирала каждый из пакетов, в том числе стандартных,— и таким образом значительная часть работы дублировалась.

В результате для решения поставленных задач и вновь возникающих проблем, связанных с масштабируемостью и надежностью, был создан Barcelona Project. В кратком описании этого проекта явно сформулирована его цель: превратить Java в полноценную операционную среду. Метод: прозрачное разделение метаданных (системных данных) между приложениями.

Первоначально было решено не менять существующую виртуальную машину в корне, но дополнить ее модулем, который, по замыслу, должен отвечать за совместно используемые ресурсы и эффективно кэшировать их. Каждая запускаемая копия виртуальной машины загружала очередную порцию классов в общую область — и таким образом эффект кэширования для каждого следующего экземпляра VM проявлялся все лучше. На следующем этапе были переработаны основные библиотеки, в которых были выделены реентерабельные блоки кода "только для чтения".

Часть данных, составляющая состояние приложения, которая не может быть разделена между приложениями, включает глобальные переменные, которые в терминах Java представлены статическими членами системных и пользовательских классов. Код — как данные только для чтения — по определению поддается повторному использованию, а значения динамических членов не нуждаются в реентерабельности.

Прирост быстродействия и экономии памяти для обоих подходов составил около 10%, однако главная цель не была достигнута, поскольку основное время при запуске приложения составляет запуск самого Java Runtime. То есть, запуская новые виртуальные машины, никогда не удастся достичь желаемой гибкости, особенно для небольших приложений, таких как утилиты командной строки. Поэтому было решено вынести Runtime в категорию (квази-)резидентных в памяти программ (демонов, сервисов) и сделать Java многопоточной и реентерабельной средой для множества приложений. Таким образом, JRE запускается только один раз (возможно — только при запуске компьютера), и накладных расходов типа "CreateProcess" при запуске отдельных приложений, в том числе и первого, не возникает.

На данном этапе существует прототип многопоточной виртуальной машины, MVM, основанной на Hot Spot Virtual Machine и соответствующем компиляторе. Основным принципом построения новой машины, по словам Гжегожа Чайковски (Grzegorz Czaikowski), руководителя проекта Barcelona, стала проверка всех компонент Java VM на предмет разделения функциональности между приложениями. В результате каждая выполняемая программа требует только ограниченных частных ресурсов, связанных с состоянием приложения. Большинство кода и метаданных теперь обслуживают все приложения одновременно. В частности, удалось сделать реентерабельными в рамках многопоточной виртуальной машины стандартные библиотеки классов, которые используют все без исключения приложения.

В результате скорость запуска приложений возросла на 60…90% — и для большинства приложений, в том числе и графических, составляет теперь около одной секунды. Стало возможным даже реализовать такие системные утилиты, как ls или grep,— раньше этому препятствовало значительное время запуска, которое сводило на нет преимущества эффективности таких команд. Оперативная память, занимаемая приложением, сократилась в полтора-два раза за счет повторного использования стандартных модулей. Когда одно приложение требует модуль, то он уже, как правило, загружен и находится в кэше виртуальной машины.

Наблюдается также рост производительности самих приложений. Причина — выполнение всех приложений в одном процессе, в результате чего переключение между приложениями не вызывает системного переключения контекста. Например, приложение баз данных (реализованное на 100% Java) и сервер приложений, работающие в одном процессе MVM, показали 11-процентный прирост пропускной способности обмена данными и на 36% лучшее время реакции на запросы.

Безусловно, объединяя приложения в одной виртуальной машине, необходимо обеспечить их "гальваническую развязку", то есть гарантировать неприкосновенность их адресных пространств. В MVM роль такого защитного контейнера выполняет изолятор — экземпляр специального типа Isolate. Это контейнер для приложений, позволяющий не только гарантировать частное адресное пространство, но и манипулировать приложением, мониторить его ресурсы и обеспечивать чистое завершение приложения. Класс Isolate является частью Application Isolation API Spec, выдвинутой на форуме Java Community Process (www.jcp.org/en/jsr/detail?id=121).

Что еще важно — менеджер загрузки модулей будет использовать изоляторы прозрачно для существующих приложений, так что они не потребуют модификации. Собственно, изоляция не подразумевает ограничений в плане реализации и может быть представлена как в мультипотоковой виртуальной машине, так и средствами нескольких VM.

Важное следствие из применения изоляторов — те полезные ограничения, которые налагает изоляция на взаимодействие приложений. В частности, изоляция обеспечивает взаимодействие процессов только средствами сокетов, файлов или JRI — то есть теми методами, которые могут быть легко спроектированы на кластерные архитектуры. Фактически, модульное приложение, использующее два и больше изоляторов, будет даже на одном компьютере работать так же, как если бы это было распределенное сетевое окружение.

Еще одним важным системным интерфейсом, служащим для мониторинга ресурсов, обещает стать Resource Management (RM) Interface. Этот основанный на изоляторах механизм позволяет гибко создавать и опрашивать счетчики системных ресурсов — от таких низкоуровневых, как процессорное время, до высокоуровневых, типа подключения к серверу приложений или баз данных. Как и в случае с изоляцией, менеджмент ресурсов легко спроектировать на сетевое окружение.

Новая виртуальная машина не только сможет обслуживать несколько приложений одновременно — вполне возможно также, что эти приложения будут выполняться под различными пользовательскими эккаунтами. В результате JVM приобретает свойства самостоятельной среды выполнения, обладающей средствами для выполнения множества приложений в многопользовательской среде.

Новая MVM была опробована как на мобильных терминалах, где экономия памяти является важнейшей задачей (хотя и отсутствуют жесткие конкурентные условия для приложений), так и на сервере приложений J2EE, который, как правило, и запускался в нескольких экземплярах для обеспечения развязки нескольких приложений. Оба теста показали эффективность MVM, причем без модификации основного кода — путем прозрачного использования изоляторов. Реализация MVM для J2SE остается под вопросом — эта среда не ориентирована на выполнение нескольких приложений или, по крайней мере, не в такой степени, как сервер приложений.

Все это может иметь значительные последствия для применения Java вообще. Если раньше средний пользователь характеризовал приложения Java как "медленные" и "расходующие ресурсы", то теперь эти характеристики уже не будут играть негативной роли. Особенно субъективно важным и значительно улучшенным параметром является время запуска приложения. Для администраторов должна показаться привлекательной возможность управлять многими приложениями и осуществлять аудит ресурсов из одной точки. А программисты к тому же получат выгоды от новых интерфейсов, которые еще больше приближают Java к выполнению решающей роли в мире вычислений.

Ссылки по теме:



Контекстное меню для Netscape Navigator и Internet Explorer


,

Почему-то на сайтах, посвященных программированию на JavaScript, традиционно считается, что создать контекстное меню для Netscape Navigator невозможно, так как Netscape по щелчку правой клавишей мыши создает собственное всплывающее меню. Попробуем развеять это заблуждение. Дело в том, что выпадающее меню в Netscape Navigator 7.1 и выше версии (ниже версии нет в наличии - если код корректно работает в версии Netscape ниже 7.1 - пожалуйста напишите мне), можно отключить с помощью знакомой инструкции:

<body oncontextmenu="return false;">

Но как же создать выпадающее меню, если в Netscape следующий код (работающий в Internet Explorer):

<body oncontextmenu="myfunction(); return false;">

не работает, то есть - функция вызывается, но в Nescape Navigator возникает выпадающее меню, поверх создаваемого в функции myfunction? Как же обойти это ограничение? Я нашел такое решение:

<body oncontextmenu="return myfunction()">

Как вы вполне можете догадаться, функция "myfunction()" в конце своего исполнения возвращает результат - return false, что с точки зрения Netscape Navigator считается корректным. Но только вот такой код не считается корректным у Internet Explorer. Что поделаешь, война браузеров! Придется воспользоваться таким интересным приемом, как условная трансляция (надеюсь, что подобрал словосочетание правильное, по аналогии с условной компиляцией в Си) браузера Internet Explorer. Дело в том, что Microsoft © позаботилась о создании специальных конструкций, которые для остальных браузеров выглядят как комментарий. С помощью этих конструкций можно создать достаточно сложные ветвления кода. Попробую создать ветвление кода на основе этих самых конструкций.

<script language="JavaScript" type="text/javascript"> <!--

/*@cc_on @if( true ) document.write( "<body oncontextmenu=\"myfunction(); return false;\">" ); @else*/ document.write( "<body oncontextmenu=\"return myfunction();\">" ); /*@end @cc_off @*/


//--> </script>

Разберу первую часть скрипта подробно. Вначале определяется тип браузера пользователя, эта часть нам не интересна - такие скрипты можно найти в Internet повсюду. Для начинающих лучше всего воспользоваться редактором с подсветкой синтаксиса. Итак, первая интересующая нас инструкция: "/*@cc_on" - это инструкция начала "условной трансляции". Обратить внимание стоит только на ее начало - "/*" - это обычный комментарий. Internet Explorer выше пятой версии разбирает даже комментарии, и ищет директивы "условной трансляции" (большой брат следит за тобой!). Теперь присмотримся к следующей строке. Тут есть, на первый взгляд, странная строка: "@if( true )" - условие, которое выполнится в любом случае! Необходимость этого условия не кажется очевидной, но на самом деле все очень просто. Если Internet Explorer прочтет эту инструкцию, он выполнит код, который предназначен только ему одному, а строку идущую после директивы "@else*/" пропустит. А теперь предположим, что условия "@if( true ) ... @else" нет - что получится? Internet Explorer выполнит сразу две инструкции - и мы получим в документе сразу два <body> с разными параметрами! Не очень приятная перспектива. Последняя инструкция @cc_off @*/ завершает "условную трансляцию". Таким образом, мы получили переносимый код, подходящий для браузеров на основе движка Internet Explorer (Avant, Maxthon, etc...) и движка Gecko (Netscape Navigator, Mozilla, Mozilla Firefox, Mozilla Firebird, etc...).

Наша задача создать не красивый, а понятный код, украсить его вы можете сами! Создаю примитивный лист стилей:

<style type="text/css"> <!-- .hidemenu { display: none; position: absolute; } .showmenu { display: block; position: absolute; } //--> </style>

Он отвечает только за отображение и сокрытие контекстного меню, что нам собственно и надо. Теперь надо определить координаты курсора в момент щелчка правой клавиши мыши по документу. Основная сложность заключается в том, что для Netscape Navigator возникает такая проблема, как обработчик события event.onmousedown. Он перекрывает обработчик, заданный в body (в общем-то вполне логично, поскольку event задается относительно окна, а не документа, как в body). И опять возникает ненавистное браузерное контекстное меню. Я решил эту проблему таким образом:



if( document.all ) {

} else if( document.getElementById ) { document.captureEvents( Event.MOUSEMOVE ); document.onmousemove = getCoord; }

Я стал отслеживать каждое передвижение мыши на экране и записывать координаты курсора в глобальные переменные:

var mouseX; var mouseY;

function getCoord( event ) { mouseX = event.pageX; mouseY = event.pageY; }

Теперь соберу саму функцию отображения меню:

function myfunction() { if( document.all ) { if( event.button == 2 event.button == 3 ) { if( !document.all.contextmenu ) return; var menu = document.all.contextmenu; menu.style.left = event.offsetX; menu.style.top = event.offsetY; menu.className = menuState ? "hidemenu" : "showmenu"; menuState = !menuState; } } else if ( document.getElementById ) { if( !document.getElementById( "contextmenu" ) ) return; var menu = document.getElementById( "contextmenu" ); menu.style.left = mouseX; menu.style.top = mouseY; menu.className = menuState ? "hidemenu" : "showmenu"; menuState = !menuState; return false; } }

Чтобы разобраться в Функции, не требуется особых навыков в JavaScript. Устанавливается положение меню на основе координат курсора с ветвлением на два браузера. Разница лишь в том, что Internet Explorer получает координаты из события event, а Netscape Navigator из глобальных переменных. Если вы правильно собрали скрипт - у вас должно получиться нечто подобное:

<html> <head> <meta http-equiv="Content-type" content="text/html; charset=Windows-1251"> <title>Context menu</title> <style type="text/css"> <!-- .hidemenu { display: none; position: absolute; } .showmenu { display: block; position: absolute; } //--> </style>

<script language="JavaScript" type="text/javascript"> <!-- if( document.all ) { document.onmousedown = myfunction; } else if( document.getElementById ) { document.captureEvents( Event.MOUSEMOVE ); document.onmousemove = getCoord; }



var mouseX; var mouseY; var menuState = false;

function getCoord( event ) { mouseX = event.pageX; mouseY = event.pageY; }

function myfunction() { if( document.all ) { if( event.button == 2 event.button == 3 ) { if( !document.all.contextmenu ) return; var menu = document.all.contextmenu; menu.style.left = event.offsetX; menu.style.top = event.offsetY; menu.className = menuState ? "hidemenu" : "showmenu"; menuState = !menuState; } } else if ( document.getElementById ) { if( !document.getElementById( "contextmenu" ) ) return; var menu = document.getElementById( "contextmenu" ); menu.style.left = mouseX; menu.style.top = mouseY; menu.className = menuState ? "hidemenu" : "showmenu"; menuState = !menuState; return false; } }

//--> </script>

<script language="JavaScript" type="text/javascript"> <!-- /*@cc_on @if( true ) document.write( "<body oncontextmenu=\"myfunction(); return false;\">" ); @else*/ document.write( "<body oncontextmenu=\"return myfunction();\">" ); /*@end @cc_off @*/ //--> </script>

<div id="contextmenu" class="hidemenu" style="background-color: Yellow;"> <span><a href="javascript: void( 0 )">Первый пункт меню</a></span><br> <span><a href="javascript: void( 0 )">Второй пункт меню</a></span><br> <span><a href="javascript: void( 0 )">Третий пункт меню</a></span> </div>

</body> </html>

Для тех же, кто желает "погорячее", привожу код, полностью основанный на "условной трансляции" Microsoft ©. Изложенного выше материала вполне достаточно, чтобы разобраться в нем самому.

<html> <head> <meta http-equiv="Content-type" content="text/html; charset=Windows-1251"> <title>Context menu</title> <style type="text/css"> <!-- .hidemenu { display: none; position: absolute; } .showmenu { display: block; position: absolute; } //--> </style>


Java

Img1.shtml


Рис. 1. Легкий дизайн, всего 2,8 кБ текста на странице...



Img2.shtml


Рис. 2. ...и 95 кБ в результате! Как видим, «КПД» такой страницы - около 3%.



Img3.shtml


Рис. 3. Текстовый контент этой страницы - меньше 2 кБ.



Img4.shtml


Рис. 4. Пока мы догрузим эти новости - они будут позавчерашними.



Img5.shtml


Рис. 5. Та же страница, что на , после упаковки.



JavaScript: создаем Человека


Прокомментировать статью можно

Умудренный опытом читатель, прочитав заголовок статьи, сразу, конечно, поймет, что это - не больше, чем шутка и речь дальше пойдет о какой-нибудь программке на JavaScript, которая поможет вам получить еще одного посетителя на сайт, или создаст интерфейс "с человеческим лицом", или...

Что я могу ответить такому проницательному читателю? Только одно: вы ошиблись! Именно созданием человека мы сегодня займемся - в самом что ни на есть прямом и нешутейном смысле этого выражения. Не больше, но и не меньше. Конечно, реализация мечты средневековых алхимиков о гомункулюсе - не самая простая программистская задача. Но, согласитесь, скучно заниматься мелочами...

Кому и верить в чудо, как не программисту? Каждый раз, когда мы пишем новую программу - мы создаем новый мир. А сегодня я обещаю научить вас настоящему волшебству: из обрывков программного кода и пригоршни медной проволоки мы сотворим реального человека. Настоящего, живого, дышащего и мыслящего человека, который до этого никогда не жил на Земле и который появится из ниоткуда по нашей воле. Хотите? Нет ничего проще! Все, что от нас потребуется - вера в свои силы, пару строк заклинаний на JavaScript и полчаса работы. Итак, приступим…

Позвольте мне начать немного издалека. Сначала сказанное ниже вам может показаться лирическим отступлением, но потом вы увидите, что на самом деле это - постановка задачи.

Итак, скажите, пожалуйста, какие ассоциации возникают у вас в связи со словом "платить"? Рубли, доллары, фунты? Тогда вы, к сожалению, ошиблись еще раз. В действительности, получая что-либо, мы всегда рассчитываемся за приобретение своей жизнью. Если мы неделю (месяц, год) зарабатывали на цифровую фотокамеру, потом пошли в магазин и вышли оттуда с покупкой в дрожащих от счастья руках - это значит, что неделю (месяц, год) своей жизни мы обменяли на возможность орать: "сейчас вылетит птичка!". Покупая, беря в кредит, обменивая любой товар, мы платим за него определенным количеством часов своей жизни. То же самое можно сказать по-другому: мы уменьшаем продолжительность своей жизни в обмен на приобретенные вещи. Вместо того, чтобы пойти в лес, на пляж, почитать хорошую книгу, мы тратим время на зарабатывание денег, то есть, попросту - добровольно вычеркиваем из жизни оговоренное количество дней, за что нам потом дадут новый компьютер, телевизор или квартиру...

Согласны, что ситуация катастрофична? Тогда давайте искать из нее выход.

Для этого разрешите мне нарисовать еще одну картинку (уже не столь апокалипсическую). Что происходит, когда мы открываем страницу какого-нибудь сайта? Если вы на минуту задумаетесь, то поймете, что при этом случаются вещи удивительные. Давайте представим: вы сидите за компьютером с быстродействием в сотни миллионов операций в секунду, который через модем прикручен к Интернету. Вы набрали в браузере нужный адрес и стартовали загрузку страницы. "Пожар!" - кричит ваш компьютер на всю сеть. - "На помощь!".

Призыв не остается без ответа. Мордатые двухметровые плечистые пожарные - вэб-серверы, маршрутизаторы, роутеры и прокси - сбегаются на зов, выстраиваются друг за другом, и... начинают передавать по цепочке наперстки воды. Да если бы наперстки!.. Если сравнить сотни миллионов операций в секунду с обычной скоростью модема (4-12 КБайт/с), то можно сказать, что между получением соседних байтов для компьютера проходят целые геологические эпохи. А пользователь, сидящий у монитора, отдает каждый раз тридцать секунд, а то и пять минут своей жизни в ожидании загрузки.

Конечно, вы уже поняли: в наших силах продлить жизнь всех пользователей вашего сайта, ускорив загрузку страниц. Представляете, что будет, если нам удастся сэкономить в среднем по пять секунд на клик на сайте со средним числом хитов - скажем, 15000 в сутки? Тем самым мы с вами вернем человечеству больше, чем двадцать четыре часа времени осмысленной жизни в течение суток. Понимаете, что это значит? Мы вызовем к жизни еще одного жителя Земли! Будем верить, что новый человек, которого мы с вами сотворим таким образом, не окажется крэкером ;) ...

И теперь, когда вы осознали все величие стоящей перед нами цели, давайте, наконец, закончим вступление и приступим к решению этой благородной задачи. Раз скорость канала от нас не зависит, остается единственный и очевидный путь: уменьшать вес страницы. Тем самым мы, кстати, вдвойне поможем обладателям платного трафика: они будут не только меньше ждать у монитора, но и быстрее зарабатывать на просмотр того же количества страниц сайта. Прямая выгода от этого и нам: при размещении сайта на виртуальном хостинге мы всегда сталкиваемся с такой неприятной штукой, как ограничение на количество одновременно выполняемых процессов. Это значит, что, пока, например, пять юзеров радостно утаскивают ваши страницы к себе домой, остальные в лучшем случае "подвисают" в очереди, а в худшем - получают сообщение об ошибке. И уменьшая вес страницы, мы тем самым уменьшаем вероятность подобной ситуации.

Итак, что мы можем? Первый и очевидный шаг: ругаемся с дизайнером и выбрасываем всю его графику неземной красоты и непомерного веса. Оставляем простой дизайн, легкие кнопочки и маленькие иконки. Но вот все это сделано. Остается почти голый HTML. На и показана страница с «минималистским» дизайном - вид снаружи и изнутри. Как видно, если ваш сайт - это не простейшая галерея, а, скажем, форум, архив статей или любой другой вариант, где преобладает текстовый контент, и/или вы используете "навороченный" движок, то сам HTML без графики тянет тоже немало - до 500Кб.


Особенно плохо обстоят дела на новостных порталах. Сопоставьте, к примеру, объем отображаемого текста - меньше 2кБ () с весом загружаемой страницы - 165 кБ только html-кода () для далеко не самого перегруженного портала .

Что можно предпринять? Конечно, обязательно паковать, по каналу гнать сжатый HTML, и распаковывать его на конечном компьютере! Вопрос только, как это делать и чем.

Если ваш стаж в сайтостроении больше пяти лет, то вы не можете не помнить энтузиазма, с которым было встречено появление XML. Основное преимущество этого языка всеми мыслилось в том, что он способен радикально уменьшить трафик, переложив основную часть обработки и форматирования информации с сервера на компьютер клиента.

И вот XML уже применяется на полную катушку. Да только одна незадача: если почитать последние статьи по этому языку, в каждой из них мы наткнемся на довольно неожиданный рефрен: "XML - это великий и могучий язык обработки и форматирования данных, это - один свет в окошке, рай земной и коммунизм в одном флаконе; но, естественно(?!), платой за его неизмеримые достоинства является здорово увеличенный(!) по сравнению с HTML размер результирующего файла." Грустно... Что же, не будем опускать руки. Есть ведь и другой способ: многие современные браузеры умеют работать с упакованными файлами. В числе других заголовков http-запроса браузер посылает серверу и список форматов компрессоров, которые он понимает.

Но тут опять не все так просто. Во-первых, разные браузеры поддерживают разные форматы (а то и вообще никаких). А во-вторых, если вы достаточно легко можете уговорить свой Apache эти файлы не перекодировать, то, пройдя через прокси, они, как правило, все равно превращаются во что-то, мягко говоря, своеобычное. Стало быть, этого делать нельзя: своими руками отсекать 10-15% потенциальных посетителей сайта - так же непрофессионально и по-детски, как писать сайты, корректно работающие только под Internet Explorer-ом.

Выходит, и тут мимо. Что же, не будем ждать милости от софтверных монстров и посмотрим, что можем сделать мы сами.

Есть среди используемых в сайтостроении языков один невзрачный и непритязательный. Да еще и в почтенном возрасте. Но именно к нему мы обратимся за помощью. В отличие от XML, язык JavaScript, предназначенный для обработки информации на стороне клиента, не обещает приносить вам тапочки и кофе в постель и писать за вас сайты в полпинка, но зато и в самом деле делает то, для чего был придуман. А значит, нужен нам именно он.

Допустимо ли использовать JavaScript в принципе? Я провел небольшое исследование на своем сайте на этот счет. Оказалось, что из более чем восьми тысяч отслеженных хостов JavaScript был отключен только у трех человек! Так что, наверно, вполне достаточно в тэгах <noscript> попросить этих троих (которые составили 0,0036% всех визитеров!) его включить. Но, конечно, если для вас это принципиально - кто мешает сделать проверку поддержки и оставить параллельно новому и существующий вариант сайта - эксклюзивно для лиц, повредившихся на почве сетевой безопасности? Тем более, что понадобится нам не пятая, - с поддержкой DOM, - а самая первая версия этого языка, так что нашим сайтом смогут насладиться даже самые изысканные эстеты - утонченные ценители антикварных браузеров.

Давайте посмотрим на любую страницу профессионально сделанного сайта. Практически наверняка мы увидим множество повторяющихся на ней элементов. Как правило, это вложенные таблицы, в строках которой выводится текст или картинки. Вот этими повторяющимися тэгами мы и займемся. Текст, за редкими исключениями, трогать нельзя - хотя бы потому, что мы хотим, чтобы наша страница нормально индексировалась поисковыми ботами, половина из которых в JavaScript - ни в зуб копытом. Но не переживайте: могу сказать, что, убрав только дублирующиеся тэги, размер HTML-кода реальной страницы можно сходу уменьшить от двух до десяти раз!

Начать нам будет проще всего с конца - с готовой HTML страницы. Будем пока считать, что ваш сайт состоит из набора статических страниц.

Итак, вы открываете страницу и видите там примерно такое безобразие, как показано на , за которым идет еще сотня строк (блоков "<tr>...</tr>") этой таблицы, отличающихся друг от друга только теми элементами, которые мы на врезке подчеркнули.

Напрашивается очевидный выход из положения: эту таблицу нужно формировать уже на стороне получателя, а в коде страницы вместо нее гнать только выполняющую эту нехитрую операцию программку на JavaScript, передавая ей данные, которые нужно подставить в ячейки таблицы.

Собственно, на этом месте можно было бы и закончить статью, так как реализация такого алгоритма не составляет никакого труда. Поэтому дальше я только для начинающих покажу, как примерно должна выглядеть соответствующая JavaScript-программа.

Первым делом, выберем разделитель между соседними строками. Им может служить любой символ, который вы заведомо не используете при выводе внутри нужной нам таблицы. Пусть это будет, скажем, "|".

Из html-файла выбрасываем все, что находится между тэгами <table> и </table> таблицы, которую мы обрабатываем. Вместо этого пишем там же <script>...</script> - контейнер, внутри которого будет находиться наша JavaScript-программа.

Все, что на врезке подчеркнуто (то есть, все меняющиеся данные), мы гуртом загоняем в переменную, разделяя выбранным символом "|" соседние значения и заканчивая строки html-таблицы парой "". И такой же разделитель поставим в самом начале текстовой строки:



x="|4|4|4|live|Иван Васильевич Грозный|23.02.163535|35|35|Билл ... "

Все, что нам осталось - написать простой код, восстанавливающий таблицу. Например, такой, как показано на .

Давайте разберемся, как это работает. Если посмотреть на исходный html-код строки таблицы, который нам нужно вывести, видно, что он состоит из 7 кусков неизменных данных, перемежающихся шестью переменными данными (последние мы выделили красным).

Наша программа в цикле for читает по одному символу переменной x. Если считан обычный символ - присоединяет его к выходной строке o. А если считан разделитель - выводит в выходную строку очередной неизменный кусок. Но как определить, какой кусок "очередной"? Для этого мы ввели переменную n и организовали несколько необычный неявный цикл на операторе switch. В заголовке этого оператора мы поместили выражение n++%7, состоящее из двух операций. Операция % - это деление по модулю, то есть, целочисленный остаток от деления n на 7. Понятно, что если последовательно увеличивать на единицу значение n, начиная от нуля, остаток от его деления на 7 будет колебаться в диапазоне от 0 до 6, принимая последовательно значения 0,1,2,3,4,5,6,0,1,2...

Вот операция ++ и есть постфиксный инкремент. Если перевести эту тарабарщину на русский язык, она значит, что каждый раз значение n увеличивается (или инкрементируется) на единицу, причем постфиксно, то есть "задним числом" - сначала используется "старое" значение переменной, а уж потом она увеличивается. То есть, раз мы присвоили переменной n начальное значение 0, то, считав символ разделителя, с которого начинается строковая переменная x, программа выполнит оператор выбора switch при значении n=0. Результатом обработки выражения n++%7 будет 0 (так как 0/7= 0 и 0 в остатке), и сработает условие case 0, включив в выходную строку o тэги начала строки таблицы вплоть до меняющейся части. Но (обратите на это внимание!) значение n после выполнения n++%7 станет уже равным 1. Значит, встретив разделитель в следующий раз, программа выполнит уже условие case 1 (ведь 1/7 = 0 и 1 в остатке), а еще один разделитель вызовет сработку case 2 и т.д. Вы конечно, уже поняли, откуда в операторе switch(n++%7) взялась именно семерка. Конечно, это число "константных" кусков html-кода, которые нам нужно поочередно выводить.

Последний оператор нашей программы - это старый добрый метод document.write(), выводящий свой аргумент в то место html-документа, где он написан. Как видим, получилась универсальная программа, которую можно применять для упаковки любых регулярных частей страницы, меняя только значение переменной x, а также количество и значения выводимых постоянных блоков.

Как быть, если страницы у вас динамические? Точно так же! Эту программу мы можем так же легко заставить формировать наш движок на php или любом другом серверном языке, вместо того, чтобы забивать ее вручную.