Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SpqrWebSocketAutoConfiguration is too specific #79

Open
iamlothian opened this issue May 19, 2020 · 1 comment
Open

SpqrWebSocketAutoConfiguration is too specific #79

iamlothian opened this issue May 19, 2020 · 1 comment

Comments

@iamlothian
Copy link

iamlothian commented May 19, 2020

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.

@iamlothian
Copy link
Author

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;
    }
}

kaqqao added a commit that referenced this issue Aug 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant