(呕心沥血)用HttpRequest对象获取服务端的二进制数据

js框架很多,JQuery算是其中比较有趣的,AJAX功能用起来也挺方便,我手头的应用,视图层的交互和XML解析全靠它了。缺点不是没有,jq的AJAX请求函数只支持接收XML/HTML/JSON/JSONP格式的服务器数据。在普通情况下,我们往往只是用AJAX呼叫服务器获取相关文本、数值或XML这种结构化的文档,这些返回数据,说到底都是基于文本的,以至于给我们制造了一个错觉,即AJAX/XMLHttpRequest[b]只能用于基于文本的[/b]内容/数据交互。在疯狂Google了一天后的结果,也证实了我的判断令人尴尬地准确着。

在网上问十个人,九个会告诉你XMLHttpRequest只能交互文本,实际上完全不是这样,不知道是不是因为太多的人一上手就开始用框架而不是研究HttpRequest对象的特性,但关于ResponseBody的使用几乎没有人谈及,这就有点遗憾了。首先,我找到下面一篇文章,较为详细地解释了HttpRequest对象的属性和方法:
[url]http://blog.chinaunix.net/u2/61797/showart_1000687.html[/url]
里面的东西我就不照抄了,这里只请大家注意“属性: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倍,对于一个基于网络的程序来说这种缺陷是不能容忍的。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Netty中解析multipart/form-data请求体中的二进制数据需要进行以下步骤: 1. 创建一个自定义的ChannelHandler来处理HTTP请求。你可以扩展Netty的`SimpleChannelInboundHandler`类,并重写`channelRead0`方法。 2. 在`channelRead0`方法中,检查请求是否是multipart/form-data类型。你可以通过检查请求头的Content-Type来判断。如果是multipart/form-data类型的请求,你需要使用Netty提供的`HttpPostRequestDecoder`来解码请求体。 3. 创建一个新的`HttpPostRequestDecoder`对象,并将HTTP请求传递给它。然后,使用`isMultipart`方法检查请求是否是一个有效的multipart请求。 4. 使用`offer`方法将所有的HTTP请求内容添加到`HttpPostRequestDecoder`中,直到请求被完全解码。 5. 使用`next()`方法从解码器中获取每个解码后的HTTP内容。通常,这将返回一个`InterfaceHttpData`对象,你可以根据其类型进行处理。 6. 如果`InterfaceHttpData`是`FileUpload`类型,那么这个对象就代表一个上传的文件。你可以使用`FileUpload`对象的方法来获取文件名、保存文件等。 7. 如果`InterfaceHttpData`是`Attribute`类型,那么这个对象就代表一个表单字段。你可以使用`Attribute`对象的方法来获取字段名和字段值。 下面是一个示例代码片段,展示了如何在Netty中解析multipart/form-data请求体中的二进制数据: ```java import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.multipart.Attribute; import io.netty.handler.codec.http.multipart.FileUpload; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; public class MultipartHandler extends SimpleChannelInboundHandler<FullHttpRequest> { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { if (request.method() == HttpMethod.POST && HttpHeaders.is100ContinueExpected(request)) { ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("HTTP/1.1 100 Continue\r\n\r\n".getBytes())); } if (request.method() == HttpMethod.POST && request.headers().contains(HttpHeaders.Names.CONTENT_TYPE)) { String contentType = request.headers().get(HttpHeaders.Names.CONTENT_TYPE); if (contentType.startsWith("multipart/form-data")) { HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request); while (decoder.hasNext()) { InterfaceHttpData data = decoder.next(); if (data != null) { if (data instanceof FileUpload) { FileUpload fileUpload = (FileUpload) data; // 处理上传的文件 String fileName = fileUpload.getFilename(); // 保存文件到磁盘等操作 } else if (data instanceof Attribute) { Attribute attribute = (Attribute) data; // 处理表单字段 String fieldName = attribute.getName(); String fieldValue = attribute.getValue(); } data.release(); } } } } } } ``` 这只是一个简单的示例,你可以根据你的需要进行调整和扩展。请注意,这个示例中使用的是Netty 4.x版本的API,如果你使用的是其他版本,可能会有一些差异。确保你根据你使用的Netty版本进行相应的调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值