Este grupo de redactores ha estado mucho tiempo investigando para darle resolución a tu pregunta, te compartimos la respuesta por eso esperamos que resulte de mucha apoyo.
Solución:
puedes intentar usar StreamingResponseBody
StreamingResponseBody
Un tipo de valor de retorno de método de controlador para el procesamiento de solicitudes asincrónicas donde la aplicación puede escribir directamente en la respuesta OutputStream sin detener el subproceso contenedor Servlet.
Debido a que está trabajando en un hilo separado, escribiendo directamente en la respuesta, su problema para llamar close()
antes de return
está resuelto.
probablemente puedas comenzar con el siguiente ejemplo
public ResponseEntity export(...) throws FileNotFoundException
//...
InputStream inputStream = new FileInputStream(new File("/path/to/example/file"));
StreamingResponseBody responseBody = outputStream ->
int numberOfBytesToWrite;
byte[] data = new byte[1024];
while ((numberOfBytesToWrite = inputStream.read(data, 0, data.length)) != -1)
System.out.println("Writing some bytes..");
outputStream.write(data, 0, numberOfBytesToWrite);
inputStream.close();
;
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=generic_file_name.bin")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(responseBody);
También puedes intentar usar Files
(desde java 7)
para que no tengas que arreglártelas InputStream
File file = new File("/path/to/example/file");
StreamingResponseBody responseBody = outputStream ->
Files.copy(file.toPath(), outputStream);
;
Como @ Stackee007 describió en el comentario, bajo una carga pesada en el entorno de producción, también es una buena práctica definir un @Configuration
clase para un TaskExecutor
para ajustar parámetros y gestionar Async
Procesos.
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
private final TaskExecutionProperties taskExecutionProperties;
public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties)
this.taskExecutionProperties = taskExecutionProperties;
// ---------------> Tune parameters here
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor()
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize());
executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize());
executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity());
executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix());
return executor;
// ---------------> Use this task executor also for async rest methods
@Bean
protected WebMvcConfigurer webMvcConfigurer()
return new WebMvcConfigurer()
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer)
configurer.setTaskExecutor(getTaskExecutor());
;
@Bean
protected ConcurrentTaskExecutor getTaskExecutor()
return new ConcurrentTaskExecutor(this.getAsyncExecutor());
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler()
return new SimpleAsyncUncaughtExceptionHandler();
Cómo probar con mockMvc
Simplemente puede seguir este código de muestra en su prueba de integración como:
.andExpect(request().asyncStarted())
.andDo(MvcResult::getAsyncResult)
.andExpect(status().isOk()).getResponse().getContentAsByteArray();
Tipo de contenido de ResponseEntity
es un MediaType.APPLICATION_OCTET_STREAM
en este ejemplo y puede obtener byte[] (.getContentAsByteArray()
) pero puede obtener String / Json / texto sin formato de todo dependiendo del tipo de contenido de respuesta de su cuerpo.
Suponiendo que está utilizando Spring, su método podría devolver un Recurso y dejar que Spring se encargue del resto (incluido el cierre de la secuencia subyacente). Hay pocas implementaciones de Resource disponibles dentro de Spring API o, de lo contrario, debe implementar la suya propia. Al final, su método se volvería simple y le gustaría algo como a continuación
public ResponseEntity getFo0(...)
return new InputStreamResource();
Puede refactorizar todos los métodos de su controlador que leen archivos locales y establecer su contenido como el cuerpo de la respuesta HTTP:
En lugar de usar el ResponseEntity
te acercas inyectas el subyacente HttpServletResponse
y copie los bytes del flujo de entrada devuelto por su getContent(...)
método a la salida de la HttpServletResponse
, por ejemplo, mediante el uso de métodos de utilidad relacionados con IO de Apache CommonsIO o la biblioteca Google Guava. En cualquier caso, asegúrese de cerrar el flujo de entrada. El siguiente código hace esto implícitamente mediante el uso de una declaración ‘try-with-resources’ que cierra el flujo de entrada declarado al final de la declaración.
@RequestMapping(value="/foo", method=RequestMethod.GET)
public void getFoo(HttpServletResponse response)
// use Java7+ try-with-resources
try (InputStream content = getContent(...))
// if needed set content type and attachment header
response.addHeader("Content-disposition", "attachment;filename=foo.txt");
response.setContentType("txt/plain");
// copy content stream to the HttpServletResponse's output stream
IOUtils.copy(myStream, response.getOutputStream());
response.flushBuffer();
referencia:
https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html https://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html https://google.github.io/guava/releases/19.0/api/docs/com/google/common/io/ByteStreams .html https://commons.apache.org/proper/commons-io/javadocs/api-release/index.html
(especialmente echa un vistazo a los métodos public static int copy(InputStream input, OutputStream output) throws IOException
y public static int copyLarge(InputStream input, OutputStream output) throws IOException
de clase org.apache.commons.io.IOUtils
)