Skip to content

Commit 03c11d0

Browse files
committed
Merge branch '1.2.x'
2 parents 5977b2d + 7dd9cf5 commit 03c11d0

File tree

4 files changed

+186
-99
lines changed

4 files changed

+186
-99
lines changed

spring-graphql/src/main/java/org/springframework/graphql/server/webflux/AbstractGraphQlHttpHandler.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@
2222
import org.springframework.core.io.buffer.DataBuffer;
2323
import org.springframework.graphql.server.WebGraphQlHandler;
2424
import org.springframework.graphql.server.support.SerializableGraphQlRequest;
25+
import org.springframework.http.HttpHeaders;
2526
import org.springframework.http.MediaType;
2627
import org.springframework.lang.Nullable;
2728
import org.springframework.util.Assert;
2829
import org.springframework.web.reactive.function.server.ServerRequest;
30+
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
2931

3032
/**
3133
* Abstract class for GraphQL Handler implementations using the HTTP transport.
3234
*
3335
* @author Brian Clozel
36+
* @author Rossen Stoyanchev
3437
*/
3538
class AbstractGraphQlHttpHandler {
3639

@@ -51,9 +54,26 @@ protected Mono<SerializableGraphQlRequest> readRequest(ServerRequest serverReque
5154
return this.codecDelegate.decode(serverRequest.bodyToFlux(DataBuffer.class), contentType);
5255
}
5356
else {
54-
return serverRequest.bodyToMono(SerializableGraphQlRequest.class);
57+
return serverRequest.bodyToMono(SerializableGraphQlRequest.class)
58+
.onErrorResume(
59+
UnsupportedMediaTypeStatusException.class,
60+
(ex) -> applyApplicationGraphQlFallback(ex, serverRequest));
5561
}
5662
}
5763

64+
private static Mono<SerializableGraphQlRequest> applyApplicationGraphQlFallback(
65+
UnsupportedMediaTypeStatusException ex, ServerRequest request) {
66+
67+
// Spec requires application/json but some clients still use application/graphql
68+
return "application/graphql".equals(request.headers().firstHeader(HttpHeaders.CONTENT_TYPE)) ?
69+
ServerRequest.from(request)
70+
.headers((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
71+
.body(request.bodyToFlux(DataBuffer.class))
72+
.build()
73+
.bodyToMono(SerializableGraphQlRequest.class)
74+
.log() :
75+
Mono.error(ex);
76+
}
77+
5878

5979
}

spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/AbstractGraphQlHttpHandler.java

+28-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.graphql.server.WebGraphQlHandler;
2828
import org.springframework.graphql.server.support.SerializableGraphQlRequest;
2929
import org.springframework.http.HttpCookie;
30+
import org.springframework.http.HttpHeaders;
3031
import org.springframework.http.MediaType;
3132
import org.springframework.http.converter.HttpMessageConverter;
3233
import org.springframework.http.server.ServletServerHttpRequest;
@@ -81,15 +82,40 @@ protected GraphQlRequest readBody(ServerRequest request) throws ServletException
8182
return (GraphQlRequest) this.messageConverter.read(SerializableGraphQlRequest.class,
8283
new ServletServerHttpRequest(request.servletRequest()));
8384
}
84-
throw new HttpMediaTypeNotSupportedException(contentType, this.messageConverter.getSupportedMediaTypes(), request.method());
85+
throw new HttpMediaTypeNotSupportedException(
86+
contentType, this.messageConverter.getSupportedMediaTypes(), request.method());
8587
}
8688
else {
87-
return request.body(SerializableGraphQlRequest.class);
89+
try {
90+
return request.body(SerializableGraphQlRequest.class);
91+
}
92+
catch (HttpMediaTypeNotSupportedException ex) {
93+
return applyApplicationGraphQlFallback(request, ex);
94+
}
8895
}
8996
}
9097
catch (IOException ex) {
9198
throw new ServerWebInputException("I/O error while reading request body", null, ex);
9299
}
93100
}
94101

