用Web Service发送文件

有时候,你不可以避免需要实现文件传送的功能。如果你可以用FTP或者共享磁盘的方法,那自然就没有其他问题。但是,如果你还是不可以避免的需要用Web Service来传送文件,那事情就不是那么简单了。我们来讨论讨论这个话题。

 

1、Base64 byte[]

    一般来说,最简单的方式就是利用byte[]作为数据类型来设计你的Web Service。byte[]无论是作为一个简单参数或者你的一个自定义对象的成员变量都可以。

    客户端把文件内容全部读出,填入一个byte[],然后经过base64的encode(这个工作一般由Web Service框架来完成),然后发送给服务端,服务端先经过base64的decode(同样,一般由框架完成),然后应用程序就可以拿到byte[]了。

    这个方法比较简单,但是其问题也不小,就是不适合传输大文件。原因base64加密后的byte[]实际上是作为SOAP报文XML的一部分的,其字节数大约比加密前的字节数多三分之一。要命的是这些内容会被全部整体读入内存(因为XML报文解析),如果文件很大,那么内存的占用就很厉害,想象系统一般是多用户共用的,是不是很容易闹出OOM(OutOfMemory)的问题?

 

2、SOAP Attachments

    类似Email的附件,SOAP也可以采用类似的方式添加附件,即二进制的文件保持不变,不需要改变编码方式以迁就XML,其内容不出现在SOAP的XML报文中。

    一下是一段样例的HTTP报文:

代码
POST /Provider/MyServiceService HTTP/1.1
Content-type: multipart/related; type="text/xml"; boundary="uuid:37d1fd95-6636-4701-b22f-90a007cf7e92"
Soapaction: ""
Accept: text/xml, multipart/related, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
User-Agent: JAX-WS RI 2.1.6 in JDK 6
Host: suseles10:7001
Connection: keep-alive
Content-Length: 468

--uuid:37d1fd95-6636-4701-b22f-90a007cf7e92
Content-Type: text/xml

<?xml version="1.0" ?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:sayHello xmlns:ns2="http://provider/"><arg0 xmlns="">Alfred</arg0></ns2:sayHello></S:Body></S:Envelope>
--uuid:37d1fd95-6636-4701-b22f-90a007cf7e92
Content-Id:null
Content-Type: text/plain
Content-Transfer-Encoding: binary

This_Is_A_Test_String
--uuid:37d1fd95-6636-4701-b22f-90a007cf7e92--

    以上示例中,This_Is_A_Test_String代表的就是你的二进制数据。从示例中也可以看出,这段二进制数据并不存放于SOAP的Envelope中。

 

   不过这种方式下对编程人员有点麻烦,你需要用SAAJ编程API来操作AttachmentPart。示例如下:

代码
Iterator attIt = soapMessage.getAttachments();
while (attIt.hasNext()) {
AttachmentPart attachPart
= (AttachmentPart) attIt.next();
System.out.println(
"MySoapHandler - attachment part, content type=" + attachPart.getContentType() + ", size=" + attachPart.getSize() + ", content:" + attachPart.getContent());
}

    你能够拿到AttachmentPart的要点是先拿到MessageContext,有两种方法,一是通过Soap Handler,一是通过WebServiceContext。

3、MTOM

    结合以上两种方式的优点,既可以保持二进制形式的文件内容,又保持相对简单的编程模式。以下即是一个样例的报文:

代码
POST /MtomService/MtomService HTTP/1.1
User
-Agent: BEA WebLogic Server 10.3.0.0
Content
-Type: multipart/related;boundary="----=_Part_0_18484533.1268196338812";type="application/xop+xml";start="<soapPart>";start-info="application/soap+xml"; action=""
Host:
192.168.70.1:8001
Accept: text
/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 808

------=_Part_0_18484533.1268196338812
Content-Type: application/xop+xml; charset=utf-8; type="application/soap+xml"; action=""
Content-Transfer-Encoding: 8bit
Content-ID: <soapPart>

