Skip to content

Commit 74688ea

Browse files
committed
Update contribution
Closes gh-608
1 parent 3f5fc1a commit 74688ea

File tree

5 files changed

+93
-95
lines changed

5 files changed

+93
-95
lines changed

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

+13-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -17,6 +17,7 @@
1717
package org.springframework.graphql.client;
1818

1919
import java.net.URI;
20+
import java.time.Duration;
2021
import java.util.Arrays;
2122
import java.util.List;
2223
import java.util.function.Consumer;
@@ -26,6 +27,7 @@
2627
import org.springframework.http.HttpHeaders;
2728
import org.springframework.http.codec.ClientCodecConfigurer;
2829
import org.springframework.http.codec.CodecConfigurer;
30+
import org.springframework.lang.Nullable;
2931
import org.springframework.util.Assert;
3032
import org.springframework.web.reactive.socket.client.WebSocketClient;
3133
import org.springframework.web.util.DefaultUriBuilderFactory;
@@ -49,7 +51,8 @@ final class DefaultWebSocketGraphQlClientBuilder
4951

5052
private final CodecConfigurer codecConfigurer;
5153

52-
private long keepalive;
54+
@Nullable
55+
private Duration keepAlive;
5356

5457
/**
5558
* Constructor to start via {@link WebSocketGraphQlClient#builder(String, WebSocketClient)}.
@@ -58,28 +61,13 @@ final class DefaultWebSocketGraphQlClientBuilder
5861
this(toURI(url), client);
5962
}
6063

61-
/**
62-
* Constructor to start via {@link WebSocketGraphQlClient#builder(String, WebSocketClient, long)}.
63-
*/
64-
DefaultWebSocketGraphQlClientBuilder(String url, WebSocketClient client, long keepalive) {
65-
this(toURI(url), client, keepalive);
66-
}
67-
6864
/**
6965
* Constructor to start via {@link WebSocketGraphQlClient#builder(URI, WebSocketClient)}.
7066
*/
7167
DefaultWebSocketGraphQlClientBuilder(URI url, WebSocketClient client) {
72-
this(url, client, 0);
73-
}
74-
75-
/**
76-
* Constructor to start via {@link WebSocketGraphQlClient#builder(URI, WebSocketClient, long)}.
77-
*/
78-
DefaultWebSocketGraphQlClientBuilder(URI url, WebSocketClient client, long keepalive) {
7968
this.url = url;
8069
this.webSocketClient = client;
8170
this.codecConfigurer = ClientCodecConfigurer.create();
82-
this.keepalive = keepalive;
8371
}
8472

8573
/**
@@ -91,7 +79,7 @@ final class DefaultWebSocketGraphQlClientBuilder
9179
this.headers.putAll(transport.getHeaders());
9280
this.webSocketClient = transport.getWebSocketClient();
9381
this.codecConfigurer = transport.getCodecConfigurer();
94-
this.keepalive = transport.getKeepAlive();
82+
this.keepAlive = transport.getKeepAlive();
9583
}
9684

9785

@@ -128,6 +116,12 @@ public DefaultWebSocketGraphQlClientBuilder codecConfigurer(Consumer<CodecConfig
128116
return this;
129117
}
130118

119+
@Override
120+
public WebSocketGraphQlClient.Builder<DefaultWebSocketGraphQlClientBuilder> keepAlive(Duration keepalive) {
121+
this.keepAlive = keepalive;
122+
return this;
123+
}
124+
131125
@Override
132126
public WebSocketGraphQlClient build() {
133127

@@ -136,18 +130,12 @@ public WebSocketGraphQlClient build() {
136130
CodecDelegate.findJsonDecoder(this.codecConfigurer));
137131

138132
WebSocketGraphQlTransport transport = new WebSocketGraphQlTransport(
139-
this.url, this.headers, this.webSocketClient, this.codecConfigurer, getInterceptor(), this.keepalive);
133+
this.url, this.headers, this.webSocketClient, this.codecConfigurer, getInterceptor(), this.keepAlive);
140134

141135
GraphQlClient graphQlClient = super.buildGraphQlClient(transport);
142136
return new DefaultWebSocketGraphQlClient(graphQlClient, transport, getBuilderInitializer());
143137
}
144138

145-
@Override
146-
public WebSocketGraphQlClient.Builder<DefaultWebSocketGraphQlClientBuilder> keepalive(long keepalive) {
147-
this.keepalive = keepalive;
148-
return this;
149-
}
150-
151139
private WebSocketGraphQlClientInterceptor getInterceptor() {
152140

153141
List<WebSocketGraphQlClientInterceptor> interceptors = getInterceptors().stream()

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

+10-33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -17,6 +17,7 @@
1717
package org.springframework.graphql.client;
1818

1919
import java.net.URI;
20+
import java.time.Duration;
2021

2122
import reactor.core.publisher.Mono;
2223

@@ -64,16 +65,6 @@ static WebSocketGraphQlClient create(URI url, WebSocketClient webSocketClient) {
6465
return builder(url, webSocketClient).build();
6566
}
6667

67-
/**
68-
* Create a {@link WebSocketGraphQlClient}.
69-
* @param url the GraphQL endpoint URL
70-
* @param webSocketClient the underlying transport client to use
71-
* @param keepalive the delay in seconds between sending ping messages, or 0 to disable
72-
*/
73-
static WebSocketGraphQlClient create(URI url, WebSocketClient webSocketClient, long keepalive) {
74-
return builder(url, webSocketClient).keepalive(keepalive).build();
75-
}
76-
7768
/**
7869
* Return a builder for a {@link WebSocketGraphQlClient}.
7970
* @param url the GraphQL endpoint URL
@@ -83,16 +74,6 @@ static Builder<?> builder(String url, WebSocketClient webSocketClient) {
8374
return new DefaultWebSocketGraphQlClientBuilder(url, webSocketClient);
8475
}
8576

86-
/**
87-
* Return a builder for a {@link WebSocketGraphQlClient}.
88-
* @param url the GraphQL endpoint URL
89-
* @param webSocketClient the underlying transport client to use
90-
* @param keepalive the delay in seconds between sending ping messages, or 0 to disable
91-
*/
92-
static Builder<?> builder(String url, WebSocketClient webSocketClient, long keepalive) {
93-
return new DefaultWebSocketGraphQlClientBuilder(url, webSocketClient, keepalive);
94-
}
95-
9677
/**
9778
* Return a builder for a {@link WebSocketGraphQlClient}.
9879
* @param url the GraphQL endpoint URL
@@ -102,31 +83,27 @@ static Builder<?> builder(URI url, WebSocketClient webSocketClient) {
10283
return new DefaultWebSocketGraphQlClientBuilder(url, webSocketClient);
10384
}
10485

105-
/**
106-
* Return a builder for a {@link WebSocketGraphQlClient}.
107-
* @param url the GraphQL endpoint URL
108-
* @param webSocketClient the underlying transport client to use
109-
* @param keepalive the delay in seconds between sending ping messages, or 0 to disable
110-
*/
111-
static Builder<?> builder(URI url, WebSocketClient webSocketClient, long keepalive) {
112-
return new DefaultWebSocketGraphQlClientBuilder(url, webSocketClient, keepalive);
113-
}
114-
11586

