описание
Стартовая страницаНаписать письмоКаталог работ

Полезные утилиты

Паковшик JavaScript кода

Паковать Очистить

Полезные статьи

03.11.2010 - Совместное использование Ajax и Web-сервисов »
25.10.2010 - Ajax и Smarty: Часть 1. Разрабатываем Ajax-приложения с помощью Smarty »
18.10.2010 - Конфигурация и пакетирование виджетов W3C »
30.09.2010 - Создание Web-сервисов с защитой на транспортном уровне при помощи Rational Application Developer V7: Часть 3. Настройка HTTPS »
30.09.2010 - Проектирование и разработка Web-сервисов JAX-WS 2.0 »
30.09.2010 - Краткие основы SOA »

Ещё »



Все статьи

30.06.2010 JavaScript EE: Часть 2. Вызов удаленных функций JavaScript с помощью Ajax

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


Эти клиентские программы будут через Ajax передавать свои параметры серверу, где происходит реальная обработка. Сервлет Java будет запускать функции на сервере и возвращать результаты клиенту с использованием формата JSON. Затем клиентские программы будут анализировать ответы Ajax и преобразовывать строки JSON обратно в объекты JavaScript, которые возвращаются приложению.


Как разработчик приложения, вы можете сосредоточиться на построении пользовательского интерфейса и функций, которые выполняются на сервере. Вам не нужно решать проблемы Ajax или RPC, потому что эта статья предлагает генератор кода JavaScript в форме тег-файла, который вы можете использовать в своих JSP-страницах для автоматического написания клиентских программ. Чтобы понять, как это работает, начнем с примера приложения.


Разработка приложения мониторинга JVM


Пример приложения для этой статьи использует API Java Management для мониторинга JVM, исполняющей сервер Java ЕЕ, на котором работает приложение. Интерфейс пользователя состоит из одной Web-страницы, на которой отображаются различные индикаторы, такие как число классов Java, потребление ресурсов памяти, активность сборщика мусора и число потоков.


Эта информация извлекается посредством Ajax и заносится в таблицу HTML. Чтобы было интереснее, Web-страница содержит форму, которая позволяет выделить некоторый объем памяти на заданное число секунд. Кроме того, можно активизировать сборщик мусора (garbage collector - GC) JVM.




Рисунок 1. Пример приложения




На стороне сервера приложение использует файл JavaScript, функции которого вызываются из Web-браузера с помощью Ajax с использованием механизма сценариев, который мы рассмотрели в первой статье этой серии. Этот простой сервлет будет обрабатывать все запросы, URL которых оканчивается расширением .jss. Сервлет разыскивает на сервере соответствующий файл JavaScript и исполняет его с помощью Java Scripting API.


Создание Web-страницы


В листинге 1 приведена таблица и форма файла MonitorPage.jsp. Каждая ячейка данных снабжена идентификатором, так что содержание таблицы можно редактировать с помощью JavaScript. Атрибут onload тега <body> используется для вызова функции JavaScript с именем init(), которая инициализирует клиентскую часть приложения. Две другие функции с именами allocMem() и gc() вызываются, когда пользователь нажимает кнопки.



Листинг 1. HTML-код MonitorPage.jsp


<html>
<head>
...
<style type="text/css">
th { text-align: left; }
td { text-align: right; }
.space { margin: 10px; }
</style>
</head>
<body onload="init()">
<table border="1" cellpadding="5">
<tr>
<th colspan=2>Classes</th>
<th colspan=2>Heap Memory</th>
<th colspan=2>Non-Heap Memory</th>
<th colspan=2>Garbage Collector</th>
<th colspan=2>Threads</th>
</tr>
<tr>
<th>Loaded</th>
<td id="info.classes.loaded"></td>
<th>Used</th>
<td id="info.memory.heap.used"></td>
<th>Used</th>
<td id="info.memory.nonHeap.used"></td>
<th>Count</th>
<td id="info.gc.count"></td>
<th>Live</th>
<td id="info.threads.live"></td>
</tr>
<tr>
<th>Unloaded</th>
<td id="info.classes.unloaded"></td>
<th>Committed</th>
<td id="info.memory.heap.committed"></td>
<th>Committed</th>
<td id="info.memory.nonHeap.committed"></td>
<th>Time</th>
<td id="info.gc.time"></td>
<th>Peak</th>
<td id="info.threads.peak"></td>
</tr>
</table>
<br>
<form name="monitorForm">
Size: <input name="size" size="10">
<span class="space"></span>
Seconds: <input name="seconds" size="4">
<span class="space"></span>
<button type="button"
onclick="allocMem(this.form.size.value, this.form.seconds.value)"
>Allocate Memory</button>
<span class="space"></span>
<button type="button" onclick="gc()">Collect Garbage</button>
</form>
</body>
</html>

