Hace no mucho me surgió un problema: quería enviar datos y archivos en un mismo POST pero no supe cómo hacerlo, así que voy a explicaros la solución que encontré.
Sistema empleado para el tutorial:
MacBook Pro-15(2018):
-
- CPU: Intel Core i7 6 núcleos a 2.6GHz
- RAM: 32 GB de DDR4 a 2400MHz
Entorno:
- JDK: 17
- Micronaut: 3.8.7
Contenidos
Introducción
Normalmente en nuestros endpoint solemos enviar y recibir datos en formato de JSON.
Un JSON, por si alguien no sabe que es, es un formato de intercambio de información que viene de JavaScript y tiene la siguiente estructura:
{ “name”: “Jose”, “lastname": “Hernandez” }
Este es un ejemplo muy simple que nos da información de alguien, pero podría ser mucho más complejo con múltiples subelementos, más atributos…
En nuestros endpoint también solemos trabajar con archivos. A lo mejor queremos subir un recurso al servidor para que otro usuario se lo descargue o que haya un sistema para que nuestros usuarios puedan poner su foto de perfil.
A mí me paso recientemente que necesitaba subir archivos y datos a la vez, pero ¿Esto se podía hacer?
Pues sí, esto se puede hacer y hoy vengo a contaros como hacerlo usando Micronaut y Kotlin.
Definir el endpoint
Lo primero que tenemos que hacer es identificar el formato del cuerpo de la petición HTTP que vamos a usar. Para ello usaremos multipart/form-data. Este es el formato que se suele usar si queremos enviar archivos. Consiste en pares clave-valor. Con la clave identificamos los recursos y el valor puede ser tanto texto como un archivo.
Entonces, visto esto, el primer paso es anotar nuestro endpoint como que recibe un multipart/form-data.
@Consumes(MediaType.MULTIPART_FORM_DATA)
La siguiente parte consiste en definir el endpoint con las claves que va a tener nuestro multipart.
@Post(“/test”) internal fun createExercise( @Part file1: CompletedFileUpload, @Part file2: CompletedFileUpload, @Part json: OurData )
Aquí tenemos 2 archivos de tipo CompletedFileUpload. Aunque podríamos usar otros tipos como StreamingFileUpload o ByteArray, en mi opinión, CompletedFileUpload es el más versátil. También tenemos la data class que queremos que Micronaut construya usando el JSON que pasemos a ese parámetro. todo ello anotado con @Part para indicar que son parte de un multipart.
Podríamos tener otra variante en la que en vez de definir el tipo como OurData lo pusiéramos como String. De esta manera nos daría el texto que contenta nuestro Json o cualquier texto que pongamos es esa clave. pero lo que buscamos es que nos construya automáticamente la clase de datos que se corresponde con el archivo JSON, por eso está así definido.
El cuerpo de la función es decisión tuya. Ya podrás hacer operaciones sobre los 2 archivos y los datos que hayas enviado.
Invocar el endpoint
Para invocar el endpoint hay que tener dos cosas en cuenta. Primero, el tipo del body no debe ser texto plano sino un multipart. Segundo, el JSON aunque sea texto plano, si lo subimos como texto, Micronaut no va a saber reconocerlo. Por lo tanto, hay que crear un archivo que contenga el texto y definir que el content-type de ese campo del multipart es application/json. De esta manera, Micronaut va a saber que lo que está recibiendo en ese parámetro es un archivo de tipo JSON y va a intentar convertirlo a nuestra clase OurData.
Quedando la llamada tal que así:
Test del endpoint
@Test fun `upload files and json`(){ val file1 = File.createTempFile("image1", ".png") val file2 = File.createTempFile("image2”, ".png") val ourData = """ { "name":"Jose", "lastname":"Hernandez" } """.trimIndent() val multipartBody = MultipartBody.builder() .addPart("file1", file1) .addPart("file2", file2) .addPart("json", "data", MediaType.APPLICATION_JSON_TYPE, ourData.toByteArray() ).build() val response = client.exchangeObject<Any>( HttpRequest.POST("/test", multipartBody) .contentType(MediaType.MULTIPART_FORM_DATA_TYPE) ) assertEquals(HttpStatus.OK, response.status()) }
Voy a explicar un poco el test: primero creamos dos archivos temporales que van a ser file1 y file2 y luego creamos un texto que sea el contenido del JSON. Lo siguiente que debemos hacer es crear un multipart. Para ello usaremos el MultipartBody.builder() al cual vamos añadiendo las distintas claves con su valor. Es muy importante marcar en la propiedad json que el tipo de datos es APPLICATION_JSON porque sino Micronaut no lo interpreta bien, al igual que nos pasaba al llamarlo desde postman anteriormente. Para pasarle el contenido del JSON en este caso he usado un byteArray, pero se puede construir también creando un archivo y metiendo dentro en contenido.
La siguiente parte se trata de una llamada HTTP a la ruta que tengamos definida indicando el tipo del contenido.
Y por último una comprobación muy simple, que el endpoint devuelva OK. Ya está en tu mano en función de la respuesta que vayas a dar o cómo se comporte tu código hacer unas comprobaciones u otras.
Referencias
https://medium.com/nerd-for-tech/micronaut-3-ways-to-upload-files-via-http-ddfa6118ab99