Skip to content

Commit

Permalink
Support application/graphql with charset information
Browse files Browse the repository at this point in the history
Prior to this commit, gh-948 added a fallback support for the
"application/graphql" content-type sent by clients. This media type is
not widely used and advised against by the spec group.

This fallback checked for an exact match of the content type, not taking
into account potential media type parameters such as charset.

This commit ensure that a `MediaType#include` comparison is used to
trigger the fallback.

Fixes gh-1038
  • Loading branch information
bclozel committed Jul 26, 2024
1 parent d064889 commit 5ffe512
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
Expand Down Expand Up @@ -103,16 +104,19 @@ public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {

private static Mono<SerializableGraphQlRequest> applyApplicationGraphQlFallback(
UnsupportedMediaTypeStatusException ex, ServerRequest request) {

// Spec requires application/json but some clients still use application/graphql
return "application/graphql".equals(request.headers().firstHeader(HttpHeaders.CONTENT_TYPE)) ?
ServerRequest.from(request)
.headers((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
.body(request.bodyToFlux(DataBuffer.class))
.build()
.bodyToMono(SerializableGraphQlRequest.class)
.log() :
Mono.error(ex);
String contentTypeHeader = request.headers().firstHeader(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasText(contentTypeHeader)) {
MediaType contentType = MediaType.parseMediaType(contentTypeHeader);
MediaType applicationGraphQl = MediaType.parseMediaType("application/graphql");
// Spec requires application/json but some clients still use application/graphql
return applicationGraphQl.includes(contentType) ? ServerRequest.from(request)
.headers((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
.body(request.bodyToFlux(DataBuffer.class))
.build()
.bodyToMono(SerializableGraphQlRequest.class)
.log() : Mono.error(ex);
}
return Mono.error(ex);
}

private static MediaType selectResponseMediaType(ServerRequest serverRequest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.springframework.util.IdGenerator;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.servlet.function.ServerRequest;
Expand Down Expand Up @@ -154,23 +155,28 @@ private static GraphQlRequest readBody(ServerRequest request) throws ServletExce

private static SerializableGraphQlRequest applyApplicationGraphQlFallback(
ServerRequest request, HttpMediaTypeNotSupportedException ex) throws HttpMediaTypeNotSupportedException {

// Spec requires application/json but some clients still use application/graphql
if ("application/graphql".equals(request.headers().firstHeader(HttpHeaders.CONTENT_TYPE))) {
try {
request = ServerRequest.from(request)
.headers((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
.body(request.body(byte[].class))
.build();
return request.body(SerializableGraphQlRequest.class);
}
catch (Throwable ex2) {
// ignore
String contentTypeHeader = request.headers().firstHeader(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasText(contentTypeHeader)) {
MediaType contentType = MediaType.parseMediaType(contentTypeHeader);
MediaType applicationGraphQl = MediaType.parseMediaType("application/graphql");
// Spec requires application/json but some clients still use application/graphql
if (applicationGraphQl.includes(contentType)) {
try {
request = ServerRequest.from(request)
.headers((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
.body(request.body(byte[].class))
.build();
return request.body(SerializableGraphQlRequest.class);
}
catch (Throwable ex2) {
// ignore
}
}
}
throw ex;
}


private static MediaType selectResponseMediaType(ServerRequest serverRequest) {
for (MediaType accepted : serverRequest.headers().accept()) {
if (SUPPORTED_MEDIA_TYPES.contains(accepted)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ void shouldSupportApplicationGraphQl() throws Exception {
.verifyComplete();
}

@Test
void shouldSupportApplicationGraphQlWithCharset() throws Exception {
String document = "{greeting}";
MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/")
.contentType(MediaType.parseMediaType("application/graphql;charset=UTF-8"))
.accept(MediaType.ALL)
.body(initRequestBody(document));

MockServerHttpResponse response = handleRequest(httpRequest, this.greetingHandler);

assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
StepVerifier.create(response.getBodyAsString())
.expectNext("{\"data\":{\"greeting\":\"Hello\"}}")
.verifyComplete();
}

@Test
void shouldProduceApplicationGraphQl() throws Exception {
MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ void shouldSupportApplicationGraphQl() throws Exception {
assertThat(response.getContentAsString()).isEqualTo("{\"data\":{\"greeting\":\"Hello\"}}");
}

@Test
void shouldSupportApplicationGraphQlWithCharset() throws Exception {
MockHttpServletRequest request = createServletRequest("{ greeting }", "*/*");
request.setContentType("application/graphql;charset=UTF-8");

MockHttpServletResponse response = handleRequest(request, this.greetingHandler);
assertThat(response.getContentAsString()).isEqualTo("{\"data\":{\"greeting\":\"Hello\"}}");
}

@Test
void shouldProduceApplicationGraphQl() throws Exception {
MockHttpServletRequest request = createServletRequest("{ greeting }", MediaType.APPLICATION_GRAPHQL_RESPONSE_VALUE);
Expand Down

0 comments on commit 5ffe512

Please sign in to comment.