subir servlet servidor que para guardar formulario form data archivos archivo java forms jersey jax-rs multipart

java - servlet - ¿Cómo leer varias entradas(archivo) con el mismo nombre de un formulario multiparte con Jersey?



subir archivos a servidor jsp (3)

He encontrado la solución siguiendo el ejemplo con FormDataMultipart . Resulta que estaba muy cerca de la respuesta.

La clase FormDataBodyPart proporciona un método que le permite a su usuario leer el valor como InputStream (o teóricamente, cualquier otra clase, para la cual esté presente un lector del cuerpo del mensaje).

Aquí está la solución final:

Formar

La forma se mantiene sin cambios. Tengo un par de campos con el mismo nombre, en los que puedo colocar archivos. Es posible usar tanto entradas de formularios multiple (que desee cuando se cargan muchos archivos de un directorio) como numerosas entradas que comparten un nombre (una forma flexible de cargar un número no especificado de archivos desde una ubicación diferente). También es posible adjuntar el formulario con más entradas usando 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>

Servicio - usando FormDataMultipart

Aquí hay un método simplificado que lee una colección de archivos de un formulario multiparte. Todas las entradas con el mismo se asignan a una List y sus valores se convierten a InputStream utilizando el método getValueAs de FormDataBodyPart . Una vez que tenga estos archivos como instancias de InputStream , es fácil hacer casi cualquier cosa con ellos.

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

He desarrollado con éxito un servicio, en el que leo archivos cargados en un formulario de varias partes en Jersey. Aquí hay una versión extremadamente simplificada de lo que he estado haciendo:

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

Esto funciona bien, pero me han dado un nuevo requisito. Además del archivo que estoy cargando, tengo que manejar un número arbitrario de recursos. Supongamos que estos son archivos de imagen.

Me di cuenta de que solo le proporcionaría al cliente un formulario con una entrada para el archivo, una entrada para la primera imagen y un botón para permitir agregar más entradas al formulario (utilizando AJAX o simplemente JavaScript simple).

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

Así que el usuario puede adjuntar el formulario con más entradas para las imágenes, como esta:

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

Esperaba que fuera lo suficientemente simple como para leer los campos con el mismo nombre que una colección. Lo he hecho con éxito con entradas de texto en MVC .NET y pensé que no sería más difícil en Jersey. Resulta que estaba equivocado.

Al no haber encontrado tutoriales sobre el tema, comencé a experimentar.

Para ver cómo hacerlo, resolví el problema con simples entradas de texto.

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

Obviamente, necesitaba tener algún tipo de colección como parámetro para mi método. Esto es lo que probé, agrupado por tipo de colección.

Formación

Al principio, verifiqué si Jersey era lo suficientemente inteligente como para manejar una matriz simple:

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

pero la matriz no fue inyectada como se esperaba.

MultiValuedMap

Después de haber fallado miserablemente, recordé que los objetos MultiValuedMap podían manejarse de forma MultiValuedMap .

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

pero tampoco funciona. Esta vez, tengo una excepción

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.

Me dijeron que esta excepción podría eliminarse incluyendo la biblioteca mimepull , así que agregué la siguiente dependencia a mi pom:

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

Lamentablemente el problema persiste. Probablemente sea cuestión de elegir el lector del cuerpo correcto y usar diferentes parámetros para el genérico. No estoy seguro de cómo hacer esto. Quiero consumir tanto entradas de archivo como de texto, así como algunas otras (en su mayoría valores Long y clases de parámetros personalizados).

FormDataMultipart

Después de un poco más de investigación, encontré la clase FormDataMultiPart . Lo he utilizado con éxito para extraer los valores de cadena de mi formulario

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

El problema es que esta es una solución a la versión simplificada de mi problema. Si bien sé que todos los parámetros inyectados por Jersey se crean al analizar una cadena en algún momento (no es de extrañar, después de todo es HTTP) y tengo algo de experiencia en escribir mis propias clases de parámetros, no sé cómo convertir estos campos a Instancias InputStream o File para su posterior procesamiento.

Por lo tanto, antes de sumergirme en el código fuente de Jersey para ver cómo se crean estos objetos, decidí preguntar aquí si hay una forma más fácil de leer un conjunto (de tamaño desconocido) de archivos. ¿Sabes cómo resolver este enigma?


Si alguien está tratando de hacer cuadros de entrada de type=text genérico con el mismo name atributo que tenía, podrá cambiarlos a entradas de type=hidden y hacer que funcionen como @FormParam("inputName") List<String> nameList en su ruta.

Obviamente, cuando se cambia a entradas ocultas, el único punto es seguir enviando los datos al servidor sin crear un elemento de UI, por lo que deberá cambiar a una IU de pantalla alternativa (por ejemplo, usé un elemento de botón para hacer clic con facilidad) -para eliminar funcionalidad).


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