使用 JAX-WS 构建大型 MTOM/XOP 消息
我有一个关于将 MTOM/XOP 与 JAX-WS 结合使用的问题。我正在编写一个发送大量二进制数据的网络服务。客户端请求多个文件,服务器在响应中返回文件。
我能够让它正确构建响应,以便它正确实现 XOP,但我遇到了与内存相关的问题,因为它在发送之前将整个响应存储在内存中。此 Web 服务发送的文件可能会变得非常大(例如千兆字节大),因此不能将响应存储在内存中。
此 Oracle 网站(以及这个)似乎解决了这个问题,但我只是不明白。我认为他们使用 DataHandler 对象来传输请求/响应,但我不知道他们如何实例化它。
我正在使用 wsimport
从现有 WSDL 生成 JAX-WS 类文件。我正在将 JAX-WS RI 2.1.6 与 Java 6 结合使用。
在构建响应时如何发送响应,而无需先将其全部存储在内存中?
提前感谢您的帮助。
更新 12/17: 我将以下属性添加到保存二进制数据的 WSDL 中的架构元素中。这会导致 wsimport
将 DataHandler
对象添加到 JAXB 类。然后可以将 FileDataHandler 添加到响应中,而不是添加文件的全部内容,从而允许服务器流式传输每个文件的内容,而不是将它们全部保存在内存中:
xmlns:xmime="http://www.w3.org/2005/05/xmlmime"
xmime:expectedContentTypes="application/octet-stream"
因此,服务器现在正确构建响应,并且客户端在收到请求时将每个文件正确保存到磁盘。然而,由于某种原因,客户端仍然将整个响应读取到内存中。
服务器代码(SIB):
@MTOM
@StreamingAttachment(parseEagerly = true, memoryThreshold = 4000000L)
@WebService(...)
public class DownloadFilesPortTypeImpl implements DownloadFilesPortType {
@Override
public FileSetResponseType downloadFileSet(FileSetRequestType body) {
FileSetResponseType response = new FileSetResponseType();
for (FileRequest freq : body.getFileRequest()){
try{
//find the file on disk
File file = findFile(freq.getFileId());
//read the file data into memory
byte[] fileData;
{
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte buf[] = new byte[8192];
int read;
while ((read = in.read(buf)) != -1){
out.write(buf, 0, read);
}
in.close();
out.close();
fileData = out.toByteArray();
}
//add the file to the response
FileResponse fresp = new FileResponse();
fresp.setFileId(freq.getFileId());
fresp.setData(fileData); //<-- type "xs:base64Binary"
response.getFileResponse().add(fresp);
}
catch (IOException e){
}
}
return response;
}
}
客户端代码:
DownloadFilesService service = new DownloadFilesService();
MTOMFeature mtomFeature = new MTOMFeature();
StreamingAttachmentFeature stf = new StreamingAttachmentFeature(null, true, 4000000L);
DownloadFilesPortType port = service.getDownloadFilesPortSoap12(mtomFeature, stf);
FileSetRequestType request = new FileSetRequestType();
FileRequest freq = new FileRequest();
freq.setFileId("1234");
request.getFileRequest().add(freq);
freq = new FileRequest();
freq.setFileId("9876");
request.getFileRequest().add(freq);
//...
FileSetResponseType response = port.downloadFileSet(request); //reads entire response into memory
for (FileResponse fres : response.getFileResponse()){
byte[] data = fres.getFileData();
//...
}
I have a question about using MTOM/XOP with JAX-WS. I'm writing a web service which sends large amounts of binary data. The client requests a number of files and the server returns the files in the response.
I'm able to get it to build the response correctly so that it correctly implements XOP, but I run into memory-related issues becasuse it stores the entire response in memory before sending it. The files this web service sends can get very large (like, giga-bytes large), so storing the response in memory is not an option.
This Oracle website (and along with this one) seems to solve this problem, but I just don't understand it. I think they use a DataHandler
object to stream the request/response, but I can't figure out how they instantiate it.
I'm generating my JAX-WS class files from an existing WSDL using wsimport
. I'm using JAX-WS RI 2.1.6 with Java 6.
How do I send the response as I'm building it without having to store in all in memory first?
Thanks in advance for your help.
UPDATE 12/17: I added the following attributes to the schema element in the WSDL that holds the binary data. This causes wsimport
to add a DataHandler
object to the JAXB class. A FileDataHandler
can then be added to the response, instead of adding the entire contents of the file, allowing the server to stream the contents of each file, instead of holding them all in memory:
xmlns:xmime="http://www.w3.org/2005/05/xmlmime"
xmime:expectedContentTypes="application/octet-stream"
So, the server correctly builds the response now, and client properly saves each file to disk when it receives the request. However, the client still reads the entire response into memory for some reason.
The server code (SIB):
@MTOM
@StreamingAttachment(parseEagerly = true, memoryThreshold = 4000000L)
@WebService(...)
public class DownloadFilesPortTypeImpl implements DownloadFilesPortType {
@Override
public FileSetResponseType downloadFileSet(FileSetRequestType body) {
FileSetResponseType response = new FileSetResponseType();
for (FileRequest freq : body.getFileRequest()){
try{
//find the file on disk
File file = findFile(freq.getFileId());
//read the file data into memory
byte[] fileData;
{
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte buf[] = new byte[8192];
int read;
while ((read = in.read(buf)) != -1){
out.write(buf, 0, read);
}
in.close();
out.close();
fileData = out.toByteArray();
}
//add the file to the response
FileResponse fresp = new FileResponse();
fresp.setFileId(freq.getFileId());
fresp.setData(fileData); //<-- type "xs:base64Binary"
response.getFileResponse().add(fresp);
}
catch (IOException e){
}
}
return response;
}
}
The client code:
DownloadFilesService service = new DownloadFilesService();
MTOMFeature mtomFeature = new MTOMFeature();
StreamingAttachmentFeature stf = new StreamingAttachmentFeature(null, true, 4000000L);
DownloadFilesPortType port = service.getDownloadFilesPortSoap12(mtomFeature, stf);
FileSetRequestType request = new FileSetRequestType();
FileRequest freq = new FileRequest();
freq.setFileId("1234");
request.getFileRequest().add(freq);
freq = new FileRequest();
freq.setFileId("9876");
request.getFileRequest().add(freq);
//...
FileSetResponseType response = port.downloadFileSet(request); //reads entire response into memory
for (FileResponse fres : response.getFileResponse()){
byte[] data = fres.getFileData();
//...
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您可以创建自己的类来实现 DataSource 并构造传入它的 DataHandler。它甚至可以是匿名的。
在 Apache CXF 中,我们使这一切变得更加容易。您可以只拥有一个返回 DataSource 或 DataHandler 的“getter”。您发布的代码中的复杂方案对我来说并不熟悉。
我认为同样的方法也适用于 JDK 的 JAX-WS+JAXB。请参阅此。
You make your own class that implements
DataSource
and construct the DataHandler passing it in. It can even be anonymous.In Apache CXF, we make it much easier to do this. You can just have a 'getter' that returns a DataSource or a DataHandler. The elaborate scheme in the code you've posted is not something familiar to me.
I think that the same methods work with the JDK's JAX-WS+JAXB. See this.
使用 Java 6(Metro 或其一部分)中包含的 JAX-WS RI,客户端代码必须设置 HTTP 分块大小,以通过 DataHandler InputStream 启用大型 MTOM 附件的流式传输。
但目前由于错误 JAX_WS-936 而无法工作,
因此,即使使用正确的分块和 DataHandler,附件也会在服务调用时立即完全加载到内存中。我检查了网络流量:在读取 DataHandler 输入流之前传输完整内容,以及 Java 堆内存使用情况:它已增加到包含至少三个附件副本。
With JAX-WS RI included in Java 6 (Metro or part of it), the client code must set HTTP chunking size to enable streaming of large MTOM attachments through DataHandler InputStream.
BUT at the moment it just not works because of a bug JAX_WS-936
As a result, even with proper chunking and DataHandler usage, an attachment gets fully loaded in memory immediately at service invocation. I have checked network traffic: full content transfert before reading DataHandler input stream, and Java heap memory usage: it has increased to contain at least three copies of the attachment.