<env:Envelope xmlns:env="
http://www.w3.org/2003/05/soap-envelope"><env:Header/><env:Body><m:echoBinaryAsString xmlns:m="http://examples/webservices/mtom"><m:bytes><Include xmlns="http://www.w3.org/2004/08/xop/include" href="cid:bytes=20b8ae8e-b061-4446-af12-dbbd125834de@http://examples/webservices/mtom"/></m:bytes></m:echoBinaryAsString></env:Body></env:Envelope>
------=_Part_0_18484533.1268196338812
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
Content-ID: <bytes=20b8ae8e-b061-4446-af12-dbbd125834de@
http://examples/webservices/mtom>

FOO
------=_Part_0_18484533.1268196338812--

    注意,其中的我的Web Service服务端参数的名字便是bytes,因此以上报文出现m:bytes元素。可以看出,在SOAP Body报文中插入了引用,引用的内容是SOAP Envelop外的二进制内容片段(part)。

    另外,值得注意的是,在不同的平台(或者说不同的Web Service框架)下使用MTOM,其设置或编程的方法是不同的,在WebLogic 10里,你可以参考如下的样例代码:

代码
import weblogic.jws.Binding;
import weblogic.jws.Policy;
import javax.jws.WebService;
import javax.jws.WebMethod;

@WebService
@Binding(Binding.Type.SOAP12)
//enable MTOM for this web service by adding the canned MTOM policy file
@Policy(uri = "policy:Mtom.xml")
public class MtomService {
@WebMethod
public String echoBinaryAsString(byte[] bytes) {
return new String(bytes);
}
}

 

    然而,值得注意的是MTOM和SOAP Attachments这两种方式对大文件都也存在内存问题,即大文件的内容字节bytes也是在内存中存放着的,如果文件很大,很容易出OOM的问题,解决的方式就是以下要讲的第四种方式。

4、Streaming Mode

    顾名思义,Streaming Mode就是流模式,对于大文件而言,其内容并不整体保存在内存中,系统拿到一小撮数据后交给应用层,应用程序把这小撮数据保存到文件中或者作另外的处理,然后再跟系统要下一小撮数据。。。如此往复,直至所有数据处理完毕。这种模式是对付大文件,避免OOM问题的最好的手段。

    服务端代码样例:

 

代码
@WebService(name = "StreamingAttachWSPortType",
serviceName
= "StreamingAttachWSService",
targetNamespace
="http://example.org")
@StreamingAttachment(parseEagerly
=false)
public class StreamingAttachWSService {
@WebMethod
public void fileUpload(String fileName, @XmlMimeType("application/octet-stream") DataHandler dataHandler) {
try {
InputStream inStream
= new BufferedInputStream(dataHandler.getInputStream(), 8192);
OutputStream outStream
= new BufferedOutputStream(new FileOutputStream(new File("/home/alfred/tmp/" + fileName + ".2")));
int bytesRead;
byte[] buffer = new byte[2048];
while ((bytesRead = inStream.read(buffer, 0, buffer.length)) != -1) {
outStream.write(buffer,
0, bytesRead);
}

outStream.close();
inStream.close();
}
catch (Exception e) {
System.out.println(
"I/O exception, " + e.getMessage());
}
}
}

    客户端代码样例:

 

 

代码
StreamingAttachWSService service = new StreamingAttachWSService();
MTOMFeature feature
= new MTOMFeature();
StreamingAttachWSPortType port
= service.getStreamingAttachWSPortTypePort(feature);
Map
<String, Object> ctxt=((BindingProvider)port).getRequestContext();
ctxt.put(JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE,
8192);

DataHandler dh
= new DataHandler(new URL("file:///" + args[0]));
port.fileUpload(
"newtest", dh);

 

    我在WebLogic下试过,服务器的heap只开到128M,客户端是J2SE,也限制了heap只用到64m,传输一个385M的文件,没有问题,速度基本跟HTTP上传下载差不多。这个测试是在WebLogic 10.3下通过的,在其他平台(应用服务器及Web Service框架)及版本下的使用方法或许不同。

posted @ 2010-09-13 11:58  长须飘飘  阅读(4319)  评论(0编辑  收藏  举报