IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Open source | SOA and Web services  >

基于 Apache Geronimo 和 Web Service 创建 Ajax 进度条

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码


喻 星 (yuxing@cn.ibm.com), 软件工程师, IBM
赵 雄伟 (zhaoxw@cn.ibm.com), 软件工程师, IBM
王 南 (wnan@cn.ibm.com), 软件工程师, IBM

2007 年 12 月 20 日

Apache Geronimo 是一个IBM支持的由 Apache Software Foundation 开发的开源 Java 2 Platform, Enterprise Edition (J2EE™) 应用服务器项目,它集成了许多技术和概念,是最具潜力的开源项目之一。本文将以实例探讨如何在Apache Geronimo环境下应用Ajax创建进度条。并进一步将获取进度信息封装为Web Service,通过使用Ajax调用Web service来获取进度信息。 读者定位于具有Web应用开发经验的开发人员,对Ajax,Web Service和Apache Geronimo有所了解。

请访问 Geronimo 技术资源中心,获取有关 Geronimo 的信息,包括大量 Geronimo 技术文章、教程、下载和相关技术资源。

RSS 订阅 Geronimo 相关文章和教程的 RSS 提要

引言

Apache Geronimo 以其灵活性和敏捷性成为目前最受欢迎的开源应用服务器之一。IBM的产品WAS CE(WebSphere Application Server Community Edition)就是构建在 Geronimo 之上的。Geronimo 集成了许多技术和概念,Apache Tomcat、Jetty、Apache Axis 和 Apache Derby 等;并对很多框架都提供支持如 Spring、Tuscany、Hibernate 等。您可以通过 developerworks 的Apache Geronimo 项目资源中心来更多的了解 Apache Geronimo。

Ajax,一种改变了 web 应用用户体验的技术,出现以来得到了快速的发展,现在已不断走向成熟。目前有很多基于 Ajax 的组件和框架,您可以通过 developerworks 的 Ajax 技术资源中心来更多的了解 Ajax 技术。

请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。

RSS 订阅 Ajax 相关文章和教程的 RSS 提要

下面我们将通过一个实例来向您演示如何在 Apache Geronimo 环境下应用 Ajax 创建进度条。 首先将在 Apache Geronimo 上用 commons fileupload 实现文件上传,上传过程中客户端与服务器端将创建一个文件流,为了显示进度条,我们在服务器端设置一个监听器,实时监听上传进度,然后通过 Ajax 从服务器端的监听器获取进度信息从而在客户端动态的显示进度条。如图1所示。






图 1. Ajax 进度条实例流程图
Ajax 进度条实例流程图

接下来,进一步将获取进度信息功能封装为 Web service,然后在客户端通过 Ajax 调用 Web service 来获取进度信息。如图 2所示。您也可以下载实例代码,对照代码看下面的介绍更加直观。


图 2. 通过 Web service 获取进度信息流程图
通过 Web_service 获取进度信息流程图




回页首


在 Apache Geronimo 上实现文件上传实例

我们使用 Apache 的 Commons fileupload 库来处理文件的上传。Apache commons fileupload 是Apache jakarta 项目组开发的一个功能强大的上传文件组件,我们可以使用它简单易行地向一个web 应用中添加健壮和高效的文件上传功能。在本例中我们使用 FileUpload(V1.2)的 Streaming API 来处理上传文件。 清单1是一段在 Servlet 中使用 FileUpload 组件 Streaming API 来处理文件上传并将文件存在服务器存储设备上的示例代码。


清单1. 文件上传
                
public class FileUploadServlet extends javax.servlet.http.HttpServlet implements 
      javax.servlet.Servlet {
    …………
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
          throws ServletException, IOException {
        Boolean multipartRequest = ServletFileUpload.isMultipartContent(request);
        if(multipartRequest){
            File tmpDir = new File("C:/temp");
            DiskFileItemFactory factory = new DiskFileItemFactory(0, tmpDir); 
            ServletFileUpload upload = new ServletFileUpload(factory);
            FileItemIterator iter = upload.getItemIterator(request); 
            try{
                while(iter.hasNext()){
                FileItemStream item = iter.next();
                String fieldName = item.getFieldName();
                InputStream stream = item.openStream();
                if(item.isFormField()){
                    …………
                }else{// process file
                    if(item.getName()!=null && !"".equals(item.getName().trim())){
                        String fileName = getFileName(item.getName());
                        File localFile = new File(tmpDir, fileName);
                        FileOutputStream fos = new FileOutputStream(localFile);
                        byte[] buffer = new byte[4096];
                        int count = 0;
                        while(-1 != (count=(stream.read(buffer)))) 
                            fos.write(buffer, 0, count);
                    }
                    fos.close();
                }
            }
        }catch(Exception e){ }
            …………
        }
        …………
    }
    …………
}






回页首


用 Ajax 实现进度条

在服务器端监视并维护文件上传进度

在文件上传过程中,服务器端需要监视和维护上传状态的信息,此信息将被提供给 Ajax 的浏览器端以计算文件上传的进度更新进度条 UI。此过程中需要获取的数据信息包括当前已上传的字节数和上传文件的总大小。FileUpload(V1.2) 组件为监视上载进度提供了内建的支持,可以通过为 ServletFileUpload 对象设置一个 ProgressListener 来实现这一功能,示例代码如下:


清单 2. 实例化 Listener
                
ProgressListener progressListener = new 
        UploadProcessListener("SampleListenerID", getServletContext());
upload.setProgressListener(progressListener);


清单 3. UploadProcessListener
                
class UploadProcessListener implements ProgressListener {
    private String id;
    private ServletContext context;
    private long[] progressData = new long[2];
    private long megaBytes = -1;
    public UploadProcessListener(String id, ServletContext context) {
        this.id = id;
        this.context = context; 
    }
    public void update(long pBytesRead, long pContentLength, int pItems) {
        long mBytes = pBytesRead / 1000;
        if (megaBytes == mBytes) {
            return;
        }
        megaBytes = mBytes;
        progressData[0] = pBytesRead;
        progressData[1] = pContentLength;
        if (context.getAttribute(id) == null) {
            context.setAttribute(id, progressData);
        }
    }
}


清单 3所示,UploadProgressListener 实现了 org.apache.commons.fileupload.ProgressListener 接口。当此 Listener 注册的 ServletFileUpload 对象读入新的文件数据之后,Listener 中的回调函数 update 将被调用,在 update 函数中可以获得当前已上载的字节数和上载文件总大小,我们将这两个数据放入此 web 应用的 ServletContext 中,随后 Ajax 浏览器端可以从服务器端获取储存在此的进度信息。

利用 Ajax 技术同步浏览器与服务器端的进度信息

无刷新的表单提交

由于在上传文件时需要在同一页面显示对应的进度条控件,因此,在提交表单时当前页面不能被刷新。我们可以通过将表单提交至一个隐藏的 iframe 中来实现。如清单 4所示:


清单 4. 无刷新的表单提交
                
<html>
<head>
…………
<script type="text/javascript" language="javascript">
    …………
    var id_index = 0;
    function submitUploadFileForm(){
        var hframeDiv = document.createElement("div");
        var hfid = "hiddenTarget"+id_index;
        hframeDiv.innerHTML="<iframe id=\""+hfid+"\" name=\""+hfid+"\" src=\"\" 
              style=\"display:none\"></iframe>";
        document.body.appendChild(hframeDiv);
        …………
        document.forms["uploadFileForm"].target = hfid;
        …………
        document.forms["uploadFileForm"].submit();
        …………
    }
</script>
</head>
<body>
    …………
    <form name="uploadFileForm" enctype="multipart/form-data" method="post" 
          onSubmit="submitUploadFileForm()">
    …………
    </form>
</body>
</html>


用 Ajax 技术同步进度数据

由于文件的上传和 Ajax 浏览器端向服务器端同步进度数据是在相互独立的不同请求序列中完成的,而这些步骤在逻辑上属于同一个“会话”,这就需要使用一个标识符来将一个上传过程和期间发生的数据同步请求相关联,因此我们设置了标识符(uploadUUID),代码如下:


清单5. 用标识符来关联上传过程和进度信息同步
                