11687
/**
11788
* Builder for a GraphQL over WebSocket client.
11889
* @param <B> the builder type
11990
*/
12091
interface Builder<B extends Builder<B>> extends WebGraphQlClient.Builder<B> {
12192

93+
/**
94+
* Configure how frequently to send ping messages.
95+
* <p>By default, this is not set, and ping messages are not sent.
96+
* @param keepAlive the value to use
97+
* @since 1.3
98+
*/
99+
Builder<B> keepAlive(Duration keepAlive);
100+
122101
/**
123102
* Build the {@code WebSocketGraphQlClient}.
124103
*/
125104
@Override
126105
WebSocketGraphQlClient build();
127106

128-
Builder<B> keepalive(long keepalive);
129-
130107
}
131108

132109
}

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

+51-26
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -68,12 +68,13 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
6868

6969
private final Mono<GraphQlSession> graphQlSessionMono;
7070

71-
private final long keepalive;
71+
@Nullable
72+
private final Duration keepAlive;
7273

7374

7475
WebSocketGraphQlTransport(
7576
URI url, @Nullable HttpHeaders headers, WebSocketClient client, CodecConfigurer codecConfigurer,
76-
WebSocketGraphQlClientInterceptor interceptor, long keepalive) {
77+
WebSocketGraphQlClientInterceptor interceptor, @Nullable Duration keepAlive) {
7778

7879
Assert.notNull(url, "URI is required");
7980
Assert.notNull(client, "WebSocketClient is required");
@@ -83,9 +84,9 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
8384
this.url = url;
8485
this.headers.putAll((headers != null) ? headers : HttpHeaders.EMPTY);
8586
this.webSocketClient = client;
86-
this.keepalive = keepalive;
87+
this.keepAlive = keepAlive;
8788

88-
this.graphQlSessionHandler = new GraphQlSessionHandler(codecConfigurer, interceptor, keepalive);
89+
this.graphQlSessionHandler = new GraphQlSessionHandler(codecConfigurer, interceptor, keepAlive);
8990

9091
this.graphQlSessionMono = initGraphQlSession(this.url, this.headers, client, this.graphQlSessionHandler)
9192
.cacheInvalidateWhen(GraphQlSession::notifyWhenClosed);
@@ -166,8 +167,9 @@ public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
166167
return this.graphQlSessionMono.flatMapMany((session) -> session.executeSubscription(request));
167168
}
168169

