From a05903b9d1a6ca138a31db5b811cc40b16eb5dbd Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 2 Feb 2022 11:12:42 -0800 Subject: [PATCH] Polish GraphQL auto-configuration --- .../graphql/GraphQlAutoConfiguration.java | 69 ++++++++++-------- .../graphql/GraphQlCorsProperties.java | 4 -- .../GraphQlSourceBuilderCustomizer.java | 7 +- ...raphQlQueryByExampleAutoConfiguration.java | 18 ++--- .../GraphQlQuerydslAutoConfiguration.java | 18 ++--- ...raphQlQuerydslSourceBuilderCustomizer.java | 72 +++++++++++++++++++ ...activeQueryByExampleAutoConfiguration.java | 15 ++-- ...phQlReactiveQuerydslAutoConfiguration.java | 16 ++--- .../GraphQlWebFluxAutoConfiguration.java | 48 +++++++------ .../GraphQlWebMvcAutoConfiguration.java | 63 ++++++++-------- 10 files changed, 192 insertions(+), 138 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslSourceBuilderCustomizer.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java index cd9c5e7462..d91b0bd14c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java @@ -19,11 +19,13 @@ package org.springframework.boot.autoconfigure.graphql; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import graphql.GraphQL; import graphql.execution.instrumentation.Instrumentation; +import graphql.schema.idl.RuntimeWiring.Builder; import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -67,29 +69,49 @@ public class GraphQlAutoConfiguration { @Bean @ConditionalOnMissingBean public GraphQlSource graphQlSource(ResourcePatternResolver resourcePatternResolver, GraphQlProperties properties, - ObjectProvider exceptionResolversProvider, - ObjectProvider instrumentationsProvider, - ObjectProvider wiringConfigurers, + ObjectProvider exceptionResolvers, + ObjectProvider instrumentations, ObjectProvider wiringConfigurers, ObjectProvider sourceCustomizers) { - - List schemaResources = resolveSchemaResources(resourcePatternResolver, - properties.getSchema().getLocations(), properties.getSchema().getFileExtensions()); - GraphQlSource.Builder builder = GraphQlSource.builder() - .schemaResources(schemaResources.toArray(new Resource[0])) - .exceptionResolvers(exceptionResolversProvider.orderedStream().collect(Collectors.toList())) - .instrumentation(instrumentationsProvider.orderedStream().collect(Collectors.toList())); + String[] schemaLocations = properties.getSchema().getLocations(); + Resource[] schemaResources = resolveSchemaResources(resourcePatternResolver, schemaLocations, + properties.getSchema().getFileExtensions()); + GraphQlSource.Builder builder = GraphQlSource.builder().schemaResources(schemaResources) + .exceptionResolvers(toList(exceptionResolvers)).instrumentation(toList(instrumentations)); if (!properties.getSchema().getIntrospection().isEnabled()) { - builder.configureRuntimeWiring((wiring) -> wiring - .fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY)); + builder.configureRuntimeWiring(this::enableIntrospection); } wiringConfigurers.orderedStream().forEach(builder::configureRuntimeWiring); sourceCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); try { return builder.build(); } - catch (MissingSchemaException exc) { - throw new InvalidSchemaLocationsException(properties.getSchema().getLocations(), resourcePatternResolver, - exc); + catch (MissingSchemaException ex) { + throw new InvalidSchemaLocationsException(schemaLocations, resourcePatternResolver, ex); + } + } + + private Builder enableIntrospection(Builder wiring) { + return wiring.fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY); + } + + private Resource[] resolveSchemaResources(ResourcePatternResolver resolver, String[] locations, + String[] extensions) { + List resources = new ArrayList<>(); + for (String location : locations) { + for (String extension : extensions) { + resources.addAll(resolveSchemaResources(resolver, location + "*" + extension)); + } + } + return resources.toArray(new Resource[0]); + } + + private List resolveSchemaResources(ResourcePatternResolver resolver, String pattern) { + try { + return Arrays.asList(resolver.getResources(pattern)); + } + catch (IOException ex) { + logger.debug("Could not resolve schema location: '" + pattern + "'", ex); + return Collections.emptyList(); } } @@ -115,21 +137,8 @@ public class GraphQlAutoConfiguration { return annotatedControllerConfigurer; } - private List resolveSchemaResources(ResourcePatternResolver resolver, String[] schemaLocations, - String[] fileExtensions) { - List schemaResources = new ArrayList<>(); - for (String location : schemaLocations) { - for (String extension : fileExtensions) { - String resourcePattern = location + "*" + extension; - try { - schemaResources.addAll(Arrays.asList(resolver.getResources(resourcePattern))); - } - catch (IOException ex) { - logger.debug("Could not resolve schema location: '" + resourcePattern + "'", ex); - } - } - } - return schemaResources; + private List toList(ObjectProvider provider) { + return provider.orderedStream().collect(Collectors.toList()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlCorsProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlCorsProperties.java index 01021b3b06..57748d28b1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlCorsProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlCorsProperties.java @@ -24,7 +24,6 @@ import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.convert.DurationUnit; -import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.web.cors.CorsConfiguration; @@ -73,7 +72,6 @@ public class GraphQlCorsProperties { /** * Whether credentials are supported. When not set, credentials are not supported. */ - @Nullable private Boolean allowCredentials; /** @@ -123,7 +121,6 @@ public class GraphQlCorsProperties { this.exposedHeaders = exposedHeaders; } - @Nullable public Boolean getAllowCredentials() { return this.allowCredentials; } @@ -140,7 +137,6 @@ public class GraphQlCorsProperties { this.maxAge = maxAge; } - @Nullable public CorsConfiguration toCorsConfiguration() { if (CollectionUtils.isEmpty(this.allowedOrigins) && CollectionUtils.isEmpty(this.allowedOriginPatterns)) { return null; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer.java index 5157d7a7f9..9ee6c66e98 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer.java @@ -20,8 +20,8 @@ import org.springframework.graphql.execution.GraphQlSource; /** * Callback interface that can be implemented by beans wishing to customize properties of - * {@link org.springframework.graphql.execution.GraphQlSource.Builder} whilst retaining - * default auto-configuration. + * {@link org.springframework.graphql.execution.GraphQlSource.Builder Builder} whilst + * retaining default auto-configuration. * * @author Rossen Stoyanchev * @since 2.7.0 @@ -30,7 +30,8 @@ import org.springframework.graphql.execution.GraphQlSource; public interface GraphQlSourceBuilderCustomizer { /** - * Customize the {@link GraphQlSource.Builder} instance. + * Customize the {@link org.springframework.graphql.execution.GraphQlSource.Builder + * Builder} instance. * @param builder builder the builder to customize */ void customize(GraphQlSource.Builder builder); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfiguration.java index b39727f874..06589bf2b0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.graphql.data; import java.util.List; -import java.util.stream.Collectors; import graphql.GraphQL; @@ -52,19 +51,10 @@ import org.springframework.graphql.execution.GraphQlSource; public class GraphQlQueryByExampleAutoConfiguration { @Bean - public GraphQlSourceBuilderCustomizer queryByExampleRegistrar( - ObjectProvider> executorsProvider, - ObjectProvider> reactiveExecutorsProvider) { - - return (builder) -> { - List> executors = executorsProvider.stream().collect(Collectors.toList()); - List> reactiveExecutors = reactiveExecutorsProvider.stream() - .collect(Collectors.toList()); - if (!executors.isEmpty()) { - builder.configureRuntimeWiring( - QueryByExampleDataFetcher.autoRegistrationConfigurer(executors, reactiveExecutors)); - } - }; + public GraphQlSourceBuilderCustomizer queryByExampleRegistrar(ObjectProvider> executors, + ObjectProvider> reactiveExecutors) { + return new GraphQlQuerydslSourceBuilderCustomizer<>(QueryByExampleDataFetcher::autoRegistrationConfigurer, + executors, reactiveExecutors); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfiguration.java index 73039e65d5..2a4d2334bf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.graphql.data; import java.util.List; -import java.util.stream.Collectors; import graphql.GraphQL; @@ -53,19 +52,10 @@ import org.springframework.graphql.execution.GraphQlSource; public class GraphQlQuerydslAutoConfiguration { @Bean - public GraphQlSourceBuilderCustomizer querydslRegistrar( - ObjectProvider> executorsProvider, - ObjectProvider> reactiveExecutorsProvider) { - - return (builder) -> { - List> executors = executorsProvider.stream().collect(Collectors.toList()); - if (!executors.isEmpty()) { - List> reactiveExecutors = reactiveExecutorsProvider.stream() - .collect(Collectors.toList()); - builder.configureRuntimeWiring( - QuerydslDataFetcher.autoRegistrationConfigurer(executors, reactiveExecutors)); - } - }; + public GraphQlSourceBuilderCustomizer querydslRegistrar(ObjectProvider> executors, + ObjectProvider> reactiveExecutors) { + return new GraphQlQuerydslSourceBuilderCustomizer<>(QuerydslDataFetcher::autoRegistrationConfigurer, executors, + reactiveExecutors); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslSourceBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslSourceBuilderCustomizer.java new file mode 100644 index 0000000000..363456964b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslSourceBuilderCustomizer.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2022 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.boot.autoconfigure.graphql.data; + +import java.util.Collections; +import java.util.List; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; +import org.springframework.graphql.execution.GraphQlSource.Builder; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; + +/** + * {@link GraphQlSourceBuilderCustomizer} to apply auto-configured QueryDSL + * {@link RuntimeWiringConfigurer RuntimeWiringConfigurers}. + * + * @param the executor type + * @param the reactive executor type + * @author Phillip Webb + * @author Rossen Stoyanchev + * @author Brian Clozel + */ +class GraphQlQuerydslSourceBuilderCustomizer implements GraphQlSourceBuilderCustomizer { + + private final BiFunction, List, RuntimeWiringConfigurer> wiringConfigurerFactory; + + private final List executors; + + private final List reactiveExecutors; + + GraphQlQuerydslSourceBuilderCustomizer( + BiFunction, List, RuntimeWiringConfigurer> wiringConfigurerFactory, ObjectProvider executors, + ObjectProvider reactiveExecutors) { + this(wiringConfigurerFactory, toList(executors), toList(reactiveExecutors)); + } + + GraphQlQuerydslSourceBuilderCustomizer( + BiFunction, List, RuntimeWiringConfigurer> wiringConfigurerFactory, List executors, + List reactiveExecutors) { + this.wiringConfigurerFactory = wiringConfigurerFactory; + this.executors = executors; + this.reactiveExecutors = reactiveExecutors; + } + + @Override + public void customize(Builder builder) { + if (!this.executors.isEmpty() || !this.reactiveExecutors.isEmpty()) { + builder.configureRuntimeWiring(this.wiringConfigurerFactory.apply(this.executors, this.reactiveExecutors)); + } + } + + private static List toList(ObjectProvider provider) { + return (provider != null) ? provider.orderedStream().collect(Collectors.toList()) : Collections.emptyList(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfiguration.java index 6c8c730b4a..0ba4717596 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfiguration.java @@ -16,9 +16,7 @@ package org.springframework.boot.autoconfigure.graphql.data; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import graphql.GraphQL; @@ -31,6 +29,7 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration; import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.repository.query.QueryByExampleExecutor; import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor; import org.springframework.graphql.data.query.QueryByExampleDataFetcher; import org.springframework.graphql.execution.GraphQlSource; @@ -53,15 +52,9 @@ public class GraphQlReactiveQueryByExampleAutoConfiguration { @Bean public GraphQlSourceBuilderCustomizer reactiveQueryByExampleRegistrar( - ObjectProvider> executorsProvider) { - - return (builder) -> { - List> executors = executorsProvider.stream().collect(Collectors.toList()); - if (!executors.isEmpty()) { - builder.configureRuntimeWiring( - QueryByExampleDataFetcher.autoRegistrationConfigurer(Collections.emptyList(), executors)); - } - }; + ObjectProvider> reactiveExecutors) { + return new GraphQlQuerydslSourceBuilderCustomizer<>(QueryByExampleDataFetcher::autoRegistrationConfigurer, + (ObjectProvider>) null, reactiveExecutors); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfiguration.java index 1b5a46fb05..de35a55210 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfiguration.java @@ -16,9 +16,7 @@ package org.springframework.boot.autoconfigure.graphql.data; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import graphql.GraphQL; @@ -31,6 +29,7 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration; import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor; import org.springframework.graphql.data.query.QuerydslDataFetcher; import org.springframework.graphql.execution.GraphQlSource; @@ -54,16 +53,9 @@ public class GraphQlReactiveQuerydslAutoConfiguration { @Bean public GraphQlSourceBuilderCustomizer reactiveQuerydslRegistrar( - ObjectProvider> executorsProvider) { - - return (builder) -> { - List> executors = executorsProvider.stream() - .collect(Collectors.toList()); - if (!executors.isEmpty()) { - builder.configureRuntimeWiring( - QuerydslDataFetcher.autoRegistrationConfigurer(Collections.emptyList(), executors)); - } - }; + ObjectProvider> reactiveExecutors) { + return new GraphQlQuerydslSourceBuilderCustomizer<>(QuerydslDataFetcher::autoRegistrationConfigurer, + (ObjectProvider>) null, reactiveExecutors); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java index 1a703e2632..8dd1763754 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import graphql.GraphQL; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import reactor.core.publisher.Mono; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -38,6 +39,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.log.LogMessage; import org.springframework.graphql.GraphQlService; import org.springframework.graphql.execution.GraphQlSource; import org.springframework.graphql.web.WebGraphQlHandler; @@ -46,6 +48,7 @@ import org.springframework.graphql.web.webflux.GraphQlHttpHandler; import org.springframework.graphql.web.webflux.GraphQlWebSocketHandler; import org.springframework.graphql.web.webflux.GraphiQlHandler; import org.springframework.graphql.web.webflux.SchemaHandler; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -54,8 +57,10 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.config.CorsRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; +import org.springframework.web.reactive.function.server.RequestPredicate; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.reactive.socket.server.support.WebSocketUpgradeHandlerPredicate; @@ -78,6 +83,9 @@ import static org.springframework.web.reactive.function.server.RequestPredicates @EnableConfigurationProperties(GraphQlCorsProperties.class) public class GraphQlWebFluxAutoConfiguration { + private static final RequestPredicate ACCEPT_JSON_CONTENT = accept(MediaType.APPLICATION_JSON) + .and(contentType(MediaType.APPLICATION_JSON)); + private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class); @Bean @@ -95,34 +103,32 @@ public class GraphQlWebFluxAutoConfiguration { } @Bean - public RouterFunction graphQlEndpoint(GraphQlHttpHandler handler, GraphQlSource graphQlSource, + public RouterFunction graphQlEndpoint(GraphQlHttpHandler httpHandler, GraphQlSource graphQlSource, GraphQlProperties properties, ResourceLoader resourceLoader) { - - String graphQLPath = properties.getPath(); - if (logger.isInfoEnabled()) { - logger.info("GraphQL endpoint HTTP POST " + graphQLPath); - } - - RouterFunctions.Builder builder = RouterFunctions.route() - .GET(graphQLPath, - (request) -> ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED) - .headers((headers) -> headers.setAllow(Collections.singleton(HttpMethod.POST))).build()) - .POST(graphQLPath, accept(MediaType.APPLICATION_JSON).and(contentType(MediaType.APPLICATION_JSON)), - handler::handleRequest); - + String path = properties.getPath(); + logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path)); + RouterFunctions.Builder builder = RouterFunctions.route(); + builder = builder.GET(path, this::onlyAllowPost); + builder = builder.POST(path, ACCEPT_JSON_CONTENT, httpHandler::handleRequest); if (properties.getGraphiql().isEnabled()) { - GraphiQlHandler graphiQlHandler = new GraphiQlHandler(graphQLPath, properties.getWebsocket().getPath()); - builder = builder.GET(properties.getGraphiql().getPath(), graphiQlHandler::handleRequest); + GraphiQlHandler graphQlHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); + builder = builder.GET(properties.getGraphiql().getPath(), graphQlHandler::handleRequest); } - if (properties.getSchema().getPrinter().isEnabled()) { SchemaHandler schemaHandler = new SchemaHandler(graphQlSource); - builder = builder.GET(graphQLPath + "/schema", schemaHandler::handleRequest); + builder = builder.GET(path + "/schema", schemaHandler::handleRequest); } - return builder.build(); } + private Mono onlyAllowPost(ServerRequest request) { + return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).headers(this::onlyAllowPost).build(); + } + + private void onlyAllowPost(HttpHeaders headers) { + headers.setAllow(Collections.singleton(HttpMethod.POST)); + } + @Configuration(proxyBeanMethods = false) public static class GraphQlEndpointCorsConfiguration implements WebFluxConfigurer { @@ -161,9 +167,7 @@ public class GraphQlWebFluxAutoConfiguration { public HandlerMapping graphQlWebSocketEndpoint(GraphQlWebSocketHandler graphQlWebSocketHandler, GraphQlProperties properties) { String path = properties.getWebsocket().getPath(); - if (logger.isInfoEnabled()) { - logger.info("GraphQL endpoint WebSocket " + path); - } + logger.info(LogMessage.format("GraphQL endpoint WebSocket %s", path)); SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setHandlerPredicate(new WebSocketUpgradeHandlerPredicate()); mapping.setUrlMap(Collections.singletonMap(path, graphQlWebSocketHandler)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java index 341a25438d..f68796ca0f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java @@ -42,6 +42,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.log.LogMessage; import org.springframework.graphql.GraphQlService; import org.springframework.graphql.execution.GraphQlSource; import org.springframework.graphql.execution.ThreadLocalAccessor; @@ -51,10 +52,12 @@ import org.springframework.graphql.web.webmvc.GraphQlHttpHandler; import org.springframework.graphql.web.webmvc.GraphQlWebSocketHandler; import org.springframework.graphql.web.webmvc.GraphiQlHandler; import org.springframework.graphql.web.webmvc.SchemaHandler; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.GenericHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.config.annotation.CorsRegistry; @@ -62,6 +65,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.function.RequestPredicates; import org.springframework.web.servlet.function.RouterFunction; import org.springframework.web.servlet.function.RouterFunctions; +import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.support.DefaultHandshakeHandler; @@ -102,34 +106,33 @@ public class GraphQlWebMvcAutoConfiguration { } @Bean - public RouterFunction graphQlRouterFunction(GraphQlHttpHandler handler, GraphQlSource graphQlSource, - GraphQlProperties properties, ResourceLoader resourceLoader) { - - String graphQLPath = properties.getPath(); - if (logger.isInfoEnabled()) { - logger.info("GraphQL endpoint HTTP POST " + graphQLPath); - } - - RouterFunctions.Builder builder = RouterFunctions.route() - .GET(graphQLPath, - (request) -> ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED) - .headers((headers) -> headers.setAllow(Collections.singleton(HttpMethod.POST))).build()) - .POST(graphQLPath, RequestPredicates.contentType(MediaType.APPLICATION_JSON) - .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), handler::handleRequest); - + public RouterFunction graphQlRouterFunction(GraphQlHttpHandler httpHandler, + GraphQlSource graphQlSource, GraphQlProperties properties, ResourceLoader resourceLoader) { + String path = properties.getPath(); + logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path)); + RouterFunctions.Builder builder = RouterFunctions.route(); + builder = builder.GET(path, this::onlyAllowPost); + builder = builder.POST(path, RequestPredicates.contentType(MediaType.APPLICATION_JSON) + .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), httpHandler::handleRequest); if (properties.getGraphiql().isEnabled()) { - GraphiQlHandler graphiQLHandler = new GraphiQlHandler(graphQLPath, properties.getWebsocket().getPath()); + GraphiQlHandler graphiQLHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); builder = builder.GET(properties.getGraphiql().getPath(), graphiQLHandler::handleRequest); } - if (properties.getSchema().getPrinter().isEnabled()) { SchemaHandler schemaHandler = new SchemaHandler(graphQlSource); - builder = builder.GET(graphQLPath + "/schema", schemaHandler::handleRequest); + builder = builder.GET(path + "/schema", schemaHandler::handleRequest); } - return builder.build(); } + private ServerResponse onlyAllowPost(ServerRequest request) { + return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).headers(this::onlyAllowPost).build(); + } + + private void onlyAllowPost(HttpHeaders headers) { + headers.setAllow(Collections.singleton(HttpMethod.POST)); + } + @Configuration(proxyBeanMethods = false) public static class GraphQlEndpointCorsConfiguration implements WebMvcConfigurer { @@ -161,25 +164,29 @@ public class GraphQlWebMvcAutoConfiguration { @ConditionalOnMissingBean public GraphQlWebSocketHandler graphQlWebSocketHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties, HttpMessageConverters converters) { - return new GraphQlWebSocketHandler(webGraphQlHandler, getJsonConverter(converters), properties.getWebsocket().getConnectionInitTimeout()); } - @SuppressWarnings("unchecked") - private static GenericHttpMessageConverter getJsonConverter(HttpMessageConverters converters) { - return converters.getConverters().stream() - .filter((candidate) -> candidate.canRead(Map.class, MediaType.APPLICATION_JSON)).findFirst() - .map((converter) -> (GenericHttpMessageConverter) converter) + private GenericHttpMessageConverter getJsonConverter(HttpMessageConverters converters) { + return converters.getConverters().stream().filter(this::canReadJsonMap).findFirst() + .map(this::asGenericHttpMessageConverter) .orElseThrow(() -> new IllegalStateException("No JSON converter")); } + private boolean canReadJsonMap(HttpMessageConverter candidate) { + return candidate.canRead(Map.class, MediaType.APPLICATION_JSON); + } + + @SuppressWarnings("unchecked") + private GenericHttpMessageConverter asGenericHttpMessageConverter(HttpMessageConverter converter) { + return (GenericHttpMessageConverter) converter; + } + @Bean public HandlerMapping graphQlWebSocketMapping(GraphQlWebSocketHandler handler, GraphQlProperties properties) { String path = properties.getWebsocket().getPath(); - if (logger.isInfoEnabled()) { - logger.info("GraphQL endpoint WebSocket " + path); - } + logger.info(LogMessage.format("GraphQL endpoint WebSocket %s", path)); WebSocketHandlerMapping mapping = new WebSocketHandlerMapping(); mapping.setWebSocketUpgradeMatch(true); mapping.setUrlMap(Collections.singletonMap(path,