From 3360e4a46ef1138bc63ea6d891036bfd2432ac1e Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Fri, 14 Feb 2025 11:48:48 +0000 Subject: [PATCH] Log unhandled exceptions in WebSocket handlers Closes gh-1122 --- .../webflux/GraphQlWebSocketHandler.java | 21 +++++++++++++++++-- .../webflux/WebSocketCodecDelegate.java | 14 ++----------- .../webmvc/GraphQlWebSocketHandler.java | 19 +++++++++++------ 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandler.java index b5e7932c..6771536c 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -28,6 +28,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import graphql.ExecutionResult; +import graphql.GraphQLError; +import graphql.GraphqlErrorBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; @@ -36,6 +38,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; +import org.springframework.graphql.execution.ErrorType; +import org.springframework.graphql.execution.SubscriptionPublisherException; import org.springframework.graphql.server.WebGraphQlHandler; import org.springframework.graphql.server.WebGraphQlResponse; import org.springframework.graphql.server.WebSocketGraphQlInterceptor; @@ -256,7 +260,20 @@ public class GraphQlWebSocketHandler implements WebSocketHandler { CloseStatus status = new CloseStatus(4409, "Subscriber for " + id + " already exists"); return GraphQlStatus.close(session, status); } - return Mono.fromCallable(() -> this.codecDelegate.encodeError(session, id, ex)); + List errors; + if (ex instanceof SubscriptionPublisherException subscriptionEx) { + errors = subscriptionEx.getErrors(); + } + else { + if (logger.isErrorEnabled()) { + logger.error("Unresolved " + ex.getClass().getSimpleName() + " for request id " + id, ex); + } + errors = Collections.singletonList(GraphqlErrorBuilder.newError() + .message("Subscription error") + .errorType(ErrorType.INTERNAL_ERROR) + .build()); + } + return Mono.fromCallable(() -> this.codecDelegate.encodeError(session, id, errors)); }); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/WebSocketCodecDelegate.java b/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/WebSocketCodecDelegate.java index b7646762..4c73ebdc 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/WebSocketCodecDelegate.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/WebSocketCodecDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,20 +16,16 @@ package org.springframework.graphql.server.webflux; -import java.util.Collections; import java.util.List; import java.util.Map; import graphql.GraphQLError; -import graphql.GraphqlErrorBuilder; import org.springframework.core.ResolvableType; import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Encoder; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.graphql.execution.ErrorType; -import org.springframework.graphql.execution.SubscriptionPublisherException; import org.springframework.graphql.server.support.GraphQlWebSocketMessage; import org.springframework.http.MediaType; import org.springframework.http.codec.CodecConfigurer; @@ -105,13 +101,7 @@ final class WebSocketCodecDelegate { return encode(session, GraphQlWebSocketMessage.next(id, responseMap)); } - WebSocketMessage encodeError(WebSocketSession session, String id, Throwable ex) { - List errors = ((ex instanceof SubscriptionPublisherException) ? - ((SubscriptionPublisherException) ex).getErrors() : - Collections.singletonList(GraphqlErrorBuilder.newError() - .message("Subscription error") - .errorType(ErrorType.INTERNAL_ERROR) - .build())); + WebSocketMessage encodeError(WebSocketSession session, String id, List errors) { return encode(session, GraphQlWebSocketMessage.error(id, errors)); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandler.java index a87706a3..efa95012 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandler.java @@ -347,12 +347,19 @@ public class GraphQlWebSocketHandler extends TextWebSocketHandler implements Sub GraphQlStatus.closeSession(session, status); return Flux.empty(); } - List errors = ((ex instanceof SubscriptionPublisherException) ? - ((SubscriptionPublisherException) ex).getErrors() : - Collections.singletonList(GraphqlErrorBuilder.newError() - .message("Subscription error") - .errorType(ErrorType.INTERNAL_ERROR) - .build())); + List errors; + if (ex instanceof SubscriptionPublisherException subscriptionEx) { + errors = subscriptionEx.getErrors(); + } + else { + if (logger.isErrorEnabled()) { + logger.error("Unresolved " + ex.getClass().getSimpleName() + " for request id " + id, ex); + } + errors = Collections.singletonList(GraphqlErrorBuilder.newError() + .message("Subscription error") + .errorType(ErrorType.INTERNAL_ERROR) + .build()); + } return Mono.just(encode(GraphQlWebSocketMessage.error(id, errors))); }); }