Skip to content

Commit d2002bb

Browse files
committed
Handle early closure of WebSocket connection
Ensure the error is routed to the graphQlSessionSink when the WebSocket connection is closed before the GraphQLSession is initialized. Closes gh-1098
1 parent 8f303e6 commit d2002bb

File tree

2 files changed

+33
-6
lines changed

2 files changed

+33
-6
lines changed

spring-graphql/src/main/java/org/springframework/graphql/client/WebSocketGraphQlTransport.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -339,19 +339,29 @@ private void registerCloseStatusHandling(GraphQlSession graphQlSession, WebSocke
339339
if (logger.isDebugEnabled()) {
340340
logger.debug(closeStatusMessage);
341341
}
342-
graphQlSession.terminateRequests(closeStatusMessage, closeStatus);
342+
terminateGraphQlSession(graphQlSession, closeStatus, closeStatusMessage, null);
343343
})
344344
.doOnError((cause) -> {
345345
CloseStatus closeStatus = CloseStatus.NO_STATUS_CODE;
346346
String closeStatusMessage = initCloseStatusMessage(closeStatus, cause, graphQlSession);
347347
if (logger.isErrorEnabled()) {
348348
logger.error(closeStatusMessage);
349349
}
350-
graphQlSession.terminateRequests(closeStatusMessage, closeStatus);
350+
terminateGraphQlSession(graphQlSession, closeStatus, closeStatusMessage, cause);
351351
})
352352
.subscribe();
353353
}
354354

355+
private void terminateGraphQlSession(
356+
GraphQlSession session, CloseStatus closeStatus, String closeStatusMessage, @Nullable Throwable cause) {
357+
358+
if (sessionNotInitialized()) {
359+
this.graphQlSessionSink.tryEmitError(new IllegalStateException(closeStatusMessage, cause));
360+
this.graphQlSessionSink = Sinks.unsafe().one();
361+
}
362+
session.terminateRequests(closeStatusMessage, closeStatus);
363+
}
364+
355365
private String initCloseStatusMessage(CloseStatus status, @Nullable Throwable ex, GraphQlSession session) {
356366
String reason = session + " disconnected";
357367
if (isStopped()) {

spring-graphql/src/test/java/org/springframework/graphql/client/WebSocketGraphQlTransportTests.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -317,17 +317,34 @@ void errorOnConnect() {
317317
}
318318

319319
@Test
320-
void errorBeforeConnectionAck() {
320+
void errorBeforeConnectionAckWithStart() {
321321

322322
// Errors before GraphQL session initialized should be routed, no hanging on start
323323

324324
MockGraphQlWebSocketServer handler = new MockGraphQlWebSocketServer();
325325
handler.connectionInitHandler(initPayload -> Mono.error(new IllegalStateException("boo")));
326326

327327
TestWebSocketClient client = new TestWebSocketClient(handler);
328+
String expectedMessage = "disconnected with CloseStatus[code=1002, reason=null]";
328329

329330
StepVerifier.create(createTransport(client).start())
330-
.expectErrorMessage("boo")
331+
.expectErrorSatisfies(ex -> assertThat(ex).hasMessageEndingWith(expectedMessage))
332+
.verify(TIMEOUT);
333+
}
334+
335+
@Test // gh-1098
336+
void errorBeforeConnectionAckWithRequest() {
337+
338+
// Errors before GraphQL session initialized should be routed, no hanging on start
339+
340+
MockGraphQlWebSocketServer handler = new MockGraphQlWebSocketServer();
341+
handler.connectionInitHandler(initPayload -> Mono.error(new IllegalStateException("boo")));
342+
343+
TestWebSocketClient client = new TestWebSocketClient(session -> session.close(CloseStatus.POLICY_VIOLATION));
344+
String expectedMessage = "disconnected with CloseStatus[code=1008, reason=null]";
345+
346+
StepVerifier.create(createTransport(client).execute(new DefaultGraphQlRequest("{Query1}")))
347+
.expectErrorSatisfies(ex -> assertThat(ex).hasMessageEndingWith(expectedMessage))
331348
.verify(TIMEOUT);
332349
}
333350

0 commit comments

Comments
 (0)