Файл MonitorPage.jsp (см. листинг 2) использует специальный тег <js:rpc> для генерации клиентских функций JavaScript, которые вызывают свои серверные аналоги. Тег <js:rpc> имеет следующие атрибуты:


scriptURL сценария, который будет исполняться на сервереfunctionсигнатура дистанционно вызываемой функции JavaScript validatorопциональное выражение, которое вычисляется Web-браузером для проверки параметров функцииjsonVarимя опциональной переменной JavaScript, которая будет хранить ответ JSONmethodметод HTTP, который может принимать значения GET или POSTasyncбулево значение, указывающее, как должен использоваться XMLHttpRequest — асинхронно или синхронно

Листинг 2. Код JavaScript MonitorPage.jsp


<%@ taglib prefix="js" tagdir="/WEB-INF/tags/js" %>

<html>
<head>
<title>Monitor</title>
<script src="xhr.js" type="text/javascript">
</script>
<script type="text/javascript">
<js:rpc function="getInfo()" script="MonitorScript.jss"
method="GET" async="true" jsonVar="json">
showInfo(json, "info");
</js:rpc>

<js:rpc function="allocMem(size, seconds)" script="MonitorScript.jss"
validator="valid('Size', size) && valid('Seconds', seconds)"
method="POST" async="true"/>

<js:rpc function="gc()" script="MonitorScript.jss"
method="POST" async="false">
alert("Garbage Collected");
</js:rpc>

function showInfo(obj, id) {
if (typeof obj == "object") {
for (var prop in obj)
showInfo(obj[prop], id + "." + prop);
} else {
var elem = document.getElementById(id);
if (elem)
elem.innerHTML = htmlEncode(String(obj));
}
}

function valid(name, value) {
if (!value || value == "") {
alert(name + " is required");
return false;
}
var n = new Number(value);
if (isNaN(n) || n <= 0 || Math.floor(n) != n) {
alert(name + " must be a positive integer.");
return false;
} else
return true;
}

function init() {
getInfo();
setInterval(getInfo, 1000);
}
</script>
...
</head>
...
</html>

Код JavaScript, заключенный в теги <js:rpc> и </js:rpc>, будет использоваться для обработки ответов Ajax. Например, в случае функции getInfo() ответ json передается другой функции с именем showInfo(), которая рекурсивно просматривает дерево объектов, заполняя ячейки данных HTML-таблицы. Так как async имеет значение true, сгенерированная функция getInfo() возвращается сразу после отправки запроса, и обратный вызов Ajax активизирует функцию showInfo().


Функция allocMem() использует атрибут validator для проверки ввода пользователя. Если для любого из двух параметров функция valid() возвращает значение false, удаленный вызов отменяется и появляется предупредительное сообщение. Функция gc() дожидается ответа, прежде чем вернуть управление, потому что async в данном случае равно false. Функция init() вызывает getInfo(), чтобы инициализировать пользовательский интерфейс, и передает ту же функцию setInterval(), так что та вызывается каждую секунду для обновления информации Web-страницы.


В листинге 3 содержится код, полученный с помощью специального тега <js:rpc>, который реализован как тег-файл с именем rpc.tag. Каждая сгенерированная функция использует объект XHR, прототип которого можно найти в файле xhr.js, который MonitorPage.jsp импортирует в свой заголовок. Исходный код обоих файлов rpc.tag и xhr.js будет представлен ниже в этой статье.



Листинг 3.
Сгенерированные функции JavaScript



var getInfoXHR = new XHR("GET", "MonitorScript.jss", true);

