处理CXF APIs中的多部分附件

4

我正在尝试使用Apache CXF开发API调用,在请求中添加附件。我遵循了这个教程,目前已经有了以下内容。

@POST
@Path("/upload")
@RequireAuthentication(false)
public Response uploadWadl(MultipartBody multipartBody){
    List<Attachment> attachments = multipartBody.getAllAttachments();
    DataHandler dataHandler = attachments.get(0).getDataHandler();
    try {
        InputStream is = dataHandler.getInputStream();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return Response("OK");
}

我已经获得了一个InputStream对象来获取附件,一切正常。然而,我需要将该附件作为java.io.File对象传递给另一个函数。我知道我可以在这里创建一个文件,从输入流中读取并写入它。但是有没有更好的解决方案?CXF是否已将其存储为文件?如果是这样,我可以直接使用它。有什么建议吗?

1个回答

16

我对这个问题也很感兴趣。在CXF邮件列表上与Sergey讨论时,我了解到如果附件超过某个阈值,CXF将使用临时文件。

在这个过程中,我发现了这篇博客文章,它解释了如何安全地使用CXF附件。 您也可以看一下此页面上的示例。

目前我正在进行调查,这就是我能说的全部,希望能有所帮助。


编辑:目前我们在CXF 2.6.x中处理附件的方式如下。关于使用多部分内容类型上传文件的方法,请参见以下代码:

  @POST
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.MULTIPART_FORM_DATA)
  @Path("/")
  public Response archive(
          @Multipart(value = "title", required = false) String title,
          @Multipart(value = "hash", required = false) @Hash(optional = true) String hash,
          @Multipart(value = "file") @NotNull Attachment attachment) {

    ...

    IncomingFile incomingFile = attachment.getObject(IncomingFile.class);

    ...
  }

有几点需要注意:

  • @Multipart 不是JAXRS标准,甚至不在JAXRS 2中,它是CXF的一部分。
  • 在我们的代码中,我们实现了bean验证(您必须在JAXRS 1中自己完成)
  • 您不必使用MultipartBody,关键是要使用Attachment类型的参数

所以,就我们所知,目前还没有直接在方法签名中获取所需类型的可能性。例如,如果您只想获取附件的InputStream,则不能将其放在方法的签名中。您必须使用org.apache.cxf.jaxrs.ext.multipart.Attachment类型并编写以下语句:

InputStream inputStream = attachment.getObject(InputStream.class);

同时我们在 Sergey Beryozkin 的帮助下发现可以转换或者包装这个 InputStream,这就是为什么在上面的片段中我们写道:

Also we discovered with the help of Sergey Beryozkin that we could transform or wrap this InputStream, that's why in the above snippet we wrote :

IncomingFile incomingFile = attachment.getObject(IncomingFile.class);

IncomingFile 是我们自定义的一个 InputStream 包装器,您需要注册一个 MessageBodyReaderParamHandler 无法帮助您,因为它们不适用于流,而是适用于 String

@Component
@Provider
@Consumes
public class IncomingFileAttachmentProvider implements MessageBodyReader<IncomingFile> {
  @Override
  public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return type != null && type.isAssignableFrom(IncomingFile.class);
  }

  @Override
  public IncomingFile readFrom(Class<IncomingFile> type,
                              Type genericType,
                              Annotation[] annotations,
                              MediaType mediaType,
                              MultivaluedMap<String, String> httpHeaders,
                              InputStream entityStream
  ) throws IOException, WebApplicationException {

    return createIncomingFile(entityStream, fixedContentHeaders(httpHeaders)); // the code that will return an IncomingFile
  }
}

请注意,已经进行了一些试验以了解传递的内容,方法以及修补错误的方式。(例如附件部分的第一个标题的第一个字母是“e”,因此您有ontent-Type而不是Content-Type

当然,entityStream代表附件的实际InputStream。该流将从内存或磁盘读取数据,具体取决于CXF放置数据的位置;为此有一个大小阈值属性(attachment-memory-threshold)。您还可以指定临时附件的位置(attachment-directory)。

完成时,请关闭流(某些工具会为您执行此操作)。

配置好所有内容后,我们使用Johan Haleby的Rest-Assured进行了测试。(其中一些代码是我们的测试工具的一部分)

given().log().all()
        .multiPart("title", "the.title")
        .multiPart("file", file.getName(), file.getBytes(), file.getMimeType())
.expect().log().all()
        .statusCode(200)
        .body("store_event_id", equalTo("1111111111"))
.when()
        .post(host().base().endWith("/store").toStringUrl());

或者,如果您需要通过curl上传文件,请这样操作:

curl --trace -v -k -f
     --header "Authorization: Bearer b46704ff-fd1d-4225-9dd4-e29065532b73"
     --header "Content-Type: multipart/form-data"
     --form "hash={SHA256}3e954efb149aeaa99e321ffe6fd581f84d5a497b6fab5c86e0d5ab20201f7eb5"
     --form "title=fantastic-video.mp4"
     --form "archive=@/the/path/to/the/file/fantastic-video.mp4;type=video/mp4"
     -X POST http://localhost:8080/api/video/event/store
为了结束这个答案,我想提一下在多部分中可以有JSON有效负载,为此您可以在签名中使用Attachment类型,然后编写。
Book book = attachment.getObject(Book.class)

或者您可以像这样编写一个参数:

@Multipart(value="book", type="application/json") Book book

只需在请求时将Content-Type头部添加到相关部分即可。

值得一提的是,可以将所有部分放在列表中,只需编写一个类型为List<Attachment>的单参数方法。但我更喜欢在方法签名中使用实际参数,因为这样更清晰,没有样板代码。

@POST
void takeAllParts(List<Attachment> attachments)

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