spring-graphql
spring-graphql copied to clipboard
Support timeout for GraphQL request handling and propagate cancellation to controller methods
Hi,
We would like to setup custom timeout for the async GraphQL query like we used to do in the kickstart GraphQL as below.
#Graphql query timeout settings graphql.servlet.async.timeout=5s
Is your goal to control malicious queries, as mentioned in this issue? A simple timeout is a very basic mechanism that can impact other scenarios as well such as a slow network that have nothing to do with that concern.
GraphQL Java provides Query Complexity and Query Depth instrumentations. We think that's a better mechanism to control malicious queries. There is a Boot issue spring-projects/spring-graphql#469 for an enhancement in that area, and we can consider improved support here as well if there are concrete suggestions.
That said if you really wanted, you can apply a timeout through an interceptor. For example:
@Bean
WebGraphQlInterceptor interceptor() {
return (request, chain) -> chain.next(request)
.timeout(Duration.ofSeconds(2), Mono.error(new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE)));
}
This ends the response and should also cancel GraphQL request processing. However, note there is currently an issue that prevents it from working. You can watch https://github.com/reactor/reactor-core/issues/3138, and when that's fixed, it should work as expected.
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.
@rstoyanchev Imho, special property for global request timeout is the first thing anyone would try to lookup. Reactor way is very non-obvious. Could it be reasonable to built in and autoconfigure such an interceptor based on corresponding property?
Hello @rstoyanchev, is there any update on this. I have tried above solution but it is not working
My apologies, indeed the above doesn't work when data is fetched synchronously because blocking occurs as part of the initial call while composing the reactive chain.
I've confirmed the following works:
public class RequestTimeoutInterceptor implements WebGraphQlInterceptor {
private final Duration timeout;
public RequestTimeoutInterceptor(Duration timeout) {
this.timeout = timeout;
}
@Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
return chain.next(request)
.subscribeOn(Schedulers.boundedElastic())
.timeout(this.timeout, Mono.error(new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE)));
}
}
I am re-opening this in order to make such an interceptor available. Using an interceptor has the advantage of targeting GraphQL request handling vs any other HTTP request.
Hi @rstoyanchev, Thanks for reopening this issue. I tried the interceptor option but it didn't work. The query that I am working on has following structure:
query getData {
appData {
name
appConfig{
name
id
}
}
}
Now appConfig
resolver's batch loader is taking too much time to fetch data because of which whole query gets aborted after 30 seconds with AsyncRequestTimeoutException.
@amruta98 can you double check that the interceptor is registered and is getting invoked, e.g. by adding a log message or putting a breakpoint? It should be as simple as declaring it as a bean but just to be sure.
@rstoyanchev I checked it by adding logs and by debugging. It is getting invoked but not working as expected
If you can create an isolated sample, I will have a look.
@rstoyanchev Actually I already had Interceptor in my project for passing custom context and I modified that with timeout related code. Please check below code
@Component
@EnableAutoConfiguration
public class DemoInterceptor implements WebGraphQlInterceptor{
@Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, WebGraphQlInterceptor.Chain chain) {
request.configureExecutionInput((executionInput, builder) ->
builder.graphQLContext(context -> {
CustomContext customContext = createCustomContext(request);
context.put("customContext", customContext);
}).build());
return chain.next(request).subscribeOn(Schedulers.boundedElastic())
.timeout(Duration.ofMillis(60000), Mono.error(new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE)));
}
}
@amruta98 What do you mean? Is your custom interceptor working? If it's not, can you share a minimal sample?
Hi @bclozel , My custom interceptor is working, I am able to fetch custom context in my api also, but the changes related to timeout are not working as expected.
.subscribeOn(Schedulers.boundedElastic()) .timeout(Duration.ofMillis(60000), Mono.error(new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE)))
ok then please provide a minimal sample.
@amruta98 as mentioned previously, I have tested the above. There must be some other reason why it doesn't work for you. Are you able to provide a small sample, e.g. via https://start.spring.io that demonstrates the issue?