function getInfo() {
var request = getInfoXHR.newRequest();
getInfoXHR.addHeader("Ajax-Call", "getInfo()");
function processResponse() {
if (getInfoXHR.isCompleted()) {
var json = eval(request.responseText);
showInfo(json, "info");
}
}
getInfoXHR.sendRequest(processResponse);
}
var allocMemXHR = new XHR("POST", "MonitorScript.jss", true);

function allocMem(size, seconds) {
if (!(valid('Size', size) && valid('Seconds', seconds)))
return;
var request = allocMemXHR.newRequest();
allocMemXHR.addHeader("Ajax-Call", "allocMem(size, seconds)");
allocMemXHR.addParam("size", size);
allocMemXHR.addParam("seconds", seconds);
function processResponse() {
if (allocMemXHR.isCompleted()) {
}
}
allocMemXHR.sendRequest(processResponse);
}
var gcXHR = new XHR("POST", "MonitorScript.jss", false);

function gc() {
var request = gcXHR.newRequest();
gcXHR.addHeader("Ajax-Call", "gc()");
function processResponse() {
if (gcXHR.isCompleted()) {
alert("Garbage Collected");
}
}
gcXHR.sendRequest(processResponse);
}

Составление сценария для сервера


Файл MonitorScript.jss содержит три функции JavaScript, которые выполняются на сервере. Функция getInfo() (показана в листинге 4) через Java Management API получает информацию JVM относительно классов, памяти и потоков. Все данные упакованы в дерево объектов, которое возвращается как JSON в ответ на запрос Ajax. Функция getInfo() не делает ничего особенного. В следующих разделах вы увидите, как реализован механизм RPC.



Листинг 4. Функция getInfo()MonitorScript.jss


importClass(java.lang.management.ManagementFactory);

function getInfo() {
var classes = ManagementFactory.getClassLoadingMXBean();
var memory = ManagementFactory.getMemoryMXBean();
var heap = memory.getHeapMemoryUsage();
var nonHeap = memory.getNonHeapMemoryUsage();
var gc = ManagementFactory.getGarbageCollectorMXBeans();
var threads = ManagementFactory.getThreadMXBean();

var gcCount = 0;
var gcTime = 0;
for (var i = 0; i < gc.size(); i++) {
gcCount += gc.get(i).collectionCount;
gcTime += gc.get(i).collectionTime;
}

return {
classes: {
loaded: classes.loadedClassCount,
unloaded: classes.unloadedClassCount,
},
memory: {
heap: {
init: heap.init,
used: heap.used,
committed: heap.committed,
max: heap.max
},
nonHeap: {
init: nonHeap.init,
used: nonHeap.used,
committed: nonHeap.committed,
max: nonHeap.max
}
},
gc: {
count: gcCount,
time: gcTime
},
threads: {
live: threads.threadCount,
peak: threads.peakThreadCount
}
};
}

Функция allocMem() (см. листинг 5) создает поток Java, который выполняет метод run(), реализованный в JavaScript. Программа создает массив byte, используя метод newInstance() класса java.lang.reflect.Array, и вызывает java.lang.Thread.sleep(). После этого массив готов к сбору мусора.



Листинг 5. Функция allocMem() MonitorScript.jss


function allocMem(size, seconds) {
var runnable = new java.lang.Runnable() {
run: function() {
var array = new java.lang.reflect.Array.newInstance(
java.lang.Byte.TYPE, size);
java.lang.Thread.sleep(seconds * 1000);
}
}
var thread = new java.lang.Thread(runnable);
thread.start();
}

В листинге 6 показана функция gc(), которая активизирует сборщик мусора JVM.



Листинг 6. Функция gc() из MonitorScript.jss


function gc() {
java.lang.System.gc();
}

Построение упаковщика XMLHttpRequest


В предыдущем разделе мы рассмотрели код JavaScript, созданный тегом <js:rpc>. Чтобы минимизировать и упростить сгенерированный код, все операции, относящиеся к API XMLHttpRequest, такие как создание нового объекта запросов или отправка запроса HTTP, помещаются в отдельный файл JavaScript с именем xhr.js. В этом разделе представлены методы объекта XHR.


