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
This commit is contained in:
Brian Clozel
2024-08-01 14:25:52 +02:00
parent e2a984c75f
commit 1738e0c743
4 changed files with 50 additions and 2 deletions

View File

@@ -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<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 Mono<ServerResponse> onlyAllowPost(ServerRequest request) {
return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).headers(this::onlyAllowPost).build();
}

View File

@@ -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();
}

View File

@@ -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()

View File

@@ -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 + "\"}"))