<script>
    var id_prefix = "<%=UUIDHelper.getUUID().toString()%>"; // 服务器生成的唯一标识符
    var id_index = 0;
    …………
    function submitUploadFileForm(){
        …………
        var uuid = id_prefix + "_" + id_index;
        var submitURL = "FileUpload?uploadUUID="+uuid;
        …………
        document.forms["uploadFileForm"].action = submitURL;
        …………
    }
    …………
    function ProgressUpdater(vProgressBar, vuuid, vName){
        …………
        var uuid = vuuid;
        …………
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            http_request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // IE
            http_request = new ActiveXObject("Microsoft.XMLHTTP");   
        }
        http_request.onreadystatechange = processRequest;
        var URL = "uploadStatusReport.jsp?uploadUUID="+uuid;
        http_request.open("GET", URL, true);
        http_request.send(null);
        …………
    }
    …………
</script>

当服务器端收到一个进度同步请求时,将试图根据该请求的标识符(uploadUUID) 到 ServletContext 中读取相关的进度信息,并构造一个 xml 文档返回给 Ajax 浏览器端。代码如下:


清单6. uploadStatusReport.jsp
                
<%@ page language="java" contentType="text/xml; charset=UTF-8" pageEncoding="UTF-8" %>
<%
    response.setHeader("Cache-Control","no-cache");
    response.setHeader("Expires", "0");
    String id = request.getParameter("uploadUUID");
    long[] stats = null;
    if(id != null){
        stats = (long[])getServletContext().getAttribute(id);
    }
    if(stats == null){
        stats =  new long[2]; 
    }
%>
<uploadStatus id="<%=id%>">
    <completed><%= stats[0] %></completed>
    <total><%= stats[1] %></total>
</uploadStatus>


清单7. 示例的 xml 片段
                
<uploadStatus id="samplefileuploaduuid_1">
    <completed>13426</completed>
    <total>128000000</total>
</uploadStatus>


清单 7. 示例的 xml 片段
                
<uploadStatus id="samplefileuploaduuid_1">
    <completed>13426</completed>
    <total>128000000</total>
</uploadStatus>

Ajax 浏览器端需要从如上的 xml 数据中得到已上传的字节数和总字节数。在本例中,我们使用一个开源的 Javascript 工具包 ajaxslt(见参考资料)来解析 xml 数据。应用代码如下:


清单 8. 解析 xml 数据
                
<script src="js/misc.js" type="text/javascript" language="javascript"></script>
<script src="js/xpath.js" type="text/javascript" language="javascript"></script>
…………
function processRequest() {
    …………
    var xml = http_request.responseXML;
    var bytesRead = 
          parseInt(xpathDomEval('uploadStatus/completed/text()',xml).stringValue());
    var contentLength = 
          parseInt(xpathDomEval('uploadStatus/total/text()',xml).stringValue())
}

进度条控件的状态控制

在一个上传过程中,进度条UI将会顺序的经历如下的状态转换: 未开始(等待初始化)->已初始化并开始更新进度状态->完成。因此,需要根据期间的数据变化来判定当前进度条所处的状态,以便控制其更新和转换。

当用户触发一个上传过程时,我们将当前的状态设置为“未开始”, 同时开始 Ajax 的进度数据同步过程,在第一次有效同步数据(所获取的 contentLength 大于 0 或 bytesRead 大于 0)完成之前,进度条将一直处于“未开始”状态。第一次有效的数据同步完成后,ajax 浏览器端判断获得了上传文件的总字节数,此时进度条开始进入“已初始化并开始更新进度状态”状态。当进入第二状态,但同步的进度数据为无效数据(所获取到的 bytesRead 和 contentLength 数据都为 -2 或 0),或者上传文件时所提交至的 iframe 已经 load 完成时,则表示上传过程完成,进度条应更新至 100%。示例代码如下:


清单 9. 进度条控件的状态控制
                
