Jak odczytać kilka (plikowych) wejść o tej samej nazwie z formularza wieloczęściowego z Jersey?
Z powodzeniem opracowałem usługę, w której czytam pliki przesłane w postaci wieloczęściowej w Jersey. Oto bardzo uproszczona wersja tego, co robiłem:
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("file") InputStream uploadedInputStream,
@FormDataParam("file") FormDataContentDisposition fileDetail) throws IOException {
//handle the file
}
To działa dobrze, ale otrzymałem nowe wymaganie. Oprócz przesyłanego przeze mnie pliku muszę obsługiwać dowolną liczbę zasobów. Załóżmy, że są to pliki obrazów.
Pomyślałem, że po prostu dostarczę klientowi formularz z jednym wejściem dla pliku, jednym wejściem dla pierwszego obrazu i przyciskiem umożliwiającym dodanie kolejnych danych do formularza (przy użyciu AJAX lub po prostu zwykłego 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>
Użytkownik może więc dołączyć formularz z większą liczbą danych wejściowych dla obrazów, takich jak:
<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>
Miałem nadzieję, że wystarczy przeczytać pola o tej samej nazwie co kolekcja. Zrobiłem to z powodzeniem przy wprowadzaniu tekstu w MVC .NET i myślałem, że nie będzie trudniej na Jersey. Okazuje się, że się myliłem.
Nie znalazłem żadnych samouczków na ten temat i zacząłem eksperymentować.
Aby zobaczyć, jak to zrobić, ograniczyłem problem do prostych wejść tekstowych.
<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>
Oczywiście musiałem mieć jakiś rodzaj kolekcji jako parametr mojej metody. Oto, co próbowałem, pogrupowane według typu kolekcji.
SzykNajpierw sprawdziłem, czy Jersey jest wystarczająco inteligentny, aby obsłużyć prostą tablicę:
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("test") String[] inputs) {
//handle the request
}
ale tablica nie została wstrzyknięta zgodnie z oczekiwaniami.
MultiValuedMapPo nieudanej porażce przypomniałem sobie toMultiValuedMap
obiekty mogą być obsługiwane z pudełka.
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(MultiValuedMap<String, String> formData) {
//handle the request
}
ale to też nie działa. Tym razem mam wyjątek
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.
Powiedziano mi, że tego wyjątku można się pozbyć, włączającmimepull
więc dodałem następującą zależność do mojego pom:
<dependency>
<groupId>org.jvnet</groupId>
<artifactId>mimepull</artifactId>
<version>1.3</version>
</dependency>
Niestety problem nadal występuje. Prawdopodobnie jest to kwestia wyboru odpowiedniego czytnika ciała i używania różnych parametrów dla ogólnego. Nie jestem pewien, jak to zrobić. Chcę konsumować zarówno pliki, jak i pliki tekstowe, a także niektóre inne (głównieLong
wartości i niestandardowe klasy parametrów).
Po kilku dalszych badaniach znalazłemFormDataMultiPart klasa. Z powodzeniem użyłem go do wyodrębnienia wartości ciągu z mojego formularza
@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
}
Problem polega na tym, że jest to rozwiązanie uproszczonej wersji mojego problemu. Chociaż wiem, że każdy pojedynczy parametr wstrzykiwany przez Jersey jest tworzony przez parsowanie łańcucha w pewnym momencie (nic dziwnego, że to HTTP), a ja mam pewne doświadczenie w pisaniu własnych klas parametrów, tak naprawdę nie wiem jak przekonwertować te pola naInputStream
lubFile
instancje do dalszego przetwarzania.
Dlatego przed nurkowaniem w kodzie źródłowym Jersey, aby zobaczyć, jak te obiekty są tworzone, zdecydowałem się zapytać, czy istnieje łatwiejszy sposób odczytania zbioru (o nieznanym rozmiarze) plików. Czy wiesz, jak rozwiązać tę zagadkę?