From 1738e0c74322fda7d7ac252de07fb080d7b15349 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 1 Aug 2024 14:25:52 +0200 Subject: [PATCH] Reply with HTTP 415 for unsupported GraphQL content-type Prior to this commit, the configured GraphQL routes would reply with an HTTP 404 status when a POST request is sent with an unsupported content type, such as "text/plain". While such requests are not supported in the first place, we should help developers and let them know that the content type sent is the problem. This commit configures new routes that reply with HTTP 415 "Unsupported Media Type" for these cases. Closes gh-41675 --- .../GraphQlWebFluxAutoConfiguration.java | 10 ++++++++++ .../GraphQlWebMvcAutoConfiguration.java | 9 +++++++++ .../GraphQlWebFluxAutoConfigurationTests.java | 18 +++++++++++++++++- .../GraphQlWebMvcAutoConfigurationTests.java | 15 ++++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java index 4649192258..05f6eaadcf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java @@ -55,6 +55,7 @@ import org.springframework.graphql.server.webflux.SchemaHandler; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.HandlerMapping; @@ -112,6 +113,7 @@ public class GraphQlWebFluxAutoConfiguration { RouterFunctions.Builder builder = RouterFunctions.route(); builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest); builder.route(GraphQlRequestPredicates.graphQlSse(path), sseHandler::handleRequest); + builder.POST(path, this::unsupportedMediaType); builder.GET(path, this::onlyAllowPost); if (properties.getGraphiql().isEnabled()) { GraphiQlHandler graphQlHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); @@ -124,6 +126,14 @@ public class GraphQlWebFluxAutoConfiguration { return builder.build(); } + private Mono unsupportedMediaType(ServerRequest request) { + return ServerResponse.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).headers(this::acceptJson).build(); + } + + private void acceptJson(HttpHeaders headers) { + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + } + private Mono onlyAllowPost(ServerRequest request) { return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).headers(this::onlyAllowPost).build(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java index 0c44464042..c36823761e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java @@ -117,6 +117,7 @@ public class GraphQlWebMvcAutoConfiguration { RouterFunctions.Builder builder = RouterFunctions.route(); builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest); builder.route(GraphQlRequestPredicates.graphQlSse(path), sseHandler::handleRequest); + builder.POST(path, this::unsupportedMediaType); builder.GET(path, this::onlyAllowPost); if (properties.getGraphiql().isEnabled()) { GraphiQlHandler graphiQLHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); @@ -129,6 +130,14 @@ public class GraphQlWebMvcAutoConfiguration { return builder.build(); } + private ServerResponse unsupportedMediaType(ServerRequest request) { + return ServerResponse.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).headers(this::acceptJson).build(); + } + + private void acceptJson(HttpHeaders headers) { + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + } + private ServerResponse onlyAllowPost(ServerRequest request) { return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).headers(this::onlyAllowPost).build(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java index bf4dc6695d..51978cc79f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java @@ -119,7 +119,23 @@ class GraphQlWebFluxAutoConfigurationTests { } @Test - void httpGetQueryShouldBeSupported() { + void unsupportedContentTypeShouldBeRejected() { + testWithWebClient((client) -> { + String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; + client.post() + .uri("/graphql") + .contentType(MediaType.TEXT_PLAIN) + .bodyValue("{ \"query\": \"" + query + "\"}") + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + .expectHeader() + .valueEquals("Accept", "application/json"); + }); + } + + @Test + void httpGetQueryShouldBeRejected() { testWithWebClient((client) -> { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; client.get() diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java index 260f122c14..9f6a455ab0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java @@ -111,7 +111,20 @@ class GraphQlWebMvcAutoConfigurationTests { } @Test - void httpGetQueryShouldBeSupported() { + void unsupportedContentTypeShouldBeRejected() { + withMockMvc((mvc) -> { + String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; + assertThat(mvc.post() + .uri("/graphql") + .content("{\"query\": \"" + query + "\"}") + .contentType(MediaType.TEXT_PLAIN)).hasStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + .headers() + .hasValue("Accept", "application/json"); + }); + } + + @Test + void httpGetQueryShouldBeRejected() { withMockMvc((mvc) -> { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; assertThat(mvc.get().uri("/graphql?query={query}", "{\"query\": \"" + query + "\"}"))