如何从Jersey的多部分表单中读取多个具有相同名称的(文件)输入? [英] How to read several (file) inputs with the same name from a multipart form with Jersey?
问题描述
我已经成功开发了一项服务,在该服务中,我阅读了在泽西岛以多部分形式上传的文件。这是我一直在做的非常简化的版本:
@POST
@Path(FileCollection)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam(file)InputStream uploadedInputStream,
@FormDataParam(file)FormDataContentDisposition fileDetail)抛出IOException {
//处理文件
}
这工作得很好,但我得到了一个新要求。除了我正在上传的文件外,我还要处理任意数量的资源。假设这些都是图片文件。
我想给客户端提供一个表单,一个文件输入,一个输入第一个图像,一个按钮以允许向表单添加更多输入(使用AJAX或简单的JavaScript)。
< form action =blahblahblahmethod =postenctype =multipart / form-data>
< input type =filename =file/>
< input type =filename =image/>
< input type =buttonvalue =add another image/>
< input type =submit/>
< / form>
所以用户可以在表单中附加更多的图片输入,例如:
< form action =blahblahblahmethod =postenctype =multipart / form-data>
< input type =filename =file/>
< input type =filename =image/>
< input type =filename =image/>
< input type =filename =image/>
< input type =buttonvalue =add another image/>
< input type =submit/>
< / form>
我希望它能够简单地读取与集合同名的字段。我已经成功地用MVC .NET中的文本输入完成了它,并且我认为它在Jersey中不会更难。原来我错了。
没有找到关于这个主题的教程,我开始尝试。
为了了解如何做到这一点,我将问题简化为简单的文本输入。
< form action =blahblabhblahmethod =postenctype =multipart / form-data>
< fieldset>
<图例>同名的多个输入< / legend>
< input type =textname =test/>
< input type =textname =test/>
< input type =textname =test/>
< input type =textname =test/>
< input type =submitvalue =上传它/>
< / fieldset>
< / form>
显然,我需要将某种集合作为我的方法的参数。
数组
起初,我检查泽西是否聪明足够处理一个简单的数组:
@POST
@Path(FileCollection)
@Consumes (MediaType.MULTIPART_FORM_DATA)
public response uploadFile(@FormDataParam(test)String [] inputs){
//处理请求
}
但是数组并未按预期注入。
$ b
MultiValuedMap
失败后,我记得可以立即处理 MultiValuedMap
对象。
@POST
@Path(FileCollection)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public response uploadFile(MultiValuedMap< String,String> formData){
//处理请求
}
但它也不起作用。这次,我得到一个异常
SEVERE:Java类javax.ws.rs.core.MultivaluedMap的消息正文阅读器,
和Java类型javax.ws.rs.core.MultivaluedMap< java.lang.String,java.lang.String> ;,
和MIME媒体类型multipart / form-data;
boundary = ----未找到WebKitFormBoundaryxgxeXiWk62fcLALU。
我被告知这个异常可以通过包含 mimepull
library,所以我添加了以下依赖项到我的pom中:
< dependency>
< groupId> org.jvnet< / groupId>
< artifactId> mimepull< / artifactId>
< version> 1.3< / version>
< /依赖关系>
不幸的是问题依然存在。这可能是选择正确的身体阅读器和使用通用的不同参数的问题。我不知道如何做到这一点。我希望同时使用文件和文本输入以及其他一些内容(主要是 Long
值和自定义参数类)。
FormDataMultipart
经过一些研究后,我发现 FormDataMultiPart 类。我已经成功地使用它从我的表单中提取字符串值。
$ p
@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());
//处理值
//准备回应
$ / code $ / pre
问题是,这是我的问题的简化版本的解决方案。虽然我知道Jersey注入的每个参数都是通过在某个时候解析一个字符串而创建的(难怪,它毕竟是HTTP),而且我有一些编写自己的参数类的经验,但我实际上并没有如何将这些字段转换为 InputStream
或 File
实例进行进一步处理。
在深入了解泽西岛源代码以查看这些对象是如何创建之前,我决定在这里问一下是否有更简单的方法来读取一组(未知大小)的文件。你知道如何解决这个难题吗?
解决方案我已经找到了解决方案,按照下面的例子 FormDataMultipart
。事实证明我非常接近答案。
$ b
FormDataBodyPart
类提供了一种方法,允许其用户读取值为 InputStream
(或理论上,任何其他类,为其提供消息正文阅读器)
这是最终的解决方案:
表格
表格保持不变。我有几个同名的字段,我可以在其中放置文件。可以同时使用 多个
表单输入(当从一个目录上传许多文件时需要这些)以及多个共享名称的输入(灵活的方式从不同位置上传未指定数量的文件)。
< form action =/ filesmethod =post也可以在表单中附加更多的输入。 enctype =multipart / form-data>
< fieldset>
<图例>同名的多个输入< / legend>
< input type =filename =testmultiple =multiple/>
< input type =filename =test/>
< input type =filename =test/>
< / fieldset>
< input type =submitvalue =上传它/>
< / form>
服务 - 使用 FormDataMultipart
以下是从多部分表单读取文件集合的简化方法。所有具有相同输入的输入都分配给一个 List
,并且它们的值使用 InputStream http://jersey.java.net/nonav/apidocs/1.1.4/contribs/jersey-multipart/com/sun/jersey/multipart/FormDataBodyPart.html#getValueAs%28java.lang.Class%29\"><$c $ c> getValueAs 方法 FormDataBodyPart
。一旦你将这些文件作为 InputStream
实例,就很容易对它们做任何事情。
@POST
@Path(files)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public response uploadMultipart(FormDataMultiPart multiPart)throws IOException {
List< FormDataBodyPart> fields = multiPart.getFields(test); (FormDataBodyPart field:fields)
{
handleInputStream(field.getValueAs(InputStream.class));
}
//准备响应
}
private void handleInputStream(InputStream is){
//以任何方式读取流
}
I have successfully developed a service, in which I read files uploaded in a multipart form in Jersey. Here's an extremely simplified version of what I've been doing:
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("file") InputStream uploadedInputStream,
@FormDataParam("file") FormDataContentDisposition fileDetail) throws IOException {
//handle the file
}
This works just fine but I've been given a new requirement. In addition to the file I'm uploading, I have to handle an arbitrary number of resources. Let's assume these are image files.
I figured I'd just provide the client with a form with one input for the file, one input for the first image and a button to allow adding more inputs to the form (using AJAX or simply plain 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>
So the user can append the form with more inputs for images, like this:
<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>
I hoped it would be simple enough to read the fields with the same name as a collection. I've done it successfully with text inputs in MVC .NET and I thought it wouldn't be harder in Jersey. It turns out I was wrong.
Having found no tutorials on the subject, I started experimenting.
In order to see how to do it, I dumbed the problem down to simple text inputs.
<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>
Obviously, I needed to have some sort of collection as a parameter to my method. Here's what I tried, grouped by collection type.
Array
At first, I checked whether Jersey was smart enough to handle a simple array:
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("test") String[] inputs) {
//handle the request
}
but the array wasn't injected as expected.
MultiValuedMap
Having failed miserably, I remembered that MultiValuedMap
objects could be handled out of the box.
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(MultiValuedMap<String, String> formData) {
//handle the request
}
but it doesn't work either. This time, I got an exception
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.
I was told that this exception could be gotten rid of by including the mimepull
library so I added the following dependency to my pom:
<dependency>
<groupId>org.jvnet</groupId>
<artifactId>mimepull</artifactId>
<version>1.3</version>
</dependency>
Unfortunately the problem persists. It's probably a matter of choosing the right body reader and using different parameters for the generic. I'm not sure how to do this. I want to consume both file and text inputs, as well as some others (mostly Long
values and custom parameter classes).
FormDataMultipart
After some more research, I found the FormDataMultiPart class. I've successfully used it to extract the string values from my form
@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
}
The problem is, this is a solution to the simplified version of my problem. While I know that every single parameter injected by Jersey is created by parsing a string at some point (no wonder, it's HTTP after all) and I have some experience writing my own parameter classes, I don't really how to convert these fields to InputStream
or File
instances for further processing.
Therefore, before diving into Jersey source code to see how these objects are created, I decided to ask here whether there is an easier way to read a set (of unknown size) of files. Do you know how to solve this conundrum?
I have found the solution by following the example with FormDataMultipart
. It turns out I was very close to the answer.
The FormDataBodyPart
class provides a method that allows its user to read the value as InputStream
(or theoretically, any other class, for which a message body reader is present).
Here's the final solution:
Form
The form remains unchanged. I have a couple of fields with the same name, in which I can place files. It's possible to use both multiple
form inputs (you want these when uploading many files from a directory) and numerous inputs that share a name (Flexible way to upload an unspecified number of files from different location). It's also possible to append the form with more inputs using 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>
Service - using FormDataMultipart
Here's a simplified method that reads a collection of files from a multipart form. All inputs with the same are assigned to a List
and their values are converted to InputStream
using the getValueAs
method of FormDataBodyPart
. Once you have these files as InputStream
instances, it's easy to do almost anything with them.
@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
}
这篇关于如何从Jersey的多部分表单中读取多个具有相同名称的(文件)输入?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!