精华帖 (1) :: 良好帖 (1) :: 新手帖 (5) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-11-18
在网上问十个人,九个会告诉你XMLHttpRequest只能交互文本,实际上完全不是这样,不知道是不是因为太多的人一上手就开始用框架而不是研究HttpRequest对象的特性,但关于ResponseBody的使用几乎没有人谈及,这就有点遗憾了。首先,我找到下面一篇文章,较为详细地解释了HttpRequest对象的属性和方法: http://blog.chinaunix.net/u2/61797/showart_1000687.html 里面的东西我就不照抄了,这里只请大家注意“属性:responseBody”这个属性的讲解。显然,只依靠JQuery的几个AJAX函数,是无法处理这种返回数据的,这种情况下,只能自己编写直接给予XMLHttpRequest的交互函数了。 根据这篇文章所述,responseBody实际上是个二进制结构,在IE8的Debugger里显示为“Array of Byte”。实际上这个数据结构是兼容于VB/C 中的Byte数组的,比如在VB里面我们会这样定义一个Byte数组: Dim bytes(10) As Byte 但是js/vbs却是不支持这种数据结构的,ByteArray对于js来说只是内存中的一个特殊对象,其结构对js来说是完全透明的,脚本能够使用其内存地址来获取对其的引用,却不能操作这个透明物体。js所能做的,就是持有这个对象的地址引用,然后pass给其它可以处理该数据的组件或程序。 很多人可能一辈子都不会用到responseBody这个属性,所以有必要解释一下我为什么那么需要它。我的项目里需要实现一个机制,用servlet提供一个TIF文件流,然后客户端js应该能接收这个二进制流的数据,并将之传给一个图像浏览组件。这个流程的核心问题就是:图像浏览组件(ActiveX)只能接受ByteArray的二进制数据,故必须要想尽一切办法得到ByteArray对象。 在本人还未意识到responseBody之前,曾天真地认为:1)ByteArray是一个js能识别的Array对象;2)HttpRequest只能传输文本数据。在这种自以为是的思想指导下,我提出了第一个解决方案: 1) 在服务端Servlet取得文件流后将之byte by byte读出,然后组装成一个JSON格式的字符串,如: "[72, 43, 43, 0, 38 ...]"的形式; 2) 通过PrintWriter.print()方法把这个JSON字符串发送到客户端js的回调函数,并由该回调函数把JSON字符串转换成js数组; 3) 由于该js数组中的所有元素都是byte数据(范围在0~255内的整数),直接将该js数组作为ByteArray参数传入图像浏览组件,并显示图片。 然而,由于没有清楚认识到ByteArray的数据性质,上述的方案无疑是失败的,图像组件直接因参数类型不匹配而报错。并且,由于所有的byte都被转换成文本后才传输的,该交互行为需要的实际的网络流量远远超过了原二进制文件的容量(*见备注的换算)。 在阅读了responseBody的相关信息后,我坚信ByteArray数据是能被它接收到的,问题是Servlet应该怎样写数据才能传给responseBody一个ByteArray。首先我想到的是用response.getOutputStream(),具体代码如下: File file = new File("c:\\001.tif"); InputStream is = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(is); OutputStream out = response.getOutputStream(); int b = 0; int len = 0; byte[] buf = new byte[1024]; while ((len = bis.read(buf)) != -1) { out.write(buf, 0, len); } out.flush(); out.close(); bis.close(); 事实证明这个试验是不成功的,到达客户端的ByteArray数据量大得惊人,对于servlet的OutputStream没什么研究,一时摸不透原因。于是,我再试了下面的代码: File file = new File("c:\\001.tif"); InputStream is = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(is); PrintWriter out = resp.getWriter(); byte[] buf = new byte[1024]; int len = 0; try { while ((len = in.read(buf)) != -1) { char[] cbuf = byteArray2CharArray(buf); write(cbuf, 0, len); out.flush(); } } catch (IOException ex) { ex.printStackTrace(); } 其中byteArray2CharArray()方法的实现我就不写了,总之它的功能就是把一个byteArray翻译成一个charArray,各个元素的整数绝对值保持不变(在JVM里,无论是byte还是char最后都将被表示成int,但char是非负的所以采用绝对值)。这里比较怪异,试验仍然不太成功,虽然客户端responseBody接收到了一个size正确的ByteArray,里面的数据却是不正确的,因为在Buffered的读取方式下,有的byte值居然是负数,这是相当怪异的也是不正确的。接下来,一个有点“笨”的方法,居然取得了成功: File file = new File("c:\\001.tif"); InputStream is = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(is); PrintWriter out = resp.getWriter(); int b = 0; while ((b = bis.read()) != -1) { out.write(b); } out.flush(); out.close(); bis.close(); 最终,这个采用PrintWriter.write(int b)的方法获得了成功,客户端拿到了正确的ByteArray数据,图像文件也得以正确还原并显示。 虽然最后一个方法取得了成功,笔者心里还是有些不舒服,因为没有用到buffer,有多少个byte就得调用多少次write(int b)方法,通过查看JDK的源代码,该方法是强制同步的,获得锁释放锁等额外开销让人无法忘怀,这样写的话性能不可能很好,目前正考虑继承PrintWriter写一个类,还望这方面有心得的朋友能一起交流一下。至于byte[]的缓冲读取方式为何出现怪异数据我还耿耿于怀,苦于没有时间再去看JBoss的实现源码,只能请有研究的朋友不吝指教了。 *备注: 例如 121 这个整数,当其类型是byte的时候,只占1个字节,而字符串"121"则会占据6个字节,因为每一个字符char都会占据2个字节。在加上连接JSON数组需要用到","作分隔符也要占据2个字节,头尾一对方括号占4个字符暂且忽略不计。因此,假设一个ByteArray里面的数据全都是(byte)121,那么当其解析成字符串后,其大小应该是源数据的约8倍,对于一个基于网络的程序来说这种缺陷是不能容忍的。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-11-18
最后修改:2009-11-18
如果“图像浏览组件”也是你做的话,我建议你用下面两个方法(任选一):
1.后台输出的byte array转成 base64 送到前台,图像浏览组件内部对base64解码显示, base64编码后长度值是原长的 1.33 倍。 2.把URL直接传给图像浏览组件,它自己从内部发起http请求(用WinInet比较方便) 下载二进制的图像数据显示。 |
|
返回顶楼 | |
发表时间:2009-11-19
myy 写道 如果“图像浏览组件”也是你做的话,我建议你用下面两个方法(任选一):
1.后台输出的byte array转成 base64 送到前台,图像浏览组件内部对base64解码显示, base64编码后长度值是原长的 1.33 倍。 2.把URL直接传给图像浏览组件,它自己从内部发起http请求(用WinInet比较方便) 下载二进制的图像数据显示。 多写指教。可惜浏览组件不是我做的而是从一家公司购买的开发包,因为它还需要对图像做各种处理如切割、拉伸、定位、像素访问等等比较复杂的功能,自己写是精力不够了,况且我的C++水平最多也就做个数据传输工具,图形领域知识几乎等于零。 1. 把二进制数据流转成base64 。这个听起来不错,有机会加强编码方面的学习了解一下,不过暂时用不上,因为开发包定死了只能收byte数组。 2. URL直接给图像组件。这个实际上原来开发包里就有这个功能,但是它只能下载实际存在的图像文件,比如这样的URL: http://host_name:8080/AppName/images/001.tif ,如果是 http://host_name:8080/AppName/SomeServlet?fileId=30 这样的URL,它是不起作用的。但是我不清楚是ActiveX的http请求都有这个限制还是说仅仅是我手头这个开发包的功能缺陷? 请指教。 |
|
返回顶楼 | |
发表时间:2009-11-19
最后修改:2009-11-19
既然http://host_name:8080/AppName/images/001.tif可以用,那么http://host_name:8080/AppName/SomeServlet?fileId=30就没有理由不成。可以尝试一下URL rewriting,如果不成看一下SomeServlet的respons header是否正确设置了content type等。
|
|
返回顶楼 | |
发表时间:2009-11-19
我现在也用ActiveX请求服务器端的XML文件
在FIREFOX下这个就不好用 现在也在找有什么脱离ACTIVEX的方法请求 |
|
返回顶楼 | |
发表时间:2009-11-19
karlmax 写道 既然http://host_name:8080/AppName/images/001.tif可以用,那么http://host_name:8080/AppName/SomeServlet?fileId=30就没有理由不成。可以尝试一下URL rewriting,如果不成看一下SomeServlet的respons header是否正确设置了content type等。
这恐怕跟服务器端的stream的类型和方式有关,我也试过用byte[]缓冲输出流(缓冲区byte[]得到数据后,把它编码成相应的char[]),诡异的是,浏览器端收到了正确的二进制数据,但IE却随后立即崩溃了。以IO流输出文件只怕与实际文件地址URL读取还是有区别的,区别在哪里还要请人指教。 |
|
返回顶楼 | |
发表时间:2009-11-19
qbq 写道 我现在也用ActiveX请求服务器端的XML文件
在FIREFOX下这个就不好用 现在也在找有什么脱离ACTIVEX的方法请求 请求XML用AJAX的标准方法就可以了,ActiveX当然可以歇了。 |
|
返回顶楼 | |
发表时间:2009-11-19
qbq 写道 我现在也用ActiveX请求服务器端的XML文件
在FIREFOX下这个就不好用 现在也在找有什么脱离ACTIVEX的方法请求 请问兄台:你是如何处理firefox下调用自己的activex的? |
|
返回顶楼 | |
发表时间:2009-11-19
jeff312 写道 qbq 写道 我现在也用ActiveX请求服务器端的XML文件
在FIREFOX下这个就不好用 现在也在找有什么脱离ACTIVEX的方法请求 请求XML用AJAX的标准方法就可以了,ActiveX当然可以歇了。 不是这么简单的 我只是说得简单 中间还有其他东西 |
|
返回顶楼 | |
发表时间:2009-11-19
zhuyx808 写道 qbq 写道 我现在也用ActiveX请求服务器端的XML文件
在FIREFOX下这个就不好用 现在也在找有什么脱离ACTIVEX的方法请求 请问兄台:你是如何处理firefox下调用自己的activex的? FIREFOX下activex不好用 我是在找脱离ACTIIVEX的方法 |
|
返回顶楼 | |