如何从与Jersey的多部分表单中读取具有相同名称的多个(文件)输入?

时间:2012-08-25 20:08:52

标签: java forms jersey jax-rs multipart

我已成功开发了一项服务,其中我读取了在泽西岛以多部分形式上传的文件。这是我一直在做的非常简化的版本:

@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("file") InputStream uploadedInputStream,
        @FormDataParam("file") FormDataContentDisposition fileDetail) throws IOException {
    //handle the file
}

这很好但我已经获得了新的要求。除了我上传的文件外,我还要处理任意数量的资源。我们假设这些是图像文件。

我想我只是为客户端提供一个表单,其中包含一个文件输入,第一个图像的一个输入和一个允许向表单添加更多输入的按钮(使用AJAX或简单的JavaScript)。

<form action="blahblahblah" method="post" enctype="multipart/form-data">
   <input type="file" name="file" />
   <input type="file" name="image" />
   <input type="button" value="add another image" />
   <input type="submit"  />
</form>

因此,用户可以为表单添加更多图像输入,如下所示:

<form action="blahblahblah" method="post" enctype="multipart/form-data">
   <input type="file" name="file" />
   <input type="file" name="image" />
   <input type="file" name="image" />
   <input type="file" name="image" />
   <input type="button" value="add another image" />
   <input type="submit"  />
</form>

我希望阅读与集合同名的字段非常简单。我在MVC .NET中使用文本输入成功完成了它,我认为在Jersey中不会更难。事实证明我错了。

没有找到关于这个主题的教程,我开始尝试。

为了了解如何操作,我将问题简化为简单的文本输入。

<form action="blahblabhblah" method="post" enctype="multipart/form-data">
   <fieldset>
       <legend>Multiple inputs with the same name</legend>
       <input type="text" name="test" />
       <input type="text" name="test" />
       <input type="text" name="test" />
       <input type="text" name="test" />
       <input type="submit" value="Upload It" />
   </fieldset>
</form>

显然,我需要将某种集合作为我方法的参数。这是我尝试过的,按集合类型分组。

阵列

首先,我检查了泽西是否足够聪明来处理一个简单的阵列:

@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("test") String[] inputs) {
    //handle the request
}

但阵列未按预期注入。

MultiValuedMap

悲惨地失败了,我记得MultiValuedMap对象可以开箱即用。

@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(MultiValuedMap<String, String> formData) {
    //handle the request
}

但它也不起作用。这一次,我得到了一个例外

SEVERE: A message body reader for Java class javax.ws.rs.core.MultivaluedMap, 
and Java type javax.ws.rs.core.MultivaluedMap<java.lang.String, java.lang.String>, 
and MIME media type multipart/form-data; 
boundary=----WebKitFormBoundaryxgxeXiWk62fcLALU was not found.

我被告知可以通过包含mimepull库来摆脱这个异常,所以我将以下依赖项添加到我的pom中:

    <dependency>
        <groupId>org.jvnet</groupId>
        <artifactId>mimepull</artifactId>
        <version>1.3</version>
    </dependency>

不幸的是问题仍然存在。这可能是选择正确的身体阅读器和使用不同参数的问题。我不知道该怎么做。我想要同时使用文件和文本输入,以及其他一些(主要是Long值和自定义参数类)。

FormDataMultipart

经过一些研究后,我找到了FormDataMultiPart课程。我已成功使用它从我的表单中提取字符串值

@POST
@Path("upload2")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadMultipart(FormDataMultiPart multiPart){
    List<FormDataBodyPart> fields = multiPart.getFields("test");
    System.out.println("Name\tValue");
    for(FormDataBodyPart field : fields){
        System.out.println(field.getName() + "\t" + field.getValue());
        //handle the values
    }
    //prepare the response
}

问题是,这是我的问题的简化版本的解决方案。虽然我知道Jersey注入的每一个参数都是通过在某个时候解析一个字符串来创建的(难怪它毕竟是HTTP)并且我有一些编写自己的参数类的经验,但我真的不知道如何将这些字段转换为InputStreamFile个实例可供进一步处理。

因此,在深入了解泽西源代码以了解这些对象是如何创建的之前,我决定在这里询问是否有更简单的方法来读取一组(未知大小)的文件。你知道如何解决这个难题吗?

3 个答案:

答案 0 :(得分:32)

我按照FormDataMultipart的示例找到了解决方案。事实证明我非常接近答案。

FormDataBodyPart类提供了一种方法,允许其用户将值读取为InputStream(或理论上,任何其他类,消息正文阅读器都存在)。

这是最终的解决方案:

表格

表格保持不变。我有几个同名的字段,我可以在其中放置文件。可以使用multiple表单输入(在从目录上载许多文件时需要这些输入)和多个共享名称的输入(从不同位置上传未指定数量的文件的灵活方式)。也可以使用JavaScript附加更多输入的表单。

<form action="/files" method="post" enctype="multipart/form-data">
   <fieldset>
       <legend>Multiple inputs with the same name</legend>
       <input type="file" name="test" multiple="multiple"/>
       <input type="file" name="test" />
       <input type="file" name="test" />
   </fieldset>
   <input type="submit" value="Upload It" />
</form>

服务 - 使用FormDataMultipart

这是一个从多部分表单中读取文件集合的简化方法。所有具有相同内容的输入都会分配给List,并使用getValueAs FormDataBodyPart方法将其值转换为InputStream。一旦将这些文件作为InputStream个实例,就可以轻松地对它们进行任何操作。

@POST
@Path("files")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadMultipart(FormDataMultiPart multiPart) throws IOException{        
    List<FormDataBodyPart> fields = multiPart.getFields("test");        
    for(FormDataBodyPart field : fields){
        handleInputStream(field.getValueAs(InputStream.class));
    }
    //prepare the response
}

private void handleInputStream(InputStream is){
    //read the stream any way you want
}

答案 1 :(得分:8)

@Path("/upload/multiples")
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public Response uploadImage(@FormDataParam("image") List<FormDataBodyPart> imageDatas){
   for( FormDataBodyPart imageData : imageDatas ){
       // Your actual code.
       imageData.getValueAs(InputStream.class);
    }
}

答案 2 :(得分:3)

如果有人尝试使用与我相同的type=text属性进行通用name输入框,您可以将它们切换为type=hidden输入并将其作为你路线@FormParam("inputName") List<String> nameList

显然,当切换到隐藏输入时,唯一的一点是仍然将数据发送到服务器而不为其创建UI元素,因此您需要切换到备用显示UI(例如,我使用了按钮元素轻松点击删除功能。)