¿Cuantas veces nos hemos vistos en la necesidad de exponer una API para la subida de archivos? Por ejemplo que el usuario suba un archivo delimitado por comas para crear o actualizar de forma masiva alguna entidad en la base de datos.
Al buscar en lugares como StackOverflow lo más común es que nos encontremos con que no existe una forma standard de realizar una subida de archivos con las API de JAX-RS y que cada implementación como Jersey o Apache CXF provee de extensiones para realizar esta tarea, sin embargo esto nos hace depender de un framework en específico más allá de las API’s standard de JavaEE / JakartaEE / MicroProfile.
Una solución es crear un servlet que se encargue de ello, sin embargo presenta algunas desventajas como el no poder utilizar de forma automática los interceptores / filtros que tengamos configurados para nuestros servicios de JAX-RS o funcionalidades de MicroProfile como OpenAPI para documentar automáticamente nuestra API.
JAX-RS funciona internamente con servlet que se configura de forma automática y podemos utilizarlo para crear WebServices para subida de archivos.
El primer paso es configurar el servlet para que soporte Multipart como parte de los tipos de datos en nuestros WebServices:
Nuestra aplicación debería de verse muy similar a esta:
package com.example;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationScoped
@ApplicationPath("/api")
public class MicroProfileApplication extends Application {
}
Luego debemos agregar el archivo webapp/WEB-INF/web.xml con el siguiente contenido:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>com.example.MicroProfileApplication</servlet-name>
<multipart-config>
<max-file-size>35000000</max-file-size> <!--in bytes-->
<max-request-size>218018841</max-request-size> <!--in bytes-->
<file-size-threshold>0</file-size-threshold> <!--in bytes-->
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>com.example.MicroProfileApplication</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
Una vez configurado el proyecto, ya podemos escribir Webservices de la siguiente manera:
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String uploadTest(@Context HttpServletRequest request){
try{
Part file = request.getPart("file"); //file es el campo donde se enviará el archivo en el request.
InputStream fileContent = file.getInputStream();
//do something with the file.
return "Ok";
}
catch (IOException | ServletException ex){
//Do some exception management
return "error";
}
}
Como por ejemplo leer el contenido de un archivo de texto:
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String uploadTest(@Context HttpServletRequest request){
try{
Part file = request.getPart("file"); //file es el campo donde se enviará el archivo en el request.
InputStream fileContent = file.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(fileContent, StandardCharsets.UTF_8.name()));
StringBuilder value = new StringBuilder();
char[] buffer = new char[1024];
for (int length; (length = reader.read(buffer)) > 0; )
{
value.append(buffer, 0, length);
}
return value.toString();
}
catch (IOException | ServletException ex){
//Do some exception management
return "error";
}
}
Y al final podemos consumir nuestro servicio de la siguiente manera: o desde nuestro cliente favorito utilizando el Content-Type: multipart/form-data.