Skip to content

Make HttpRequestHandlerImpl.handle() async when possible (fixes #259) #268

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

Merged
merged 11 commits into from
Dec 19, 2020

Conversation

olim7t
Copy link
Contributor

@olim7t olim7t commented Sep 17, 2020

Not complete but I wanted to open the discussion before I modify the servlets: I would personally have AbstractGraphQLHttpServlet.doRequestAsync() invoke handler.handle() directly, and deprecate configuration.getAsyncExecutor() and configuration.isAsyncServletModeEnabled(). This means the query will execute synchronously if the data fetchers are not async -- or if the HttpServletRequest doesn't support async for some reason. But I agree with @artem-ag that it's probably not that useful to reschedule on another thread pool in that case.

I've tried to follow the existing code style, let me know if anything is off.

@@ -27,25 +28,36 @@
}

public GraphQLQueryResult query(GraphQLInvocationInput invocationInput) {
return queryAsync(invocationInput).join();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I preserved the original signatures for binary compatibility.

This one is also still used by AbstractGraphQLHttpServlet.executeQuery() for JMX.

.map(CompletableFuture::join)
.collect(toList());
@SuppressWarnings({"unchecked", "rawtypes"})
private <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the JDK doesn't provide this common operation out of the box 😞
It's also a bit hacky to work around the vararg argument of allOf().

Copy link
Contributor

@artem-ag artem-ag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for doing this.

@@ -48,37 +51,61 @@ public void handle(HttpServletRequest request, HttpServletResponse response) thr

private void execute(GraphQLInvocationInput invocationInput, HttpServletRequest request,
HttpServletResponse response) {
try {
GraphQLQueryResult queryResult = invoke(invocationInput, request, response);
if (request.isAsyncSupported()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated comment. as you are touching this already.
Do you mind changing line 41 to GraphQLException | IOException e? invocationInputParser.getGraphQLInvocationInput throws IOException (JsonProcessingException or JsonMappingException specifically) in case of unparseable input request. That results in #258

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those the only IOException instances that can happen here? Just making sure that we're not also catching others that would qualify as legitimate server errors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Official method signature throws IOException. So it's hard to guarantee that. The use case right now is that servlet returns http 500 for bad requests that can't be deserialized. And that makes external attacks last longer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added JsonProcessingException instead of IOException.

@oliemansm
Copy link
Member

@olim7t Is this PR really still a draft or ready to merge from your POV?

@oliemansm
Copy link
Member

@olim7t Any update on this PR?

@olim7t
Copy link
Contributor Author

olim7t commented Dec 16, 2020

Sorry @oliemansm I completely missed your comments.

This is not ready yet. The part about modifying the servlets that I mentioned in my initial comment is not done:

Not complete but I wanted to open the discussion before I modify the servlets: I would personally have AbstractGraphQLHttpServlet.doRequestAsync() invoke handler.handle() directly, and deprecate configuration.getAsyncExecutor() and configuration.isAsyncServletModeEnabled().

It feels wasteful to keep that executor if handle() is already fully async. But maybe my suggestion of deprecating is too aggressive, there could be some form of backward compatibility for people that use non-async fetchers.

To be completely honest, I won't have time to finish this PR: I ended up dropping the dependency to graphql-java-servlet and switching to a custom implementation in my project (not solely because of this issue; my use case is a bit peculiar). But if someone else is willing to address the servlet part, the basics are here.

# Conflicts:
#	graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java
#	graphql-java-servlet/src/main/java/graphql/kickstart/servlet/HttpRequestHandlerImpl.java
@oliemansm oliemansm added this to the 10.1.0 milestone Dec 19, 2020
@oliemansm oliemansm marked this pull request as ready for review December 19, 2020 14:02
@oliemansm oliemansm merged commit 6dad9c6 into graphql-java-kickstart:master Dec 19, 2020
@olim7t olim7t deleted the async branch December 21, 2020 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants