Skip to content

Commit 7dd9cf5

Browse files
committed
Support application/graphql for request body
Closes gh-948
1 parent bf9c584 commit 7dd9cf5

File tree

4 files changed

+71
-1
lines changed

4 files changed

+71
-1
lines changed

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

+20
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@
2525
import reactor.core.publisher.Mono;
2626

2727
import org.springframework.core.ParameterizedTypeReference;
28+
import org.springframework.core.io.buffer.DataBuffer;
2829
import org.springframework.graphql.server.WebGraphQlHandler;
2930
import org.springframework.graphql.server.WebGraphQlRequest;
3031
import org.springframework.graphql.server.support.SerializableGraphQlRequest;
32+
import org.springframework.http.HttpHeaders;
3133
import org.springframework.http.MediaType;
3234
import org.springframework.util.Assert;
3335
import org.springframework.web.reactive.function.server.ServerRequest;
3436
import org.springframework.web.reactive.function.server.ServerResponse;
37+
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
3538

3639
/**
3740
* WebFlux.fn Handler for GraphQL over HTTP requests.
@@ -73,6 +76,9 @@ public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler) {
7376
*/
7477
public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
7578
return serverRequest.bodyToMono(SerializableGraphQlRequest.class)
79+
.onErrorResume(
80+
UnsupportedMediaTypeStatusException.class,
81+
(ex) -> applyApplicationGraphQlFallback(ex, serverRequest))
7682
.flatMap((body) -> {
7783
WebGraphQlRequest graphQlRequest = new WebGraphQlRequest(
7884
serverRequest.uri(), serverRequest.headers().asHttpHeaders(),
@@ -95,6 +101,20 @@ public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
95101
});
96102
}
97103

104+
private static Mono<SerializableGraphQlRequest> applyApplicationGraphQlFallback(
105+
UnsupportedMediaTypeStatusException ex, ServerRequest request) {
106+
107+
// Spec requires application/json but some clients still use application/graphql
108+
return "application/graphql".equals(request.headers().firstHeader(HttpHeaders.CONTENT_TYPE)) ?
109+
ServerRequest.from(request)
110+
.headers((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
111+
.body(request.bodyToFlux(DataBuffer.class))
112+
.build()
113+
.bodyToMono(SerializableGraphQlRequest.class)
114+
.log() :
115+
Mono.error(ex);
116+
}
117+
98118
private static MediaType selectResponseMediaType(ServerRequest serverRequest) {
99119
for (MediaType accepted : serverRequest.headers().accept()) {
100120
if (SUPPORTED_MEDIA_TYPES.contains(accepted)) {

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

+23
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.graphql.server.WebGraphQlRequest;
3636
import org.springframework.graphql.server.support.SerializableGraphQlRequest;
3737
import org.springframework.http.HttpCookie;
38+
import org.springframework.http.HttpHeaders;
3839
import org.springframework.http.MediaType;
3940
import org.springframework.util.AlternativeJdkIdGenerator;
4041
import org.springframework.util.Assert;
@@ -146,6 +147,28 @@ private static GraphQlRequest readBody(ServerRequest request) throws ServletExce
146147
catch (IOException ex) {
147148
throw new ServerWebInputException("I/O error while reading request body", null, ex);
148149
}
150+
catch (HttpMediaTypeNotSupportedException ex) {
151+
return applyApplicationGraphQlFallback(request, ex);
152+
}
153+
}
154+
155+
private static SerializableGraphQlRequest applyApplicationGraphQlFallback(
156+
ServerRequest request, HttpMediaTypeNotSupportedException ex) throws HttpMediaTypeNotSupportedException {
157+
158+
// Spec requires application/json but some clients still use application/graphql
159+
if ("application/graphql".equals(request.headers().firstHeader(HttpHeaders.CONTENT_TYPE))) {
160+
try {
161+
request = ServerRequest.from(request)
162+
.headers((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
163+
.body(request.body(byte[].class))
164+
.build();
165+
return request.body(SerializableGraphQlRequest.class);
166+
}
167+
catch (Throwable ex2) {
168+
// ignore
169+
}
170+
}
171+
throw ex;
149172
}
150173

151174
private static MediaType selectResponseMediaType(ServerRequest serverRequest) {

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

+16
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@ void shouldProduceApplicationJsonByDefault() throws Exception {
7575
.verifyComplete();
7676
}
7777

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

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.graphql.GraphQlSetup;
3737
import org.springframework.graphql.server.support.SerializableGraphQlRequest;
3838
import org.springframework.http.MediaType;
39+
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
3940
import org.springframework.http.converter.HttpMessageConverter;
4041
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
4142
import org.springframework.mock.web.MockHttpServletRequest;
@@ -55,7 +56,7 @@
5556
public class GraphQlHttpHandlerTests {
5657

5758
private static final List<HttpMessageConverter<?>> MESSAGE_READERS =
58-
Collections.singletonList(new MappingJackson2HttpMessageConverter());
59+
List.of(new MappingJackson2HttpMessageConverter(), new ByteArrayHttpMessageConverter());
5960

6061
private final GraphQlHttpHandler greetingHandler = GraphQlSetup.schemaContent("type Query { greeting: String }")
6162
.queryFetcher("greeting", (env) -> "Hello").toHttpHandler();
@@ -66,6 +67,16 @@ void shouldProduceApplicationJsonByDefault() throws Exception {
6667
MockHttpServletRequest request = createServletRequest("{ greeting }", "*/*");
6768
MockHttpServletResponse response = handleRequest(request, this.greetingHandler);
6869
assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
70+
assertThat(response.getContentAsString()).isEqualTo("{\"data\":{\"greeting\":\"Hello\"}}");
71+
}
72+
73+
@Test
74+
void shouldSupportApplicationGraphQl() throws Exception {
75+
MockHttpServletRequest request = createServletRequest("{ greeting }", "*/*");
76+
request.setContentType("application/graphql");
77+
78+
MockHttpServletResponse response = handleRequest(request, this.greetingHandler);
79+
assertThat(response.getContentAsString()).isEqualTo("{\"data\":{\"greeting\":\"Hello\"}}");
6980
}
7081

7182
@Test

0 commit comments

Comments
 (0)