<script type="text/javascript" language="javascript">
    …………
    function ProgressUpdater(vProgressBar, vuuid, vName){
        var progressBar = vProgressBar;
        //当进入“完成”状态时,将其置于true,并停止进度数据的同步过程
        var isUploadCompleted = false;
        //当所提交至的iframe触发onload事件时,将其置于true.
        var actionFinished = false;
        …………
        function processRequest() {
            if(!http_request){
                return;
            }
            if (http_request.readyState == 4) {
                if (http_request.status == 200) {
                    var xml = http_request.responseXML;
                    var bytesRead = parseInt(
                        xpathDomEval('uploadStatus/completed/text()',xml).stringValue());
                    var contentLength = parseInt(
                          xpathDomEval('uploadStatus/total/text()',xml).stringValue())
                    …………
                    //进入“已初始化并开始更新进度”状态
                    if(contentLength > 0){
                        …………
                    }
                    //进入“已初始化并开始更新进度”状态
                    else if(contentLength == -1 && bytesRead > 0){ 
                        …………
                    }
                    //已完成
                    else if((bytesRead==-2 && contentLength==-2) || actionFinished || 
                      (progressBar.getValue()>1 && contentLength==0 && contentLength==0)){
                        …………
                    }
                    …………
                }
            }
            …………
        }
    }
</script>





回页首


结合使用 Ajax 技术和 Web Service 同步进度信息

通过上面的步骤我们已经实现了 Ajax 进度条,但是为了进一步将获取进度信息这一功能进行封装,下面我们将创建一个获取进度信息 Web service,然后在客户端使用 Ajax 技术调用这一 Web service。

创建 Web service

我们采用自底向上的模式来创建web service。首先,创建 JavaBean 来代表 web service 接口输入和输出参数的数据类型。StatusReport 服务的输入很简单,就是一个 String 类型的 id,因此只需要创建代表输出参数的数据类型 StatusData,包含已完成的大小和总大小。


清单 10. StatusData.java
                
public class StatusData implements Serializable{
    private long completed;
    private long total;
    
    public long getCompleted() {
        return completed;
    }
    public void setCompleted(long completed) {
        this.completed = completed;
    }
    public long getTotal() {
        return total;
    }
    public void setTotal(long total) {
        this.total = total;
    }
}

然后,创建 web service 的实现类 StatusReport。它只有一个方法 getStatusData,输入 id(String),返回该 id 对应的进度信息 StatusData。


清单 11. StatusReport.java
                
public class StatusReport {
    public StatusData getStatusData(String id){
        long[] status = (long[])FileUploadServlet.servletContext.getAttribute(id);
        if(status == null){
            status =  new long[2];
        }
        StatusData result = new StatusData();
        result.setCompleted(status[0]);
        result.setTotal(status[1]);
        return result;
    }
}

接下来,利用工具创建 web service。Eclipse 中的 web service 向导会根据 Java 类的签名生成 WSDL 文档,为该 Java 类的公共方法所使用的每个数据类型 (JavaBean,即前面创建的StatusData) 创建一个嵌入到该 WSDL 文档的 types 元素中的复杂类型定义,为该 Java 类的每个公共方法在 portType 元素中创建一个 operation 元素,并生成将该实现部署为应用程序服务器上的 Web service 所需的任何其他提供程序端构件。


图3. 在 Eclipse 中创建 web service
在 Eclipse 中创建 web service

图4. 配置 Server 为 Geronimo,运行时为 Apache Axis
Server 为 Geronimo,运行时为 Apache Axis

用 Ajax 调用 Web service

创建完 web service,就要回到如何用 Ajax 技术在浏览器端调用 web service 这个主题了。developerWorks 上一篇由 James Snell 撰写的文章《使用 AJAX 调用 SOAP Web 服务,第 1 部分: 构建 Web 服务客户机》中提供了一个JavaScript脚本库ws.js,该脚本库是一组 JavaScript 对象和实用功能,它们为基于 SOAP 1.1 的 Web service提供了基本的支持。ws.js 的核心是 WS.Call 对象,该对象提供了调用 Web service的方法。WS.Call 主要负责与 XMLHttpRequest 对象进行交互,并处理 SOAP 响应。WS.Call 对象公开了以下三个方法:

  • add_handler。向处理链添加请求/响应处理程序。处理程序对象在调用 Web service的前后被调用,以支持可扩展的预调用处理和后调用处理。
  • invoke。将指定的 SOAP.Envelope 对象发送给 Web service,然后在接收到响应后调用回调函数。当调用使用文本 XML 编码的文档样式的 Web service时(document/literal),请使用此方法。
  • invoke_rpc。创建一个封装 RPC 样式请求的 SOAP.Envelope,并将其发送到 Web service。当接收到响应时,调用回调函数。

