Polish GraphQL auto-configuration

pull/29636/head
Phillip Webb 3 years ago
parent ce08b519b1
commit a05903b9d1

@ -19,11 +19,13 @@ package org.springframework.boot.autoconfigure.graphql;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import graphql.GraphQL; import graphql.GraphQL;
import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.Instrumentation;
import graphql.schema.idl.RuntimeWiring.Builder;
import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility; import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -67,29 +69,49 @@ public class GraphQlAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public GraphQlSource graphQlSource(ResourcePatternResolver resourcePatternResolver, GraphQlProperties properties, public GraphQlSource graphQlSource(ResourcePatternResolver resourcePatternResolver, GraphQlProperties properties,
ObjectProvider<DataFetcherExceptionResolver> exceptionResolversProvider, ObjectProvider<DataFetcherExceptionResolver> exceptionResolvers,
ObjectProvider<Instrumentation> instrumentationsProvider, ObjectProvider<Instrumentation> instrumentations, ObjectProvider<RuntimeWiringConfigurer> wiringConfigurers,
ObjectProvider<RuntimeWiringConfigurer> wiringConfigurers,
ObjectProvider<GraphQlSourceBuilderCustomizer> sourceCustomizers) { ObjectProvider<GraphQlSourceBuilderCustomizer> sourceCustomizers) {
String[] schemaLocations = properties.getSchema().getLocations();
List<Resource> schemaResources = resolveSchemaResources(resourcePatternResolver, Resource[] schemaResources = resolveSchemaResources(resourcePatternResolver, schemaLocations,
properties.getSchema().getLocations(), properties.getSchema().getFileExtensions()); properties.getSchema().getFileExtensions());
GraphQlSource.Builder builder = GraphQlSource.builder() GraphQlSource.Builder builder = GraphQlSource.builder().schemaResources(schemaResources)
.schemaResources(schemaResources.toArray(new Resource[0])) .exceptionResolvers(toList(exceptionResolvers)).instrumentation(toList(instrumentations));
.exceptionResolvers(exceptionResolversProvider.orderedStream().collect(Collectors.toList()))
.instrumentation(instrumentationsProvider.orderedStream().collect(Collectors.toList()));
if (!properties.getSchema().getIntrospection().isEnabled()) { if (!properties.getSchema().getIntrospection().isEnabled()) {
builder.configureRuntimeWiring((wiring) -> wiring builder.configureRuntimeWiring(this::enableIntrospection);
.fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY));
} }
wiringConfigurers.orderedStream().forEach(builder::configureRuntimeWiring); wiringConfigurers.orderedStream().forEach(builder::configureRuntimeWiring);
sourceCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); sourceCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
try { try {
return builder.build(); return builder.build();
} }
catch (MissingSchemaException exc) { catch (MissingSchemaException ex) {
throw new InvalidSchemaLocationsException(properties.getSchema().getLocations(), resourcePatternResolver, throw new InvalidSchemaLocationsException(schemaLocations, resourcePatternResolver, ex);
exc); }
}
private Builder enableIntrospection(Builder wiring) {
return wiring.fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY);
}
private Resource[] resolveSchemaResources(ResourcePatternResolver resolver, String[] locations,
String[] extensions) {
List<Resource> 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<Resource> 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; return annotatedControllerConfigurer;
} }
private List<Resource> resolveSchemaResources(ResourcePatternResolver resolver, String[] schemaLocations, private <T> List<T> toList(ObjectProvider<T> provider) {
String[] fileExtensions) { return provider.orderedStream().collect(Collectors.toList());
List<Resource> 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;
} }
} }

@ -24,7 +24,6 @@ import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.convert.DurationUnit; import org.springframework.boot.convert.DurationUnit;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
@ -73,7 +72,6 @@ public class GraphQlCorsProperties {
/** /**
* Whether credentials are supported. When not set, credentials are not supported. * Whether credentials are supported. When not set, credentials are not supported.
*/ */
@Nullable
private Boolean allowCredentials; private Boolean allowCredentials;
/** /**
@ -123,7 +121,6 @@ public class GraphQlCorsProperties {
this.exposedHeaders = exposedHeaders; this.exposedHeaders = exposedHeaders;
} }
@Nullable
public Boolean getAllowCredentials() { public Boolean getAllowCredentials() {
return this.allowCredentials; return this.allowCredentials;
} }
@ -140,7 +137,6 @@ public class GraphQlCorsProperties {
this.maxAge = maxAge; this.maxAge = maxAge;
} }
@Nullable
public CorsConfiguration toCorsConfiguration() { public CorsConfiguration toCorsConfiguration() {
if (CollectionUtils.isEmpty(this.allowedOrigins) && CollectionUtils.isEmpty(this.allowedOriginPatterns)) { if (CollectionUtils.isEmpty(this.allowedOrigins) && CollectionUtils.isEmpty(this.allowedOriginPatterns)) {
return null; return null;

@ -20,8 +20,8 @@ import org.springframework.graphql.execution.GraphQlSource;
/** /**
* Callback interface that can be implemented by beans wishing to customize properties of * Callback interface that can be implemented by beans wishing to customize properties of
* {@link org.springframework.graphql.execution.GraphQlSource.Builder} whilst retaining * {@link org.springframework.graphql.execution.GraphQlSource.Builder Builder} whilst
* default auto-configuration. * retaining default auto-configuration.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 2.7.0 * @since 2.7.0
@ -30,7 +30,8 @@ import org.springframework.graphql.execution.GraphQlSource;
public interface GraphQlSourceBuilderCustomizer { 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 * @param builder builder the builder to customize
*/ */
void customize(GraphQlSource.Builder builder); void customize(GraphQlSource.Builder builder);

@ -17,7 +17,6 @@
package org.springframework.boot.autoconfigure.graphql.data; package org.springframework.boot.autoconfigure.graphql.data;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import graphql.GraphQL; import graphql.GraphQL;
@ -52,19 +51,10 @@ import org.springframework.graphql.execution.GraphQlSource;
public class GraphQlQueryByExampleAutoConfiguration { public class GraphQlQueryByExampleAutoConfiguration {
@Bean @Bean
public GraphQlSourceBuilderCustomizer queryByExampleRegistrar( public GraphQlSourceBuilderCustomizer queryByExampleRegistrar(ObjectProvider<QueryByExampleExecutor<?>> executors,
ObjectProvider<QueryByExampleExecutor<?>> executorsProvider, ObjectProvider<ReactiveQueryByExampleExecutor<?>> reactiveExecutors) {
ObjectProvider<ReactiveQueryByExampleExecutor<?>> reactiveExecutorsProvider) { return new GraphQlQuerydslSourceBuilderCustomizer<>(QueryByExampleDataFetcher::autoRegistrationConfigurer,
executors, reactiveExecutors);
return (builder) -> {
List<QueryByExampleExecutor<?>> executors = executorsProvider.stream().collect(Collectors.toList());
List<ReactiveQueryByExampleExecutor<?>> reactiveExecutors = reactiveExecutorsProvider.stream()
.collect(Collectors.toList());
if (!executors.isEmpty()) {
builder.configureRuntimeWiring(
QueryByExampleDataFetcher.autoRegistrationConfigurer(executors, reactiveExecutors));
}
};
} }
} }

@ -17,7 +17,6 @@
package org.springframework.boot.autoconfigure.graphql.data; package org.springframework.boot.autoconfigure.graphql.data;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import graphql.GraphQL; import graphql.GraphQL;
@ -53,19 +52,10 @@ import org.springframework.graphql.execution.GraphQlSource;
public class GraphQlQuerydslAutoConfiguration { public class GraphQlQuerydslAutoConfiguration {
@Bean @Bean
public GraphQlSourceBuilderCustomizer querydslRegistrar( public GraphQlSourceBuilderCustomizer querydslRegistrar(ObjectProvider<QuerydslPredicateExecutor<?>> executors,
ObjectProvider<QuerydslPredicateExecutor<?>> executorsProvider, ObjectProvider<ReactiveQuerydslPredicateExecutor<?>> reactiveExecutors) {
ObjectProvider<ReactiveQuerydslPredicateExecutor<?>> reactiveExecutorsProvider) { return new GraphQlQuerydslSourceBuilderCustomizer<>(QuerydslDataFetcher::autoRegistrationConfigurer, executors,
reactiveExecutors);
return (builder) -> {
List<QuerydslPredicateExecutor<?>> executors = executorsProvider.stream().collect(Collectors.toList());
if (!executors.isEmpty()) {
List<ReactiveQuerydslPredicateExecutor<?>> reactiveExecutors = reactiveExecutorsProvider.stream()
.collect(Collectors.toList());
builder.configureRuntimeWiring(
QuerydslDataFetcher.autoRegistrationConfigurer(executors, reactiveExecutors));
}
};
} }
} }

@ -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 <E> the executor type
* @param <R> the reactive executor type
* @author Phillip Webb
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
class GraphQlQuerydslSourceBuilderCustomizer<E, R> implements GraphQlSourceBuilderCustomizer {
private final BiFunction<List<E>, List<R>, RuntimeWiringConfigurer> wiringConfigurerFactory;
private final List<E> executors;
private final List<R> reactiveExecutors;
GraphQlQuerydslSourceBuilderCustomizer(
BiFunction<List<E>, List<R>, RuntimeWiringConfigurer> wiringConfigurerFactory, ObjectProvider<E> executors,
ObjectProvider<R> reactiveExecutors) {
this(wiringConfigurerFactory, toList(executors), toList(reactiveExecutors));
}
GraphQlQuerydslSourceBuilderCustomizer(
BiFunction<List<E>, List<R>, RuntimeWiringConfigurer> wiringConfigurerFactory, List<E> executors,
List<R> 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 <T> List<T> toList(ObjectProvider<T> provider) {
return (provider != null) ? provider.orderedStream().collect(Collectors.toList()) : Collections.emptyList();
}
}

@ -16,9 +16,7 @@
package org.springframework.boot.autoconfigure.graphql.data; package org.springframework.boot.autoconfigure.graphql.data;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import graphql.GraphQL; import graphql.GraphQL;
@ -31,6 +29,7 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration;
import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor; import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
import org.springframework.graphql.data.query.QueryByExampleDataFetcher; import org.springframework.graphql.data.query.QueryByExampleDataFetcher;
import org.springframework.graphql.execution.GraphQlSource; import org.springframework.graphql.execution.GraphQlSource;
@ -53,15 +52,9 @@ public class GraphQlReactiveQueryByExampleAutoConfiguration {
@Bean @Bean
public GraphQlSourceBuilderCustomizer reactiveQueryByExampleRegistrar( public GraphQlSourceBuilderCustomizer reactiveQueryByExampleRegistrar(
ObjectProvider<ReactiveQueryByExampleExecutor<?>> executorsProvider) { ObjectProvider<ReactiveQueryByExampleExecutor<?>> reactiveExecutors) {
return new GraphQlQuerydslSourceBuilderCustomizer<>(QueryByExampleDataFetcher::autoRegistrationConfigurer,
return (builder) -> { (ObjectProvider<QueryByExampleExecutor<?>>) null, reactiveExecutors);
List<ReactiveQueryByExampleExecutor<?>> executors = executorsProvider.stream().collect(Collectors.toList());
if (!executors.isEmpty()) {
builder.configureRuntimeWiring(
QueryByExampleDataFetcher.autoRegistrationConfigurer(Collections.emptyList(), executors));
}
};
} }
} }

@ -16,9 +16,7 @@
package org.springframework.boot.autoconfigure.graphql.data; package org.springframework.boot.autoconfigure.graphql.data;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import graphql.GraphQL; import graphql.GraphQL;
@ -31,6 +29,7 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration;
import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor; import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
import org.springframework.graphql.data.query.QuerydslDataFetcher; import org.springframework.graphql.data.query.QuerydslDataFetcher;
import org.springframework.graphql.execution.GraphQlSource; import org.springframework.graphql.execution.GraphQlSource;
@ -54,16 +53,9 @@ public class GraphQlReactiveQuerydslAutoConfiguration {
@Bean @Bean
public GraphQlSourceBuilderCustomizer reactiveQuerydslRegistrar( public GraphQlSourceBuilderCustomizer reactiveQuerydslRegistrar(
ObjectProvider<ReactiveQuerydslPredicateExecutor<?>> executorsProvider) { ObjectProvider<ReactiveQuerydslPredicateExecutor<?>> reactiveExecutors) {
return new GraphQlQuerydslSourceBuilderCustomizer<>(QuerydslDataFetcher::autoRegistrationConfigurer,
return (builder) -> { (ObjectProvider<QuerydslPredicateExecutor<?>>) null, reactiveExecutors);
List<ReactiveQuerydslPredicateExecutor<?>> executors = executorsProvider.stream()
.collect(Collectors.toList());
if (!executors.isEmpty()) {
builder.configureRuntimeWiring(
QuerydslDataFetcher.autoRegistrationConfigurer(Collections.emptyList(), executors));
}
};
} }
} }

