我试图公开一个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]
有人可以帮忙吗? 提前致谢
答案 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);
}
};
}
}