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

3

我在CXF列表中发布了这个问题,但没有得到任何帮助。所以我们来试试吧。我正在尝试将大文件上传到远程服务器(将它们视为虚拟机磁盘)。所以我有一个接受上传请求的restful服务。上传处理程序如下:

@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
我在这里发现了一个类似的问题。解决方案说使用CXF 2.2.3或更高版本,但我使用的是2.4.4(并尝试过2.7.0),但没有成功。
谢谢。
2个回答

3

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

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

public Response receiveStream(InputStream input) {

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

1
谢谢。我认为你有点头绪了。但请注意,当它进入我的处理程序并调用getAllAttachments()函数时,整个消息已经被读取和缓冲了。不过你给了我一个想法。我认为是MIME类型导致这种行为。我将尝试看看是否可以使用另一种MIME类型来将InputStream传递给处理程序。再次感谢,这给了我一些可供使用的东西。 - Virtually Real

1

我最终用一种不太优雅的方式解决了问题,但它确实有效,所以我想分享我的经验。如果有一些“标准”或更好的方法,请务必让我知道。

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

此外,我编写了一个名为SequentialAttachment的Attachment子类,它的行为类似于链表。它有一个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();
            }
        }
    }
}

虽然这解决了我的问题,但我仍相信必须有一种标准的做法。我希望这能帮到别人。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接