diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 9a494fdb4dfdd..9f991d404a896 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -71,6 +71,7 @@ import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem; import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter; import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem; +import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.smallrye.graphql.api.AdaptWith; import io.smallrye.graphql.api.Deprecated; import io.smallrye.graphql.api.Entry; @@ -148,6 +149,8 @@ public class SmallRyeGraphQLProcessor { private static final int GRAPHQL_WEBSOCKET_HANDLER_ORDER = -10000; + private static final String GRAPHQL_MEDIA_TYPE = "application/graphql+json"; + @BuildStep void feature(BuildProducer featureProducer) { featureProducer.produce(new FeatureBuildItem(Feature.SMALLRYE_GRAPHQL)); @@ -349,7 +352,8 @@ void buildExecutionEndpoint( BodyHandlerBuildItem bodyHandlerBuildItem, SmallRyeGraphQLConfig graphQLConfig, BeanContainerBuildItem beanContainer, - BuildProducer webSocketSubProtocols) { + BuildProducer webSocketSubProtocols, + HttpBuildTimeConfig httpBuildTimeConfig) { /* * Ugly Hack @@ -395,8 +399,11 @@ void buildExecutionEndpoint( // Queries and Mutations boolean allowGet = getBooleanConfigValue(ConfigKey.ALLOW_GET, false); boolean allowQueryParametersOnPost = getBooleanConfigValue(ConfigKey.ALLOW_POST_WITH_QUERY_PARAMETERS, false); + boolean allowCompression = httpBuildTimeConfig.enableCompression && httpBuildTimeConfig.compressMediaTypes + .map(mediaTypes -> mediaTypes.contains(GRAPHQL_MEDIA_TYPE)) + .orElse(false); Handler executionHandler = recorder.executionHandler(graphQLInitializedBuildItem.getInitialized(), - allowGet, allowQueryParametersOnPost, runBlocking); + allowGet, allowQueryParametersOnPost, runBlocking, allowCompression); HttpRootPathBuildItem.Builder requestBuilder = httpRootPathBuildItem.routeBuilder() .routeFunction(graphQLConfig.rootPath, recorder.routeFunction(bodyHandlerBuildItem.getHandler())) diff --git a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/CompressionTest.java b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/CompressionTest.java new file mode 100644 index 0000000000000..853034eb9b1e8 --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/CompressionTest.java @@ -0,0 +1,80 @@ +package io.quarkus.smallrye.graphql.deployment; + +import java.util.Arrays; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class CompressionTest extends AbstractGraphQLTest { + + private static final String PI = "3.141592653589793238462643383279502884197169399375105820"; + private static final String TAU = "6.28"; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")) + .overrideConfigKey("quarkus.http.enable-compression", "true"); + + @Test + public void singleValidQueryCompressedResponseTest() { + String body = getPayload("{ validPiQuery }"); + assertCompressed(body, PI); + } + + @Test + public void multipleValidQueriesCompressedResponseTest() { + String body = getPayload("{ validPiQuery validTauQuery }"); + assertCompressed(body, PI, TAU); + } + + @Test + public void singleInvalidQueryCompressedResponseTest() { + String body = getPayload("{ invalidQuery }"); + assertCompressed(body, "errors"); + } + + private void assertCompressed(String body, String... expectedOutput) { + org.hamcrest.Matcher messageMatcher = Arrays.stream(expectedOutput) + .map(CoreMatchers::containsString) + .reduce(Matchers.allOf(), (a, b) -> Matchers.allOf(a, b)); + + RestAssured.given() + .body(body) + .contentType(MEDIATYPE_JSON) + .post("/graphql") + .prettyPeek() + .then() + .assertThat() + .statusCode(200) + .header("Content-Encoding", "gzip") + .body(messageMatcher); + } + + @GraphQLApi + public static class Schema { + @Query + public String validPiQuery() { + return PI; + } + + @Query + public String validTauQuery() { + return TAU; + } + + @Query + public String invalidQuery() { + throw new RuntimeException(); + } + } + +} diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLCompressionHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLCompressionHandler.java new file mode 100644 index 0000000000000..417c6028e9d59 --- /dev/null +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLCompressionHandler.java @@ -0,0 +1,25 @@ +package io.quarkus.smallrye.graphql.runtime; + +import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.ext.web.RoutingContext; + +public class SmallRyeGraphQLCompressionHandler implements Handler { + + private final Handler routeHandler; + + public SmallRyeGraphQLCompressionHandler(Handler routeHandler) { + this.routeHandler = routeHandler; + } + + @Override + public void handle(RoutingContext context) { + context.addHeadersEndHandler(new Handler() { + @Override + public void handle(Void event) { + context.response().headers().remove(HttpHeaders.CONTENT_ENCODING); + } + }); + routeHandler.handle(context); + } +} diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java index 048ac5e65d622..a46f622d9022f 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java @@ -32,11 +32,16 @@ public RuntimeValue createExecutionService(BeanContainer beanContainer, } public Handler executionHandler(RuntimeValue initialized, boolean allowGet, - boolean allowPostWithQueryParameters, boolean runBlocking) { + boolean allowPostWithQueryParameters, boolean runBlocking, boolean allowCompression) { if (initialized.getValue()) { - return new SmallRyeGraphQLExecutionHandler(allowGet, allowPostWithQueryParameters, runBlocking, + Handler handler = new SmallRyeGraphQLExecutionHandler(allowGet, + allowPostWithQueryParameters, runBlocking, getCurrentIdentityAssociation(), Arc.container().instance(CurrentVertxRequest.class).get()); + if (allowCompression) { + return new SmallRyeGraphQLCompressionHandler(handler); + } + return handler; } else { return new SmallRyeGraphQLNoEndpointHandler(); } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java index e672c5e7e0769..c1a2819bd3a88 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java @@ -90,7 +90,7 @@ public class HttpBuildTimeConfig { * List of media types for which the compression should be enabled automatically, unless declared explicitly via * {@link Compressed} or {@link Uncompressed}. */ - @ConfigItem(defaultValue = "text/html,text/plain,text/xml,text/css,text/javascript,application/javascript") + @ConfigItem(defaultValue = "text/html,text/plain,text/xml,text/css,text/javascript,application/javascript,application/graphql+json") public Optional> compressMediaTypes; /**