Инициализация запросов


Конструктор XHR() (см. листинг 7) берет три параметра (method, url и async) и сохраняет их как свойства. Метод newRequest() прерывает предыдущий запрос, если он все еще активен, освобождает память с помощью оператора delete и создает новый экземпляр XMLHttpRequest. Такое поведение подходит для приложений, использующих Ajax для связи с каналом передачи данных или для сохранения ввода пользователя. В типичном случае интерес представляет загрузка или сохранение только самых последних данных.



Листинг 7.
Функции XHR() и newRequest()конструктора xhr.js



function XHR(method, url, async) {
this.method = method.toUpperCase();
this.url = url;
this.async = async;
}

XHR.prototype.newRequest = function() {
var request = this.request;
if (request) {
request.onreadystatechange = function() { };
if (request.readyState != 4)
request.abort();
delete request;
}
request = null;
if (window.ActiveXObject)
request = new ActiveXObject("Microsoft.XMLHTTP");
else if (window.XMLHttpRequest)
request = new XMLHttpRequest();
this.request = request;
this.sent = false;
this.params = new Array();
this.headers = new Array();
return request;
}

В листинге 8 показаны методы addParam() и addHeader(), которые позволяют добавить параметры запроса и заголовки, включенные в запрос HTTP при его отправке. Эти методы можно использовать сразу, как только создан новый запрос. Функция checkRequest() выдаст предупреждение, если объект XMLHttpRequest не был создан или если запрос уже был отправлен.



Листинг 8.
Функции xhr.js checkRequest(), addParam() и addHeader()



XHR.prototype.checkRequest = function() {
if (!this.request)
throw "Request not created";
if (this.sent)
throw "Request already sent";
return true;
}

XHR.prototype.addParam = function(pname, pvalue) {
this.checkRequest();
this.params[this.params.length] = { name: pname, value: pvalue };
}

XHR.prototype.addHeader = function(hname, hvalue) {
this.checkRequest();
this.headers[this.headers.length] = { name: hname, value: hvalue };
}

Отправка запросов


Функция sendRequest() (см. листинг 9) кодирует параметры, открывает запрос, добавляет заголовки, устанавливает обратный вызов Ajax, если async равна true, а затем отправляет запрос HTTP. Если async равна false, обратный вызов активизируется после send().



Листинг 9. Функция xhr.js sendRequest()


XHR.prototype.sendRequest = function(callback) {
this.checkRequest();
var query = "";
for (var i = 0; i < this.params.length; i++) {
if (query.length > 0)
query += "&";
query += encodeURIComponent(this.params[i].name) + "="
+ encodeURIComponent(this.params[i].value);
}
var url = this.url;
if (this.method == "GET" && query.length > 0) {
if (url.indexOf("?") == -1)
url += "?";
else
url += "&";
url += query;
}
this.request.open(this.method, url, this.async);
for (var i = 0; i < this.headers.length; i++)
this.request.setRequestHeader(
this.headers[i].name, this.headers[i].value);
if (this.async)
this.request.onreadystatechange = callback;
var body = null;
if (this.method == "POST") {
body = query;
this.request.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
}
this.sent = true;
this.request.send(body);
if (!this.async)
callback();
}

В обратном вызове Ajax можно использовать isCompleted() (см. листинг 10) для проверки статуса запроса.



Листинг 10.
Функция xhr.js isCompleted()



XHR.prototype.isCompleted = function() {
if (this.request && this.sent)
if (this.request.readyState == 4)
if (this.request.status == 200)
return true;
else
this.showRequestInfo();
return false;
}

Сообщения об ошибках


В случае ошибки на стороне сервера isCompleted() вызывает функцию showRequestInfo() (см. листинг 11). Эта функция открывает окно и распечатывает информацию запроса: метод HTTP, URL, параметры, заголовки и ответ.



Листинг 11. Функция xhr.js showRequestInfo()


var xhrErrorWindow = null;

