Skip to content

Commit cfad30e

Browse files
committed
Make HttpRequestHandlerImpl.handle() async when possible (fixes #259)
1 parent 263fc86 commit cfad30e

File tree

2 files changed

+63
-25
lines changed

2 files changed

+63
-25
lines changed

graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java

+24-12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput;
99
import graphql.kickstart.execution.input.GraphQLInvocationInput;
1010
import graphql.kickstart.execution.input.GraphQLSingleInvocationInput;
11+
import java.util.ArrayList;
1112
import java.util.List;
1213
import java.util.concurrent.CompletableFuture;
1314
import lombok.AllArgsConstructor;
@@ -27,25 +28,36 @@ public CompletableFuture<ExecutionResult> executeAsync(GraphQLSingleInvocationIn
2728
}
2829

2930
public GraphQLQueryResult query(GraphQLInvocationInput invocationInput) {
31+
return queryAsync(invocationInput).join();
32+
}
33+
34+
public CompletableFuture<GraphQLQueryResult> queryAsync(GraphQLInvocationInput invocationInput) {
3035
if (invocationInput instanceof GraphQLSingleInvocationInput) {
31-
return GraphQLQueryResult.create(query((GraphQLSingleInvocationInput) invocationInput));
36+
return executeAsync((GraphQLSingleInvocationInput)invocationInput).thenApply(GraphQLQueryResult::create);
3237
}
3338
GraphQLBatchedInvocationInput batchedInvocationInput = (GraphQLBatchedInvocationInput) invocationInput;
34-
return GraphQLQueryResult.create(query(batchedInvocationInput));
39+
return executeAsync(batchedInvocationInput).thenApply(GraphQLQueryResult::create);
3540
}
3641

37-
private ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput) {
38-
return executeAsync(singleInvocationInput).join();
42+
private CompletableFuture<List<ExecutionResult>> executeAsync(GraphQLBatchedInvocationInput batchedInvocationInput) {
43+
GraphQL graphQL = batchedDataLoaderGraphQLBuilder.newGraphQL(batchedInvocationInput, graphQLBuilder);
44+
return sequence(
45+
batchedInvocationInput.getExecutionInputs().stream()
46+
.map(executionInput -> proxy.executeAsync(graphQL, executionInput))
47+
.collect(toList()));
3948
}
4049

41-
private List<ExecutionResult> query(GraphQLBatchedInvocationInput batchedInvocationInput) {
42-
GraphQL graphQL = batchedDataLoaderGraphQLBuilder.newGraphQL(batchedInvocationInput, graphQLBuilder);
43-
return batchedInvocationInput.getExecutionInputs().stream()
44-
.map(executionInput -> proxy.executeAsync(graphQL, executionInput))
45-
.collect(toList())
46-
.stream()
47-
.map(CompletableFuture::join)
48-
.collect(toList());
50+
@SuppressWarnings({"unchecked", "rawtypes"})
51+
private <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
52+
CompletableFuture[] futuresArray = futures.toArray(new CompletableFuture[0]);
53+
return CompletableFuture.allOf(futuresArray).thenApply(aVoid -> {
54+
List<T> result = new ArrayList<>();
55+
for (CompletableFuture future : futuresArray) {
56+
assert future.isDone(); // per the API contract of allOf()
57+
result.add((T) future.join());
58+
}
59+
return result;
60+
});
4961
}
5062
}
5163

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/HttpRequestHandlerImpl.java

+39-13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import graphql.kickstart.execution.input.GraphQLInvocationInput;
1010
import graphql.kickstart.execution.input.GraphQLSingleInvocationInput;
1111
import java.io.IOException;
12+
import java.io.UncheckedIOException;
13+
import java.util.concurrent.CompletableFuture;
14+
import javax.servlet.AsyncContext;
1215
import javax.servlet.http.HttpServletRequest;
1316
import javax.servlet.http.HttpServletResponse;
1417
import lombok.extern.slf4j.Slf4j;
@@ -48,37 +51,60 @@ public void handle(HttpServletRequest request, HttpServletResponse response) thr
4851

4952
private void execute(GraphQLInvocationInput invocationInput, HttpServletRequest request,
5053
HttpServletResponse response) {
51-
try {
52-
GraphQLQueryResult queryResult = invoke(invocationInput, request, response);
54+
if (request.isAsyncSupported()) {
55+
AsyncContext asyncContext = request.startAsync(request, response);
56+
asyncContext.setTimeout(configuration.getAsyncTimeout());
57+
invoke(invocationInput, request, response)
58+
.thenAccept(result -> writeResultResponse(result, request, response))
59+
.exceptionally(t -> writeErrorResponse(t, response))
60+
.thenAccept(aVoid -> asyncContext.complete());
61+
} else {
62+
try {
63+
GraphQLQueryResult result = invoke(invocationInput, request, response).join();
64+
writeResultResponse(result, request, response);
65+
} catch (Throwable t) {
66+
writeErrorResponse(t, response);
67+
}
68+
}
69+
}
5370

54-
QueryResponseWriter queryResponseWriter = QueryResponseWriter.createWriter(queryResult, configuration.getObjectMapper(),
55-
configuration.getSubscriptionTimeout());
71+
private void writeResultResponse(GraphQLQueryResult queryResult, HttpServletRequest request,
72+
HttpServletResponse response) {
73+
QueryResponseWriter queryResponseWriter = QueryResponseWriter.createWriter(queryResult, configuration.getObjectMapper(),
74+
configuration.getSubscriptionTimeout());
75+
try {
5676
queryResponseWriter.write(request, response);
57-
} catch (Throwable t) {
58-
response.setStatus(STATUS_BAD_REQUEST);
59-
log.info("Bad GET request: path was not \"/schema.json\" or no query variable named \"query\" given");
60-
log.debug("Possibly due to exception: ", t);
77+
} catch (IOException e) {
78+
// will be processed by the exceptionally() call below
79+
throw new UncheckedIOException(e);
6180
}
6281
}
6382

64-
private GraphQLQueryResult invoke(GraphQLInvocationInput invocationInput, HttpServletRequest request,
83+
private Void writeErrorResponse(Throwable t, HttpServletResponse response) {
84+
response.setStatus(STATUS_BAD_REQUEST);
85+
log.info("Bad GET request: path was not \"/schema.json\" or no query variable named \"query\" given");
86+
log.debug("Possibly due to exception: ", t);
87+
return null;
88+
}
89+
90+
private CompletableFuture<GraphQLQueryResult> invoke(GraphQLInvocationInput invocationInput, HttpServletRequest request,
6591
HttpServletResponse response) {
6692
if (invocationInput instanceof GraphQLSingleInvocationInput) {
67-
return graphQLInvoker.query(invocationInput);
93+
return graphQLInvoker.queryAsync(invocationInput);
6894
}
6995
return invokeBatched((GraphQLBatchedInvocationInput) invocationInput, request, response);
7096
}
7197

72-
private GraphQLQueryResult invokeBatched(GraphQLBatchedInvocationInput batchedInvocationInput,
98+
private CompletableFuture<GraphQLQueryResult> invokeBatched(GraphQLBatchedInvocationInput batchedInvocationInput,
7399
HttpServletRequest request,
74100
HttpServletResponse response) {
75101
BatchInputPreProcessor preprocessor = configuration.getBatchInputPreProcessor();
76102
BatchInputPreProcessResult result = preprocessor.preProcessBatch(batchedInvocationInput, request, response);
77103
if (result.isExecutable()) {
78-
return graphQLInvoker.query(result.getBatchedInvocationInput());
104+
return graphQLInvoker.queryAsync(result.getBatchedInvocationInput());
79105
}
80106

81-
return GraphQLQueryResult.createError(result.getStatusCode(), result.getStatusMessage());
107+
return CompletableFuture.completedFuture(GraphQLQueryResult.createError(result.getStatusCode(), result.getStatusMessage()));
82108
}
83109

84110
}

0 commit comments

Comments
 (0)