I'm trying to implement an asynchronous controller using SprintBoot. I want to make REST request to a controller so that the controller returns immediately, while the work continues on the server.
I'm following this Spring example: http://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support
I suspect this is a configuration problem. Can someone please tell me what I'm missing? I'm new to Spring so if you could please provide as much details as possible it would be appreciated.
Using a working controller I made the following changes:
// Before
@RequestMapping(method=RequestMethod.POST)
public String processUpload(final MultipartFile file) {
// ...
return "someView";
}
// After
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public Object call() throws Exception {
// ...
return "someView";
}
};
}
I am able to call the new controller but I have two issues below:
- The new controller is not called asynchronously. The browser hangs during the call. The call does execute the code though.
The request times out with this error:
2015-03-06 16:36:10.592 ERROR 13012 --- [ MvcAsync1] o.s.w.c.request.async.WebAsyncManager : Could not complete async processing due to timeout or network error
Update:
I was able to resolve the timeout by creating the following bean in my application file:
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
connector.setPort(9000);
connector.setAsyncTimeout(60000);
}
});
return factory;
}
But the call the to the controller is still not asynchronous. The browser still hangs for the duration of the call.
I'm still looking for help on how to make a the REST call to the controller return immediately while doing the work in the background.
Update II
Thank you Dave. I have attempted to implement an async method in a bean.
Here is my application class:
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
connector.setPort(9000);
connector.setAsyncTimeout(60000);
}
});
return factory;
}
}
Here is my bean class:
public class LongProcess {
@Async
public Future<String> call() {
try {
System.out.println("Sleeping now...");
Thread.sleep(10000);
return new AsyncResult<String>("Hey");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
My configuration class:
@Configuration
@EnableAsync
public class LongProcessConfiguration implements AsyncConfigurer {
@Bean
public LongProcess longProcessBean() {
return new LongProcess();
}
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setMaxPoolSize(10);
taskExecutor.setThreadNamePrefix("LULExecutor-");
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
My controller method:
@RequestMapping("/utilities/longProcess")
public String longProcess() {
System.out.println("Starting long process...");
CsvFileDifferConfiguration context = new CsvFileDifferConfiguration();
LongProcess process = context.longProcessBean();
Future<String> result = process.call();
System.out.println("Done!");
return "{success: 1}";
}
This unfortunately still does not return immediately. Note that I don't care about the result from LongProcess. The method is called successfully, but not in the background. Any idea what I might be missing?
As a test, if I change the controller method to wait for the result, the wait block is never entered:
@RequestMapping("/utilities/longProcess")
public String longProcess() throws InterruptedException {
System.out.println("Starting long process...");
CsvFileDifferConfiguration context = new CsvFileDifferConfiguration();
LongProcess process = context.longProcessBean();
Future<String> result = process.call();
while (!(result.isDone())) {
Thread.sleep(1); //10-millisecond pause between each check
System.out.println("Waiting for Long Process...");
}
System.out.println("Done!");
return "{success: 1}";
}
Update III
I replaced
CsvFileDifferConfiguration context = new CsvFileDifferConfiguration();
LongProcess process = context.longProcessBean();
with
@Autowired
private LongProcess process;
and this solved the issue.
See Question&Answers more detail:
os