XHR.prototype.showRequestInfo = function() {
if (xhrErrorWindow && (xhrErrorWindow.closed || xhrErrorWindow._freeze))
return;
xhrErrorWindow = window.open("", "XHRError", "menubar=no, resizable=yes, "
+ "scrollbars=yes, width=600, height=600");
var doc = xhrErrorWindow.document;
doc.writeln("<p align='right'>");
doc.writeln("<button onclick='window._freeze = true'>Freeze</button>")
doc.writeln("</p>");
doc.writeln(htmlEncode(this.method + " " + this.url));
doc.writeln("<pre>" + this.request.status + "</pre>");
doc.writeln("Parameters:");
doc.writeln("<pre>");
for (var i = 0; i < this.params.length; i++) {
doc.writeln(htmlEncode(this.params[i].name
+ "=" + this.params[i].value));
}
doc.writeln("</pre>");
doc.writeln("Headers:");
doc.writeln("<pre>");
for (var i = 0; i < this.headers.length; i++) {
doc.writeln(htmlEncode(this.headers[i].name
+ "=" + this.headers[i].value));
}
doc.writeln("</pre>");
doc.writeln("Response:");
var response = this.request.responseText;
doc.writeln(response);
doc.close();
xhrErrorWindow.focus();
}

Разрешение window.focus() в Firefox



Выберите Tools > Options > Content, нажмите кнопку Advanced рядом с Enable JavaScript и установите флажок Rise or lower windows на вкладке Advanced JavaScript Settings.

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


Чтобы решить эти проблемы, функция showRequestInfo() добавляет кнопку, которая устанавливает переменную с именем _freeze. Если значение _freeze равно true, информация запросов не обновляется. Кроме того, окно ошибок больше не открывается, если пользователь его закрыл. После внесения изменений в код можно просто обновить страницу приложения, чтобы проверить, осталась ли ошибка или исправилась.


Функция htmlEncode() (см. листинг 12) берет параметр строки и заменяет символы &, < и > соответственно символами &amp;, &lt; и &gt;.



Листинг 12.
Функция xhr.js htmlEncode()



function htmlEncode(value) {
return value ? value.replace(/&/g, "&amp;")
.replace(/</g, "&lt;").replace(/>/g, "&gt;") : "";
}

Реализация механизма JavaScript-RPC


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


Авторизация вызова функций


Сценарии на сервере можно рассматривать как обычные ресурсы, и доступ к ним можно ограничить с применением стандартных процедур безопасности Java ЕЕ. Обычно определяют одну или несколько ролей, наделенных правами доступа к сценариям в зависимости от ограничений безопасности, прописанных в файле web.xml.


Ограничиваете ли вы доступ к своим сценариям или нет, Web-клиенты не должны иметь возможность активизировать какие-либо функции сценария на сервере. Простой способ управления тем, какие функции могут вызываться через механизм RPC, заключается в том, чтобы собрать их в коллекцию Set. Компонент AuthorizedCalls (см. листинг 13) предоставляет безопасные методы управления набором авторизованных вызовов.



Листинг 13. Класс AuthorizedCalls


package jsee.rpc;

import java.util.*;

public class AuthorizedCalls implements java.io.Serializable {
private Set<String> calls;

public AuthorizedCalls() {
calls = new HashSet<String>();
}

protected String encode(String scriptURI, String functionName) {
return scriptURI + '#' + functionName;
}

public synchronized void add(String scriptURI, String functionName) {
calls.add(encode(scriptURI, functionName));
}

public synchronized void remove(String scriptURI, String functionName) {
calls.remove(encode(scriptURI, functionName));
}

public synchronized boolean contains(
String scriptURI, String functionName) {
return calls.contains(encode(scriptURI, functionName));
}

public String toString() {
return calls.toString();
}

}

Пример приложения для этой статьи должен авторизовать вызовы со страницы JSP. Файл authorize.tag (см. листинг 14) имеет два атрибута (с именами function и script), значения которых передаются методу add() компонента AuthorizedCalls. Кроме того, относительный URI любого сценария преобразуется в абсолютный URI, чтобы гарантировать уникальную идентификацию каждого сценария соответствующим URI. Так как экземпляр AuthorizedCalls хранится в области session, функции на стороне сервера будут выполняться только по поручению авторизованных пользователей (в том случае, если вы ограничили доступ к сценарию для некоторых из своих пользователей).



