Polishing in GraphQlHttpHandler

This commit is contained in:
rstoyanchev
2024-04-26 14:36:25 +01:00
parent 1ea64dca5c
commit 14c87b0935
3 changed files with 52 additions and 34 deletions

View File

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

View File

@@ -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<Object> getMessageConverter() {
return this.messageConverter;
protected ServerResponse.HeadersBuilder.WriteFunction getWriteFunction(
Map<String, Object> 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<WebGraphQlResponse> responseMono) throws ServletException;
/**
* WriteFunction that writes with a given, fixed {@link HttpMessageConverter}.
*/
private record MessageConverterWriteFunction(
Map<String, Object> resultMap, MediaType contentType, HttpMessageConverter<Object> 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;
}
}
}

View File

@@ -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.
* <p>If no converter is provided, this will use
* Create a new instance with a custom message converter for GraphQL payloads.
* <p>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<String, Object> 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<Object> converter, MediaType contentType, Map<String, Object> resultMap) {
return (servletRequest, servletResponse) -> {
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(servletResponse);
converter.write(resultMap, contentType, httpResponse);
return null;
};
}
}