Skip to content

Commit

Permalink
Enable WebSocket endpoint with config property
Browse files Browse the repository at this point in the history
Prior to this commit, the WebSocket endpoint (MVC or WebFlux) would be
enabled by default. In the case of MVC, the presence of
`spring-websocket` and the required dependencies was an additional hint
that could enable/disable this support depending on the application.

This commit changes the websocket path configuration so that it doesn't
hold a default value anymore. Configuring this property will trigger the
WebSocket support.

Right now developers cannot configure it to be the same as the
`spring.graphql.path`, but this will be solved in spring-io#30.

This commit also revisits the configuration properties namespace for
clarity, moving the websocket bits under their own section.

Closes spring-iogh-31
  • Loading branch information
bclozel committed Feb 2, 2021
1 parent 5696b8c commit 36121f3
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 49 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<WebInterceptor> interceptors) {

return new GraphQLWebSocketHandler(
graphQLBuilder.build(), interceptors.orderedStream().collect(Collectors.toList()),
configurer, properties.getConnectionInitTimeoutDuration()
);
}

@Bean
public RouterFunction<ServerResponse> graphQLEndpoint(
GraphQLHttpHandler handler, GraphQLProperties properties, ResourceLoader resourceLoader) {
Expand All @@ -82,15 +71,33 @@ public RouterFunction<ServerResponse> 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<WebInterceptor> 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;
}

}


}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -80,6 +81,7 @@ public RouterFunction<ServerResponse> graphQLQueryEndpoint(


@ConditionalOnClass({ServerContainer.class, WebSocketHandler.class})
@ConditionalOnProperty(prefix = "spring.graphql.websocket", name = "path")
static class WebSocketConfiguration {

@Bean
Expand All @@ -95,7 +97,7 @@ public GraphQLWebSocketHandler graphQLWebSocketHandler(

return new GraphQLWebSocketHandler(
graphQLBuilder.build(), interceptors.orderedStream().collect(Collectors.toList()),
converter, properties.getConnectionInitTimeoutDuration()
converter, properties.getWebsocket().getConnectionInitTimeout()
);
}

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit 36121f3

Please sign in to comment.