Листинг 14. Файл authorize.tag


<%@ attribute name="function" required="true" rtexprvalue="true" %>
<%@ attribute name="script" required="true" rtexprvalue="true" %>

<jsp:useBean id="authorizedCalls"
class="jsee.rpc.AuthorizedCalls" scope="session"/>
<%
String functionName = (String) jspContext.getAttribute("function");
String scriptURI = (String) jspContext.getAttribute("script");
if (!scriptURI.startsWith("/")) {
String base = request.getRequestURI();
base = base.substring(0, base.lastIndexOf("/"));
scriptURI = base + "/" + scriptURI;
}
authorizedCalls.add(scriptURI, functionName);
%>

Другой связанный с безопасностью аспект, который очень важно проанализировать, - это то, как параметры дистанционно вызываемой функции обрабатываются на стороне сервера. Можно было бы попытаться зашифровать объекты JavaScript как строки JSON в Web-браузере и отправить их на сервер, где их легко расшифровать с помощью eval(). Но это было бы большой ошибкой; это позволило бы злоумышленникам подбрасывать код, который будет исполняться на вашем сервере.


Пример кода для этой статьи допускает в качестве параметров, передаваемых с помощью Ajax, только примитивные типы (такие как строки и числа). На стороне сервера они рассматриваются как строки, что позволяет механизму JavaScript при необходимости автоматически преобразовывать их в числа. Если нужны более сложные типы, не следует опираться на eval() для расшифровки параметров на сервере. Используйте свои собственные методы шифрования/дешифрования.


Применение тег-файла для создания кода JavaScript


Файл MonitorPage.jsp, приведенный в первой части этой статьи, использует тег <js:rpc> (см. листинг 15) для генерирования подпрограмм JavaScript, которые вызывают функции на стороне сервера.



Листинг 15. Использование <js:rpc> в MonitorPage.jsp


<js:rpc function="getInfo()" script="MonitorScript.jss"
method="GET" async="true" jsonVar="json">
showInfo(json, "info");
</js:rpc>

<js:rpc function="allocMem(size, seconds)" script="MonitorScript.jss"
validator="valid('Size', size) && valid('Seconds', seconds)"
method="POST" async="true"/>

<js:rpc function="gc()" script="MonitorScript.jss"
method="POST" async="false">
alert("Garbage Collected");
</js:rpc>

В листинге 16 приведен файл rpc.tag, который реализует этот специальный тег. Тег-файл декларирует свои атрибуты и используемые библиотеки JSP, активизирует файл authorize.tag с помощью <js:authorize>, устанавливает две переменные JSP с именами xhrVar и paramList и генерирует функцию JavaScript на стороне клиента с заданным именем и параметрами.


Переменная xhrVar используется на стороне сервера для определения имени переменной JavaScript, используемой в сгенерированном коде JavaScript, который будет исполняться в Web-браузере. Значение переменной xhrVar состоит из имени функции и строки XHR. Например, если function это getInfo(), то значение переменной JSP (и имя переменной JavaScript) будет getInfoXHR.


Другая переменная JSP с именем paramList хранит список параметров, которые передаются через атрибут function между ( и ). Например, если function - это allocMem(size, seconds), то переменная paramList будет хранить список size, seconds.



Листинг 16. Файл rpc.tag


<%@ attribute name="function" required="true" rtexprvalue="true" %>
<%@ attribute name="script" required="true" rtexprvalue="true" %>
<%@ attribute name="validator" required="false" rtexprvalue="true" %>
<%@ attribute name="jsonVar" required="false" rtexprvalue="true" %>
<%@ attribute name="method" required="false" rtexprvalue="true" %>
<%@ attribute name="async" required="true" rtexprvalue="true"
type="java.lang.Boolean" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="js" tagdir="/WEB-INF/tags/js" %>

<js:authorize script="${script}" function="${function}"/>

<c:set var="xhrVar" value="${fn:trim(fn:substringBefore(function, '('))}XHR"/>
<c:set var="paramList"
value="${fn:substringBefore(fn:substringAfter(function, '('), ')')}"/>

var ${xhrVar} = new XHR("${method}", "${script}", ${async});

