在CXF jax-rs中禁用多部分缓存

时间:2012-11-28 02:36:05

标签: java cxf jax-rs

posted将此问题提交至CXF列表,没有任何运气。所以我们走了。我正在尝试将大文件上传到远程服务器(想想它们是虚拟机磁盘)。所以我有一个接受上传请求的宁静服务。上传的处理程序如下所示:

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/doupload")
public Response receiveStream(MultipartBody multipart) {
    List<Attachment> allAttachments = body.getAllAttachments();
    Attachment att = null;
    for (Attachment b : allAttachments) {
        if (UPLOAD_FILE_DESCRIPTOR.equals(b.getContentId())) {
            att = b;
        }
    }
    Assert.notNull(att);
    DataHandler dh = att.getDataHandler();
    if (dh == null) {
        throw new WebApplicationException(HTTP_BAD_REQUEST);
    }
    try {
        InputStream is = dh.getInputStream();
        byte[] buf = new byte[65536];
        int n;
        OutputStream os = getOutputStream();
        while ((n = is.read(buf)) > 0) {
            os.write(buf, 0, n);
        }
        ResponseBuilder rb = Response.status(HTTP_CREATED);
        return rb.build();
    } catch (IOException e) {
        log.error("Got exception=", e);
        throw new WebApplicationException(HTTP_INTERNAL_ERROR);
    } catch (NoSuchAlgorithmException e) {
        log.error("Got exception=", e);
        throw new WebApplicationException(HTTP_INTERNAL_ERROR);
    } finally {}

}

此代码的客户端非常简单:

public void sendLargeFile(String filename) {
    WebClient wc = WebClient.create(targetUrl);
    InputStream is = new FileInputStream(new File(filename));
    Response r = wc.post(new Attachment(Constants.UPLOAD_FILE_DESCRIPTOR,
        MediaType.APPLICATION_OCTET_STREAM, is));
}

代码在功能方面运行良好。在性能方面,我注意到在我的处理程序(receiveStream()方法)获取流的第一个字节之前,整个流实际上被持久化到一个临时文件中(使用CachedOutputStream)。不幸的是,这对我来说是不可接受的。

  • 我的处理程序只是将传入的字节传递给后端存储系统(虚拟机磁盘存储库),并等待整个磁盘写入缓存只是为了再次读取需要花费很多时间,占用大量的资源,并降低吞吐量。
  • 由于应用程序在云中运行,并且云提供商每个块读取/写入费用,因此编写块并再次读取它们会产生相关成本。
  • 由于每个字节都写入本地磁盘,因此我的服务VM必须有足够的磁盘空间来容纳所有上传的流的总大小(即,如果我有10个上传的每个100GB,我必须有1TB的磁盘只是为了缓存内容)。这也是额外的资金,因为服务VM的规模急剧增长,云提供商也为配置的磁盘大小收费。

鉴于所有这些,我正在寻找一种方法来使用HTTP InputStream(或尽可能接近它)直接从那里读取附件并在之后处理它。我猜这个问题转化为以下之一: - 有没有办法告诉CXF不要缓存 - 或者 - 是否有办法传递CXF输出流(我写的一个),而不是使用CachedOutputStream

我发现了一个类似的问题here。决议说使用CXF 2.2.3或更高版本,我使用2.4.4(并试用2.7.0)没有运气。

感谢。

2 个答案:

答案 0 :(得分:2)

我认为这在逻辑上是不可能的(无论是在CXF还是其他任何地方)。您正在调用getAllAttachements(),这意味着服务器应该从HTTP输入流中收集有关它们的信息。这意味着整个流必须进入内存进行MIME解析。

在您的情况下,您应该直接使用流,并自己进行MIME解析:

public Response receiveStream(InputStream input) {

现在您可以完全控制输入,并可以逐字节地将其消耗到内存中。

答案 1 :(得分:0)

我最终以一种不雅的方式修复问题,但它确实有效,所以我想分享我的经验。如果有一些“标准”或更好的方法,请告诉我。

由于我正在编写服务器端,因此我知道我按照发送顺序访问所有附件,并在流式传输时处理它们。因此,要反映处理程序方法的行为(receiveStream()方法上面),我在服务器端创建了一个名为“@SequentialAttachmentProcessing”的新注释,并使用它注释了我的上述方法。

另外,写了一个Attachment的子类,名为SequentialAttachment,其作用类似于链表。它有一个跳过当前附件的skip()方法,当附件结束时,hasMore()方法会告诉你是否有另一个附件。

然后我编写了一个自定义的multipart / form-data提供程序,其行为如下:如果目标方法如上所述,请处理附件,否则调用默认提供程序进行处理。当它由我的提供者处理时,它总是最多返回一个附件。因此,它可能会误导非怀疑的处理方法。但是,我认为这是可以接受的,因为服务器的编写者必须将方法注释为“@SequentialAttachmentProcessing”,因此必须知道这需要什么。

因此,receiveStream()方法的实现现在类似于:

@POST
@SequentialAttachmentProcessing
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/doupload")
public Response receiveStream(MultipartBody multipart) {
    List<Attachment> allAttachments = body.getAllAttachments();
    Assert.isTrue(allAttachments.size() <= 1);
    if (allAttachment.size() > 0) {
        Attachment head = allAttachments.get(0);
        Assert.isTrue(head instanceof SequentialAttachment);
        SequentialAttachment att = (SequentialAttachment) head;
        while (att != null) {
            DataHandler dh = att.getDataHandler();
            InputStream is = dh.getInputStream();
            byte[] buf = new byte[65536];
            int n;
            OutputStream os = getOutputStream();
            while ((n = is.read(buf)) > 0) {
                os.write(buf, 0, n);
            }
            if (att.hasMore()) {
                att = att.next();
            }
        }
    }
}

虽然这解决了我的直接问题,但我仍然认为必须有一种标准的方法来做到这一点。我希望这有助于某人。