Skip to content

Commit 736c33a

Browse files
committed
Add GraphQlClient support for WebSocket
See gh-10
1 parent 371c601 commit 736c33a

File tree

5 files changed

+1416
-0
lines changed

5 files changed

+1416
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql.client;
18+
19+
import java.util.List;
20+
21+
import graphql.GraphQLError;
22+
23+
/**
24+
*
25+
* @author Rossen Stoyanchev
26+
* @since 1.0.0
27+
*/
28+
@SuppressWarnings("serial")
29+
public class SubscriptionErrorException extends RuntimeException {
30+
31+
private final List<GraphQLError> errors;
32+
33+
34+
public SubscriptionErrorException(List<GraphQLError> errors) {
35+
super("GraphQL subscription error: " + errors);
36+
this.errors = errors;
37+
}
38+
39+
40+
public List<GraphQLError> getErrors() {
41+
return this.errors;
42+
}
43+
44+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.graphql.client;
17+
18+
import org.springframework.core.ResolvableType;
19+
import org.springframework.core.codec.Decoder;
20+
import org.springframework.core.codec.Encoder;
21+
import org.springframework.core.io.buffer.DataBuffer;
22+
import org.springframework.core.io.buffer.DataBufferUtils;
23+
import org.springframework.graphql.web.webflux.GraphQlWebSocketMessage;
24+
import org.springframework.http.MediaType;
25+
import org.springframework.http.codec.ClientCodecConfigurer;
26+
import org.springframework.http.codec.CodecConfigurer;
27+
import org.springframework.http.codec.DecoderHttpMessageReader;
28+
import org.springframework.http.codec.EncoderHttpMessageWriter;
29+
import org.springframework.util.Assert;
30+
import org.springframework.util.MimeTypeUtils;
31+
import org.springframework.web.reactive.socket.WebSocketMessage;
32+
import org.springframework.web.reactive.socket.WebSocketSession;
33+
34+
/**
35+
* Delegate that can be embedded in a class to help with encoding and decoding
36+
* GraphQL over WebSocket messages.
37+
*
38+
* @author Rossen Stoyanchev
39+
* @since 1.0.0
40+
*/
41+
final class WebSocketCodecDelegate {
42+
43+
private static final ResolvableType MESSAGE_TYPE = ResolvableType.forClass(GraphQlWebSocketMessage.class);
44+
45+
46+
private final Decoder<?> decoder;
47+
48+
private final Encoder<?> encoder;
49+
50+
51+
WebSocketCodecDelegate() {
52+
this(ClientCodecConfigurer.create());
53+
}
54+
55+
WebSocketCodecDelegate(CodecConfigurer codecConfigurer) {
56+
Assert.notNull(codecConfigurer, "CodecConfigurer is required");
57+
this.decoder = initDecoder(codecConfigurer);
58+
this.encoder = initEncoder(codecConfigurer);
59+
}
60+
61+
private static Decoder<?> initDecoder(CodecConfigurer configurer) {
62+
return configurer.getReaders().stream()
63+
.filter((reader) -> reader.canRead(MESSAGE_TYPE, MediaType.APPLICATION_JSON))
64+
.map((reader) -> ((DecoderHttpMessageReader<?>) reader).getDecoder())
65+
.findFirst()
66+
.orElseThrow(() -> new IllegalArgumentException("No JSON Decoder"));
67+
}
68+
69+
private static Encoder<?> initEncoder(CodecConfigurer configurer) {
70+
return configurer.getWriters().stream()
71+
.filter((writer) -> writer.canWrite(MESSAGE_TYPE, MediaType.APPLICATION_JSON))
72+
.map((writer) -> ((EncoderHttpMessageWriter<?>) writer).getEncoder())
73+
.findFirst()
74+
.orElseThrow(() -> new IllegalArgumentException("No JSON Encoder"));
75+
}
76+
77+
78+
@SuppressWarnings("unchecked")
79+
public <T> WebSocketMessage encode(WebSocketSession session, GraphQlWebSocketMessage message) {
80+
81+
DataBuffer buffer = ((Encoder<T>) this.encoder).encodeValue(
82+
(T) message, session.bufferFactory(), MESSAGE_TYPE, MimeTypeUtils.APPLICATION_JSON, null);
83+
84+
return new WebSocketMessage(WebSocketMessage.Type.TEXT, buffer);
85+
}
86+
87+
@SuppressWarnings("ConstantConditions")
88+
public GraphQlWebSocketMessage decode(WebSocketMessage webSocketMessage) {
89+
DataBuffer buffer = DataBufferUtils.retain(webSocketMessage.getPayload());
90+
return (GraphQlWebSocketMessage) this.decoder.decode(buffer, MESSAGE_TYPE, null, null);
91+
}
92+
93+
94+
}

0 commit comments

Comments
 (0)