Saltar al contenido

Forma correcta de transmitir usando ResponseEntity y asegurarse de que InputStream se cierre

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)

Te mostramos las reseñas y valoraciones de los lectores

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *