From b378231d39fd4d1b79b0ad3eb5c4853d5ece75aa Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 8 Apr 2021 19:01:37 +0100 Subject: [PATCH] Add GraphQLRequestHandler See gh-42 --- .../boot/WebFluxGraphQLAutoConfiguration.java | 23 +++-- .../boot/WebMvcGraphQLAutoConfiguration.java | 22 +++-- ...ractInterceptingGraphQLRequestHandler.java | 83 +++++++++++++++++++ .../graphql/DefaultGraphQLRequestHandler.java | 43 ++++++++++ .../graphql/GraphQLRequestHandler.java | 33 ++++++++ .../graphql/WebInterceptorExecutionChain.java | 75 ----------------- .../graphql/webflux/GraphQLHttpHandler.java | 20 ++--- .../webflux/GraphQLWebSocketHandler.java | 23 +++-- .../graphql/webmvc/GraphQLHttpHandler.java | 24 +++--- .../webmvc/GraphQLWebSocketHandler.java | 22 +++-- ...=> DefaultGraphQLRequestHandlerTests.java} | 12 +-- .../webflux/GraphQLWebSocketHandlerTests.java | 12 ++- .../webmvc/GraphQLWebSocketHandlerTests.java | 11 ++- 13 files changed, 254 insertions(+), 149 deletions(-) create mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/AbstractInterceptingGraphQLRequestHandler.java create mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/DefaultGraphQLRequestHandler.java create mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/GraphQLRequestHandler.java delete mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/WebInterceptorExecutionChain.java rename spring-graphql-web/src/test/java/org/springframework/graphql/{WebInterceptorExecutionChainTests.java => DefaultGraphQLRequestHandlerTests.java} (90%) diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java index 28346502..d8ede1c5 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java @@ -34,6 +34,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.graphql.DefaultGraphQLRequestHandler; +import org.springframework.graphql.GraphQLRequestHandler; import org.springframework.graphql.WebInterceptor; import org.springframework.graphql.webflux.GraphQLHttpHandler; import org.springframework.graphql.webflux.GraphQLWebSocketHandler; @@ -58,11 +60,21 @@ public class WebFluxGraphQLAutoConfiguration { private static final Log logger = LogFactory.getLog(WebFluxGraphQLAutoConfiguration.class); + @Bean @ConditionalOnMissingBean - public GraphQLHttpHandler graphQLHandler(GraphQL graphQL, ObjectProvider interceptors) { - return new GraphQLHttpHandler(graphQL, interceptors.orderedStream().collect(Collectors.toList())); + public GraphQLRequestHandler graphQLRequestHandler(GraphQL graphQL, ObjectProvider interceptors) { + DefaultGraphQLRequestHandler handler = new DefaultGraphQLRequestHandler(graphQL); + handler.setInterceptors(interceptors.orderedStream().collect(Collectors.toList())); + return handler; } + + @Bean + @ConditionalOnMissingBean + public GraphQLHttpHandler graphQLHandler(GraphQLRequestHandler requestHandler) { + return new GraphQLHttpHandler(requestHandler); + } + @Bean public RouterFunction graphQLEndpoint( GraphQLHttpHandler handler, GraphQLProperties properties, ResourceLoader resourceLoader) { @@ -86,13 +98,10 @@ public class WebFluxGraphQLAutoConfiguration { @Bean @ConditionalOnMissingBean public GraphQLWebSocketHandler graphQLWebSocketHandler( - GraphQL graphql, GraphQLProperties properties, ServerCodecConfigurer configurer, - ObjectProvider interceptors) { + GraphQLRequestHandler handler, GraphQLProperties properties, ServerCodecConfigurer configurer) { return new GraphQLWebSocketHandler( - graphql, interceptors.orderedStream().collect(Collectors.toList()), - configurer, properties.getWebsocket().getConnectionInitTimeout() - ); + handler, configurer, properties.getWebsocket().getConnectionInitTimeout()); } @Bean diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java index 4f8fd61f..9b4a7ab2 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java @@ -38,6 +38,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.graphql.DefaultGraphQLRequestHandler; +import org.springframework.graphql.GraphQLRequestHandler; import org.springframework.graphql.WebInterceptor; import org.springframework.graphql.webmvc.GraphQLHttpHandler; import org.springframework.graphql.webmvc.GraphQLWebSocketHandler; @@ -65,10 +67,19 @@ public class WebMvcGraphQLAutoConfiguration { private static final Log logger = LogFactory.getLog(WebMvcGraphQLAutoConfiguration.class); + @Bean @ConditionalOnMissingBean - public GraphQLHttpHandler graphQLHandler(GraphQL graphQL, ObjectProvider interceptors) { - return new GraphQLHttpHandler(graphQL, interceptors.orderedStream().collect(Collectors.toList())); + public GraphQLRequestHandler graphQLRequestHandler(GraphQL graphQL, ObjectProvider interceptors) { + DefaultGraphQLRequestHandler handler = new DefaultGraphQLRequestHandler(graphQL); + handler.setInterceptors(interceptors.orderedStream().collect(Collectors.toList())); + return handler; + } + + @Bean + @ConditionalOnMissingBean + public GraphQLHttpHandler graphQLHandler(GraphQLRequestHandler requestHandler) { + return new GraphQLHttpHandler(requestHandler); } @Bean @@ -96,8 +107,7 @@ public class WebMvcGraphQLAutoConfiguration { @Bean @ConditionalOnMissingBean public GraphQLWebSocketHandler graphQLWebSocketHandler( - GraphQL graphQL, GraphQLProperties properties, HttpMessageConverters converters, - ObjectProvider interceptors) { + GraphQLRequestHandler handler, GraphQLProperties properties, HttpMessageConverters converters) { HttpMessageConverter converter = converters.getConverters().stream() .filter(candidate -> candidate.canRead(Map.class, MediaType.APPLICATION_JSON)) @@ -105,9 +115,7 @@ public class WebMvcGraphQLAutoConfiguration { .orElseThrow(() -> new IllegalStateException("No JSON converter")); return new GraphQLWebSocketHandler( - graphQL, interceptors.orderedStream().collect(Collectors.toList()), - converter, properties.getWebsocket().getConnectionInitTimeout() - ); + handler, converter, properties.getWebsocket().getConnectionInitTimeout()); } @Bean diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/AbstractInterceptingGraphQLRequestHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/AbstractInterceptingGraphQLRequestHandler.java new file mode 100644 index 00000000..db5c72c3 --- /dev/null +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/AbstractInterceptingGraphQLRequestHandler.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2021 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.List; +import java.util.concurrent.CompletableFuture; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import reactor.core.publisher.Mono; + +/** + * Base class for {@link GraphQLRequestHandler} implementations that support a + * {@link WebInterceptor} chain. + */ +public abstract class AbstractInterceptingGraphQLRequestHandler implements GraphQLRequestHandler { + + private final List interceptors = new ArrayList<>(); + + + /** + * Set the interceptors to invoke to handle request. + * @param interceptors the interceptors to use + */ + public void setInterceptors(List interceptors) { + this.interceptors.clear(); + this.interceptors.addAll(interceptors); + } + + /** + * Return the {@link #setInterceptors(List) configured} interceptors. + */ + public List getInterceptors() { + return this.interceptors; + } + + + @Override + public final Mono handle(WebInput input) { + return preHandle(input) + .flatMap(executionInput -> Mono.fromFuture(handleInternal(executionInput))) + .flatMap(executionResult -> postHandle(new WebOutput(input, executionResult, null))); + } + + private Mono preHandle(WebInput input) { + Mono resultMono = Mono.just(input.toExecutionInput()); + for (WebInterceptor interceptor : this.interceptors) { + resultMono = resultMono.flatMap(executionInput -> interceptor.preHandle(executionInput, input)); + } + return resultMono; + } + + private Mono postHandle(WebOutput output) { + Mono outputMono = Mono.just(output); + for (int i = this.interceptors.size() - 1 ; i >= 0; i--) { + WebInterceptor interceptor = this.interceptors.get(i); + outputMono = outputMono.flatMap(interceptor::postHandle); + } + return outputMono; + } + + /** + * Sub-classes must implement this method to actually handle the request. + * @param input the input to invoke {@link graphql.GraphQL} with + * @return the result from handling + */ + protected abstract CompletableFuture handleInternal(ExecutionInput input); + +} diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/DefaultGraphQLRequestHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/DefaultGraphQLRequestHandler.java new file mode 100644 index 00000000..984aae51 --- /dev/null +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/DefaultGraphQLRequestHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2021 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.concurrent.CompletableFuture; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; + +/** + * Default implementation that invokes {@link GraphQL} and supports a + * {@link WebInterceptor} chain for pre- and post-handling. + */ +public class DefaultGraphQLRequestHandler extends AbstractInterceptingGraphQLRequestHandler { + + private final GraphQL graphQL; + + + public DefaultGraphQLRequestHandler(GraphQL graphQL) { + this.graphQL = graphQL; + } + + + @Override + protected CompletableFuture handleInternal(ExecutionInput input) { + return this.graphQL.executeAsync(input); + } + +} diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/GraphQLRequestHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/GraphQLRequestHandler.java new file mode 100644 index 00000000..66971334 --- /dev/null +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/GraphQLRequestHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2021 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 reactor.core.publisher.Mono; + +/** + * Contract to handle a GraphQL request. + */ +@FunctionalInterface +public interface GraphQLRequestHandler { + + /** + * Handle the request and return the result of execution. + * @param input the GraphQL query + * @return the execution result + */ + Mono handle(WebInput input); + +} diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/WebInterceptorExecutionChain.java b/spring-graphql-web/src/main/java/org/springframework/graphql/WebInterceptorExecutionChain.java deleted file mode 100644 index 848f6cef..00000000 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/WebInterceptorExecutionChain.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 java.util.concurrent.CompletableFuture; - -import graphql.ExecutionInput; -import graphql.ExecutionResult; -import graphql.GraphQL; -import reactor.core.publisher.Mono; - -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * Supports the use of {@link WebInterceptor}s to customize the - * {@link ExecutionInput} and the {@link ExecutionResult} of {@link GraphQL} - * query execution. - */ -public class WebInterceptorExecutionChain { - - private final GraphQL graphQL; - - private final List interceptors; - - - public WebInterceptorExecutionChain(GraphQL graphQL, List interceptors) { - Assert.notNull(graphQL, "GraphQL is required"); - this.graphQL = graphQL; - this.interceptors = (!CollectionUtils.isEmpty(interceptors) ? - Collections.unmodifiableList(new ArrayList<>(interceptors)) : Collections.emptyList()); - } - - - public Mono execute(WebInput input) { - return createInputChain(input).flatMap(executionInput -> { - CompletableFuture future = this.graphQL.executeAsync(executionInput); - return createOutputChain(input, Mono.fromFuture(future)); - }); - } - - private Mono createInputChain(WebInput webInput) { - Mono preHandleMono = Mono.just(webInput.toExecutionInput()); - for (WebInterceptor interceptor : this.interceptors) { - preHandleMono = preHandleMono.flatMap(input -> interceptor.preHandle(input, webInput)); - } - return preHandleMono; - } - - private Mono createOutputChain(WebInput input, Mono resultMono) { - Mono outputMono = resultMono.map((ExecutionResult result) -> new WebOutput(input, result, null)); - for (int i = this.interceptors.size() - 1 ; i >= 0; i--) { - WebInterceptor interceptor = this.interceptors.get(i); - outputMono = outputMono.flatMap(interceptor::postHandle); - } - return outputMono; - } - -} diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/webflux/GraphQLHttpHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/webflux/GraphQLHttpHandler.java index 2928cf31..fb1ab4f8 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/webflux/GraphQLHttpHandler.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/webflux/GraphQLHttpHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2020 the original author or authors. + * Copyright 2020-2021 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. @@ -15,18 +15,16 @@ */ package org.springframework.graphql.webflux; -import java.util.List; import java.util.Map; -import graphql.GraphQL; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.graphql.GraphQLRequestHandler; import org.springframework.graphql.WebInput; -import org.springframework.graphql.WebInterceptor; -import org.springframework.graphql.WebInterceptorExecutionChain; +import org.springframework.util.Assert; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; @@ -41,16 +39,16 @@ public class GraphQLHttpHandler { new ParameterizedTypeReference>() {}; - private final WebInterceptorExecutionChain executionChain; + private final GraphQLRequestHandler requestHandler; /** * Create a new instance. - * @param graphQL the GraphQL instance to use for query execution - * @param interceptors 0 or more interceptors to customize input and output + * @param requestHandler the handler to use for GraphQL query handling */ - public GraphQLHttpHandler(GraphQL graphQL, List interceptors) { - this.executionChain = new WebInterceptorExecutionChain(graphQL, interceptors); + public GraphQLHttpHandler(GraphQLRequestHandler requestHandler) { + Assert.notNull(requestHandler, "GraphQLRequestHandler is required"); + this.requestHandler = requestHandler; } @@ -64,7 +62,7 @@ public class GraphQLHttpHandler { if (logger.isDebugEnabled()) { logger.debug("Executing: " + webInput); } - return this.executionChain.execute(webInput); + return this.requestHandler.handle(webInput); }) .flatMap(output -> { Map spec = output.toSpecification(); diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/webflux/GraphQLWebSocketHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/webflux/GraphQLWebSocketHandler.java index 6d3139aa..ae8f46f6 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/webflux/GraphQLWebSocketHandler.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/webflux/GraphQLWebSocketHandler.java @@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import graphql.ErrorType; import graphql.ExecutionResult; -import graphql.GraphQL; import graphql.GraphqlErrorBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -40,8 +39,7 @@ import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Encoder; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.graphql.WebInterceptor; -import org.springframework.graphql.WebInterceptorExecutionChain; +import org.springframework.graphql.GraphQLRequestHandler; import org.springframework.graphql.WebOutput; import org.springframework.graphql.WebSocketMessageInput; import org.springframework.http.MediaType; @@ -52,7 +50,6 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeTypeUtils; -import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.socket.CloseStatus; import org.springframework.web.reactive.socket.HandshakeInfo; import org.springframework.web.reactive.socket.WebSocketHandler; @@ -75,7 +72,7 @@ public class GraphQLWebSocketHandler implements WebSocketHandler { ResolvableType.forType(new ParameterizedTypeReference>() {}); - private final WebInterceptorExecutionChain executionChain; + private final GraphQLRequestHandler requestHandler; private final Decoder decoder; @@ -86,19 +83,19 @@ public class GraphQLWebSocketHandler implements WebSocketHandler { /** * Create a new instance. - * @param graphQL the GraphQL instance to use for query execution - * @param interceptors 0 or more interceptors to customize input and output + * @param requestHandler the handler to use for GraphQL query handling * @param configurer codec configurer for JSON encoding and decoding - * @param initTimeoutDuration the time within which the {@code CONNECTION_INIT} + * @param connectionInitTimeout the time within which the {@code CONNECTION_INIT} * type message must be received. */ - public GraphQLWebSocketHandler(GraphQL graphQL, List interceptors, - ServerCodecConfigurer configurer, Duration initTimeoutDuration) { + public GraphQLWebSocketHandler(GraphQLRequestHandler requestHandler, + ServerCodecConfigurer configurer, Duration connectionInitTimeout) { - this.executionChain = new WebInterceptorExecutionChain(graphQL, interceptors); + Assert.notNull(requestHandler, "GraphQLRequestHandler is required"); + this.requestHandler = requestHandler; this.decoder = initDecoder(configurer); this.encoder = initEncoder(configurer); - this.initTimeoutDuration = initTimeoutDuration; + this.initTimeoutDuration = connectionInitTimeout; } private static Decoder initDecoder(ServerCodecConfigurer configurer) { @@ -167,7 +164,7 @@ public class GraphQLWebSocketHandler implements WebSocketHandler { if (logger.isDebugEnabled()) { logger.debug("Executing: " + input); } - return this.executionChain.execute(input) + return this.requestHandler.handle(input) .flatMapMany(output -> handleWebOutput(session, id, subscriptions, output)) .doOnTerminate(() -> subscriptions.remove(id)); case COMPLETE: diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/webmvc/GraphQLHttpHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/webmvc/GraphQLHttpHandler.java index c13ca10b..84cf86bc 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/webmvc/GraphQLHttpHandler.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/webmvc/GraphQLHttpHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2020 the original author or authors. + * Copyright 2020-2021 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. @@ -16,20 +16,18 @@ package org.springframework.graphql.webmvc; import java.io.IOException; -import java.util.List; import java.util.Map; import javax.servlet.ServletException; -import graphql.GraphQL; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.graphql.GraphQLRequestHandler; import org.springframework.graphql.WebInput; -import org.springframework.graphql.WebInterceptor; -import org.springframework.graphql.WebInterceptorExecutionChain; +import org.springframework.util.Assert; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.server.ServerWebInputException; import org.springframework.web.servlet.function.ServerRequest; @@ -47,18 +45,16 @@ public class GraphQLHttpHandler { new ParameterizedTypeReference>() {}; - private final WebInterceptorExecutionChain executionChain; + private final GraphQLRequestHandler requestHandler; /** - * Create a handler that executes queries through the given {@link GraphQL} - * and and invokes the given interceptors to customize input to and the - * result from the execution of the query. - * @param graphQL the GraphQL instance to use for query execution - * @param interceptors 0 or more interceptors to customize input and output + * Create a new instance. + * @param requestHandler the handler to use for GraphQL query handling */ - public GraphQLHttpHandler(GraphQL graphQL, List interceptors) { - this.executionChain = new WebInterceptorExecutionChain(graphQL, interceptors); + public GraphQLHttpHandler(GraphQLRequestHandler requestHandler) { + Assert.notNull(requestHandler, "GraphQLRequestHandler is required"); + this.requestHandler = requestHandler; } @@ -73,7 +69,7 @@ public class GraphQLHttpHandler { if (logger.isDebugEnabled()) { logger.debug("Executing: " + webInput); } - Mono responseMono = this.executionChain.execute(webInput) + Mono responseMono = this.requestHandler.handle(webInput) .map(output -> { if (logger.isDebugEnabled()) { logger.debug("Execution complete"); diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/webmvc/GraphQLWebSocketHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/webmvc/GraphQLWebSocketHandler.java index 8b977219..0357d552 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/webmvc/GraphQLWebSocketHandler.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/webmvc/GraphQLWebSocketHandler.java @@ -30,7 +30,6 @@ import java.util.concurrent.ConcurrentHashMap; import graphql.ErrorType; import graphql.ExecutionResult; -import graphql.GraphQL; import graphql.GraphqlErrorBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -42,8 +41,7 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; -import org.springframework.graphql.WebInterceptor; -import org.springframework.graphql.WebInterceptorExecutionChain; +import org.springframework.graphql.GraphQLRequestHandler; import org.springframework.graphql.WebOutput; import org.springframework.graphql.WebSocketMessageInput; import org.springframework.http.HttpHeaders; @@ -73,7 +71,7 @@ public class GraphQLWebSocketHandler extends TextWebSocketHandler implements Sub Arrays.asList("graphql-transport-ws", "subscriptions-transport-ws"); - private final WebInterceptorExecutionChain executionChain; + private final GraphQLRequestHandler requestHandler; private final Duration initTimeoutDuration; @@ -84,18 +82,18 @@ public class GraphQLWebSocketHandler extends TextWebSocketHandler implements Sub /** * Create a new instance. - * @param graphQL the GraphQL instance to use for query execution - * @param interceptors 0 or more interceptors to customize input and output + * @param requestHandler the handler to use for GraphQL query handling * @param converter for JSON encoding and decoding - * @param initTimeoutDuration the time within which the {@code CONNECTION_INIT} + * @param connectionInitTimeout the time within which the {@code CONNECTION_INIT} * type message must be received. */ - public GraphQLWebSocketHandler(GraphQL graphQL, List interceptors, - HttpMessageConverter converter, Duration initTimeoutDuration) { + public GraphQLWebSocketHandler( + GraphQLRequestHandler requestHandler, HttpMessageConverter converter, + Duration connectionInitTimeout) { Assert.notNull(converter, "HttpMessageConverter for JSON is required"); - this.executionChain = new WebInterceptorExecutionChain(graphQL, interceptors); - this.initTimeoutDuration = initTimeoutDuration; + this.requestHandler = requestHandler; + this.initTimeoutDuration = connectionInitTimeout; this.converter = converter; } @@ -156,7 +154,7 @@ public class GraphQLWebSocketHandler extends TextWebSocketHandler implements Sub if (logger.isDebugEnabled()) { logger.debug("Executing: " + input); } - this.executionChain.execute(input) + this.requestHandler.handle(input) .flatMapMany(output -> handleWebOutput(session, input.requestId(), output)) .publishOn(sessionState.getScheduler()) // Serial blocking send via single thread .subscribe(new SendMessageSubscriber(id, session, sessionState)); diff --git a/spring-graphql-web/src/test/java/org/springframework/graphql/WebInterceptorExecutionChainTests.java b/spring-graphql-web/src/test/java/org/springframework/graphql/DefaultGraphQLRequestHandlerTests.java similarity index 90% rename from spring-graphql-web/src/test/java/org/springframework/graphql/WebInterceptorExecutionChainTests.java rename to spring-graphql-web/src/test/java/org/springframework/graphql/DefaultGraphQLRequestHandlerTests.java index c90fbea4..6fe9f81d 100644 --- a/spring-graphql-web/src/test/java/org/springframework/graphql/WebInterceptorExecutionChainTests.java +++ b/spring-graphql-web/src/test/java/org/springframework/graphql/DefaultGraphQLRequestHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2020 the original author or authors. + * Copyright 2020-2021 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. @@ -40,9 +40,9 @@ import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring; import static org.assertj.core.api.Assertions.assertThat; /** - * Unit tests for {@link WebInterceptorExecutionChain}. + * Unit tests for {@link DefaultGraphQLRequestHandler}. */ -public class WebInterceptorExecutionChainTests { +public class DefaultGraphQLRequestHandlerTests { @Test void testInterceptorInvocation() throws Exception { @@ -64,8 +64,10 @@ public class WebInterceptorExecutionChainTests { Map body = mapper.reader().readValue("{\"query\": \"" + query + "\"}", Map.class); WebInput webInput = new WebInput(URI.create("/graphql"), new HttpHeaders(), body); - WebOutput webOutput = new WebInterceptorExecutionChain(createGraphQL(), interceptors) - .execute(webInput).block(); + DefaultGraphQLRequestHandler requestHandler = new DefaultGraphQLRequestHandler(createGraphQL()); + requestHandler.setInterceptors(interceptors); + + WebOutput webOutput = requestHandler.handle(webInput).block(); assertThat(sb.toString()).isEqualTo(":pre1:pre2:pre3:post3:post2:post1"); assertThat(webOutput.isDataPresent()).isTrue(); diff --git a/spring-graphql-web/src/test/java/org/springframework/graphql/webflux/GraphQLWebSocketHandlerTests.java b/spring-graphql-web/src/test/java/org/springframework/graphql/webflux/GraphQLWebSocketHandlerTests.java index 6e5d80bc..f327ff78 100644 --- a/spring-graphql-web/src/test/java/org/springframework/graphql/webflux/GraphQLWebSocketHandlerTests.java +++ b/spring-graphql-web/src/test/java/org/springframework/graphql/webflux/GraphQLWebSocketHandlerTests.java @@ -41,8 +41,9 @@ import reactor.test.StepVerifier; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.graphql.GraphQLDataFetchers; import org.springframework.graphql.ConsumeOneAndNeverCompleteInterceptor; +import org.springframework.graphql.DefaultGraphQLRequestHandler; +import org.springframework.graphql.GraphQLDataFetchers; import org.springframework.graphql.WebInterceptor; import org.springframework.http.HttpHeaders; import org.springframework.http.codec.ServerCodecConfigurer; @@ -278,8 +279,13 @@ public class GraphQLWebSocketHandlerTests { @Nullable List interceptors, @Nullable Duration initTimeoutDuration) throws Exception { GraphQL graphQL = initGraphQL(); - return new GraphQLWebSocketHandler(graphQL, - (interceptors != null ? interceptors : Collections.emptyList()), + + DefaultGraphQLRequestHandler requestHandler = new DefaultGraphQLRequestHandler(graphQL); + if (interceptors != null) { + requestHandler.setInterceptors(interceptors); + } + + return new GraphQLWebSocketHandler(requestHandler, ServerCodecConfigurer.create(), (initTimeoutDuration != null ? initTimeoutDuration : Duration.ofSeconds(60))); } diff --git a/spring-graphql-web/src/test/java/org/springframework/graphql/webmvc/GraphQLWebSocketHandlerTests.java b/spring-graphql-web/src/test/java/org/springframework/graphql/webmvc/GraphQLWebSocketHandlerTests.java index ac1294a1..f36c5dcb 100644 --- a/spring-graphql-web/src/test/java/org/springframework/graphql/webmvc/GraphQLWebSocketHandlerTests.java +++ b/spring-graphql-web/src/test/java/org/springframework/graphql/webmvc/GraphQLWebSocketHandlerTests.java @@ -37,6 +37,7 @@ import org.junit.jupiter.api.Test; import reactor.test.StepVerifier; import org.springframework.graphql.ConsumeOneAndNeverCompleteInterceptor; +import org.springframework.graphql.DefaultGraphQLRequestHandler; import org.springframework.graphql.GraphQLDataFetchers; import org.springframework.graphql.WebInterceptor; import org.springframework.http.HttpHeaders; @@ -261,8 +262,14 @@ public class GraphQLWebSocketHandlerTests { @Nullable List interceptors, @Nullable Duration initTimeoutDuration) { try { - return new GraphQLWebSocketHandler(initGraphQL(), - (interceptors != null ? interceptors : Collections.emptyList()), converter, + GraphQL graphQL = initGraphQL(); + + DefaultGraphQLRequestHandler requestHandler = new DefaultGraphQLRequestHandler(graphQL); + if (interceptors != null) { + requestHandler.setInterceptors(interceptors); + } + + return new GraphQLWebSocketHandler(requestHandler, converter, (initTimeoutDuration != null ? initTimeoutDuration : Duration.ofSeconds(60))); } catch (Exception ex) {