Подтвердить что ты не робот

Как читать несколько (файлов) входов с тем же именем из многочастной формы с Джерси?

Я успешно разработал сервис, в котором я читаю файлы, загруженные в многопрофильной форме на Джерси. Здесь очень упрощенная версия того, что я делал:

@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, и я думал, что в Джерси не будет сложнее. Оказывается, я ошибался.

Не найдя учебников по этому вопросу, я начал экспериментировать.

Чтобы увидеть, как это сделать, я подавил проблему до простых текстовых входов.

<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
}

Проблема в том, что это решение упрощенной версии моей проблемы. Хотя я знаю, что каждый отдельный параметр, введенный Джерси, создается путем разбора строки в какой-то момент (неудивительно, что это HTTP в конце концов), и у меня есть некоторый опыт написания моих собственных классов параметров, я действительно не как конвертировать эти поля в InputStream или File для дальнейшей обработки.

Поэтому перед тем, как погрузиться в исходный код Джерси, чтобы увидеть, как эти объекты создаются, я решил спросить здесь, есть ли более простой способ прочитать набор (неизвестного размера) файлов. Вы знаете, как решить эту загадку?

4b9b3361

Ответ 1

Я нашел решение, следуя примеру 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, и их значения преобразуются в InputStream с помощью метода getValueAs FormDataBodyPart. Когда у вас есть эти файлы как экземпляры 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
}

Ответ 2

@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);
    }
}

Ответ 3

Если кто-то пытается создать общие type=text поля ввода с тем же атрибутом name, что и я, вы сможете переключить их на вкладки type=hidden и работать на @FormParam("inputName") List<String> nameList на своем маршруте.

Очевидно, что при переключении на скрытые входы единственная точка заключается в том, чтобы по-прежнему отправлять данные на сервер, не создавая для него элемент пользовательского интерфейса, поэтому вам нужно будет переключиться на альтернативный интерфейс отображения (например, я использовал элемент кнопки для удобства работы с щелчком для удаления).