17
17
package org .springframework .graphql .client ;
18
18
19
19
import java .net .URI ;
20
+ import java .time .Duration ;
20
21
import java .util .Collections ;
21
22
import java .util .List ;
22
23
import java .util .Map ;
@@ -67,10 +68,12 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
67
68
68
69
private final Mono <GraphQlSession > graphQlSessionMono ;
69
70
71
+ private final long keepalive ;
72
+
70
73
71
74
WebSocketGraphQlTransport (
72
75
URI url , @ Nullable HttpHeaders headers , WebSocketClient client , CodecConfigurer codecConfigurer ,
73
- WebSocketGraphQlClientInterceptor interceptor ) {
76
+ WebSocketGraphQlClientInterceptor interceptor , long keepalive ) {
74
77
75
78
Assert .notNull (url , "URI is required" );
76
79
Assert .notNull (client , "WebSocketClient is required" );
@@ -80,8 +83,9 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
80
83
this .url = url ;
81
84
this .headers .putAll ((headers != null ) ? headers : HttpHeaders .EMPTY );
82
85
this .webSocketClient = client ;
86
+ this .keepalive = keepalive ;
83
87
84
- this .graphQlSessionHandler = new GraphQlSessionHandler (codecConfigurer , interceptor );
88
+ this .graphQlSessionHandler = new GraphQlSessionHandler (codecConfigurer , interceptor , keepalive );
85
89
86
90
this .graphQlSessionMono = initGraphQlSession (this .url , this .headers , client , this .graphQlSessionHandler )
87
91
.cacheInvalidateWhen (GraphQlSession ::notifyWhenClosed );
@@ -162,6 +166,10 @@ public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
162
166
return this .graphQlSessionMono .flatMapMany ((session ) -> session .executeSubscription (request ));
163
167
}
164
168
169
+ public long getKeepAlive () {
170
+ return keepalive ;
171
+ }
172
+
165
173
166
174
/**
167
175
* Client {@code WebSocketHandler} for GraphQL that deals with WebSocket
@@ -183,11 +191,15 @@ private static class GraphQlSessionHandler implements WebSocketHandler {
183
191
184
192
private final AtomicBoolean stopped = new AtomicBoolean ();
185
193
194
+ private final long keepalive ;
186
195
187
- GraphQlSessionHandler (CodecConfigurer codecConfigurer , WebSocketGraphQlClientInterceptor interceptor ) {
196
+
197
+ GraphQlSessionHandler (CodecConfigurer codecConfigurer , WebSocketGraphQlClientInterceptor interceptor ,
198
+ long keepalive ) {
188
199
this .codecDelegate = new CodecDelegate (codecConfigurer );
189
200
this .interceptor = interceptor ;
190
201
this .graphQlSessionSink = Sinks .unsafe ().one ();
202
+ this .keepalive = keepalive ;
191
203
}
192
204
193
205
@@ -245,7 +257,7 @@ public Mono<Void> handle(WebSocketSession session) {
245
257
session .send (connectionInitMono .concatWith (graphQlSession .getRequestFlux ())
246
258
.map ((message ) -> this .codecDelegate .encode (session , message )));
247
259
248
- Mono <Void > receiveCompletion = session .receive ()
260
+ Flux <Void > receiveCompletion = session .receive ()
249
261
.flatMap ((webSocketMessage ) -> {
250
262
if (sessionNotInitialized ()) {
251
263
try {
@@ -276,6 +288,7 @@ public Mono<Void> handle(WebSocketSession session) {
276
288
switch (message .resolvedType ()) {
277
289
case NEXT -> graphQlSession .handleNext (message );
278
290
case PING -> graphQlSession .sendPong (null );
291
+ case PONG -> { }
279
292
case ERROR -> graphQlSession .handleError (message );
280
293
case COMPLETE -> graphQlSession .handleComplete (message );
281
294
default -> throw new IllegalStateException (
@@ -290,10 +303,21 @@ public Mono<Void> handle(WebSocketSession session) {
290
303
}
291
304
}
292
305
return Mono .empty ();
293
- })
294
- .then ();
306
+ });
307
+
308
+ if (keepalive > 0 ) {
309
+ Duration keepAliveDuration = Duration .ofSeconds (keepalive );
310
+ receiveCompletion = receiveCompletion
311
+ .mergeWith (Flux .interval (keepAliveDuration , keepAliveDuration )
312
+ .flatMap (i -> {
313
+ graphQlSession .sendPing (null );
314
+ return Mono .empty ();
315
+ })
316
+ );
317
+ }
318
+
295
319
296
- return Mono .zip (sendCompletion , receiveCompletion ).then ();
320
+ return Mono .zip (sendCompletion , receiveCompletion . then () ).then ();
297
321
}
298
322
299
323
private boolean sessionNotInitialized () {
@@ -459,6 +483,11 @@ void sendPong(@Nullable Map<String, Object> payload) {
459
483
this .requestSink .sendRequest (message );
460
484
}
461
485
486
+ public void sendPing (@ Nullable Map <String , Object > payload ) {
487
+ GraphQlWebSocketMessage message = GraphQlWebSocketMessage .ping (payload );
488
+ this .requestSink .sendRequest (message );
489
+ }
490
+
462
491
463
492
// Inbound messages
464
493
0 commit comments