From 04faec1d3ed12ad890c1709fa8e79d6233b2fcd5 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 11 Apr 2024 18:50:13 +0200 Subject: [PATCH] Add spring.graphql.websocket.keep-alive property As of spring-projects/spring-graphql#534, Spring for GraphQL supports the configuration of keep-alive PINGs for WebSocket connections. This commit auto-configures this value in the `GraphQlWebSocketHandler` WebFlux and MVC implementations if the `spring.graphql.websocket.keep-alive` property is configured. Closes gh-40320 --- .../graphql/GraphQlProperties.java | 13 +++++++++++++ .../GraphQlWebFluxAutoConfiguration.java | 2 +- .../servlet/GraphQlWebMvcAutoConfiguration.java | 2 +- .../GraphQlWebFluxAutoConfigurationTests.java | 17 ++++++++++++++++- .../GraphQlWebMvcAutoConfigurationTests.java | 15 +++++++++++++++ 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java index 046155b1aa4..4a424925ef5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java @@ -217,6 +217,11 @@ public class GraphQlProperties { */ private Duration connectionInitTimeout = Duration.ofSeconds(60); + /** + * Maximum idle period before a server keep-alive ping is sent to client. + */ + private Duration keepAlive = null; + public String getPath() { return this.path; } @@ -233,6 +238,14 @@ public class GraphQlProperties { this.connectionInitTimeout = connectionInitTimeout; } + public Duration getKeepAlive() { + return this.keepAlive; + } + + public void setKeepAlive(Duration keepAlive) { + this.keepAlive = keepAlive; + } + } public static class Rsocket { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java index 1a87f4cc0af..1b38044a972 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java @@ -163,7 +163,7 @@ public class GraphQlWebFluxAutoConfiguration { public GraphQlWebSocketHandler graphQlWebSocketHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties, ServerCodecConfigurer configurer) { return new GraphQlWebSocketHandler(webGraphQlHandler, configurer, - properties.getWebsocket().getConnectionInitTimeout()); + properties.getWebsocket().getConnectionInitTimeout(), properties.getWebsocket().getKeepAlive()); } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java index ff68ef1203d..abedefa7a7f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java @@ -169,7 +169,7 @@ public class GraphQlWebMvcAutoConfiguration { public GraphQlWebSocketHandler graphQlWebSocketHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties, HttpMessageConverters converters) { return new GraphQlWebSocketHandler(webGraphQlHandler, getJsonConverter(converters), - properties.getWebsocket().getConnectionInitTimeout()); + properties.getWebsocket().getConnectionInitTimeout(), properties.getWebsocket().getKeepAlive()); } private GenericHttpMessageConverter getJsonConverter(HttpMessageConverters converters) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java index 0ad33de9a92..bf4dc6695da 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.graphql.reactive; +import java.time.Duration; import java.util.Collections; import java.util.Map; import java.util.function.Consumer; @@ -220,6 +221,20 @@ class GraphQlWebFluxAutoConfigurationTests { .run((context) -> assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class)); } + @Test + void shouldConfigureWebSocketProperties() { + this.contextRunner + .withPropertyValues("spring.graphql.websocket.path=/ws", + "spring.graphql.websocket.connection-init-timeout=120s", "spring.graphql.websocket.keep-alive=30s") + .run((context) -> { + assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class); + GraphQlWebSocketHandler graphQlWebSocketHandler = context.getBean(GraphQlWebSocketHandler.class); + assertThat(graphQlWebSocketHandler).extracting("initTimeoutDuration") + .isEqualTo(Duration.ofSeconds(120)); + assertThat(graphQlWebSocketHandler).extracting("keepAliveDuration").isEqualTo(Duration.ofSeconds(30)); + }); + } + @Test void routerFunctionShouldHaveOrderZero() { this.contextRunner.withUserConfiguration(CustomRouterFunctions.class).run((context) -> { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java index 98be87ddaaa..85418adef91 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.graphql.servlet; +import java.time.Duration; import java.util.Map; import graphql.schema.idl.TypeRuntimeWiring; @@ -184,6 +185,20 @@ class GraphQlWebMvcAutoConfigurationTests { }); } + @Test + void shouldConfigureWebSocketProperties() { + this.contextRunner + .withPropertyValues("spring.graphql.websocket.path=/ws", + "spring.graphql.websocket.connection-init-timeout=120s", "spring.graphql.websocket.keep-alive=30s") + .run((context) -> { + assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class); + GraphQlWebSocketHandler graphQlWebSocketHandler = context.getBean(GraphQlWebSocketHandler.class); + assertThat(graphQlWebSocketHandler).extracting("initTimeoutDuration") + .isEqualTo(Duration.ofSeconds(120)); + assertThat(graphQlWebSocketHandler).extracting("keepAliveDuration").isEqualTo(Duration.ofSeconds(30)); + }); + } + @Test void routerFunctionShouldHaveOrderZero() { this.contextRunner.withUserConfiguration(CustomRouterFunctions.class).run((context) -> {