function ${function} {
<c:if test="${!empty validator}">
if (!(${validator}))
return;
</c:if>
var request = ${xhrVar}.newRequest();
${xhrVar}.addHeader("Ajax-Call", "${function}");
<c:forEach var="paramName" items="${paramList}">
<c:set var="paramName" value="${fn:trim(paramName)}"/>
${xhrVar}.addParam("${paramName}", ${paramName});
</c:forEach>

function processResponse() {
if (${xhrVar}.isCompleted()) {
<c:if test="${!empty jsonVar}">
var ${jsonVar} = eval(request.responseText);
</c:if>
<jsp:doBody/>
}
}

${xhrVar}.sendRequest(processResponse);
}

Первая строка кода JavaScript, сгенерированная rpc.tag, создает объект XHR. Затем тег-файл создает функцию JavaScript, которая может использоваться в Web-браузере для активизации своего аналога на сервере. Если атрибут validator имеет непустое значение, выражение JavaScript включается в сгенерированный код, чтобы решить, можно ли выполнить дистанционный вызов.


Затем функцией newRequest() инициализируется новый объект XMLHttpRequest, который хранится в локальной переменной JavaScript с именем request. Сгенерированный код добавит заголовок Ajax-Call, значение которого является сигнатурой функции. После этого добавляются параметры в объект XHR. В листинге 17 приведен код, сгенерированный для функции allocMem().



Листинг 17.

Сгенерированная функция allocMem()



var allocMemXHR = new XHR("POST", "MonitorScript.jss", true);

function allocMem(size, seconds) {
if (!(valid('Size', size) && valid('Seconds', seconds)))
return;
var request = allocMemXHR.newRequest();
allocMemXHR.addHeader("Ajax-Call", "allocMem(size, seconds)");
allocMemXHR.addParam("size", size);
allocMemXHR.addParam("seconds", seconds);
function processResponse() {
if (allocMemXHR.isCompleted()) {
}
}
allocMemXHR.sendRequest(processResponse);
}


После инициализации объекта XHR файл rpc.tag генерирует обратный вызов Ajax с именем processResponse(). Эта функция проверяет, скомпилирован ли запрос Ajax, и анализирует ответ, если присутствует атрибут jsonVar.


То, что расположено на странице JSP между <js:rpc> и </js:rpc>, включается в обратный вызов Ajax с помощью <jsp:doBody/>. Например, элемент MonitorPage <js:rpc function="getInfo()" ...>.jsp содержит showInfo(json, "info"); для обработки ответа JSON. В листинге 18 показано, где этот код размещается внутри функции getInfo(), генерируемой с помощью rpc.tag.



Листинг 18.

Сгенерированная функция getInfo()



var getInfoXHR = new XHR("GET", "MonitorScript.jss", true);

function getInfo() {
var request = getInfoXHR.newRequest();
getInfoXHR.addHeader("Ajax-Call", "getInfo()");
function processResponse() {
if (getInfoXHR.isCompleted()) {
var json = eval(request.responseText);
showInfo(json, "info");
}
}
getInfoXHR.sendRequest(processResponse);
}

Активизация функции сценария


Каждый раз, когда в Web-браузере вызывается сгенерированная функция, через объект XHR отправляется запрос Ajax, URL которого оканчивается на .jss. В дополнение к этому в качестве заголовка HTTP с именем Ajax-Call передается сигнатура функции, которая должна активизироваться. Запросами .jss управляет сервлет с именем JSServlet, который описан в первой части этой серии.


Когда из примера приложения запрашивается MonitorScript.jss, на самом деле JSServlet исполняет три сценария: init.jss, MonitorScript.jss и finalize.jss. Сценарий init.jss (см. листинг 19) получает параметры запроса, которые представляют собой строки Java, преобразуя их в строки JavaScript и сохраняя параметры как свойства объекта param. Сценарий init.jss также предоставляет функции для получения и установки bean-компонентов Java.



Листинг 19.

Файл init.jss



var debug = true;
var debugStartTime = java.lang.System.nanoTime();

var param = new Object();
var paramValues = new Object();

function initParams() {
var paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
var name = paramNames.nextElement();
param[name] = String(request.getParameter(name));
paramValues[name] = new Array();
var values = request.getParameterValues(name);
for (var i = 0; i < values.length; i++)
paramValues[name][i] = String(values[i]);
}
}

initParams();

function getBean(scope, id) {
return eval(scope).getAttribute(id);
}

function setBean(scope, id, bean) {
if (!bean)
bean = eval(id);
return eval(scope).setAttribute(id, bean);
}

Так как все три сценария исполняются в одном и том же контексте, finalize.jss (показан в листинге 20) может использовать переменные и функции init.jss и MonitorScript.jss. Сценарий finalize.jss script получает заголовок Ajax-Call, проверяет, авторизован ли вызов, через eval() активизирует функцию сценария и преобразует возвращенный объект в строку JSON с применением toSource(). Поскольку параметры функции передаются как параметры запроса, их значения извлекаются из объекта param.



Листинг 20.

Файл finalize.jss



var ajaxCall = request.getHeader("Ajax-Call");
if (ajaxCall != null) {
var authorizedCalls = getBean("session", "authorizedCalls");
if (authorizedCalls.contains(request.requestURI, ajaxCall)) {
var ajaxResponse = eval("with(param) " + ajaxCall);
if (ajaxResponse)
print(ajaxResponse.toSource());
}
}

var debugEndTime = java.lang.System.nanoTime();
if (debug)
println("// Time: " + (debugEndTime - debugStartTime) + " ns");

Использовать eval() для выполнения функции безопасно, так как заголовок Ajax-Call проверен с помощью authorizedCalls.contains().


Заключение


Из этой статьи вы узнали, как применять RPC в приложениях Ajax и Java, которые используют код JavaScript на стороне серверов и клиентов. Вы увидели также, как реализовать интерфейсы Java с помощью JavaScript, как создавать массивы Java и запускать потоки в своем коде JavaScript и как управлять жизненным циклом запросов Ajax при подсоединении к каналам передачи данных.




Загрузка

ОписаниеИмяРазмерМетод загрузкиПример приложения для этой статьиjsee_part2_src.zip23 КБHTTP

Информация о методах загрузки



Ресурсы

Примите участие в обсуждении материала на форуме.

Оригинал статьи (EN)


В статье JavaScript EE, часть 1: Выполнение файлов JavaScript на стороне сервера показано, как компилировать и выполнять файлы JavaScript с помощью API javax.script, как экспортировать объекты Java в виде переменных сценария и как создавать файлы JavaScript, которые исполняются на стороне сервера. (EN)


Ajax и Java development made simpler, Part 1 (developerWorks, апрель 2008 г.) — еще одна статья, в которой рассматривается идея динамического генерирования кода JavaScript с помощью тег-файлов JSP (EN).


Центр ресурсов по Ajax developerWorks содержит постоянно пополняемую библиотеку материалов по Ajax, а также полезные ресурсы для тех, кто только приступает к разработке Ajax-приложений. (EN)


Об авторе

Андрей Чиорояну (Andrei Cioroianu) является основателем компании Devsphere, специализирующейся на консалтинговых услугах в сфере разработок Java EE и Ajax/JSF. Он работает с Java и Web-технологиями начиная с 1997 г. и имеет более чем десятилетний опыт решения сложных технических задач и управления полным жизненным циклом коммерческих продуктов, специализированных приложений и инфраструктур с открытым кодом. С Андреем можно связаться, заполнив контактную форму на сайте devsphere.com.


»

Назад к списку

Новости студии

С Новым Годом!
31.12.2009
Уважаемые клиенты студии и гости нашего сайта!
Коллектив студии программирования Bleaksoft искренне поздравляет Вас с наступающим годом Тигра, и желает счастья, радости и успехов в следующем году.
Пусть в любых начинаниях Вас преследует постоянная удача!
Трекер заданий
20.12.2009
Для повышения эффективности работы запущен трекер заданий, в котором клиенты студии могут оставлять свои технические задания и замечания по проектам.
JavaScript Packer
01.12.2009
Для удобства, на сайте запущен собственный паковщик и деобфускатор Java - скриптов
Все новости

Rambler's Top100 Яндекс индекс цитирования Рейтинг@Mail.ru