Reputation: 116
We have a Rest Controller that returns a WebAsyncTask. The code is as follows:
@RequestMapping(value="/test", produces="text/plain", method=RequestMethod.POST)
@ResponseBody
public WebAsyncTask<ResponseEntity<String>> test(@RequestParam(value="foo", required=false) String data) throws IOException {
Callable<ResponseEntity<String>> callable=() -> {
HttpHeadersresponseHeaders = newHttpHeaders();
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
return new ResponseEntity("testping",responseHeaders,HttpStatus.OK);
};
WebAsyncTask<ResponseEntity<String>> webAsyncTask = new WebAsyncTask<>(getTimeOut(), getAsyncTaskExecutor(), callable);
return webAsyncTask;
}
AsyncTaskExecutor is defined as follows:
<bean id="asyncTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<propertyname="corePoolSize" value="50"/>
<propertyname="maxPoolSize" value="50"/>
<propertyname="queueCapacity" value="5000"/>
</bean>
This is my Boot Application:
@SpringBootApplication(exclude={JacksonAutoConfiguration.class,
CodecsAutoConfiguration.class,HttpMessageConvertersAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,WebMvcAutoConfiguration.class})
@ImportResource(locations={"classpath:META-INF/spring/test-boot-applicationContext.xml"})
@EnableWebSecurity
@EnableAsync
public class TestBootApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(TestBootApplication.class, args);
}
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurityhttpSecurity) throws Exception {
httpSecurity
.authorizeRequests().antMatchers("/**").permitAll()
.and().csrf().disable();
}
}
@Bean
public ServletRegistrationBean dispatcherController() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setNamespace("tempspace");
XmlWebApplicationContext applicationContext = new XmlWebApplicationContext();
applicationContext.setConfigLocations("classpath:META-INF/spring/test-dispatcher-servlet.xml");
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet,"/temp/*");
servletRegistrationBean.setName("testdispatcher");
servletRegistrationBean.setLoadOnStartup(20);
return servletRegistrationBean;
}
@Bean
public ObjectMapper defaultObjectMapper() {
return new ObjectMapper();
}
}
I am using Postman to send the POST request and I get a 200 OK response, which is just an acknowledgement and not the actual response:
KEY. VALUE
X-Frame-Options DENY
Content-Security-Policy frame-ancestors 'none'
X-Content-Type-Options nosniff
X-XSS-Protection 1; mode=block
Content-Length 0
Date Thu, 29 Dec 2022 09:33:38 GMT
Keep-Alive timeout=60
Connection keep-alive
So, after a little investigation, I understood that I had to add DispatcherType ASYNC to my filter. This is my Filter:
@Component
public class AsyncFilter implements Filter {
private static final Logger LOG = LoggerFactory.getLogger(AsyncFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
ContentCachingResponseWrapper responseWrapper = newContentCachingResponseWrapper((HttpServletResponse) response);
try {
filterChain.doFilter(requestWrapper,responseWrapper);
} finally {
LOG.trace("Before - ResponseWrapper: {}",
getResponsePayload(responseWrapper));
LOG.trace("Before - Response: {}",
getResponsePayload((HttpServletResponse) response));
byte[] responseArray = responseWrapper.getContentAsByteArray();
String responseString = new String(responseArray,
responseWrapper.getCharacterEncoding());
LOG.trace("####### responseString = {}", responseString);
responseWrapper.copyBodyToResponse();
LOG.trace("After - ResponseWrapper: {}",
getResponsePayload(responseWrapper));
LOG.trace("After - Response: {}",
getResponsePayload((HttpServletResponse) response));
}
}
This is how I am registering the Filter in the TestBootApplication:
@Bean
public FilterRegistrationBean<AsyncFilter> asyncFilter() {
FilterRegistrationBean<AsyncFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new AsyncFilter());
registrationBean.setDispatcherTypes(EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
Now I get the actual response but I get an empty body. Also, the content-length is 0.
KEY. VALUE
X-Frame-Options DENY
Content-Security-Policy frame-ancestors 'none'
X-Content-Type-Options nosniff
X-XSS-Protection 1; mode=block
Content-Type text/plain
Content-Length 0
Date Thu, 29 Dec 2022 09:33:38 GMT
Keep-Alive timeout=60
Connection keep-alive
Here is an excerpt from the logs:
16:57:11.099 [http-nio-8081-exec-5] TRACE c.b.a.filters.AsyncFilter - Before - Response Wrapper: testping
16:57:11.100 [http-nio-8081-exec-5] TRACE c.b.a.filters.AsyncFilter - Before - Response: [unknown]
16:57:11.100 [http-nio-8081-exec-5] TRACE c.b.a.filters.AsyncFilter - ####### Response = testping
16:57:11.100 [http-nio-8081-exec-5] TRACE c.b.a.filters.AsyncFilter - After - Response Wrapper: [unknown]
16:57:11.100 [http-nio-8081-exec-5] TRACE c.b.a.filters.AsyncFilter - After - Response: [unknown]
I think the response is being dispatched before the controller finishes processing. I have invested a lot of time on this and haven't been able to find out the cause. What am I missing here? I am using spring-boot version 2.6.12
Upvotes: 1
Views: 704
Reputation: 3373
You could try omitting the WebAsyncTask
and diretly return the Callable
from the controller method like this:
public Callable<ResponseEntity<String>> test(@RequestParam(value = "foo", required = false) String data) {
return () -> {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
return new ResponseEntity("testping", responseHeaders, HttpStatus.OK);
};
}
To configure your async task executor, you can create a global config bean like this:
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(10);
executor.setThreadNamePrefix("mvc-task-executor-");
executor.initialize();
configurer.setTaskExecutor(executor);
}
}
Side note: you can boost your chances of good answers if you format your code properly for SO.
Upvotes: 1