-
Notifications
You must be signed in to change notification settings - Fork 319
Provide a WebGraphQlInterceptor that supports HTTP timeouts #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
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 #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 reactor/reactor-core#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 |
@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. |
Thanks @tstocker-black-cape , this is enough to reproduce. I managed to isolate this behavior in a minimal sample and it appears the signal is lost somewhere in |
I was mistaken, it seems this is a well-known limitation of CompletableFuture and there's nothing we can do about it here. |
Buuuumer... Thanks for looking into this. Here's a minimal example of our workaround. I'm not sure if you'd be interested in integrating this into spring boot. There may be edge cases we're missing, but this works for our use cases.
|
Thanks for all the feedback. We can do something along the lines of #450 (comment), and propagate the cancellation from This would have an important limitation though since we can only propagate cancellation via reactive types. There is no way to cancel a controller method executing synchronously. For that at best, we could have a flag in the |
I've just addressed the CANCEL signal propagation part in #1149 - repurposing this issue to be about the |
This is now available in 1.4.0-SNAPSHOT with a new section in the documentation. |
Reopening to reconsider the blocking case. |
I have a sort of "hacky" workaround in my app to power the cancellation of blocking tasks. It could certainly use some polish, but it works well enough. I'll share it in case it inspires a more general solution. protected <T> T wrapResponse(Callable<T> task) { // NOSONAR
// Submit the work to the virtual thread executor
Future<T> future = VIRTUAL_EXECUTOR.submit(task);
// Catch any exceptions
try {
// Wait for the task to complete or time out
return future.get(REQUEST_TIMEOUT_SECONDS, SECONDS);
} catch (Exception e) {
// Cancel the task
future.cancel(true);
// If this is an interrupted exception, interrupt the current thread
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
// If this is an execution exception, extract the cause
Throwable cause = e;
if (e instanceof ExecutionException executionException) {
cause = executionException.getCause();
}
// Throw the wrapped exception
if (cause instanceof RuntimeException runtimeException) {
throw runtimeException;
} else {
// TODO make a better exception...
throw new RuntimeException(cause);
}
}
} This wrap function is then used in the controller methods like this @QueryMapping
public Person person(UUID id) {
return wrapResponse(()->personService.findById(id));
} Obviously for a general solution you wouldn't want to have an explicit wrapper function, but I didn't want to get way down in the weeds with the GraphQL library, so this worked for my app. I should also note that my app is using virtual threads as configured by sprint boot. So the |
Re-closing this issue in favor of #1153 |
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
The text was updated successfully, but these errors were encountered: