在Spring Boot上使用StreamingResponseBody下载大文件的异步超时

时间:2017-03-18 17:22:31

标签: spring-mvc asynchronous spring-boot

我试图公开一个REST服务,它可以下载大型文件流,而不需要先保存在内存中。另外我需要这个来支持异步调用,如果(至少)两个用户在同一时间调用这个URL应该能够同时下载它。 应用程序使用Spring Boot设置 这就是我在Controller上所拥有的:

@RestController
public class MyController {

   private MyService service;

   @Autowired
   public MyController(MyService service) {
       this.service = service;
   }

   @RequestMapping(
        value = "download",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
   public ResponseEntity<StreamingResponseBody> downloadAsync() throws IOException {

       StreamingResponseBody responseBody = outputStream -> {
           service.download(outputStream);
           outputStream.close();
       };

       return ResponseEntity.ok(responseBody);
    }
}

这就是我在服务上的内容(下载URL只是测试此行为的示例):

@Service
public class MyService {

    private RestTemplate restTemplate;

    @Autowired
    public MyService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public void download(OutputStream outputStream) {

        ResponseExtractor<Void> responseExtractor = clientHttpResponse -> {
            InputStream inputStream = clientHttpResponse.getBody();
            StreamUtils.copy(inputStream, outputStream);
            return null;
        };

        restTemplate.execute("http://download.thinkbroadband.com/1GB.zip",
            HttpMethod.GET,
            clientHttpRequest -> {},
            responseExtractor);
    }
}

在其他application.yml中,我有这些属性,根本没有任何想象力:

server:
  port: 9999
  context-path: /rest

这是JavaConfig文件:

@Configuration
public class ApplicationConfig {
    @Bean
    public RestTemplate restTemplate() {
        ClientHttpRequestFactory requestFactory =
            new HttpComponentsClientHttpRequestFactory(HttpClients.createDefault());

        RestTemplate restTemplate = new RestTemplate(requestFactory);
        restTemplate.setErrorHandler(new ClientErrorHandler());
        return restTemplate;
    }
}

当我调用此端点时localhost:9999/rest/download下载启动并下载一些MB,但过了一段时间后,它会停止,这就是我的控制台上显示的内容:

2017-03-18 17:11:54.808 INFO  --- [nio-9999-exec-1] o.a.c.c.C.[.[.[/rest]                    : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-03-18 17:11:54.811 INFO  --- [nio-9999-exec-1] o.s.w.s.DispatcherServlet                : FrameworkServlet 'dispatcherServlet': initialization started
2017-03-18 17:11:54.895 INFO  --- [nio-9999-exec-1] o.s.w.s.DispatcherServlet                : FrameworkServlet 'dispatcherServlet': initialization completed in 84 ms
2017-03-18 17:12:25.334 ERROR --- [nio-9999-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Async timeout for GET [/rest/download]
2017-03-18 17:12:25.335 WARN  --- [nio-9999-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.web.context.request.async.AsyncRequestTimeoutException
2017-03-18 17:12:25.366 INFO  --- [nio-9999-exec-2] o.a.c.c.CoyoteAdapter                    : Encountered a non-recycled response and recycled it forcedly.
org.apache.catalina.connector.CoyoteAdapter$RecycleRequiredException: null
    at org.apache.catalina.connector.CoyoteAdapter.checkRecycled(CoyoteAdapter.java:494) [tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.coyote.http11.Http11Processor.recycle(Http11Processor.java:1627) [tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.release(AbstractProtocol.java:977) [tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:869) [tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434) [tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.11.jar:8.5.11]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_60]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_60]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.11.jar:8.5.11]
    at java.lang.Thread.run(Thread.java:745) [?:1.8.0_60]

有人可以帮忙吗? 提前致谢

2 个答案:

答案 0 :(得分:7)

如果您使用Spring-Boot遇到此问题,则将以下属性设置为更高的值就足够了 - 例如:

spring:
  mvc:
    async:
      request-timeout: 3600000

spring.mvc.async.request-timeout = 3600000

答案 1 :(得分:4)

您的异步任务执行程序似乎遇到了超时问题。您可以使用WebMvcConfigurerAdapter配置所需的超时(和其他设置)。此代码应该有助于解决此问题。请务必将省略号(...)替换为所需的值。

此示例还注册了一个拦截器,当您需要进行特殊处理时,会在超时时调用该拦截器。

@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {

    private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);

    @Override
    @Bean(name = "taskExecutor")
    public AsyncTaskExecutor getAsyncExecutor() {
        log.debug("Creating Async Task Executor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(...);
        executor.setMaxPoolSize(...);
        executor.setQueueCapacity(...);
        executor.setThreadNamePrefix(...);
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }

    /** Configure async support for Spring MVC. */
    @Bean
    public WebMvcConfigurerAdapter webMvcConfigurerAdapter(AsyncTaskExecutor taskExecutor, CallableProcessingInterceptor callableProcessingInterceptor) {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
                configurer.setDefaultTimeout(...)
                    .setTaskExecutor(taskExecutor);
                configurer.registerCallableInterceptors(callableProcessingInterceptor);
                super.configureAsyncSupport(configurer);
            }
        };
    }

    @Bean
    public CallableProcessingInterceptor callableProcessingInterceptor() {
        return new TimeoutCallableProcessingInterceptor() {
            @Override
            public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
                log.error("timeout!");
                return super.handleTimeout(request, task);
            }
        };
    }
}