graphql-spqr-spring-boot-starter
graphql-spqr-spring-boot-starter copied to clipboard
SpqrWebSocketAutoConfiguration is too specific
Firstly, great work so far on this package.
The SpqrWebSocketAutoConfiguration defines a number of beans used to set up WebSocket handlers. I need to decorate the default WebSocketHandler to add extra functionality, without reimplementing it.
My attempt at a configuration class to override the SpqrWebSocketAutoConfiguration:
@Configuration
public class WebSocketSecurityConfig {
/**
* security context for webSocket connections
* @return
*/
@Bean
public WebSocketContextFactory webSocketContextFactory() {
return params -> new WebSocketSecurityGlobalContext(params.getNativeRequest());
}
/**
* Authorized webSocketExecutor
* @param contextFactory
* @param dataLoaderRegistryFactory
* @return
*/
@Bean
public GraphQLWebSocketExecutor webSocketExecutor(
WebSocketContextFactory contextFactory,
@Autowired(required = false) DataLoaderRegistryFactory dataLoaderRegistryFactory
) {
return new AuthorizedGraphQLWebSocketExecutor(contextFactory, dataLoaderRegistryFactory);
}
/**
* FAILS: won't work as the `webSocketHandler` expects type `PerConnectionApolloHandler` when `WebSocketHandler` would work just as well here.
* Decorated PerConnectionApolloHandler which extracts and validates BearerToken during the lifecycle of the connection
* @param apolloWebSocketHandler
* @return
*/
@Bean
public WebSocketHandler webSocketHandler(PerConnectionApolloHandler apolloWebSocketHandler) {
return new BearerTokenWebSocketHandler(apolloWebSocketHandler);
}
// copied out of SpqrWebSocketAutoConfiguration due to poor abstraction of WebSocketHandler Bean
@Bean
public PerConnectionApolloHandler apolloWebSocketHandler(GraphQLWebSocketExecutor executor, GraphQL graphQL, SpqrProperties config) {
boolean keepAliveEnabled = config.getWs().getKeepAlive().isEnabled();
int keepAliveInterval = config.getWs().getKeepAlive().getIntervalMillis();
return new PerConnectionApolloHandler(graphQL, executor,
keepAliveEnabled ? defaultTaskScheduler() : null, keepAliveInterval);
}
// copied out of SpqrWebSocketAutoConfiguration due to poor abstraction of WebSocketHandler Bean
private TaskScheduler defaultTaskScheduler() {
ThreadPoolTaskScheduler threadPoolScheduler = new ThreadPoolTaskScheduler();
threadPoolScheduler.setThreadNamePrefix("GraphQLWSKeepAlive-");
threadPoolScheduler.setPoolSize(Runtime.getRuntime().availableProcessors());
threadPoolScheduler.setRemoveOnCancelPolicy(true);
threadPoolScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
threadPoolScheduler.initialize();
return threadPoolScheduler;
}
}
I think customization of this could be simplified by providing better access to the webSocketHandlerRegistry to configure the handlers and interceptors, and separating the GraphQL and SpqrProperties logic out from the ConditionalOnMissingBean definitions so it can be reused if you only want to extend the existing configuration.
If I find time I'll make a PR with some of the above for your consideration.
As a workaround, I have to reimplement and disable SpqrWebSocketAutoConfiguration
like so
/**
* Reimplemented the io.leangen.graphql.spqr.spring.autoconfigure.SpqrWebSocketAutoConfiguration
* make sure to set the property "graphql.spqr.ws.enabled" to false
*/
@Configuration
@EnableWebSocket
@Slf4j
public class WebSocketSecurityConfigurer implements WebSocketConfigurer {
private final GraphQL graphQL;
private final SpqrProperties config;
private final DataLoaderRegistryFactory dataLoaderRegistryFactory;
@Autowired
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public WebSocketSecurityConfigurer(GraphQL graphQL, SpqrProperties config,
Optional<DataLoaderRegistryFactory> dataLoaderRegistryFactory) {
this.graphQL = graphQL;
this.config = config;
this.dataLoaderRegistryFactory = dataLoaderRegistryFactory.orElse(null);
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
String webSocketEndpoint = config.getWs().getEndpoint();
String graphQLEndpoint = config.getHttp().getEndpoint();
String endpointUrl = webSocketEndpoint == null ? graphQLEndpoint : webSocketEndpoint;
boolean keepAliveEnabled = config.getWs().getKeepAlive().isEnabled();
int keepAliveInterval = config.getWs().getKeepAlive().getIntervalMillis();
SecuredWebSocketContextFactory connectionFactory = new SecuredWebSocketContextFactory();
AuthorizedGraphQLWebSocketExecutor executor = new AuthorizedGraphQLWebSocketExecutor(connectionFactory, dataLoaderRegistryFactory);
WebSocketHandler apolloHandler = new PerConnectionApolloHandler(graphQL, executor, keepAliveEnabled ? defaultTaskScheduler() : null, keepAliveInterval);
WebSocketHandler securityHandler = new BearerTokenWebSocketHandler(apolloHandler);
webSocketHandlerRegistry
.addHandler(securityHandler, endpointUrl)
.setAllowedOrigins(config.getWs().getAllowedOrigins());
}
private TaskScheduler defaultTaskScheduler() {
ThreadPoolTaskScheduler threadPoolScheduler = new ThreadPoolTaskScheduler();
threadPoolScheduler.setThreadNamePrefix("GraphQLWSKeepAlive-");
threadPoolScheduler.setPoolSize(Runtime.getRuntime().availableProcessors());
threadPoolScheduler.setRemoveOnCancelPolicy(true);
threadPoolScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
threadPoolScheduler.initialize();
return threadPoolScheduler;
}
}