102+
private static SerializableGraphQlRequest applyApplicationGraphQlFallback(
103+
ServerRequest request, HttpMediaTypeNotSupportedException ex) throws HttpMediaTypeNotSupportedException {
104+
105+
// Spec requires application/json but some clients still use application/graphql
106+
if ("application/graphql".equals(request.headers().firstHeader(HttpHeaders.CONTENT_TYPE))) {
107+
try {
108+
request = ServerRequest.from(request)
109+
.headers((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
110+
.body(request.body(byte[].class))
111+
.build();
112+
return request.body(SerializableGraphQlRequest.class);
113+
}
114+
catch (Throwable ex2) {
115+
// ignore
116+
}
117+
}
118+
throw ex;
119+
}
120+
95121
}

spring-graphql/src/test/java/org/springframework/graphql/server/webflux/GraphQlHttpHandlerTests.java

+73-50
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,29 @@
2626
import com.jayway.jsonpath.JsonPath;
2727
import org.junit.jupiter.api.Test;
2828
import reactor.core.publisher.Flux;
29-
import reactor.core.publisher.Mono;
29+
import reactor.test.StepVerifier;
3030

3131
import org.springframework.core.codec.DataBufferEncoder;
32+
import org.springframework.core.io.buffer.DefaultDataBuffer;
3233
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
33-
import org.springframework.graphql.GraphQlRequest;
3434
import org.springframework.graphql.GraphQlSetup;
3535
import org.springframework.graphql.server.WebGraphQlHandler;
3636
import org.springframework.graphql.server.support.SerializableGraphQlRequest;
3737
import org.springframework.http.MediaType;
3838
import org.springframework.http.codec.CodecConfigurer;
39+
import org.springframework.http.codec.DecoderHttpMessageReader;
3940
import org.springframework.http.codec.EncoderHttpMessageWriter;
41+
import org.springframework.http.codec.HttpMessageReader;
4042
import org.springframework.http.codec.HttpMessageWriter;
4143
import org.springframework.http.codec.ServerCodecConfigurer;
4244
import org.springframework.http.codec.json.Jackson2JsonDecoder;
4345
import org.springframework.http.codec.json.Jackson2JsonEncoder;
4446
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
4547
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
46-
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
4748
import org.springframework.mock.web.server.MockServerWebExchange;
49+
import org.springframework.web.reactive.function.server.ServerRequest;
4850
import org.springframework.web.reactive.function.server.ServerResponse;
4951
import org.springframework.web.reactive.result.view.ViewResolver;
50-
import org.springframework.web.server.ServerWebExchange;
5152

5253
import static org.assertj.core.api.Assertions.assertThat;
5354

@@ -57,72 +58,101 @@
5758
*/
5859
public class GraphQlHttpHandlerTests {
5960

60-
private final GraphQlHttpHandler greetingHandler = GraphQlSetup.schemaContent("type Query { greeting: String }")
61-
.queryFetcher("greeting", (env) -> "Hello").toHttpHandlerWebFlux();
61+
private static final List<HttpMessageReader<?>> MESSAGE_READERS =
62+
List.of(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
63+
64+
private final GraphQlHttpHandler greetingHandler =
65+
GraphQlSetup.schemaContent("type Query { greeting: String }")
66+
.queryFetcher("greeting", (env) -> "Hello")
67+
.toHttpHandlerWebFlux();
6268

6369

6470
@Test
65-
void shouldProduceApplicationJsonByDefault() {
71+
void shouldProduceApplicationJsonByDefault() throws Exception {
72+
String document = "{greeting}";
6673
MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/")
67-
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.ALL).build();
74+
.contentType(MediaType.APPLICATION_JSON)
75+
.accept(MediaType.ALL)
76+
.body(initRequestBody(document));
6877

78+
MockServerHttpResponse response = handleRequest(httpRequest, this.greetingHandler);
79+
80+
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
81+
StepVerifier.create(response.getBodyAsString())
82+
.expectNext("{\"data\":{\"greeting\":\"Hello\"}}")
83+
.verifyComplete();
84+
}
85+
86+
@Test
87+
void shouldSupportApplicationGraphQl() throws Exception {
6988
String document = "{greeting}";
70-
MockServerHttpResponse httpResponse = handleRequest(
71-
httpRequest, this.greetingHandler, initRequest(document));
89+
MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/")
90+
.contentType(MediaType.parseMediaType("application/graphql"))
91+
.accept(MediaType.ALL)
92+
.body(initRequestBody(document));
7293

73-
assertThat(httpResponse.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
94+
MockServerHttpResponse response = handleRequest(httpRequest, this.greetingHandler);
95+
96+
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
97+
StepVerifier.create(response.getBodyAsString())
98+
.expectNext("{\"data\":{\"greeting\":\"Hello\"}}")
99+
.verifyComplete();
74100
}
75101

76102
@Test
77-
void shouldProduceApplicationGraphQl() {
103+
void shouldProduceApplicationGraphQl() throws Exception {
78104
MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/")
79-
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_GRAPHQL_RESPONSE).build();
105+
.contentType(MediaType.APPLICATION_JSON)
106+
.accept(MediaType.APPLICATION_GRAPHQL_RESPONSE)
107+
.body(initRequestBody("{greeting}"));
80108

81-
MockServerHttpResponse httpResponse = handleRequest(
82-
httpRequest, this.greetingHandler, initRequest("{greeting}"));
109+
MockServerHttpResponse httpResponse = handleRequest(httpRequest, this.greetingHandler);
83110

84111
assertThat(httpResponse.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_GRAPHQL_RESPONSE);
85112
}
86113

87114
@Test
88-
void shouldProduceApplicationJson() {
115+
void shouldProduceApplicationJson() throws Exception {
89116
MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/")
90-
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).build();
117+
.contentType(MediaType.APPLICATION_JSON)
118+
.accept(MediaType.APPLICATION_JSON)
119+
.body(initRequestBody("{greeting}"));
91120

92-
MockServerHttpResponse httpResponse = handleRequest(
93-
httpRequest, this.greetingHandler, initRequest("{greeting}"));
121+
MockServerHttpResponse httpResponse = handleRequest(httpRequest, this.greetingHandler);
94122

95123
assertThat(httpResponse.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
96124
}
97125

98126
@Test
99-
void locale() {
127+
void locale() throws Exception {
100128
GraphQlHttpHandler handler = GraphQlSetup.schemaContent("type Query { greeting: String }")
101129
.queryFetcher("greeting", (env) -> "Hello in " + env.getLocale())
102130
.toHttpHandlerWebFlux();
103131

104132
MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/")
105-
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_GRAPHQL_RESPONSE)
106-
.acceptLanguageAsLocales(Locale.FRENCH).build();
133+
.contentType(MediaType.APPLICATION_JSON)
134+
.accept(MediaType.APPLICATION_GRAPHQL_RESPONSE)
135+
.acceptLanguageAsLocales(Locale.FRENCH)
136+
.body(initRequestBody("{greeting}"));
107137

108-
MockServerHttpResponse httpResponse = handleRequest(
109-
httpRequest, handler, initRequest("{greeting}"));
138+
MockServerHttpResponse httpResponse = handleRequest(httpRequest, handler);
110139

111140
assertThat(httpResponse.getBodyAsString().block())
112141
.isEqualTo("{\"data\":{\"greeting\":\"Hello in fr\"}}");
113142
}
114143

115144
@Test
116-
void shouldSetExecutionId() {
145+
void shouldSetExecutionId() throws Exception {
117146
GraphQlHttpHandler handler = GraphQlSetup.schemaContent("type Query { showId: String }")
118147
.queryFetcher("showId", (env) -> env.getExecutionId().toString())
119148
.toHttpHandlerWebFlux();
120149

121150
MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/")
122-
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_GRAPHQL_RESPONSE).build();
151+
.contentType(MediaType.APPLICATION_JSON)
152+
.accept(MediaType.APPLICATION_GRAPHQL_RESPONSE)
153+
.body(initRequestBody("{showId}"));
123154

124-
MockServerHttpResponse httpResponse = handleRequest(
125-
httpRequest, handler, initRequest("{showId}"));
155+
MockServerHttpResponse httpResponse = handleRequest(httpRequest, handler);
126156

127157
DocumentContext document = JsonPath.parse(httpResponse.getBodyAsString().block());
128158
String id = document.read("data.showId", String.class);
@@ -134,24 +164,25 @@ void shouldUseCustomCodec() {
134164
WebGraphQlHandler webGraphQlHandler = GraphQlSetup.schemaContent("type Query { showId: String }")
135165
.queryFetcher("showId", (env) -> env.getExecutionId().toString())
136166
.toWebGraphQlHandler();
167+
137168
ObjectMapper mapper = new ObjectMapper();
138169
CodecConfigurer configurer = ServerCodecConfigurer.create();
139170
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper));
140171
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper));
141-
GraphQlHttpHandler httpHandler = new GraphQlHttpHandler(webGraphQlHandler, configurer);
172+
173+
byte[] bytes = "{\"query\": \"{showId}\"}".getBytes(StandardCharsets.UTF_8);
174+
Flux<DefaultDataBuffer> body = Flux.just(DefaultDataBufferFactory.sharedInstance.wrap(bytes));
142175

143176
MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/")
144-
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_GRAPHQL_RESPONSE).build();
177+
.contentType(MediaType.APPLICATION_JSON)
178+
.accept(MediaType.APPLICATION_GRAPHQL_RESPONSE)
179+
.body(body);
145180

146181
MockServerWebExchange exchange = MockServerWebExchange.from(httpRequest);
147-
MockServerRequest serverRequest = MockServerRequest.builder()
148-
.exchange(exchange)
149-
.uri(((ServerWebExchange) exchange).getRequest().getURI())
150-
.method(((ServerWebExchange) exchange).getRequest().getMethod())
151-
.headers(((ServerWebExchange) exchange).getRequest().getHeaders())
152-
.body(Flux.just(DefaultDataBufferFactory.sharedInstance.wrap("{\"query\":\"{showId}\"}".getBytes(StandardCharsets.UTF_8))));
153-
154-
httpHandler.handleRequest(serverRequest)
182+
ServerRequest request = ServerRequest.create(exchange, configurer.getReaders());
183+
184+
new GraphQlHttpHandler(webGraphQlHandler, configurer)
185+
.handleRequest(request)
155186
.flatMap(response -> response.writeTo(exchange, new EmptyContext()))
156187
.block();
157188

@@ -160,23 +191,15 @@ void shouldUseCustomCodec() {
160191
assertThat(id).isEqualTo(httpRequest.getId());
161192
}
162193

163-
private static SerializableGraphQlRequest initRequest(String document) {
194+
private static String initRequestBody(String document) throws Exception {
164195
SerializableGraphQlRequest request = new SerializableGraphQlRequest();
165196
request.setQuery(document);
166-
return request;
197+
return new ObjectMapper().writeValueAsString(request);
167198
}
168199

169-
private MockServerHttpResponse handleRequest(
170-
MockServerHttpRequest httpRequest, GraphQlHttpHandler handler, GraphQlRequest body) {
171-
200+
private MockServerHttpResponse handleRequest(MockServerHttpRequest httpRequest, GraphQlHttpHandler handler) {
172201
MockServerWebExchange exchange = MockServerWebExchange.from(httpRequest);
173-
174-
MockServerRequest serverRequest = MockServerRequest.builder()
175-
.exchange(exchange)
176-
.uri(((ServerWebExchange) exchange).getRequest().getURI())
177-
.method(((ServerWebExchange) exchange).getRequest().getMethod())
178-
.headers(((ServerWebExchange) exchange).getRequest().getHeaders())
179-
.body(Mono.just(body));
202+
ServerRequest serverRequest = ServerRequest.create(exchange, MESSAGE_READERS);
180203

181204
handler.handleRequest(serverRequest)
182205
.flatMap(response -> response.writeTo(exchange, new DefaultContext()))

0 commit comments

Comments
 (0)