Support for invocation of interceptors

This commit is contained in:
Rossen Stoyanchev
2020-09-18 12:55:19 +01:00
parent e8a8334009
commit 6044017d31
7 changed files with 127 additions and 70 deletions

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.boot.graphql;
import java.util.Collections;
import graphql.GraphQL;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -39,7 +41,7 @@ public class WebFluxGraphQLAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public WebFluxGraphQLHandler graphQLHandler(GraphQL.Builder graphQLBuilder) {
return new WebFluxGraphQLHandler(graphQLBuilder);
return new WebFluxGraphQLHandler(graphQLBuilder, Collections.emptyList());
}
@Bean

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.boot.graphql;
import java.util.Collections;
import graphql.GraphQL;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -42,7 +44,7 @@ public class WebMvcGraphQLAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public WebMvcGraphQLHandler graphQLHandler(GraphQL.Builder graphQLBuilder) {
return new WebMvcGraphQLHandler(graphQLBuilder);
return new WebMvcGraphQLHandler(graphQLBuilder, Collections.emptyList());
}
@Bean

View File

@@ -15,8 +15,8 @@
*/
package org.springframework.graphql;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import java.util.List;
import graphql.GraphQL;
import reactor.core.publisher.Mono;
@@ -25,45 +25,24 @@ import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
/**
* GraphQL handler to be exposed as a WebFlux.fn endpoint via
* GraphQL handler to expose as a WebFlux.fn endpoint via
* {@link org.springframework.web.reactive.function.server.RouterFunctions}.
*/
public class WebFluxGraphQLHandler implements HandlerFunction<ServerResponse> {
public class WebFluxGraphQLHandler extends WebHandlerSupport implements HandlerFunction<ServerResponse> {
private final GraphQL graphQL;
public WebFluxGraphQLHandler(GraphQL.Builder graphQLBuilder) {
this.graphQL = graphQLBuilder.build();
public WebFluxGraphQLHandler(GraphQL.Builder builder, List<WebInterceptor> interceptors) {
super(builder, interceptors);
}
public Mono<ServerResponse> handle(ServerRequest request) {
return request.bodyToMono(WebInput.MAP_PARAMETERIZED_TYPE_REF)
.flatMap(body -> {
WebInput webInput = new WebInput(
request.uri(), request.headers().asHttpHeaders(), body);
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(webInput.getQuery())
.operationName(webInput.getOperationName())
.variables(webInput.getVariables())
.build();
// Invoke GraphQLInterceptor's preHandle here
return extendInput(executionInput, webInput);
WebInput webInput = new WebInput(request.uri(), request.headers().asHttpHeaders(), body);
return executeQuery(webInput);
})
.flatMap(executionInput -> {
// Invoke handleResult here
return execute(executionInput);
})
.flatMap(result -> ServerResponse.ok().bodyValue(result.toSpecification()));
}
protected Mono<ExecutionInput> extendInput(ExecutionInput executionInput, WebInput webInput) {
return Mono.just(executionInput);
}
protected Mono<ExecutionResult> execute(ExecutionInput input) {
return Mono.fromFuture(graphQL.executeAsync(input));
.flatMap(output -> ServerResponse.ok().bodyValue(output.toSpecification()));
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import reactor.core.publisher.Mono;
import org.springframework.util.CollectionUtils;
/**
* Base class for GraphQL over HTTP handlers.
*/
public abstract class WebHandlerSupport {
private final GraphQL graphQL;
private final List<WebInterceptor> interceptors;
public WebHandlerSupport(GraphQL.Builder builder, List<WebInterceptor> interceptors) {
this.graphQL = builder.build();
this.interceptors = (!CollectionUtils.isEmpty(interceptors) ?
Collections.unmodifiableList(new ArrayList<>(interceptors)) : Collections.emptyList());
}
public GraphQL getGraphQL() {
return this.graphQL;
}
public List<WebInterceptor> getInterceptors() {
return this.interceptors;
}
protected Mono<WebOutput> executeQuery(WebInput webInput) {
return createInputChain(webInput).flatMap(executionInput -> {
Mono<ExecutionResult> resultMono = Mono.fromFuture(getGraphQL().executeAsync(executionInput));
return createOutputChain(resultMono);
});
}
protected Mono<ExecutionInput> createInputChain(WebInput webInput) {
Mono<ExecutionInput> preHandleMono = Mono.just(webInput.toExecutionInput());
for (WebInterceptor interceptor : this.interceptors) {
preHandleMono = preHandleMono.flatMap(input -> interceptor.preHandle(input, webInput));
}
return preHandleMono;
}
protected Mono<WebOutput> createOutputChain(Mono<ExecutionResult> resultMono) {
Mono<WebOutput> outputMono = resultMono.map(WebOutput::new);
for (WebInterceptor interceptor : this.interceptors) {
outputMono = outputMono.flatMap(interceptor::postHandle);
}
return outputMono;
}
}

View File

@@ -17,8 +17,12 @@ package org.springframework.graphql;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import graphql.ExecutionInput;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
@@ -50,7 +54,7 @@ public class WebInput {
@SuppressWarnings("unchecked")
WebInput(URI uri, HttpHeaders headers, Map<String, Object> body) {
public WebInput(URI uri, HttpHeaders headers, Map<String, Object> body) {
this.uri = UriComponentsBuilder.fromUri(uri).build(true);
this.headers = headers;
this.query = getAndValidateQuery(body);
@@ -71,21 +75,29 @@ public class WebInput {
return this.uri;
}
public HttpHeaders getHeaders() {
public HttpHeaders headers() {
return this.headers;
}
public String getQuery() {
public String query() {
return this.query;
}
@Nullable
public String getOperationName() {
public String operationName() {
return this.operationName;
}
public Map<String, Object> getVariables() {
public Map<String, Object> variables() {
return this.variables;
}
public ExecutionInput toExecutionInput() {
return ExecutionInput.newExecutionInput()
.query(query())
.operationName(operationName())
.variables(variables())
.build();
}
}

View File

@@ -16,11 +16,11 @@
package org.springframework.graphql;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import reactor.core.publisher.Mono;
@@ -32,17 +32,17 @@ import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
/**
* GraphQL handler to be exposed as a WebMvc.fn endpoint via
* GraphQL handler to expose as a WebMvc.fn endpoint via
* {@link org.springframework.web.servlet.function.RouterFunctions}.
*/
public class WebMvcGraphQLHandler implements HandlerFunction<ServerResponse> {
public class WebMvcGraphQLHandler extends WebHandlerSupport implements HandlerFunction<ServerResponse> {
private final GraphQL graphQL;
public WebMvcGraphQLHandler(GraphQL.Builder graphQL) {
this.graphQL = graphQL.build();
public WebMvcGraphQLHandler(GraphQL.Builder builder, List<WebInterceptor> interceptors) {
super(builder, interceptors);
}
/**
* {@inheritDoc}
*
@@ -50,38 +50,18 @@ public class WebMvcGraphQLHandler implements HandlerFunction<ServerResponse> {
* e.g. {@link HttpMediaTypeNotSupportedException}.
*/
public ServerResponse handle(ServerRequest request) throws ServletException {
WebInput webInput = createWebInput(request);
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(webInput.getQuery())
.operationName(webInput.getOperationName())
.variables(webInput.getVariables())
.build();
Mono<Map<String, Object>> body = extendInput(executionInput, webInput)
.flatMap(this::execute)
.map(ExecutionResult::toSpecification);
return ServerResponse.ok().body(body);
WebInput webInput = new WebInput(request.uri(), request.headers().asHttpHeaders(), readBody(request));
Mono<WebOutput> outputMono = executeQuery(webInput);
return ServerResponse.ok().body(outputMono.map(ExecutionResult::toSpecification));
}
private static WebInput createWebInput(ServerRequest request) throws ServletException {
Map<String, Object> body;
private static Map<String, Object> readBody(ServerRequest request) throws ServletException {
try {
body = request.body(WebInput.MAP_PARAMETERIZED_TYPE_REF);
return request.body(WebInput.MAP_PARAMETERIZED_TYPE_REF);
}
catch (IOException ex) {
throw new ServerWebInputException("I/O error while reading request body", null, ex);
}
return new WebInput(request.uri(), request.headers().asHttpHeaders(), body);
}
protected Mono<ExecutionInput> extendInput(ExecutionInput executionInput, WebInput webInput) {
return Mono.just(executionInput);
}
protected Mono<ExecutionResult> execute(ExecutionInput input) {
return Mono.fromFuture(this.graphQL.executeAsync(input));
}
}

View File

@@ -26,12 +26,16 @@ import graphql.GraphQLError;
import org.springframework.lang.Nullable;
/**
* Simple wrapper around a GraphQL {@link ExecutionResult} that allows
* {@link #transform(Consumer) transformation} via a {@link Builder Builder}.
*/
public class WebOutput implements ExecutionResult {
private final ExecutionResult executionResult;
WebOutput(ExecutionResult executionResult) {
public WebOutput(ExecutionResult executionResult) {
this.executionResult = executionResult;
}