@ -22,6 +22,7 @@ import java.util.stream.Collectors;
import graphql.GraphQL; import graphql.GraphQL;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.graphql.GraphQlService; import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.execution.GraphQlSource; import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.web.WebGraphQlHandler; 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.GraphQlWebSocketHandler;
import org.springframework.graphql.web.webflux.GraphiQlHandler; import org.springframework.graphql.web.webflux.GraphiQlHandler;
import org.springframework.graphql.web.webflux.SchemaHandler; import org.springframework.graphql.web.webflux.SchemaHandler;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; 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.HandlerMapping;
import org.springframework.web.reactive.config.CorsRegistry; import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer; 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.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions; 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.function.server.ServerResponse;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.server.support.WebSocketUpgradeHandlerPredicate; 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) @EnableConfigurationProperties(GraphQlCorsProperties.class)
public class GraphQlWebFluxAutoConfiguration { 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); private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class);
@Bean @Bean
@ -95,34 +103,32 @@ public class GraphQlWebFluxAutoConfiguration {
} }
@Bean @Bean
public RouterFunction<ServerResponse> graphQlEndpoint(GraphQlHttpHandler handler, GraphQlSource graphQlSource, public RouterFunction<ServerResponse> graphQlEndpoint(GraphQlHttpHandler httpHandler, GraphQlSource graphQlSource,
GraphQlProperties properties, ResourceLoader resourceLoader) { GraphQlProperties properties, ResourceLoader resourceLoader) {
String path = properties.getPath();
String graphQLPath = properties.getPath(); logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path));
if (logger.isInfoEnabled()) { RouterFunctions.Builder builder = RouterFunctions.route();
logger.info("GraphQL endpoint HTTP POST " + graphQLPath); builder = builder.GET(path, this::onlyAllowPost);
} builder = builder.POST(path, ACCEPT_JSON_CONTENT, httpHandler::handleRequest);
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);
if (properties.getGraphiql().isEnabled()) { if (properties.getGraphiql().isEnabled()) {
GraphiQlHandler graphiQlHandler = new GraphiQlHandler(graphQLPath, properties.getWebsocket().getPath()); GraphiQlHandler graphQlHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath());
builder = builder.GET(properties.getGraphiql().getPath(), graphiQlHandler::handleRequest); builder = builder.GET(properties.getGraphiql().getPath(), graphQlHandler::handleRequest);
} }
if (properties.getSchema().getPrinter().isEnabled()) { if (properties.getSchema().getPrinter().isEnabled()) {
SchemaHandler schemaHandler = new SchemaHandler(graphQlSource); SchemaHandler schemaHandler = new SchemaHandler(graphQlSource);
builder = builder.GET(graphQLPath + "/schema", schemaHandler::handleRequest); builder = builder.GET(path + "/schema", schemaHandler::handleRequest);
} }
return builder.build(); return builder.build();
} }
private Mono<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) @Configuration(proxyBeanMethods = false)
public static class GraphQlEndpointCorsConfiguration implements WebFluxConfigurer { public static class GraphQlEndpointCorsConfiguration implements WebFluxConfigurer {
@ -161,9 +167,7 @@ public class GraphQlWebFluxAutoConfiguration {
public HandlerMapping graphQlWebSocketEndpoint(GraphQlWebSocketHandler graphQlWebSocketHandler, public HandlerMapping graphQlWebSocketEndpoint(GraphQlWebSocketHandler graphQlWebSocketHandler,
GraphQlProperties properties) { GraphQlProperties properties) {
String path = properties.getWebsocket().getPath(); String path = properties.getWebsocket().getPath();
if (logger.isInfoEnabled()) { logger.info(LogMessage.format("GraphQL endpoint WebSocket %s", path));
logger.info("GraphQL endpoint WebSocket " + path);
}
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setHandlerPredicate(new WebSocketUpgradeHandlerPredicate()); mapping.setHandlerPredicate(new WebSocketUpgradeHandlerPredicate());
mapping.setUrlMap(Collections.singletonMap(path, graphQlWebSocketHandler)); mapping.setUrlMap(Collections.singletonMap(path, graphQlWebSocketHandler));

@ -42,6 +42,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.graphql.GraphQlService; import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.execution.GraphQlSource; import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.execution.ThreadLocalAccessor; 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.GraphQlWebSocketHandler;
import org.springframework.graphql.web.webmvc.GraphiQlHandler; import org.springframework.graphql.web.webmvc.GraphiQlHandler;
import org.springframework.graphql.web.webmvc.SchemaHandler; import org.springframework.graphql.web.webmvc.SchemaHandler;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.config.annotation.CorsRegistry; 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.RequestPredicates;
import org.springframework.web.servlet.function.RouterFunction; import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions; 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.servlet.function.ServerResponse;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler; import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
@ -102,34 +106,33 @@ public class GraphQlWebMvcAutoConfiguration {
} }
@Bean @Bean
public RouterFunction<ServerResponse> graphQlRouterFunction(GraphQlHttpHandler handler, GraphQlSource graphQlSource, public RouterFunction<ServerResponse> graphQlRouterFunction(GraphQlHttpHandler httpHandler,
GraphQlProperties properties, ResourceLoader resourceLoader) { GraphQlSource graphQlSource, GraphQlProperties properties, ResourceLoader resourceLoader) {
String path = properties.getPath();
String graphQLPath = properties.getPath(); logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path));
if (logger.isInfoEnabled()) { RouterFunctions.Builder builder = RouterFunctions.route();
logger.info("GraphQL endpoint HTTP POST " + graphQLPath); builder = builder.GET(path, this::onlyAllowPost);
} builder = builder.POST(path, RequestPredicates.contentType(MediaType.APPLICATION_JSON)
.and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), httpHandler::handleRequest);
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);
if (properties.getGraphiql().isEnabled()) { 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); builder = builder.GET(properties.getGraphiql().getPath(), graphiQLHandler::handleRequest);
} }
if (properties.getSchema().getPrinter().isEnabled()) { if (properties.getSchema().getPrinter().isEnabled()) {
SchemaHandler schemaHandler = new SchemaHandler(graphQlSource); SchemaHandler schemaHandler = new SchemaHandler(graphQlSource);
builder = builder.GET(graphQLPath + "/schema", schemaHandler::handleRequest); builder = builder.GET(path + "/schema", schemaHandler::handleRequest);
} }
return builder.build(); 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) @Configuration(proxyBeanMethods = false)
public static class GraphQlEndpointCorsConfiguration implements WebMvcConfigurer { public static class GraphQlEndpointCorsConfiguration implements WebMvcConfigurer {
@ -161,25 +164,29 @@ public class GraphQlWebMvcAutoConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
public GraphQlWebSocketHandler graphQlWebSocketHandler(WebGraphQlHandler webGraphQlHandler, public GraphQlWebSocketHandler graphQlWebSocketHandler(WebGraphQlHandler webGraphQlHandler,
GraphQlProperties properties, HttpMessageConverters converters) { GraphQlProperties properties, HttpMessageConverters converters) {
return new GraphQlWebSocketHandler(webGraphQlHandler, getJsonConverter(converters), return new GraphQlWebSocketHandler(webGraphQlHandler, getJsonConverter(converters),
properties.getWebsocket().getConnectionInitTimeout()); properties.getWebsocket().getConnectionInitTimeout());
} }
@SuppressWarnings("unchecked") private GenericHttpMessageConverter<Object> getJsonConverter(HttpMessageConverters converters) {
private static GenericHttpMessageConverter<Object> getJsonConverter(HttpMessageConverters converters) { return converters.getConverters().stream().filter(this::canReadJsonMap).findFirst()
return converters.getConverters().stream() .map(this::asGenericHttpMessageConverter)
.filter((candidate) -> candidate.canRead(Map.class, MediaType.APPLICATION_JSON)).findFirst()
.map((converter) -> (GenericHttpMessageConverter<Object>) converter)
.orElseThrow(() -> new IllegalStateException("No JSON converter")); .orElseThrow(() -> new IllegalStateException("No JSON converter"));
} }
private boolean canReadJsonMap(HttpMessageConverter<?> candidate) {
return candidate.canRead(Map.class, MediaType.APPLICATION_JSON);
}
@SuppressWarnings("unchecked")
private GenericHttpMessageConverter<Object> asGenericHttpMessageConverter(HttpMessageConverter<?> converter) {
return (GenericHttpMessageConverter<Object>) converter;
}
@Bean @Bean
public HandlerMapping graphQlWebSocketMapping(GraphQlWebSocketHandler handler, GraphQlProperties properties) { public HandlerMapping graphQlWebSocketMapping(GraphQlWebSocketHandler handler, GraphQlProperties properties) {
String path = properties.getWebsocket().getPath(); String path = properties.getWebsocket().getPath();
if (logger.isInfoEnabled()) { logger.info(LogMessage.format("GraphQL endpoint WebSocket %s", path));
logger.info("GraphQL endpoint WebSocket " + path);
}
WebSocketHandlerMapping mapping = new WebSocketHandlerMapping(); WebSocketHandlerMapping mapping = new WebSocketHandlerMapping();
mapping.setWebSocketUpgradeMatch(true); mapping.setWebSocketUpgradeMatch(true);
mapping.setUrlMap(Collections.singletonMap(path, mapping.setUrlMap(Collections.singletonMap(path,

Loading…
Cancel
Save