diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java index 3a161fe4544b..69e92eee2069 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java @@ -20,6 +20,7 @@ import java.util.Arrays; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.core.io.Resource; /** @@ -31,31 +32,35 @@ @ConfigurationProperties("spring.graphql") public class GraphQlProperties { - /** - * Path at which to expose a GraphQL request HTTP endpoint. - */ - private String path = "/graphql"; + private final Http http = new Http(); private final Graphiql graphiql = new Graphiql(); + private final Rsocket rsocket = new Rsocket(); + private final Schema schema = new Schema(); - private final Websocket websocket = new Websocket(); + private final DeprecatedSse sse = new DeprecatedSse(this.http.getSse()); - private final Rsocket rsocket = new Rsocket(); + private final Websocket websocket = new Websocket(); - private final Sse sse = new Sse(); + public Http getHttp() { + return this.http; + } public Graphiql getGraphiql() { return this.graphiql; } + @DeprecatedConfigurationProperty(replacement = "spring.graphql.http.path", since = "3.5.0") + @Deprecated(since = "3.5.0", forRemoval = true) public String getPath() { - return this.path; + return getHttp().getPath(); } + @Deprecated(since = "3.5.0", forRemoval = true) public void setPath(String path) { - this.path = path; + getHttp().setPath(path); } public Schema getSchema() { @@ -70,10 +75,33 @@ public Rsocket getRsocket() { return this.rsocket; } - public Sse getSse() { + public DeprecatedSse getSse() { return this.sse; } + public static class Http { + + /** + * Path at which to expose a GraphQL request HTTP endpoint. + */ + private String path = "/graphql"; + + private final Sse sse = new Sse(); + + public String getPath() { + return this.path; + } + + public void setPath(String path) { + this.path = path; + } + + public Sse getSse() { + return this.sse; + } + + } + public static class Schema { /** @@ -178,7 +206,7 @@ public static class Printer { /** * Whether the endpoint that prints the schema is enabled. Schema is available - * under spring.graphql.path + "/schema". + * under spring.graphql.http.path + "/schema". */ private boolean enabled = false; @@ -302,4 +330,25 @@ public void setTimeout(Duration timeout) { } + public static class DeprecatedSse { + + private final Sse sse; + + public DeprecatedSse(Sse sse) { + this.sse = sse; + } + + @DeprecatedConfigurationProperty(replacement = "spring.graphql.http.sse.timeout", since = "3.5.0") + @Deprecated(since = "3.5.0", forRemoval = true) + public Duration getTimeout() { + return this.sse.getTimeout(); + } + + @Deprecated(since = "3.5.0", forRemoval = true) + public void setTimeout(Duration timeout) { + this.sse.setTimeout(timeout); + } + + } + } 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 4a9d03d7e9c6..72fc4b2751ec 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 @@ -87,28 +87,29 @@ public class GraphQlWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean - public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service, - ObjectProvider interceptors) { - return WebGraphQlHandler.builder(service).interceptors(interceptors.orderedStream().toList()).build(); + public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) { + return new GraphQlHttpHandler(webGraphQlHandler); } @Bean @ConditionalOnMissingBean - public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) { - return new GraphQlHttpHandler(webGraphQlHandler); + public GraphQlSseHandler graphQlSseHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties) { + return new GraphQlSseHandler(webGraphQlHandler, properties.getHttp().getSse().getTimeout()); } @Bean @ConditionalOnMissingBean - public GraphQlSseHandler graphQlSseHandler(WebGraphQlHandler webGraphQlHandler) { - return new GraphQlSseHandler(webGraphQlHandler); + public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service, + ObjectProvider interceptors) { + return WebGraphQlHandler.builder(service).interceptors(interceptors.orderedStream().toList()).build(); } @Bean @Order(0) public RouterFunction graphQlRouterFunction(GraphQlHttpHandler httpHandler, - GraphQlSseHandler sseHandler, GraphQlSource graphQlSource, GraphQlProperties properties) { - String path = properties.getPath(); + GraphQlSseHandler sseHandler, ObjectProvider graphQlSourceProvider, + GraphQlProperties properties) { + String path = properties.getHttp().getPath(); logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path)); RouterFunctions.Builder builder = RouterFunctions.route(); builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest); @@ -119,7 +120,8 @@ public RouterFunction graphQlRouterFunction(GraphQlHttpHandler h GraphiQlHandler graphQlHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); builder.GET(properties.getGraphiql().getPath(), graphQlHandler::handleRequest); } - if (properties.getSchema().getPrinter().isEnabled()) { + GraphQlSource graphQlSource = graphQlSourceProvider.getIfAvailable(); + if (properties.getSchema().getPrinter().isEnabled() && graphQlSource != null) { SchemaHandler schemaHandler = new SchemaHandler(graphQlSource); builder.GET(path + "/schema", schemaHandler::handleRequest); } @@ -158,7 +160,7 @@ public GraphQlEndpointCorsConfiguration(GraphQlProperties graphQlProps, GraphQlC public void addCorsMappings(CorsRegistry registry) { CorsConfiguration configuration = this.corsProperties.toCorsConfiguration(); if (configuration != null) { - registry.addMapping(this.graphQlProperties.getPath()).combine(configuration); + registry.addMapping(this.graphQlProperties.getHttp().getPath()).combine(configuration); } } 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 8b335eda890a..f5c7edee7159 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 @@ -37,7 +37,6 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration; import org.springframework.boot.autoconfigure.graphql.GraphQlCorsProperties; import org.springframework.boot.autoconfigure.graphql.GraphQlProperties; -import org.springframework.boot.autoconfigure.graphql.GraphQlProperties.Sse; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -99,8 +98,7 @@ public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler @Bean @ConditionalOnMissingBean public GraphQlSseHandler graphQlSseHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties) { - Sse sse = properties.getSse(); - return new GraphQlSseHandler(webGraphQlHandler, sse.getTimeout()); + return new GraphQlSseHandler(webGraphQlHandler, properties.getHttp().getSse().getTimeout()); } @Bean @@ -113,8 +111,9 @@ public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service, @Bean @Order(0) public RouterFunction graphQlRouterFunction(GraphQlHttpHandler httpHandler, - GraphQlSseHandler sseHandler, GraphQlSource graphQlSource, GraphQlProperties properties) { - String path = properties.getPath(); + GraphQlSseHandler sseHandler, ObjectProvider graphQlSourceProvider, + GraphQlProperties properties) { + String path = properties.getHttp().getPath(); logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path)); RouterFunctions.Builder builder = RouterFunctions.route(); builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest); @@ -125,7 +124,8 @@ public RouterFunction graphQlRouterFunction(GraphQlHttpHandler h GraphiQlHandler graphiQLHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); builder.GET(properties.getGraphiql().getPath(), graphiQLHandler::handleRequest); } - if (properties.getSchema().getPrinter().isEnabled()) { + GraphQlSource graphQlSource = graphQlSourceProvider.getIfAvailable(); + if (properties.getSchema().getPrinter().isEnabled() && graphQlSource != null) { SchemaHandler schemaHandler = new SchemaHandler(graphQlSource); builder.GET(path + "/schema", schemaHandler::handleRequest); } @@ -164,7 +164,7 @@ public GraphQlEndpointCorsConfiguration(GraphQlProperties graphQlProps, GraphQlC public void addCorsMappings(CorsRegistry registry) { CorsConfiguration configuration = this.corsProperties.toCorsConfiguration(); if (configuration != null) { - registry.addMapping(this.graphQlProperties.getPath()).combine(configuration); + registry.addMapping(this.graphQlProperties.getHttp().getPath()).combine(configuration); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java index 9bd6618804fa..9466fd9c9e4b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java @@ -81,7 +81,7 @@ void shouldContributeDefaultBeans() { @Test void shouldConfigureSseTimeout() { - this.contextRunner.withPropertyValues("spring.graphql.sse.timeout=10s").run((context) -> { + this.contextRunner.withPropertyValues("spring.graphql.http.sse.timeout=10s").run((context) -> { assertThat(context).hasSingleBean(GraphQlSseHandler.class); GraphQlSseHandler handler = context.getBean(GraphQlSseHandler.class); assertThat(handler).hasFieldOrPropertyWithValue("timeout", Duration.ofSeconds(10)); diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-graphql.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-graphql.adoc index fbddcbc82246..0add65c8a45c 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-graphql.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-graphql.adoc @@ -94,7 +94,7 @@ are detected by Spring Boot and considered as candidates for javadoc:graphql.sch The GraphQL HTTP endpoint is at HTTP POST `/graphql` by default. It also supports the `"text/event-stream"` media type over Server Sent Events for subscriptions only. -The path can be customized with configprop:spring.graphql.path[]. +The path can be customized with configprop:spring.graphql.http.path[]. TIP: The HTTP endpoint for both Spring MVC and Spring WebFlux is provided by a `RouterFunction` bean with an javadoc:org.springframework.core.annotation.Order[format=annotation] of `0`. If you define your own `RouterFunction` beans, you may want to add appropriate javadoc:org.springframework.core.annotation.Order[format=annotation] annotations to ensure that they are sorted correctly. diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/HttpGraphQlTesterAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/HttpGraphQlTesterAutoConfiguration.java index 953571a1c9bc..12c017377557 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/HttpGraphQlTesterAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/HttpGraphQlTesterAutoConfiguration.java @@ -43,7 +43,7 @@ public class HttpGraphQlTesterAutoConfiguration { @ConditionalOnBean(WebTestClient.class) @ConditionalOnMissingBean public HttpGraphQlTester webTestClientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) { - WebTestClient mutatedWebTestClient = webTestClient.mutate().baseUrl(properties.getPath()).build(); + WebTestClient mutatedWebTestClient = webTestClient.mutate().baseUrl(properties.getHttp().getPath()).build(); return HttpGraphQlTester.create(mutatedWebTestClient); } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizer.java index 446063bd8c06..f91ee263ba41 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizer.java @@ -171,7 +171,7 @@ private String deduceBasePath() { } private String findConfiguredGraphQlPath() { - String configuredPath = this.applicationContext.getEnvironment().getProperty("spring.graphql.path"); + String configuredPath = this.applicationContext.getEnvironment().getProperty("spring.graphql.http.path"); return StringUtils.hasText(configuredPath) ? configuredPath : "/graphql"; }