diff --git a/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/AbstractGraphQlHttpHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/AbstractGraphQlHttpHandler.java index 60449af4..6f869254 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/AbstractGraphQlHttpHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/AbstractGraphQlHttpHandler.java @@ -76,14 +76,15 @@ public abstract class AbstractGraphQlHttpHandler { return readRequest(request) .flatMap((body) -> { WebGraphQlRequest graphQlRequest = new WebGraphQlRequest( - request.uri(), request.headers().asHttpHeaders(), - request.cookies(), request.remoteAddress().orElse(null), - request.attributes(), body, + request.uri(), request.headers().asHttpHeaders(), request.cookies(), + request.remoteAddress().orElse(null), request.attributes(), body, request.exchange().getRequest().getId(), request.exchange().getLocaleContext().getLocale()); + if (this.logger.isDebugEnabled()) { this.logger.debug("Executing: " + graphQlRequest); } + return this.graphQlHandler.handleRequest(graphQlRequest); }) .flatMap((response) -> { @@ -92,6 +93,7 @@ public abstract class AbstractGraphQlHttpHandler { this.logger.debug("Execution result " + (!CollectionUtils.isEmpty(errors) ? "has errors: " + errors : "is ready") + "."); } + return prepareResponse(request, response); }); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/AbstractGraphQlHttpHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/AbstractGraphQlHttpHandler.java index df30f085..f697dd05 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/AbstractGraphQlHttpHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/AbstractGraphQlHttpHandler.java @@ -18,9 +18,12 @@ package org.springframework.graphql.server.webmvc; import java.io.IOException; import java.util.List; +import java.util.Map; import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; @@ -36,7 +39,9 @@ import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.lang.Nullable; import org.springframework.util.AlternativeJdkIdGenerator; import org.springframework.util.Assert; @@ -46,6 +51,7 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.server.ServerWebInputException; +import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; @@ -79,11 +85,19 @@ public abstract class AbstractGraphQlHttpHandler { /** - * Return the custom message converter, if configured, to read and write with. + * Exposes a {@link ServerResponse.HeadersBuilder.WriteFunction} that writes + * with the {@code HttpMessageConverter} provided to the constructor. + * @param resultMap the result map to write + * @param contentType to set the response content type to + * @return the write function, or {@code null} if a + * {@code HttpMessageConverter} was not provided to the constructor */ @Nullable - public HttpMessageConverter getMessageConverter() { - return this.messageConverter; + protected ServerResponse.HeadersBuilder.WriteFunction getWriteFunction( + Map resultMap, MediaType contentType) { + + return (this.messageConverter != null) ? + new MessageConverterWriteFunction(resultMap, contentType, this.messageConverter) : null; } @@ -133,8 +147,8 @@ public abstract class AbstractGraphQlHttpHandler { if (this.messageConverter != null) { MediaType contentType = request.headers().contentType().orElse(MediaType.APPLICATION_JSON); if (this.messageConverter.canRead(SerializableGraphQlRequest.class, contentType)) { - return (GraphQlRequest) this.messageConverter.read(SerializableGraphQlRequest.class, - new ServletServerHttpRequest(request.servletRequest())); + ServerHttpRequest httpRequest = new ServletServerHttpRequest(request.servletRequest()); + return (GraphQlRequest) this.messageConverter.read(SerializableGraphQlRequest.class, httpRequest); } throw new HttpMediaTypeNotSupportedException( contentType, this.messageConverter.getSupportedMediaTypes(), request.method()); @@ -181,4 +195,20 @@ public abstract class AbstractGraphQlHttpHandler { protected abstract ServerResponse prepareResponse( ServerRequest request, Mono responseMono) throws ServletException; + + /** + * WriteFunction that writes with a given, fixed {@link HttpMessageConverter}. + */ + private record MessageConverterWriteFunction( + Map resultMap, MediaType contentType, HttpMessageConverter converter) + implements ServerResponse.HeadersBuilder.WriteFunction { + + @Override + public ModelAndView write(HttpServletRequest request, HttpServletResponse response) throws Exception { + ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); + this.converter.write(this.resultMap, this.contentType, httpResponse); + return null; + } + } + } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlHttpHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlHttpHandler.java index 7568cd9c..80a59655 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlHttpHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlHttpHandler.java @@ -27,7 +27,6 @@ import org.springframework.graphql.server.WebGraphQlHandler; import org.springframework.graphql.server.WebGraphQlResponse; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.lang.Nullable; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; @@ -56,12 +55,12 @@ public class GraphQlHttpHandler extends AbstractGraphQlHttpHandler { } /** - * Create a new instance with a custom message converter. - *

If no converter is provided, this will use + * Create a new instance with a custom message converter for GraphQL payloads. + *

If no converter is provided, the handler will use * {@link org.springframework.web.servlet.config.annotation.WebMvcConfigurer#configureMessageConverters(List) - * the one configured in the web framework}. + * the one configured for web use}. * @param graphQlHandler common handler for GraphQL over HTTP requests - * @param converter custom {@link HttpMessageConverter} to read and write GraphQL payloads + * @param converter the converter to use to read and write GraphQL payloads */ public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler, @Nullable HttpMessageConverter converter) { super(graphQlHandler, converter); @@ -77,15 +76,12 @@ public class GraphQlHttpHandler extends AbstractGraphQlHttpHandler { builder.headers((headers) -> headers.putAll(response.getResponseHeaders())); builder.contentType(contentType); - if (getMessageConverter() != null) { - return builder.build(writeFunction(getMessageConverter(), contentType, response.toMap())); - } - else { - return builder.body(response.toMap()); - } + Map resultMap = response.toMap(); + ServerResponse.HeadersBuilder.WriteFunction writer = getWriteFunction(resultMap, contentType); + return (writer != null) ? builder.build(writer) : builder.body(resultMap); }).toFuture(); - // This won't be needed with a Spring Framework 6.2 baseline: + // This won't be needed on a Spring Framework 6.2 baseline: // https://github.com/spring-projects/spring-framework/issues/32223 if (future.isDone() && !future.isCancelled() && !future.isCompletedExceptionally()) { @@ -100,23 +96,13 @@ public class GraphQlHttpHandler extends AbstractGraphQlHttpHandler { return ServerResponse.async(future); } - private static MediaType selectResponseMediaType(ServerRequest serverRequest) { - for (MediaType accepted : serverRequest.headers().accept()) { - if (SUPPORTED_MEDIA_TYPES.contains(accepted)) { - return accepted; + private static MediaType selectResponseMediaType(ServerRequest request) { + for (MediaType mediaType : request.headers().accept()) { + if (SUPPORTED_MEDIA_TYPES.contains(mediaType)) { + return mediaType; } } return MediaType.APPLICATION_JSON; } - private static ServerResponse.HeadersBuilder.WriteFunction writeFunction( - HttpMessageConverter converter, MediaType contentType, Map resultMap) { - - return (servletRequest, servletResponse) -> { - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(servletResponse); - converter.write(resultMap, contentType, httpResponse); - return null; - }; - } - }