We are getting started in Spring Webflux and we are using Annotated Controllers for REST API. We would like to measure the total time the Spring boot server takes to process a request. Looks like we could use Spring WebFilter however I am not sure how to set StartTime (some kind of attribute in ServerWebExchange or other request headers)? Also once the response is completed how could we get the startTime and calculate the time difference ?
Thanks!
Yes, you can use a WebFilter for this. See the example code below:
#Component
#Slf4j
public class RequestTimingFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
long startMillis = System.currentTimeMillis();
return chain.filter(exchange)
.doOnSuccess(aVoid ->
log.info("Elapsed Time: {}ms", System.currentTimeMillis() - startMillis)
);
}
}
Note the doOnSuccess call, which is only executed when the request is successful. For errors, you can add doOnError call to see the request time.
Related
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:
Is there any situation QueryString is present but HttpServletRequest.getParameterMap() is empty?
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
}
}
Do not pass HttpServletRequest into any async method!
Must reads for solving above problem:
Never pass a request to an asynchronous thread! There are pits!
How to correctly use request in asynchronous threads in springboot
Occasional MissingServletRequestParameterException, who moved my parameters?
Using Spring web a simple OncePerRequestFilter (see below) can maintain a request id for the span of the request.
Storing the generated request id in a request attribute, adding it to the logging MDC, and returning in a response header.
I understand the reactive webflux stack is completely different, so how should one tackle this?
I found https://github.com/spring-projects/spring-framework/issues/20239 but it is not clear what is now supported or not.
#Component
public class RequestIdFilter extends OncePerRequestFilter implements Ordered {
private static final String MDC_KEY = "requestId";
private static final String REQUEST_ATTRIBUTE_NAME = "requestId";
private static final String RESPONSE_HEADER_NAME = "X-Request-Id";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
var requestId = UUID.randomUUID().toString();
MDC.put(MDC_KEY, requestId);
request.setAttribute(REQUEST_ATTRIBUTE_NAME, requestId);
response.setHeader(RESPONSE_HEADER_NAME, requestId);
try {
filterChain.doFilter(request, response);
} finally {
MDC.remove(MDC_KEY);
}
}
#Override
public int getOrder() {
return requestIdProperties.getServerFilterOrder();
}
}
You don't need a OncePerRequestFilter implementation in WebFlux, as Filters are executed only once because request forwarding (like in Servlet) is not supported in WebFlux.
Now you can implement a WebFilter that adds a requestId as a request attribute, pretty much like the version you're showing.
There are several things to pay attention to:
you should avoid calling blocking methods within your reactive pipeline, UUID.randomUUID() is blocking
Adding data to the MDC is not straightforward in a reactive environment, since this feature originally relies on ThreadLocal. See this blog post for now and keep an eye on this issue for more guidance
with this use case in mind, it sounds like Spring Cloud Sleuth might achieve what you want, and more (supporting spans, etc).
If the return type of one controller method is CompletableFuture, the result would be completed latter asynchronously, but how to set timeout for this request so that the spring would abort the request if it's not completed in time?
In legacy way, via AsyncContext, I could do it. But what about CompletableFuture case? I could not find any related doc.
Note that I know the global default timeout setting, but my question is how to set timeout per request.
I try to answer my question.
The processing of CompletableFuture is same to DeferredResult?
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-async-processing
The spring would do request.startAsync() only after the handler method returns, then I think the only way to change timeout is to enable a AsyncHandlerInterceptor and do request.getAsyncContext().setTimeout() in afterConcurrentHandlingStarted()?
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/AsyncHandlerInterceptor.html#afterConcurrentHandlingStarted-javax.servlet.http.HttpServletRequest-javax.servlet.http.HttpServletResponse-java.lang.Object-
This is how to do it
#Configuration
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AsyncHandlerInterceptor() {
#Override
public void afterConcurrentHandlingStarted(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
request.getAsyncContext().setTimeout(myTimeoutInMillisHere);
}
});
}
}
please note that the timeout can be configured through spring.mvc.async.request-timeout property.
For example spring.mvc.async.request-timeout: "180s" set it to 3 minutes
I've developed an API using SpringBoot
but my API takes long to respond, as its a heavy application.
So I'm planning to give a response with meaningful message immediately and then run the heavy application in the back ground .
Is anyone familiar with this scenario.
I have a SpringBootWebApplication class which has main()
#SpringBootApplication
#EnableAsync
public class SpringBootWebApplication extends SpringBootServletInitializer {
}
Also have OnDemandController class
#Controller
#RequestMapping("/run/lob")
public class OnDemandControllerForLob{
#RequestMapping(value="get",method=RequestMethod.GET)
#Produces({MediaType.APPLICATION_JSON})
public #ResponseBody String processOndemandService(#Context
HttpServletRequest request, #Context HttpServletResponse response) {
//here is a method call that takes lot of time to execute
OnDemand.processtherequest();
return response
}
}
Can someone please suggest me the best way to get response immediately and run the application logic (or the method that invokes the heavy java application in the back ground). The response i need is just a string saying "your request is served".
From your post, #EnableAsync is already applied.
Now the OnDemand.processtherequest() method must be public and marked with #Asnyc.
However, OnDemand.processtherequest(); looks like a static method call (based on the name). This will not work. There must be a Spring managed bean with a non-static method. If OnDemand is not in your control then create a wrapper bean which delegates to the static method. For example:
#Service
public class OnDemandService {
#Async
public void processTheRequest() {
OnDemand.processtherequest();
}
}
And in the controller, autowire the service, and call the method. The controller method will return immediately while the method marked as #Async will run in a different thread.
#Controller
#RequestMapping("/run/lob")
public class OnDemandControllerForLob {
#Autowired
private OnDemandService onDemandService;
#RequestMapping(value="get",method=RequestMethod.GET)
#Produces({MediaType.APPLICATION_JSON})
public #ResponseBody String processOndemandService(#Context
HttpServletRequest request, #Context HttpServletResponse response) {
//here is a method call that takes lot of time to execute
// OnDemand.processtherequest();
onDemandService.processTheRequest();
return "your request is served";
}
}
I am using Spring WebFlux in my project. I want to create an interceptor to calculate the time taken by each API. In Spring MVC we have HandlerInterceptor which is not present in spring-boot-starter-webflux. I tried adding spring-boot-starter-web and wrote my interceptor but it didn't work. Here is the code:
#Component
public class TimeInterceptor implements HandlerInterceptor {
public static Logger logger = Logger.getLogger(TimeInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
long totaltime = System.currentTimeMillis() - (long) request.getAttribute("startTime");
request.setAttribute("totaltime", totaltime);
logger.info("Logging total time" + totaltime);
}
...
...
I want to add similar functionality to my application and intercept time taken by each call.
Thanks in advance.
If you want to handle a request when it starts and when it completes, you can use WebFilter.
Try something like this
#Component
public class CustomWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
long startTime = System.currentTimeMillis();
return chain.filter(exchange).doFinally(signalType -> {
long totalTime = System.currentTimeMillis() - startTime;
exchange.getAttributes().put("totalTime", totalTime);
System.out.println(totalTime);
});
}
}
When request processing starts all defined filters are called. Mono is returned from filter. It indicates when request processing is complete.
There is no concept of HandlerInterceptor in Spring WebFlux, but you can use your own WebFilter for that instead.
The feature you're describing sounds a lot like the metrics support provided by Actuator and Micrometer. If you'd like to try it:
Add the actuator dependency to your project
Expose the relevant endpoints (here, metrics)
Go to "/actuator/metrics and select the metric for server HTTP requests (see the reference documentation).
Micrometer offers way more and helps you to get your metrics right, like: taking into account GC pauses when measuring time, providing histograms/percentiles/..., and more.
Note: adding spring-boot-starter-web to your application will turn it into a Spring MVC application.
Use the following project as dependency as jar / ant / maven / gradle
https://github.com/TurquoiseSpace/spring-webflux-http-interceptor
<dependency>
<groupId>com.github.TurquoiseSpace</groupId>
<artifactId>spring-webflux-http-interceptor</artifactId>
<version>0.0.7</version>
</dependency>
It provides ReactiveApiInterceptor which is a custom implementation of WebFilter
If required, you can override ReactiveApiInterceptor as well, to add your own custom logic, besides having the default logic, by calling
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
// Your custom implementation, when api is hit and the request lands
super.filter(serverWebExchange, webFilterChain)
.doFinally(signalType -> {
// Your custom implementation, when request-response exchange gets completed
});
}