RPC 机制非常简单。服务器上有一组 JavaScript 函数,现在希望能从 Web 浏览器调用它们,就像有一个 JavaScript 引擎在同时执行客户机代码和服务器代码一样。因此,需要有一些客户端例程,这些例程应该与服务器端的对应例程有相同的名称和参数。
客户端例程将使用 Ajax 把它们的参数传递到服务器,在服务器上进行真正的处理。一个 Java servlet 将调用服务器端函数,并使用 JSON 格式将结果返回到客户机。然后,客户端例程计算 Ajax 响应,将 JSON 字符串转换回 JavaScript 对象,后者被返回到应用程序。
作为应用程序开发人员,可以将精力放在构建用户界面和服务器中执行的函数上。您不需要处理 Ajax 或 RPC 问题,因为本文以标记文件的形式提供了一个 JavaScript 代码生成器,您可以在 JSP 页面使用它来自动生成客户端例程。为了理解其中的工作原理,我们从一个示例应用程序开始。
本文中的示例应用程序使用 Java Management API 监视运行托管应用程序的 Java EE 服务器的 JVM。用户界面由一个简单的 Web 页面组成,该页面显示各种不同的指示器,例如 Java 类的数量、内存消耗、垃圾收集器的活动和线程的数量。
这些信息通过 Ajax 获得,并插入到一个 HTML 表中(如图 1 所示;单击 这里 查看图 1 的放大图)。更有趣的是,这个 Web 页面包含一个表单,通过该表单可以为给定的秒数分配内存。还可以调用 JVM 的垃圾收集器(GC)。
在服务器端,应用程序使用一个 JavaScript 文件,Web 浏览器将借助 Ajax 并使用本系列第 1 部分提供的脚本运行器调用该文件中的函数。这是一个简单的 servlet,它处理 URL 以 .jss 扩展名结束的所有请求。该 servlet 查找服务器上相应的 JavaScript 文件,并使用 Java Scripting API 执行它。
清单 1 显示 MonitorPage.jsp 文件中的表和表单。每个数据单元有一个 ID,以便用 JavaScript 更新表中的内容。<body>
标记的 onload
属性用于调用一个名为 init()
的 JavaScript 函数,该函数将初始化应用程序的客户端部分。另外两个函数 allocMem()
和 gc()
是在用户单击按钮时调用的。
清单 1. MonitorPage.jsp 的 HTML 代码
<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>
标记有以下属性:
script |
将在服务器上执行的脚本的 URL |
function |
被远程调用的 JavaScript 函数的签名 |
validator |
一个可选表达式,在 Web 浏览器中将计算该表达式以验证函数的参数 |
jsonVar |
一个可选 JavaScript 变量的名称,该变量用于存储 JSON 响应 |
method |
HTTP 方法,可以是 GET 或 POST |
async |
表明应该异步还是同步使用 XMLHttpRequest 的一个布尔属性 |
清单 2. MonitorPage.jsp 的 JavaScript 代码
<%@ 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> |
<js:rpc>
与
</js:rpc>
之间的 JavaScript 代码将用于处理 Ajax 响应。例如,对于 getInfo()
函数,json
响应被传递到另一个名为 showInfo()
的函数,该函数递归地遍历对象树,将信息插入到 HTML 表的数据单元中。由于 async
为 true
,生成的 getInfo()
函数将在发送请求后立即返回,然后通过 Ajax 回调调用 showInfo()
函数。
allocMem()
函数使用 validator
属性验证用户输入。如果对于两个参数中的任何一个参数,valid()
函数返回 false
,那么取消远程调用,并显示一条警告消息。由于在这里 async
为 false
,gc()
函数在返回控制前会一直等待响应。init()
函数调用 getInfo()
来初始化 UI,并将同一个函数传递到 setInterval()
,以便每秒钟都调用该函数刷新 Web 页面的信息。
清单 3 包含用 <js:rpc>
定制标记产生的代码,它被实现为一个名为 rpc.tag 的标记文件。每个生成的函数使用一个 XHR
对象,对象的原型可以在 MonitorPage.jsp
在其头部导入的 xhr.js 文件中找到。本文后面将提供 rpc.tag 和 xhr.js 文件的源代码。
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 文件包含 3 个在服务器上执行的 JavaScript 函数。getInfo()
函数(见清单 4)使用 Java Management API 获得与类、内存和线程相关的 JVM 信息。全部数据被打包到一个对象树中,在对 Ajax 请求的响应中将以 JSON 的形式返回该对象树。getInfo()
函数不必做任何特别的事情。在接下来的小节中,您将看到如何实现 RPC 机制。
清单 4. MonitorScript.jss 的 getInfo() 函数
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 线程,该 Java 线程执行一个用 JavaScript 实现的 run()
方法。代码使用 java.lang.reflect.Array
类的 newInstance()
方法创建一个 byte
数组,并调用 java.lang.Thread.sleep()
。然后,数组就可以执行垃圾收集。
清单 5. MonitorScript.jss 的 allocMem() 函数
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. MonitorScript.jss 的 gc() 函数
function gc() { java.lang.System.gc(); } |
在上一小节,您看到了由 <js:rpc>
标记生成的 JavaScript 代码。为了简化生成的代码,所有与 XMLHttpRequest
API 相关的操作,例如创建新的请求对象或发送 HTTP 请求,都被放到一个单独的名为 xhr.js 的 JavaScript 文件中。本节展示 XHR
对象的方法。
XHR()
构造函数(见清单 7)带有 3 个参数(method
、url
和 async
),它将这些参数存储为属性。newRequest()
方法终止仍在活动的前一个请求,用 delete
操作符释放内存,并创建一个新的 XMLHttpRequest
实例。该行为适合使用 Ajax 连接到一个数据提要或保存用户输入的应用程序。通常情况下,您只对装载或保存最新的数据感兴趣。
清单 7. xhr.js 的 XHR() 和 newRequest() 函数
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 请求中的请求参数和头部。一旦创建新的请求后,便可以使用这些方法。如果还没有创建 XMLHttpRequest
对象,或者已经发送了请求,那么 checkRequest()
函数将抛出一个异常。
清单 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)将参数编码,打开请求,添加头部,当 async
为 true
时设置 Ajax 回调,并发送 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(); } |
如果之前的 HTTP 错误已经打开了错误窗口,那么 focus()
调用将使之成为当前窗口,如果再三发生 HTTP 错误,那么会导致可用性问题。在这种情况下,由于每秒钟都会刷新窗口的内容,导致无法使用滚动,因此也难以对错误进行分析。
为了修复这些可用性问题,showRequestInfo()
函数增加了一个按钮,当单击此按钮时,该按钮设置一个名为 _freeze
的变量。如果 _freeze
为 true
,则不刷新请求的信息。此外,如果用户关闭了错误窗口,该窗口将不再重新打开。在对代码做出更改之后,只需刷新应用程序的页面,就可以验证错误是否仍然存在还是已经被修复。
htmlEncode()
函数(见清单 12)以一个字符串作为参数,并分别用 &
、<
和 >
替换 &
、<
和 >
字符。
清单 12. xhr.js 的 htmlEncode() 函数
function htmlEncode(value) { return value ? value.replace(/&/g, "&") .replace(/</g, "<").replace(/>/g, ">") : ""; } |
本节介绍生成 JavaScript 函数的 JSP 标记文件,这些 JavaScript 函数实现 RPC 机制的客户端部分。您还将看到如何调用与这些函数对应的服务器端函数,以及如何返回结果。但是首先了解一些关于安全性的注意事项。
服务器端脚本可以视作常规的资源,可以使用 Java EE 的标准安全性过程限制对它们的访问。根据 web.xml 文件中指定的安全性约束,通常会定义一个或多个角色,这些角色的用户将拥有访问这些脚本的适当权限。
无论是否限制对脚本的访问,Web 客户机应该不能调用服务器端脚本的任何函数。要控制可以通过 RPC 机制调用哪些 JavaScript 函数,一种简单的方式是在一个 Set
集合中维护它们。AuthorizedCalls
bean(见清单 13)提供了一些线程安全的方法,用于管理这个经过授权的调用的集合。
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
),这两个属性的值被传递到 AuthorizedCalls
的 add()
方法。此外,任何相对的脚本 URI 被转换为绝对 URI,以确保可以用 URI 惟一地标识每个脚本。由于 AuthorizedCalls
实例存储在 session
作用域,因此在限制某些用户对脚本的访问的情况下,只能以授权用户的身份执行服务器端函数。
<%@ 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); %> |
另一个需要分析的与安全性相关的方面是,如何在服务器端处理远程调用函数的参数。一种常见的想法是在 Web 浏览器中将 JavaScript 对象编码为 JSON 字符串,然后将它们发送到服务器,在服务器上则可以使用 eval()
轻松地进行解码。然而,这个想法犯了一个很大的错误。这样会使恶意用户乘机注入将在服务器上执行的代码。
本文中的示例代码只允许通过 Ajax 提交的原语类型(例如字符串和数字)的参数。在服务器端,它们被视作字符串,必要时由 JavaScript 引擎自动将它们转换成数字。如果需要更复杂的类型,不应该依靠 eval()
在服务器上进行解码,而应该使用自己的编码/解码方法。
本文第一节提供的 MonitorPage.jsp 文件使用 <js:rpc>
标记(见清单 15)生成调用服务器端函数的 JavaScript 例程。
清单 15. 在 MonitorPage.jsp 中使用 <js:rpc>
<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 库,用 <js:authorize>
调用 authorize.tag 文件,设置两个 JSP 变量 xhrVar
和 paramList
,并生成具有给定名称和参数的客户端 JavaScript 函数。
对于在生成的 JavaScript 代码(将在 Web 浏览器中执行)中使用的 JavaScript 变量,服务器端使用一个 xhrVar
变量维护该变量的名称。xhrVar
变量的值由函数名和 XHR
字符串组成。例如,如果 function
是 getInfo()
,那么这个 JSP 变量的值(以及 JavaScript 变量的名称)为 getInfoXHR
。
另一个 JSP 变量 paramList
将保存通过 (
和 )
之间的 function
属性提供的参数列表。例如,如果 function
为 allocMem(size, seconds)
,paramList
变量将保存 size, seconds
列表。
<%@ 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); } |
rpc.tag 生成的 JavaScript 代码的第一行创建 XHR
对象。然后,该标记文件生成一些 JavaScript 函数,这些函数可在 Web 浏览器中用于调用相应的服务器端函数。如果 validator
属性有一个非空值,则生成的代码中会包括一个 JavaScript 表达式,以决定是否可以进行远程调用。
接下来,使用 newRequest()
函数初始化一个新的 XMLHttpRequest
对象,该对象存储在一个名为 request
的本地 JavaScript 变量中。生成的代码将添加 Ajax-Call
头部,它的值就是函数的签名。然后,将参数添加到 XHR 对象。清单 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 文件生成一个名为 processResponse()
的 Ajax 回调。该函数验证 Ajax 请求是否完成,如果提供了 jsonVar
属性,则还会计算响应。
对于 JSP 页面中在 <js:rpc>
和 </js:rpc>
之间的内容,将使用 <jsp:doBody/>
将其包括在 Ajax 回调中。例如,MonitorPage.jsp 的 <js:rpc function="getInfo()" ...>
元素包含 showInfo(json, "info");
,用于处理 JSON 响应。清单 18 显示该代码在由 rpc.tag 生成的 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
对象发送一个 URL 以 .jss 结束的 Ajax 请求。此外,对于必须在服务器端调用的函数,必须以名为 Ajax-Call
的 HTTP 头部的形式提供它的签名。.jss 请求由本系列第一部分提供的一个名为 JSServlet
的 servlet 处理。
当示例应用程序的 MonitorScript.jss 被请求时,JSServlet
实际执行 3 个脚本:init.jss、 MonitorScript.jss 和 finalize.jss。init.jss 脚本(见清单 19)获取请求参数(这些参数是 Java 字符串),将它们转换成 JavaScript 字符串,并将参数存储为 param
对象的属性。init.jss 脚本还提供用于访问和设置 bean 的函数。
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); } |
由于所有 3 个脚本都在同一个上下文中执行,finalize.jss(见清单 20)可以使用 init.jss 和 MonitorScript.jss 的变量和函数。finalize.jss 脚本获取 Ajax-Call
头部,验证调用是否被授权,使用 eval()
调用脚本的函数,并使用 toSource()
将返回的对象转换成 JSON 字符串。由于函数参数是作为请求参数传输的,可以从 param
对象获得它们的值。
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()
执行函数是安全的,因为使用 authorizedCalls.contains()
对 Ajax-Call
头部进行了验证。
在本文中,您学习了如何在 Ajax 和同时在服务器和客户机上使用 JavaScript 代码的 Java 应用程序中使用 RPC。您还看到了如何用 JavaScript 实现 Java 接口,如何在 JavaScript 代码中创建 Java 数组和启动线程,以及如何在连接到数据提要时管理 Ajax 请求的生命周期。