From 1c71567c946e2e0f643785b4d20138f2b7c4ced2 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 4 Apr 2022 16:45:11 +0200 Subject: [PATCH] Support "application/graphql+json" media type in GraphQL HTTP mapping As seen in spring-projects/spring-graphql#108, the GraphQL HTTP spec now requires the "application/graphql+json" media type and accepts "application/json" for backwards compatibility. This commit updates the `RouterFunction` definition for the GraphQL HTTP endpoints so that both types are accepted. Closes gh-30407 --- .../graphql/reactive/GraphQlWebFluxAutoConfiguration.java | 6 +++--- .../graphql/servlet/GraphQlWebMvcAutoConfiguration.java | 6 +++--- .../reactive/GraphQlWebFluxAutoConfigurationTests.java | 7 ++++--- .../servlet/GraphQlWebMvcAutoConfigurationTests.java | 8 ++++---- ...ttpGraphQlTesterContextCustomizerIntegrationTests.java | 2 +- ...hQlTesterContextCustomizerWithCustomBasePathTests.java | 2 +- ...TesterContextCustomizerWithCustomContextPathTests.java | 2 +- 7 files changed, 17 insertions(+), 16 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 5987fe4a9161..a9261117d0f8 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 @@ -81,8 +81,8 @@ @EnableConfigurationProperties(GraphQlCorsProperties.class) public class GraphQlWebFluxAutoConfiguration { - private static final RequestPredicate ACCEPT_JSON_CONTENT = accept(MediaType.APPLICATION_JSON) - .and(contentType(MediaType.APPLICATION_JSON)); + private static final RequestPredicate SUPPORTS_MEDIATYPES = accept(MediaType.APPLICATION_GRAPHQL, + MediaType.APPLICATION_JSON).and(contentType(MediaType.APPLICATION_GRAPHQL, MediaType.APPLICATION_JSON)); private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class); @@ -107,7 +107,7 @@ public RouterFunction graphQlEndpoint(GraphQlHttpHandler httpHan logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path)); RouterFunctions.Builder builder = RouterFunctions.route(); builder = builder.GET(path, this::onlyAllowPost); - builder = builder.POST(path, ACCEPT_JSON_CONTENT, httpHandler::handleRequest); + builder = builder.POST(path, SUPPORTS_MEDIATYPES, httpHandler::handleRequest); if (properties.getGraphiql().isEnabled()) { GraphiQlHandler graphQlHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); builder = builder.GET(properties.getGraphiql().getPath(), graphQlHandler::handleRequest); 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 e964a4ec87e5..0c0ab9d1a948 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 @@ -87,7 +87,7 @@ public class GraphQlWebMvcAutoConfiguration { private static final Log logger = LogFactory.getLog(GraphQlWebMvcAutoConfiguration.class); - private static MediaType[] SUPPORTED_MEDIA_TYPES = new MediaType[] { MediaType.valueOf("application/graphql+json"), + private static MediaType[] SUPPORTED_MEDIA_TYPES = new MediaType[] { MediaType.APPLICATION_GRAPHQL, MediaType.APPLICATION_JSON }; @Bean @@ -113,8 +113,8 @@ public RouterFunction graphQlRouterFunction(GraphQlHttpHandler h logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path)); RouterFunctions.Builder builder = RouterFunctions.route(); builder = builder.GET(path, this::onlyAllowPost); - builder = builder.POST(path, RequestPredicates.contentType(MediaType.APPLICATION_JSON) - .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), httpHandler::handleRequest); + builder = builder.POST(path, RequestPredicates.contentType(SUPPORTED_MEDIA_TYPES) + .and(RequestPredicates.accept(SUPPORTED_MEDIA_TYPES)), httpHandler::handleRequest); if (properties.getGraphiql().isEnabled()) { GraphiQlHandler graphiQLHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); builder = builder.GET(properties.getGraphiql().getPath(), graphiQLHandler::handleRequest); 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 cd06337b0d35..38e1d0307bb6 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 @@ -75,7 +75,8 @@ void simpleQueryShouldWork() { testWithWebClient((client) -> { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; client.post().uri("/graphql").bodyValue("{ \"query\": \"" + query + "\"}").exchange().expectStatus().isOk() - .expectBody().jsonPath("data.bookById.name").isEqualTo("GraphQL for beginners"); + .expectHeader().contentType("application/graphql+json").expectBody().jsonPath("data.bookById.name") + .isEqualTo("GraphQL for beginners"); }); } @@ -150,8 +151,8 @@ private void testWithWebClient(Consumer consumer) { this.contextRunner.run((context) -> { WebTestClient client = WebTestClient.bindToApplicationContext(context).configureClient() .defaultHeaders((headers) -> { - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.APPLICATION_GRAPHQL); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_GRAPHQL)); }).baseUrl(BASE_URL).build(); consumer.accept(client); }); 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 99d69aba3110..268bbee1d105 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 @@ -81,7 +81,7 @@ void simpleQueryShouldWork() { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; MvcResult result = mockMvc.perform(post("/graphql").content("{\"query\": \"" + query + "\"}")).andReturn(); mockMvc.perform(asyncDispatch(result)).andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_GRAPHQL)) .andExpect(jsonPath("data.bookById.name").value("GraphQL for beginners")); }); } @@ -155,9 +155,9 @@ void shouldConfigureWebSocketBeans() { private void testWith(MockMvcConsumer mockMvcConsumer) { this.contextRunner.run((context) -> { - MediaType mediaType = MediaType.APPLICATION_JSON; - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context) - .defaultRequest(post("/graphql").contentType(mediaType).accept(mediaType)).build(); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).defaultRequest( + post("/graphql").contentType(MediaType.APPLICATION_GRAPHQL).accept(MediaType.APPLICATION_GRAPHQL)) + .build(); mockMvcConsumer.accept(mockMvc); }); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerIntegrationTests.java index b66eec19be32..d319c8ff2700 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerIntegrationTests.java @@ -79,7 +79,7 @@ static class TestHandler implements HttpHandler { @Override public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { response.setStatusCode(HttpStatus.OK); - response.getHeaders().setContentType(MediaType.APPLICATION_JSON); + response.getHeaders().setContentType(MediaType.APPLICATION_GRAPHQL); return response.writeWith(Mono.just(factory.wrap("{\"data\":{}}".getBytes()))); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomBasePathTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomBasePathTests.java index f7167d07e104..27b3fce49c14 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomBasePathTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomBasePathTests.java @@ -79,7 +79,7 @@ static class TestHandler implements HttpHandler { @Override public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { response.setStatusCode(HttpStatus.OK); - response.getHeaders().setContentType(MediaType.APPLICATION_JSON); + response.getHeaders().setContentType(MediaType.APPLICATION_GRAPHQL); return response.writeWith(Mono.just(factory.wrap("{\"data\":{}}".getBytes()))); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomContextPathTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomContextPathTests.java index fec60ac44248..786481023e98 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomContextPathTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomContextPathTests.java @@ -70,7 +70,7 @@ DispatcherServlet dispatcherServlet() { @RestController static class TestController { - @PostMapping(path = "/graphql", produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(path = "/graphql", produces = MediaType.APPLICATION_GRAPHQL_VALUE) String graphql() { return "{}"; }