SSE handlers support keep-alive

Closes gh-1048
This commit is contained in:
rstoyanchev
2025-03-17 18:13:54 +00:00
parent 44ca9b0da0
commit 0d672c1383
4 changed files with 31 additions and 24 deletions

View File

@@ -104,8 +104,8 @@ public class GraphQlWebSocketHandler implements WebSocketHandler {
* @param codecConfigurer codec configurer for JSON encoding and decoding
* @param connectionInitTimeout how long to wait after the establishment of
* the WebSocket for the {@code "connection_ini"} message from the client.
* @param keepAliveDuration how frequently to send ping messages; if not
* set then ping messages are not sent.
* @param keepAliveDuration how frequently to send ping messages when no
* other messages are sent
* @since 1.3
*/
public GraphQlWebSocketHandler(

View File

@@ -26,6 +26,7 @@ import graphql.ErrorType;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import org.apache.commons.logging.Log;
import org.reactivestreams.Publisher;
import reactor.core.publisher.BaseSubscriber;
import reactor.core.publisher.Flux;
@@ -125,8 +126,8 @@ public class GraphQlSseHandler extends AbstractGraphQlHttpHandler {
});
return ((this.timeout != null) ?
ServerResponse.sse(SseSubscriber.connect(resultFlux, this.keepAliveDuration), this.timeout) :
ServerResponse.sse(SseSubscriber.connect(resultFlux, this.keepAliveDuration)));
ServerResponse.sse(SseSubscriber.connect(resultFlux, this.logger, this.keepAliveDuration), this.timeout) :
ServerResponse.sse(SseSubscriber.connect(resultFlux, this.logger, this.keepAliveDuration)));
}
@@ -137,9 +138,12 @@ public class GraphQlSseHandler extends AbstractGraphQlHttpHandler {
private final ServerResponse.SseBuilder sseBuilder;
private SseSubscriber(ServerResponse.SseBuilder sseBuilder) {
private final Log logger;
private SseSubscriber(ServerResponse.SseBuilder sseBuilder, Log logger) {
this.sseBuilder = sseBuilder;
this.sseBuilder.onTimeout(() -> cancelWithError(new AsyncRequestTimeoutException()));
this.logger = logger;
}
@Override
@@ -180,20 +184,24 @@ public class GraphQlSseHandler extends AbstractGraphQlHttpHandler {
@Override
protected void hookOnError(Throwable ex) {
sendNext(exceptionToResultMap(ex));
Map<String, Object> errorMap;
if (ex instanceof SubscriptionPublisherException spe) {
errorMap = spe.toMap();
}
else {
if (this.logger.isErrorEnabled()) {
this.logger.error("Unresolved " + ex.getClass().getSimpleName(), ex);
}
errorMap = GraphqlErrorBuilder.newError()
.message("Subscription error")
.errorType(org.springframework.graphql.execution.ErrorType.INTERNAL_ERROR)
.build()
.toSpecification();
}
sendNext(errorMap);
sendComplete();
}
private static Map<String, Object> exceptionToResultMap(Throwable ex) {
return ((ex instanceof SubscriptionPublisherException spe) ?
spe.toMap() :
GraphqlErrorBuilder.newError()
.message("Subscription error")
.errorType(org.springframework.graphql.execution.ErrorType.INTERNAL_ERROR)
.build()
.toSpecification());
}
private void sendComplete() {
try {
this.sseBuilder.event("complete").data("");
@@ -210,10 +218,10 @@ public class GraphQlSseHandler extends AbstractGraphQlHttpHandler {
}
static Consumer<ServerResponse.SseBuilder> connect(
Flux<Map<String, Object>> resultFlux, @Nullable Duration keepAliveDuration) {
Flux<Map<String, Object>> resultFlux, Log logger, @Nullable Duration keepAliveDuration) {
return (sseBuilder) -> {
SseSubscriber subscriber = new SseSubscriber(sseBuilder);
SseSubscriber subscriber = new SseSubscriber(sseBuilder, logger);
if (keepAliveDuration != null) {
KeepAliveHandler handler = new KeepAliveHandler(keepAliveDuration);
handler.compose(resultFlux).subscribe(subscriber);

View File

@@ -129,8 +129,8 @@ public class GraphQlWebSocketHandler extends TextWebSocketHandler implements Sub
* @param converter for JSON encoding and decoding
* @param connectionInitTimeout how long to wait after the establishment of
* the WebSocket for the {@code "connection_ini"} message from the client.
* @param keepAliveDuration how frequently to send ping messages; if not
* set then ping messages are not sent.
* @param keepAliveDuration how frequently to send ping messages when no
* other messages are sent
* @since 1.3
*/
public GraphQlWebSocketHandler(

View File

@@ -41,7 +41,6 @@ import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
@@ -181,9 +180,9 @@ class GraphQlSseHandlerTests {
MockServerRequest serverRequest = MockServerRequest.builder()
.exchange(exchange)
.uri(((ServerWebExchange) exchange).getRequest().getURI())
.method(((ServerWebExchange) exchange).getRequest().getMethod())
.headers(((ServerWebExchange) exchange).getRequest().getHeaders())
.uri(exchange.getRequest().getURI())
.method(exchange.getRequest().getMethod())
.headers(exchange.getRequest().getHeaders())
.body(Mono.just(body));
handler.handleRequest(serverRequest)