I've got a Spring Boot 2.7.3 app with the following controller defined:
@RestController
@EnableAutoConfiguration
public class TrainController {
@CrossOrigin(origins = "http://localhost:3000")
@RequestMapping(value = "/trains/history", method = RequestMethod.GET)
public List<TrainStatus> getTrainStatusesForTimestamp(
@RequestParam long timestamp
) {
// do stuff
}
}
Invoking this API endpoint typically works just fine, certainly when I'm running the app locally, but in production under heavier load, e.g. repeated calls to this API endpoint in parallel with lots of calls to other API endpoints defined by my app across multiple controllers, I start to see messages like these in my logs:
2022-09-06 20:48:37.939 DEBUG 19282 --- [https-openssl-nio-443-exec-10] o.s.w.f.CommonsRequestLoggingFilter : Before request [GET /trains/history?timestamp=1662511707]
2022-09-06 20:48:37.945 WARN 19282 --- [https-openssl-nio-443-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'timestamp' for method parameter type long is not present]
2022-09-06 20:48:37.945 DEBUG 19282 --- [https-openssl-nio-443-exec-10] o.s.w.f.CommonsRequestLoggingFilter : After request [GET /trains/history?timestamp=1662511707]
(The CommonsRequestLoggingFilter
DEBUG log lines are coming from a bean I've defined in accordance with this doc; I was curious if the required timestamp
parameter was actually being defined or not, which is why I added it.)
Furthermore, when these errant MissingServletRequestParameterException
exceptions are thrown, the response is a 400 Bad Request. I've confirmed from the client side of things that timestamp
is indeed being included as a request parameter, and the Spring Boot app logs seem to confirm this, yet I'm intermittently seeing these exceptions under heavy load.
What's going on? Am I hitting some kind of connection or thread limit defined by Tomcat or something? As far as I can tell the app has plenty of additional headroom with regards to memory.
Thanks in advance for any help anyone can provide!
For reference, here are some apparently similar issues I've found:
CodePudding user response:
After reading this blog post, I believe I've just figured out what's going on: I've got another filter PublicApiFilter
operating on a separate set of API endpoints that is asynchronously invoking a function where I pass the request object, i.e. the instance of HttpServletRequest
, into it and invoke various methods offered by it. These asynchronous operations on these requests appear to be affecting subsequent requests, even ones to other API endpoints not covered by PublicApiFilter
. I was able to simply make the invocation of this function synchronous instead of asynchronous by removing the @Async
annotation I was using and now the issue appears to have been resolved!
Here are some snippets of my code in case it's useful to someone else someday:
@EnableScheduling
@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan
@EnableAsync
public class Application implements WebMvcConfigurer, AsyncConfigurer {
// ...
@Override // AsyncConfigurer
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(1);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
@Override // AsyncConfigurer
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
@Component
public class PublicApiFilter extends GenericFilterBean {
private final PublicApiService publicApiService;
@Autowired
public PublicApiFilter(PublicApiService publicApiService) {
this.publicApiService = publicApiService;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// ...
chain.doFilter(request, response);
this.publicApiService.logRequest(httpRequest);
}
}
@Service
public class PublicApiService {
// ...
@Async // <- simply removing this annotation appears to have done the trick!
public void logRequest(HttpServletRequest request) {
// invoke request.getRequestURI(), request.getHeader(...), request.getRemoteAddr, and request.getParameterMap() for logging purposes
}
}