Skip to content

Commit

Permalink
Polishing in GraphQlHttpHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
rstoyanchev committed Apr 26, 2024
1 parent 1ea64dc commit 14c87b0
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,15 @@ public Mono<ServerResponse> handleRequest(ServerRequest request) {
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) -> {
Expand All @@ -92,6 +93,7 @@ public Mono<ServerResponse> handleRequest(ServerRequest request) {
this.logger.debug("Execution result " +
(!CollectionUtils.isEmpty(errors) ? "has errors: " + errors : "is ready") + ".");
}

return prepareResponse(request, response);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@

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;
Expand All @@ -36,7 +39,9 @@
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;
Expand All @@ -46,6 +51,7 @@
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;

Expand Down Expand Up @@ -79,11 +85,19 @@ protected 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;
}


Expand Down Expand Up @@ -133,8 +147,8 @@ private GraphQlRequest readBody(ServerRequest request) throws ServletException {
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());
Expand Down Expand Up @@ -181,4 +195,20 @@ private static SerializableGraphQlRequest applyApplicationGraphQlFallback(
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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
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;
Expand Down Expand Up @@ -56,12 +55,12 @@ public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler) {
}

/**
* 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);
Expand All @@ -77,15 +76,12 @@ protected ServerResponse prepareResponse(ServerRequest request, Mono<WebGraphQlR
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()) {
Expand All @@ -100,23 +96,13 @@ protected ServerResponse prepareResponse(ServerRequest request, Mono<WebGraphQlR
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;
};
}

}

0 comments on commit 14c87b0

Please sign in to comment.