From 0d46a98f62dccc0490abc3c0e2ca13f1e07cdc70 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Wed, 16 Sep 2020 16:16:13 +0200 Subject: [PATCH] Create web stack-specific GraphQL handlers Instead of trying to create a single handler for both reactive and non-reactive implementations, this commit splits the implementations. --- .../graphql/GraphQLHandler.java | 57 -------------- .../graphql/reactive/GraphQLHandler.java | 52 +++++++++++++ .../graphql/reactive/package-info.java | 6 ++ .../graphql/servlet/GraphQLHandler.java | 74 +++++++++++++++++++ .../graphql/servlet/package-info.java | 6 ++ 5 files changed, 138 insertions(+), 57 deletions(-) delete mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/GraphQLHandler.java create mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/reactive/GraphQLHandler.java create mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/reactive/package-info.java create mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/servlet/GraphQLHandler.java create mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/servlet/package-info.java diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/GraphQLHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/GraphQLHandler.java deleted file mode 100644 index b83f32b4..00000000 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/GraphQLHandler.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.springframework.graphql; - -import graphql.ExecutionInput; -import graphql.ExecutionResult; -import graphql.GraphQL; -import org.springframework.http.HttpHeaders; -import org.springframework.util.MultiValueMap; -import reactor.core.publisher.Mono; - -import java.util.List; -import java.util.Map; - -public class GraphQLHandler { - private GraphQL graphQL; - - private GraphQLInterceptor interceptor; - - public GraphQLHandler(GraphQL graphQL, GraphQLInterceptor interceptor) { - this.graphQL = graphQL; - this.interceptor = interceptor; - } - - public Mono graphqlPOST(GraphQLHttpRequest graphQLHttpRequest) { - String query = graphQLHttpRequest.getQuery(); - ExecutionInput input = ExecutionInput.newExecutionInput() - .query(query) - .operationName(graphQLHttpRequest.getOperationName()) - .variables(graphQLHttpRequest.getVariables()) - .build(); - MultiValueMap requestParams = graphQLHttpRequest.getRequestParams(); - Mono executionInput = interceptor.preHandle(input, - graphQLHttpRequest.getHttpHeaders(), - requestParams); - return executionInput - .flatMap(this::execute) - .flatMap(result -> interceptor.postHandle(result, graphQLHttpRequest.getHttpHeaders(), requestParams)) - .flatMap(result -> toResponseBody(result, graphQLHttpRequest)); - } - - private Mono toResponseBody(ExecutionResult executionResult, GraphQLHttpRequest graphQLHttpRequest) { - Map responseBodyRaw = executionResult.toSpecification(); - Object data = responseBodyRaw.get("data"); - List> errors = (List>) responseBodyRaw.get("errors"); - Map extensions = (Map) responseBodyRaw.get("extensions"); - GraphQLHttpResponse responseBody = new GraphQLHttpResponse(data, - errors, - extensions, - new HttpHeaders()); - Mono graphQLResponseBodyMono = interceptor.customizeGraphQLHttpResponse(responseBody, executionResult, graphQLHttpRequest); - return graphQLResponseBodyMono; - } - - - protected Mono execute(ExecutionInput input) { - return Mono.fromCompletionStage(graphQL.executeAsync(input)); - } -} diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/reactive/GraphQLHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/reactive/GraphQLHandler.java new file mode 100644 index 00000000..725a488b --- /dev/null +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/reactive/GraphQLHandler.java @@ -0,0 +1,52 @@ +package org.springframework.graphql.reactive; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import reactor.core.publisher.Mono; + +import org.springframework.graphql.GraphQLRequestBody; +import org.springframework.http.HttpHeaders; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; + +public class GraphQLHandler { + + private final GraphQL graphQL; + + public GraphQLHandler(GraphQL.Builder graphQLBuilder) { + this.graphQL = graphQLBuilder.build(); + } + + public Mono handle(ServerRequest request) { + Mono bodyMono = request.bodyToMono(GraphQLRequestBody.class); + return bodyMono.map(body -> { + String query = body.getQuery(); + if (query == null) { + query = ""; + } + ExecutionInput executionInput = ExecutionInput.newExecutionInput() + .query(query) + .operationName(body.getOperationName()) + .variables(body.getVariables()) + .build(); + return customizeExecutionInput(executionInput, request.headers().asHttpHeaders()) + .then(execute(executionInput)); + }) + .flatMap(this::toServerResponse); + } + + protected Mono customizeExecutionInput(ExecutionInput input, HttpHeaders headers) { + return Mono.just(input); + } + + protected Mono execute(ExecutionInput input) { + return Mono.fromFuture((graphQL.executeAsync(input))); + } + + protected Mono toServerResponse(Mono result) { + return result.map(ExecutionResult::toSpecification) + .flatMap(spec -> ServerResponse.ok().bodyValue(spec)); + } + +} diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/reactive/package-info.java b/spring-graphql-web/src/main/java/org/springframework/graphql/reactive/package-info.java new file mode 100644 index 00000000..93ba6435 --- /dev/null +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/reactive/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package org.springframework.graphql.reactive; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/servlet/GraphQLHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/servlet/GraphQLHandler.java new file mode 100644 index 00000000..d9669c55 --- /dev/null +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/servlet/GraphQLHandler.java @@ -0,0 +1,74 @@ +package org.springframework.graphql.servlet; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import javax.servlet.ServletException; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; + +import org.springframework.graphql.GraphQLRequestBody; +import org.springframework.http.HttpHeaders; +import org.springframework.web.server.ServerErrorException; +import org.springframework.web.server.ServerWebInputException; +import org.springframework.web.servlet.function.ServerRequest; +import org.springframework.web.servlet.function.ServerResponse; + +public class GraphQLHandler { + + private final GraphQL graphQL; + + public GraphQLHandler(GraphQL.Builder graphQL) { + this.graphQL = graphQL.build(); + } + + public ServerResponse handle(ServerRequest serverRequest) { + GraphQLRequestBody body; + try { + body = serverRequest.body(GraphQLRequestBody.class); + } + catch (ServletException | IOException ex) { + throw new ServerWebInputException("Failed to read request body", null, ex); + } + String query = body.getQuery(); + if (query == null) { + query = ""; + } + ExecutionInput input = ExecutionInput.newExecutionInput() + .query(query) + .operationName(body.getOperationName()) + .variables(body.getVariables()) + .build(); + // Invoke GraphQLInterceptor's preHandle here + CompletableFuture resultFuture = + customizeExecutionInput(input, serverRequest.headers().asHttpHeaders()).thenCompose(this::execute); + // Invoke GraphQLInterceptor's postHandle here + return customizeExecutionResult(resultFuture); + } + + protected CompletableFuture customizeExecutionInput(ExecutionInput input, HttpHeaders headers) { + return CompletableFuture.completedFuture(input); + } + + protected CompletableFuture execute(ExecutionInput input) { + return graphQL.executeAsync(input); + } + + protected ServerResponse customizeExecutionResult(CompletableFuture resultFuture) { + return resultFuture.isDone() ? + ServerResponse.ok().body(getResult(resultFuture)) : + ServerResponse.ok().body(resultFuture); + } + + private ExecutionResult getResult(CompletableFuture resultFuture) { + try { + return resultFuture.get(); + } + catch (InterruptedException | ExecutionException ex) { + throw new ServerErrorException("Failed to get result", ex); + } + } +} \ No newline at end of file diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/servlet/package-info.java b/spring-graphql-web/src/main/java/org/springframework/graphql/servlet/package-info.java new file mode 100644 index 00000000..b58e8344 --- /dev/null +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/servlet/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package org.springframework.graphql.servlet; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields;