diff --git a/README.md b/README.md index a97c307..7551753 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,23 @@ management.metrics.graphql.autotime.enabled=true You can contribute `RuntimeWiringCustomizer` beans to the context in order to configure the runtime wiring of your GraphQL application. +### WebSocket support + +This project also supports WebSocket as a transport for GraphQL requests - you can use it to build [`Subscription` queries](http://spec.graphql.org/draft/#sec-Subscription). +This use case is powered by Reactor `Flux`, check out the `samples/webflux-websocket` sample application for more. + +To enable this support, you need to configure the `spring.graphql.websocket.path` property in your application +and have the required dependencies on classpath. In the case of a Servlet application, adding the `spring-boot-starter-websocket` should be enough. + +WebSocket support comes with dedicated properties: + +````properties +# Path of the GraphQL WebSocket subscription endpoint. +spring.graphql.websocket.path=/graphql/websocket +# Time within which the initial {@code CONNECTION_INIT} type message must be received. +spring.graphql.websocket.connection-init-timeout=60s +```` + ### Extension points You can contribute [`WebInterceptor` beans](https://github.com/spring-projects-experimental/spring-graphql/blob/master/spring-graphql-web/src/main/java/org/springframework/graphql/WebInterceptor.java) diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQLProperties.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQLProperties.java index b473f85..bc992aa 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQLProperties.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQLProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2020 the original author or authors. + * Copyright 2020-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,47 +32,54 @@ public class GraphQLProperties { */ private String path = "/graphql"; - /** - * Path of the GraphQL WebSocket subscription endpoint. - */ - private String webSocketPath = path + "/websocket"; - - /** - * For the GraphQL over WebSocket endpoint, this is time within which the - * initial {@code CONNECTION_INIT} type message must be received. - */ - private Duration connectionInitTimeoutDuration = Duration.ofSeconds(60); - + private WebSocket websocket = new WebSocket(); public String getPath() { - return path; + return this.path; } public void setPath(String path) { this.path = path; } - public String getWebSocketPath() { - return webSocketPath; - } - - public void setWebSocketPath(String webSocketPath) { - this.webSocketPath = webSocketPath; - } - public String getSchemaLocation() { - return schemaLocation; + return this.schemaLocation; } public void setSchemaLocation(String schemaLocation) { this.schemaLocation = schemaLocation; } - public Duration getConnectionInitTimeoutDuration() { - return this.connectionInitTimeoutDuration; + public WebSocket getWebsocket() { + return this.websocket; } - public void setConnectionInitTimeoutDuration(Duration connectionInitTimeoutDuration) { - this.connectionInitTimeoutDuration = connectionInitTimeoutDuration; + static class WebSocket { + + /** + * Path of the GraphQL WebSocket subscription endpoint. + */ + private String path; + + /** + * Time within which the initial {@code CONNECTION_INIT} type message must be received. + */ + private Duration connectionInitTimeout = Duration.ofSeconds(60); + + public String getPath() { + return this.path; + } + + public void setPath(String path) { + this.path = path; + } + + public Duration getConnectionInitTimeout() { + return this.connectionInitTimeout; + } + + public void setConnectionInitTimeout(Duration connectionInitTimeout) { + this.connectionInitTimeout = connectionInitTimeout; + } } } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java index 06bfade..23829ee 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -57,18 +58,6 @@ public GraphQLHttpHandler graphQLHandler(GraphQL.Builder graphQLBuilder, ObjectP return new GraphQLHttpHandler(graphQLBuilder.build(), interceptors.orderedStream().collect(Collectors.toList())); } - @Bean - @ConditionalOnMissingBean - public GraphQLWebSocketHandler graphQLWebSocketHandler( - GraphQL.Builder graphQLBuilder, GraphQLProperties properties, ServerCodecConfigurer configurer, - ObjectProvider interceptors) { - - return new GraphQLWebSocketHandler( - graphQLBuilder.build(), interceptors.orderedStream().collect(Collectors.toList()), - configurer, properties.getConnectionInitTimeoutDuration() - ); - } - @Bean public RouterFunction graphQLEndpoint( GraphQLHttpHandler handler, GraphQLProperties properties, ResourceLoader resourceLoader) { @@ -82,15 +71,33 @@ public RouterFunction graphQLEndpoint( .build(); } - @Bean - public HandlerMapping graphQLWebSocketEndpoint( - GraphQLWebSocketHandler handler, GraphQLProperties properties) { - - String path = properties.getWebSocketPath(); - SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); - mapping.setUrlMap(Collections.singletonMap(path, handler)); - mapping.setOrder(-1); // Ahead of annotated controllers - return mapping; + @ConditionalOnProperty(prefix = "spring.graphql.websocket", name = "path") + static class WebSocketConfiguration { + + @Bean + @ConditionalOnMissingBean + public GraphQLWebSocketHandler graphQLWebSocketHandler( + GraphQL.Builder graphQLBuilder, GraphQLProperties properties, ServerCodecConfigurer configurer, + ObjectProvider interceptors) { + + return new GraphQLWebSocketHandler( + graphQLBuilder.build(), interceptors.orderedStream().collect(Collectors.toList()), + configurer, properties.getWebsocket().getConnectionInitTimeout() + ); + } + + @Bean + public HandlerMapping graphQLWebSocketEndpoint( + GraphQLWebSocketHandler handler, GraphQLProperties properties) { + + String path = properties.getWebsocket().getPath(); + SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); + mapping.setUrlMap(Collections.singletonMap(path, handler)); + mapping.setOrder(-1); // Ahead of annotated controllers + return mapping; + } + } + } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java index fe10528..7d95734 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.context.annotation.Bean; @@ -80,6 +81,7 @@ public RouterFunction graphQLQueryEndpoint( @ConditionalOnClass({ServerContainer.class, WebSocketHandler.class}) + @ConditionalOnProperty(prefix = "spring.graphql.websocket", name = "path") static class WebSocketConfiguration { @Bean @@ -95,7 +97,7 @@ public GraphQLWebSocketHandler graphQLWebSocketHandler( return new GraphQLWebSocketHandler( graphQLBuilder.build(), interceptors.orderedStream().collect(Collectors.toList()), - converter, properties.getConnectionInitTimeoutDuration() + converter, properties.getWebsocket().getConnectionInitTimeout() ); } @@ -104,7 +106,7 @@ public HandlerMapping graphQLWebSocketEndpoint(GraphQLWebSocketHandler handler, WebSocketHttpRequestHandler httpRequestHandler = new WebSocketHttpRequestHandler(handler, new DefaultHandshakeHandler()); - String path = properties.getWebSocketPath(); + String path = properties.getWebsocket().getPath(); SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setUrlMap(Collections.singletonMap(path, httpRequestHandler)); mapping.setOrder(-1); // Ahead of annotated controllers diff --git a/samples/webflux-websocket/src/main/resources/application.properties b/samples/webflux-websocket/src/main/resources/application.properties index 256081a..2f8df9c 100644 --- a/samples/webflux-websocket/src/main/resources/application.properties +++ b/samples/webflux-websocket/src/main/resources/application.properties @@ -1,3 +1,4 @@ +spring.graphql.websocket.path=/graphql/websocket management.endpoints.web.exposure.include=health,metrics,info logging.level.org.springframework.web=debug logging.level.org.springframework.http=debug