繁体   English   中英

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

[英]Disabling Multipart Caching in CXF jax-rs

这个问题发布到了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

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

谢谢。

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

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

public Response receiveStream(InputStream input) {

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

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

由于我正在编写服务器端,因此我知道我按照它们发送的顺序访问所有附件,并在流式传输时处理它们。因此,为了反映处理程序方法的行为(上面的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();
            }
        }
    }
}

虽然这解决了我当前的问题,但我仍然认为必须有一种标准的方法来做到这一点。 我希望这可以帮助别人。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM