Skip to content

Instantly share code, notes, and snippets.

@maciejwalkowiak
Last active October 13, 2022 09:51
Show Gist options
  • Save maciejwalkowiak/3d1f251da7f18dc090cad53518da8409 to your computer and use it in GitHub Desktop.
Save maciejwalkowiak/3d1f251da7f18dc090cad53518da8409 to your computer and use it in GitHub Desktop.
Support for GET requests in Spring for GraphQL
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.graphql.server.WebGraphQlHandler;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.IdGenerator;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
@Component
public class GetRequestGraphQlHttpHandler {
private static final Log logger = LogFactory.getLog(GetRequestGraphQlHttpHandler.class);
private static final List<MediaType> SUPPORTED_MEDIA_TYPES =
Arrays.asList(MediaType.APPLICATION_GRAPHQL, MediaType.APPLICATION_JSON);
private final IdGenerator idGenerator = new AlternativeJdkIdGenerator();
private final WebGraphQlHandler graphQlHandler;
GetRequestGraphQlHttpHandler(WebGraphQlHandler graphQlHandler) {
this.graphQlHandler = graphQlHandler;
}
public ServerResponse handleRequest(ServerRequest serverRequest) {
String query = serverRequest.param("query").orElseThrow(() -> new RuntimeException("'query' parameter not set"));
WebGraphQlRequest graphQlRequest = new WebGraphQlRequest(
serverRequest.uri(), serverRequest.headers().asHttpHeaders(), Map.of("query", query),
this.idGenerator.generateId().toString(), LocaleContextHolder.getLocale());
if (logger.isDebugEnabled()) {
logger.debug("Executing: " + graphQlRequest);
}
Mono<ServerResponse> responseMono = this.graphQlHandler.handleRequest(graphQlRequest)
.map(response -> {
if (logger.isDebugEnabled()) {
logger.debug("Execution complete");
}
ServerResponse.BodyBuilder builder = ServerResponse.ok();
builder.headers(headers -> headers.putAll(response.getResponseHeaders()));
builder.contentType(selectResponseMediaType(serverRequest));
return builder.body(response.toMap());
});
return ServerResponse.async(responseMono);
}
private static MediaType selectResponseMediaType(ServerRequest serverRequest) {
for (MediaType accepted : serverRequest.headers().accept()) {
if (SUPPORTED_MEDIA_TYPES.contains(accepted)) {
return accepted;
}
}
return MediaType.APPLICATION_JSON;
}
}
import java.util.Objects;
import org.springframework.boot.autoconfigure.graphql.GraphQlProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.function.RequestPredicates;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerResponse;
@Configuration
public class GraphQlConfiguration {
@Bean
@Order(-1)
public RouterFunction<ServerResponse> graphQlGetRouterFunction(GetRequestGraphQlHttpHandler gethttpHandler,
GraphQlProperties properties) {
return RouterFunctions
.route()
.GET(properties.getPath(), RequestPredicates.param("query", Objects::nonNull), gethttpHandler::handleRequest)
.build();
}
}
@maciejwalkowiak
Copy link
Author

maciejwalkowiak commented Oct 11, 2022

With httpie:

$ http ":8080/graphql?query=query{books{title}}"

This is a naive implementation so use with caution:

  • it accepts any request via GET while it should be possible to execute only queries (no mutations or subscriptions)
  • it doesn't support persisted queries that may become handy if query exceeds the maximum length or the URL

Read more:

@orubel
Copy link

orubel commented Oct 11, 2022

How do you secure that endpoint via CORS? Usually you have to incorporate mapping. In this case you are changing the mapping used to what appears to be SimpleUrlMapping

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment