diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/RequestInput.java b/spring-graphql-web/src/main/java/org/springframework/graphql/RequestInput.java index d46dd0a8..99c77093 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/RequestInput.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/RequestInput.java @@ -4,6 +4,8 @@ import java.util.Collections; import java.util.Map; import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebInputException; /** * @author Andreas Marek @@ -50,4 +52,10 @@ class RequestInput { public void setVariables(Map variables) { this.variables = variables; } + + public void validate() { + if (!StringUtils.hasText(getQuery())) { + throw new ServerWebInputException("Missing query"); + } + } } diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/WebFluxGraphQLHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/WebFluxGraphQLHandler.java index 20f8d724..a9c574f8 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/WebFluxGraphQLHandler.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/WebFluxGraphQLHandler.java @@ -6,10 +6,15 @@ import graphql.GraphQL; import reactor.core.publisher.Mono; import org.springframework.http.HttpHeaders; +import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; -public class WebFluxGraphQLHandler { +/** + * GraphQL handler to be exposed as a WebFlux.fn endpoint via + * {@link org.springframework.web.reactive.function.server.RouterFunctions}. + */ +public class WebFluxGraphQLHandler implements HandlerFunction { private final GraphQL graphQL; @@ -19,15 +24,12 @@ public class WebFluxGraphQLHandler { public Mono handle(ServerRequest request) { return request.bodyToMono(RequestInput.class) - .flatMap(body -> { - String query = body.getQuery(); - if (query == null) { - query = ""; - } + .flatMap(requestInput -> { + requestInput.validate(); ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .query(query) - .operationName(body.getOperationName()) - .variables(body.getVariables()) + .query(requestInput.getQuery()) + .operationName(requestInput.getOperationName()) + .variables(requestInput.getVariables()) .build(); // Invoke GraphQLInterceptor's preHandle here return customizeExecutionInput(executionInput, request.headers().asHttpHeaders()); diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/WebMvcGraphQLHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/WebMvcGraphQLHandler.java index 71af3b0f..439481b5 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/WebMvcGraphQLHandler.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/WebMvcGraphQLHandler.java @@ -12,12 +12,18 @@ import graphql.ExecutionResult; import graphql.GraphQL; import org.springframework.http.HttpHeaders; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.server.ServerErrorException; import org.springframework.web.server.ServerWebInputException; +import org.springframework.web.servlet.function.HandlerFunction; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; -public class WebMvcGraphQLHandler { +/** + * GraphQL handler to be exposed as a WebMvc.fn endpoint via + * {@link org.springframework.web.servlet.function.RouterFunctions}. + */ +public class WebMvcGraphQLHandler implements HandlerFunction { private final GraphQL graphQL; @@ -25,36 +31,38 @@ public class WebMvcGraphQLHandler { this.graphQL = graphQL.build(); } - public ServerResponse handle(ServerRequest serverRequest) { - RequestInput body; + /** + * {@inheritDoc} + * + * @throws ServletException may be raised when reading the request body, + * e.g. {@link HttpMediaTypeNotSupportedException}. + */ + public ServerResponse handle(ServerRequest request) throws ServletException { + RequestInput requestInput; try { - body = serverRequest.body(RequestInput.class); + requestInput = request.body(RequestInput.class); + requestInput.validate(); } - catch (ServletException | IOException ex) { - throw new ServerWebInputException("Failed to read request body", null, ex); + catch (IOException ex) { + throw new ServerWebInputException("I/O error while reading request body", null, ex); } - String query = body.getQuery(); - if (query == null) { - query = ""; - } - ExecutionInput input = ExecutionInput.newExecutionInput() - .query(query) - .operationName(body.getOperationName()) - .variables(body.getVariables()) + + ExecutionInput executionInput = ExecutionInput.newExecutionInput() + .query(requestInput.getQuery()) + .operationName(requestInput.getOperationName()) + .variables(requestInput.getVariables()) .build(); // Invoke GraphQLInterceptor's preHandle here CompletableFuture> future = - customizeExecutionInput(input, serverRequest.headers().asHttpHeaders()) + customizeExecutionInput(executionInput, request.headers().asHttpHeaders()) .thenCompose(this::execute) .thenApply(ExecutionResult::toSpecification); // Invoke GraphQLInterceptor's postHandle here - return future.isDone() ? - ServerResponse.ok().body(getResult(future)) : - ServerResponse.ok().body(future); + return ServerResponse.ok().body(future.isDone() ? getResult(future) : future); } protected CompletableFuture customizeExecutionInput(ExecutionInput input, HttpHeaders headers) {