169-
public long getKeepAlive() {
170-
return keepalive;
170+
@Nullable
171+
Duration getKeepAlive() {
172+
return this.keepAlive;
171173
}
172174

173175

@@ -191,15 +193,18 @@ private static class GraphQlSessionHandler implements WebSocketHandler {
191193

192194
private final AtomicBoolean stopped = new AtomicBoolean();
193195

194-
private final long keepalive;
196+
@Nullable
197+
private final Duration keepAlive;
198+
195199

200+
GraphQlSessionHandler(
201+
CodecConfigurer codecConfigurer, WebSocketGraphQlClientInterceptor interceptor,
202+
@Nullable Duration keepAlive) {
196203

197-
GraphQlSessionHandler(CodecConfigurer codecConfigurer, WebSocketGraphQlClientInterceptor interceptor,
198-
long keepalive) {
199204
this.codecDelegate = new CodecDelegate(codecConfigurer);
200205
this.interceptor = interceptor;
201206
this.graphQlSessionSink = Sinks.unsafe().one();
202-
this.keepalive = keepalive;
207+
this.keepAlive = keepAlive;
203208
}
204209

205210

@@ -257,7 +262,7 @@ public Mono<Void> handle(WebSocketSession session) {
257262
session.send(connectionInitMono.concatWith(graphQlSession.getRequestFlux())
258263
.map((message) -> this.codecDelegate.encode(session, message)));
259264

260-
Flux<Void> receiveCompletion = session.receive()
265+
Mono<Void> receiveCompletion = session.receive()
261266
.flatMap((webSocketMessage) -> {
262267
if (sessionNotInitialized()) {
263268
try {
@@ -303,20 +308,22 @@ public Mono<Void> handle(WebSocketSession session) {
303308
}
304309
}
305310
return Mono.empty();
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-
);
311+
})
312+
.mergeWith((this.keepAlive != null) ?
313+
Flux.interval(this.keepAlive, this.keepAlive)
314+
.filter((aLong) -> graphQlSession.checkSentOrReceivedMessagesAndClear())
315+
.doOnNext((aLong) -> graphQlSession.sendPing())
316+
.then() :
317+
Flux.empty())
318+
.then();
319+
320+
if (this.keepAlive != null) {
321+
Flux.interval(this.keepAlive, this.keepAlive)
322+
.filter((aLong) -> graphQlSession.checkSentOrReceivedMessagesAndClear())
323+
.doOnNext((aLong) -> graphQlSession.sendPing())
324+
.subscribe();
317325
}
318326

319-
320327
return Mono.zip(sendCompletion, receiveCompletion.then()).then();
321328
}
322329

@@ -413,6 +420,8 @@ private static class GraphQlSession {
413420

414421
private final Map<String, RequestState> requestStateMap = new ConcurrentHashMap<>();
415422

423+
private boolean hasReceivedMessages;
424+
416425

417426
GraphQlSession(WebSocketSession webSocketSession) {
418427
this.connection = DisposableConnection.from(webSocketSession);
@@ -483,11 +492,16 @@ void sendPong(@Nullable Map<String, Object> payload) {
483492
this.requestSink.sendRequest(message);
484493
}
485494

486-
public void sendPing(@Nullable Map<String, Object> payload) {
487-
GraphQlWebSocketMessage message = GraphQlWebSocketMessage.ping(payload);
495+
void sendPing() {
496+
GraphQlWebSocketMessage message = GraphQlWebSocketMessage.ping(null);
488497
this.requestSink.sendRequest(message);
489498
}
490499

500+
boolean checkSentOrReceivedMessagesAndClear() {
501+
boolean received = this.hasReceivedMessages;
502+
this.hasReceivedMessages = false;
503+
return (this.requestSink.checkSentMessagesAndClear() || received);
504+
}
491505

492506
// Inbound messages
493507

@@ -504,6 +518,8 @@ void handleNext(GraphQlWebSocketMessage message) {
504518
return;
505519
}
506520

521+
this.hasReceivedMessages = true;
522+
507523
if (requestState instanceof SingleResponseRequestState) {
508524
this.requestStateMap.remove(id);
509525
}
@@ -631,6 +647,8 @@ private static final class RequestSink {
631647
@Nullable
632648
private FluxSink<GraphQlWebSocketMessage> requestSink;
633649

650+
private boolean hasSentMessages;
651+
634652
private final Flux<GraphQlWebSocketMessage> requestFlux = Flux.create((sink) -> {
635653
Assert.state(this.requestSink == null, "Expected single subscriber only for outbound messages");
636654
this.requestSink = sink;
@@ -642,9 +660,16 @@ Flux<GraphQlWebSocketMessage> getRequestFlux() {
642660

643661
void sendRequest(GraphQlWebSocketMessage message) {
644662
Assert.state(this.requestSink != null, "Unexpected request before Flux is subscribed to");
663+
this.hasSentMessages = true;
645664
this.requestSink.next(message);
646665
}
647666

667+
boolean checkSentMessagesAndClear() {
668+
boolean result = this.hasSentMessages;
669+
this.hasSentMessages = false;
670+
return result;
671+
}
672+
648673
}
649674

650675

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.

0 commit comments

Comments
 (0)