Separate transports in GraphQL auto-configurations
This commit revisits the existing GraphQL configuration properties to better reflect which ones belong to specific transports. This also relaxes the Web auto-configurations to only require the `ExecutionGraphQlService` as a bean. The `GraphQlSource` is now an optional bean dependency. Closes gh-44495
This commit is contained in:
parent
e886785f76
commit
83f678a8b1
@ -20,6 +20,7 @@ import java.time.Duration;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,31 +32,35 @@ import org.springframework.core.io.Resource;
|
|||||||
@ConfigurationProperties("spring.graphql")
|
@ConfigurationProperties("spring.graphql")
|
||||||
public class GraphQlProperties {
|
public class GraphQlProperties {
|
||||||
|
|
||||||
/**
|
private final Http http = new Http();
|
||||||
* Path at which to expose a GraphQL request HTTP endpoint.
|
|
||||||
*/
|
|
||||||
private String path = "/graphql";
|
|
||||||
|
|
||||||
private final Graphiql graphiql = new Graphiql();
|
private final Graphiql graphiql = new Graphiql();
|
||||||
|
|
||||||
|
private final Rsocket rsocket = new Rsocket();
|
||||||
|
|
||||||
private final Schema schema = new Schema();
|
private final Schema schema = new Schema();
|
||||||
|
|
||||||
|
private final DeprecatedSse sse = new DeprecatedSse(this.http.getSse());
|
||||||
|
|
||||||
private final Websocket websocket = new Websocket();
|
private final Websocket websocket = new Websocket();
|
||||||
|
|
||||||
private final Rsocket rsocket = new Rsocket();
|
public Http getHttp() {
|
||||||
|
return this.http;
|
||||||
private final Sse sse = new Sse();
|
}
|
||||||
|
|
||||||
public Graphiql getGraphiql() {
|
public Graphiql getGraphiql() {
|
||||||
return this.graphiql;
|
return this.graphiql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DeprecatedConfigurationProperty(replacement = "spring.graphql.http.path", since = "3.5.0")
|
||||||
|
@Deprecated(since = "3.5.0", forRemoval = true)
|
||||||
public String getPath() {
|
public String getPath() {
|
||||||
return this.path;
|
return getHttp().getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated(since = "3.5.0", forRemoval = true)
|
||||||
public void setPath(String path) {
|
public void setPath(String path) {
|
||||||
this.path = path;
|
getHttp().setPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Schema getSchema() {
|
public Schema getSchema() {
|
||||||
@ -70,10 +75,33 @@ public class GraphQlProperties {
|
|||||||
return this.rsocket;
|
return this.rsocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sse getSse() {
|
public DeprecatedSse getSse() {
|
||||||
return this.sse;
|
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 {
|
public static class Schema {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,7 +206,7 @@ public class GraphQlProperties {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the endpoint that prints the schema is enabled. Schema is available
|
* 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;
|
private boolean enabled = false;
|
||||||
|
|
||||||
@ -302,4 +330,25 @@ public class GraphQlProperties {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -85,13 +85,6 @@ public class GraphQlWebFluxAutoConfiguration {
|
|||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class);
|
private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class);
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service,
|
|
||||||
ObjectProvider<WebGraphQlInterceptor> interceptors) {
|
|
||||||
return WebGraphQlHandler.builder(service).interceptors(interceptors.orderedStream().toList()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) {
|
public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) {
|
||||||
@ -100,15 +93,23 @@ public class GraphQlWebFluxAutoConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public GraphQlSseHandler graphQlSseHandler(WebGraphQlHandler webGraphQlHandler) {
|
public GraphQlSseHandler graphQlSseHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties) {
|
||||||
return new GraphQlSseHandler(webGraphQlHandler);
|
return new GraphQlSseHandler(webGraphQlHandler, properties.getHttp().getSse().getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service,
|
||||||
|
ObjectProvider<WebGraphQlInterceptor> interceptors) {
|
||||||
|
return WebGraphQlHandler.builder(service).interceptors(interceptors.orderedStream().toList()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Order(0)
|
@Order(0)
|
||||||
public RouterFunction<ServerResponse> graphQlRouterFunction(GraphQlHttpHandler httpHandler,
|
public RouterFunction<ServerResponse> graphQlRouterFunction(GraphQlHttpHandler httpHandler,
|
||||||
GraphQlSseHandler sseHandler, GraphQlSource graphQlSource, GraphQlProperties properties) {
|
GraphQlSseHandler sseHandler, ObjectProvider<GraphQlSource> graphQlSourceProvider,
|
||||||
String path = properties.getPath();
|
GraphQlProperties properties) {
|
||||||
|
String path = properties.getHttp().getPath();
|
||||||
logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path));
|
logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path));
|
||||||
RouterFunctions.Builder builder = RouterFunctions.route();
|
RouterFunctions.Builder builder = RouterFunctions.route();
|
||||||
builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest);
|
builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest);
|
||||||
@ -119,7 +120,8 @@ public class GraphQlWebFluxAutoConfiguration {
|
|||||||
GraphiQlHandler graphQlHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath());
|
GraphiQlHandler graphQlHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath());
|
||||||
builder.GET(properties.getGraphiql().getPath(), graphQlHandler::handleRequest);
|
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);
|
SchemaHandler schemaHandler = new SchemaHandler(graphQlSource);
|
||||||
builder.GET(path + "/schema", schemaHandler::handleRequest);
|
builder.GET(path + "/schema", schemaHandler::handleRequest);
|
||||||
}
|
}
|
||||||
@ -158,7 +160,7 @@ public class GraphQlWebFluxAutoConfiguration {
|
|||||||
public void addCorsMappings(CorsRegistry registry) {
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
CorsConfiguration configuration = this.corsProperties.toCorsConfiguration();
|
CorsConfiguration configuration = this.corsProperties.toCorsConfiguration();
|
||||||
if (configuration != null) {
|
if (configuration != null) {
|
||||||
registry.addMapping(this.graphQlProperties.getPath()).combine(configuration);
|
registry.addMapping(this.graphQlProperties.getHttp().getPath()).combine(configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
|
|||||||
import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration;
|
import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.graphql.GraphQlCorsProperties;
|
import org.springframework.boot.autoconfigure.graphql.GraphQlCorsProperties;
|
||||||
import org.springframework.boot.autoconfigure.graphql.GraphQlProperties;
|
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.autoconfigure.http.HttpMessageConverters;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -99,8 +98,7 @@ public class GraphQlWebMvcAutoConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public GraphQlSseHandler graphQlSseHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties) {
|
public GraphQlSseHandler graphQlSseHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties) {
|
||||||
Sse sse = properties.getSse();
|
return new GraphQlSseHandler(webGraphQlHandler, properties.getHttp().getSse().getTimeout());
|
||||||
return new GraphQlSseHandler(webGraphQlHandler, sse.getTimeout());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -113,8 +111,9 @@ public class GraphQlWebMvcAutoConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@Order(0)
|
@Order(0)
|
||||||
public RouterFunction<ServerResponse> graphQlRouterFunction(GraphQlHttpHandler httpHandler,
|
public RouterFunction<ServerResponse> graphQlRouterFunction(GraphQlHttpHandler httpHandler,
|
||||||
GraphQlSseHandler sseHandler, GraphQlSource graphQlSource, GraphQlProperties properties) {
|
GraphQlSseHandler sseHandler, ObjectProvider<GraphQlSource> graphQlSourceProvider,
|
||||||
String path = properties.getPath();
|
GraphQlProperties properties) {
|
||||||
|
String path = properties.getHttp().getPath();
|
||||||
logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path));
|
logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path));
|
||||||
RouterFunctions.Builder builder = RouterFunctions.route();
|
RouterFunctions.Builder builder = RouterFunctions.route();
|
||||||
builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest);
|
builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest);
|
||||||
@ -125,7 +124,8 @@ public class GraphQlWebMvcAutoConfiguration {
|
|||||||
GraphiQlHandler graphiQLHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath());
|
GraphiQlHandler graphiQLHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath());
|
||||||
builder.GET(properties.getGraphiql().getPath(), graphiQLHandler::handleRequest);
|
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);
|
SchemaHandler schemaHandler = new SchemaHandler(graphQlSource);
|
||||||
builder.GET(path + "/schema", schemaHandler::handleRequest);
|
builder.GET(path + "/schema", schemaHandler::handleRequest);
|
||||||
}
|
}
|
||||||
@ -164,7 +164,7 @@ public class GraphQlWebMvcAutoConfiguration {
|
|||||||
public void addCorsMappings(CorsRegistry registry) {
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
CorsConfiguration configuration = this.corsProperties.toCorsConfiguration();
|
CorsConfiguration configuration = this.corsProperties.toCorsConfiguration();
|
||||||
if (configuration != null) {
|
if (configuration != null) {
|
||||||
registry.addMapping(this.graphQlProperties.getPath()).combine(configuration);
|
registry.addMapping(this.graphQlProperties.getHttp().getPath()).combine(configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ class GraphQlWebMvcAutoConfigurationTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldConfigureSseTimeout() {
|
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);
|
assertThat(context).hasSingleBean(GraphQlSseHandler.class);
|
||||||
GraphQlSseHandler handler = context.getBean(GraphQlSseHandler.class);
|
GraphQlSseHandler handler = context.getBean(GraphQlSseHandler.class);
|
||||||
assertThat(handler).hasFieldOrPropertyWithValue("timeout", Duration.ofSeconds(10));
|
assertThat(handler).hasFieldOrPropertyWithValue("timeout", Duration.ofSeconds(10));
|
||||||
|
@ -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.
|
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.
|
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`.
|
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.
|
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.
|
||||||
|
@ -43,7 +43,7 @@ public class HttpGraphQlTesterAutoConfiguration {
|
|||||||
@ConditionalOnBean(WebTestClient.class)
|
@ConditionalOnBean(WebTestClient.class)
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public HttpGraphQlTester webTestClientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) {
|
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);
|
return HttpGraphQlTester.create(mutatedWebTestClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ class HttpGraphQlTesterContextCustomizer implements ContextCustomizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String findConfiguredGraphQlPath() {
|
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";
|
return StringUtils.hasText(configuredPath) ? configuredPath : "/graphql";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user