From 9178075da50e22240d281ae4c95984ed815b7503 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 1 Jul 2021 15:48:55 +0100 Subject: [PATCH] Return 405 "Allow:POST" for HTTP GET queries See gh-70 --- .../boot/GraphQlWebFluxAutoConfiguration.java | 13 +++++++++++- .../boot/GraphQlWebMvcAutoConfiguration.java | 18 +++++++++++++--- .../GraphQlWebFluxAutoConfigurationTests.java | 21 ++++++++++++++++++- .../GraphQlWebMvcAutoConfigurationTests.java | 19 ++++++++++++++++- 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java index 341d0ab9..b1bcc5ee 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java @@ -42,6 +42,8 @@ import org.springframework.graphql.web.WebGraphQlHandler; import org.springframework.graphql.web.WebInterceptor; import org.springframework.graphql.web.webflux.GraphQlHttpHandler; import org.springframework.graphql.web.webflux.GraphQlWebSocketHandler; +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.reactive.HandlerMapping; @@ -87,18 +89,27 @@ public class GraphQlWebFluxAutoConfiguration { @Bean public RouterFunction graphQlEndpoint(GraphQlHttpHandler handler, GraphQlSource graphQlSource, GraphQlProperties properties, ResourceLoader resourceLoader) { + String graphQLPath = properties.getPath(); if (logger.isInfoEnabled()) { logger.info("GraphQL endpoint HTTP POST " + graphQLPath); } // @formatter:off RouterFunctions.Builder builder = RouterFunctions.route() - .POST(graphQLPath, accept(MediaType.APPLICATION_JSON).and(contentType(MediaType.APPLICATION_JSON)), handler::handleRequest); + .GET(graphQLPath, request -> + ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED) + .headers(headers -> headers.setAllow(Collections.singleton(HttpMethod.POST))) + .build()) + .POST(graphQLPath, + accept(MediaType.APPLICATION_JSON).and(contentType(MediaType.APPLICATION_JSON)), + handler::handleRequest); + if (properties.getGraphiql().isEnabled()) { Resource resource = resourceLoader.getResource("classpath:graphiql/index.html"); GraphiQlWebFluxHandler graphiQlHandler = new GraphiQlWebFluxHandler(graphQLPath, resource); builder = builder.GET(properties.getGraphiql().getPath(), graphiQlHandler::showGraphiQlPage); } + if (properties.getSchema().getPrinter().isEnabled()) { SchemaPrinter printer = new SchemaPrinter(); builder = builder.GET(graphQLPath + properties.getSchema().getPrinter().getPath(), diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java index d38792d1..3220aabd 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java @@ -47,6 +47,8 @@ import org.springframework.graphql.web.WebGraphQlHandler; import org.springframework.graphql.web.WebInterceptor; import org.springframework.graphql.web.webmvc.GraphQlHttpHandler; import org.springframework.graphql.web.webmvc.GraphQlWebSocketHandler; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.HandlerMapping; @@ -94,20 +96,30 @@ public class GraphQlWebMvcAutoConfiguration { } @Bean - public RouterFunction graphQlRouterFunction(GraphQlHttpHandler handler, GraphQlSource graphQlSource, - GraphQlProperties properties, ResourceLoader resourceLoader) { + public RouterFunction graphQlRouterFunction(GraphQlHttpHandler handler, + GraphQlSource graphQlSource, GraphQlProperties properties, ResourceLoader resourceLoader) { + String graphQLPath = properties.getPath(); if (logger.isInfoEnabled()) { logger.info("GraphQL endpoint HTTP POST " + graphQLPath); } + // @formatter:off RouterFunctions.Builder builder = RouterFunctions.route() - .POST(graphQLPath, contentType(MediaType.APPLICATION_JSON).and(accept(MediaType.APPLICATION_JSON)), handler::handleRequest); + .GET(graphQLPath, request -> + ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED) + .headers(headers -> headers.setAllow(Collections.singleton(HttpMethod.POST))) + .build()) + .POST(graphQLPath, + contentType(MediaType.APPLICATION_JSON).and(accept(MediaType.APPLICATION_JSON)), + handler::handleRequest); + if (properties.getGraphiql().isEnabled()) { Resource resource = resourceLoader.getResource("classpath:graphiql/index.html"); GraphiQlWebMvcHandler graphiQLHandler = new GraphiQlWebMvcHandler(graphQLPath, resource); builder = builder.GET(properties.getGraphiql().getPath(), graphiQLHandler::showGraphiQlPage); } + if (properties.getSchema().getPrinter().isEnabled()) { SchemaPrinter printer = new SchemaPrinter(); builder = builder.GET(graphQLPath + properties.getSchema().getPrinter().getPath(), diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfigurationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfigurationTests.java index 4a2e2440..ea2914d4 100644 --- a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfigurationTests.java +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfigurationTests.java @@ -31,6 +31,7 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.graphql.web.WebInterceptor; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; @@ -63,7 +64,6 @@ class GraphQlWebFluxAutoConfigurationTests { " author" + " }" + "}"; - client.post().uri("").bodyValue("{ \"query\": \"" + query + "\"}") .exchange() .expectStatus() @@ -74,6 +74,25 @@ class GraphQlWebFluxAutoConfigurationTests { }); } + @Test + void queryHttpGet() { + testWithWebClient((client) -> { + String query = "{" + + " bookById(id: \\\"book-1\\\"){ " + + " id" + + " name" + + " pageCount" + + " author" + + " }" + + "}"; + client.get().uri("?query={query}", "{ \"query\": \"" + query + "\"}") + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.METHOD_NOT_ALLOWED) + .expectHeader().valueEquals("Allow", "POST"); + }); + } + @Test void queryMissing() { testWithWebClient((client) -> diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfigurationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfigurationTests.java index ff7c726f..14734e18 100644 --- a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfigurationTests.java +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfigurationTests.java @@ -58,7 +58,7 @@ class GraphQlWebMvcAutoConfigurationTests { "spring.graphql.schema.locations=classpath:books/"); @Test - void endpointHandlesGraphQlQuery() { + void query() { testWith((mockMvc) -> { String query = "{" + " bookById(id: \\\"book-1\\\"){ " + @@ -76,6 +76,23 @@ class GraphQlWebMvcAutoConfigurationTests { }); } + @Test + void queryHttpGet() { + testWith((mockMvc) -> { + String query = "{" + + " bookById(id: \\\"book-1\\\"){ " + + " id" + + " name" + + " pageCount" + + " author" + + " }" + + "}"; + mockMvc.perform(get("/graphql?query={query}", "{\"query\": \"" + query + "\"}")) + .andExpect(status().isMethodNotAllowed()) + .andExpect(header().string("Allow", "POST")); + }); + } + @Test void missingQuery() { testWith((mockMvc) -> mockMvc.perform(post("/graphql").content("{}")).andExpect(status().isBadRequest()));