有了ws.js脚本库,调用 web service 变得很简单。首先,以 web service URI 为参数创建一个 WS.Call 对象,将所要调用的操作名称(WS.QName对象,根据操作名和 web service 的名字空间来构建),参数数组(名值对),编码样式(可以为空),以及回调函数传递给 WS.Call 对象的 invoke_rpc 方法。在调用 invoke_rpc 方法时,WS.Call 对象会创建一个基本的 XMLHttpRequest 对象,用包含 SOAP Envelop 的 XML 元素进行传递,并接收和解析响应,然后调用前面提供的回调函数。


清单 12. 使用 WS.Call 对象调用 Web Service
                
<script type="text/javascript" src="js/prototype.js"></script>
<script type="text/javascript" src="js/ws.js"></script>
…………
this.pullData = function pullData(){
    var call = new WS.Call('http://localhost:8080/ProgressBar/services/StatusReport'); 
    var nsuri = 'http://service.fileupload.ibm.com';
    var qn_op = new WS.QName('getStatusData',nsuri); 
    call.invoke_rpc(qn_op, new Array({name:'id',value:uuid}),null,processRequest);
}

回调函数 processRequest(call, envelope),第一个参数是 WS.Call 对象,第二个参数是一个 SOAP.Envelope 对象,它代表的是 web service 返回的响应的 XML 数据。解析响应 XML 数据也很简单,但是需要了解 web service 的 response SOAP 消息的格式。以 StatusReport service 为例,用 SOAP.Envelope 的 get_body 方法可以得到响应的 SOAP 消息体(SOAP.Body对象),然后用get_all_children()[0]即可获得getStatusDataResponse节点,对getStatusDataResponse节点调用get_all_children()[0]得到getStatusDataReturn节点,getStatusDataReturn节点的第一个子节点即已上传的字节数,第二个子节点即总字节数。


清单13. SOAP 响应消息示例
                
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance">
    <soapenv:Body>
        <getStatusDataResponse xmlns="http://service.fileupload.ibm.com">
            <getStatusDataReturn>
                <completed>28205191</completed>
                <total>67237182</total>
            </getStatusDataReturn>
        </getStatusDataResponse>
    </soapenv:Body>
</soapenv:Envelope>


清单14. 从 SOAP 响应消息中获取进度信息
                
   function processRequest(call, envelope) {
    var bytesRead = envelope.get_body().get_all_children()[0].get_all_children()[0].
          get_all_children()[0].get_value();
    var contentLength = envelope.get_body().get_all_children()[0].get_all_children()[0].
          get_all_children()[1].get_value();
    …………
}

最后让我们体验一下运行结果吧。将本文提供的 ProgressBarWeb.war 装入到 Geronimo server 上,然后通过 http://localhost:8080/ProgressBar/ 就可以进行文件上传,并能看到文件上传过程中的 Ajax 进度条了,如图5所示。


图 5. 文件上传过程中的 Ajax 进度条
文件上传过程中的 Ajax 进度条




回页首


结束语

本文在介绍了如何创建 Ajax 进度条的同时,向您演示了如何应用 common fileupload 实现文件上传,如何应用 Ajax 解析 XML 数据,如何基于 Geronimo 创建 Web service,如何应用 Ajax 调用 Web service 等。

通过创建本实例,我们也充分体会到了Ajax技术给用户带来的桌面应用体验,以及 Apache Geronimo 的灵活和敏捷。






回页首


下载

描述名字大小下载方法
样本代码ProgressBarWeb.war1790KBHTTP
关于下载方法的信息


参考资料



作者简介

喻星是一位IBM软件工程师,工作在IBM中国软件开发实验室企业应用开发部门,在Java开发和Web开发方面有丰富的经验,现在正从事企业电子商务应用的开发和支持工作, 您可以通过 yuxing@cn.ibm.com 与她联系。


赵雄伟是一位IBM软件工程师,工作在IBM中国软件开发实验室企业应用开发部门,在 Java开发和Web开发方面有丰富的经验,现在正从事企业电子商务应用的开发和支持工作,您可以通过zhaoxw@cn.ibm.com与她联系。


王南是一位工作在 IBM CSDL 的软件工程师,目前从事企业电子商务应用的开发。您可以通过wnan@cn.ibm.com和他联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款