From 76f7fbb9843018145e4cc7b9deb65392eb7e4865 Mon Sep 17 00:00:00 2001 From: stianst Date: Wed, 26 Aug 2020 20:06:03 +0200 Subject: [PATCH] KEYCLOAK-14548 Add support for cached gzip encoding of resources --- .../keycloak-services/main/module.xml | 1 + .../GzipResourceEncodingProvider.java | 70 ++++++++++++++ .../GzipResourceEncodingProviderFactory.java | 76 +++++++++++++++ .../encoding/ResourceEncodingHelper.java | 26 +++++ .../encoding/ResourceEncodingProvider.java | 24 +++++ .../ResourceEncodingProviderFactory.java | 23 +++++ .../encoding/ResourceEncodingSpi.java | 29 ++++++ .../services/resources/JsResource.java | 23 ++++- .../resources/KeycloakApplication.java | 2 +- .../services/resources/ThemeResource.java | 22 ++++- ...k.encoding.ResourceEncodingProviderFactory | 1 + .../services/org.keycloak.provider.Spi | 3 +- .../undertow/KeycloakOnUndertow.java | 4 + .../theme/ThemeResourceProviderTest.java | 94 ++++++++++++++++--- .../keycloak/testsuite/KeycloakServer.java | 1 + 15 files changed, 376 insertions(+), 23 deletions(-) create mode 100644 services/src/main/java/org/keycloak/encoding/GzipResourceEncodingProvider.java create mode 100644 services/src/main/java/org/keycloak/encoding/GzipResourceEncodingProviderFactory.java create mode 100644 services/src/main/java/org/keycloak/encoding/ResourceEncodingHelper.java create mode 100644 services/src/main/java/org/keycloak/encoding/ResourceEncodingProvider.java create mode 100644 services/src/main/java/org/keycloak/encoding/ResourceEncodingProviderFactory.java create mode 100644 services/src/main/java/org/keycloak/encoding/ResourceEncodingSpi.java create mode 100644 services/src/main/resources/META-INF/services/org.keycloak.encoding.ResourceEncodingProviderFactory diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml index a70a57eb8d..fcd1e44cd5 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml @@ -68,6 +68,7 @@ + diff --git a/services/src/main/java/org/keycloak/encoding/GzipResourceEncodingProvider.java b/services/src/main/java/org/keycloak/encoding/GzipResourceEncodingProvider.java new file mode 100644 index 0000000000..89decbabab --- /dev/null +++ b/services/src/main/java/org/keycloak/encoding/GzipResourceEncodingProvider.java @@ -0,0 +1,70 @@ +package org.keycloak.encoding; + +import org.apache.commons.io.IOUtils; +import org.jboss.logging.Logger; +import org.keycloak.models.KeycloakSession; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.zip.GZIPOutputStream; + +public class GzipResourceEncodingProvider implements ResourceEncodingProvider { + + private static final Logger logger = Logger.getLogger(ResourceEncodingProvider.class); + + private KeycloakSession session; + private File cacheDir; + + public GzipResourceEncodingProvider(KeycloakSession session, File cacheDir) { + this.session = session; + this.cacheDir = cacheDir; + } + + public InputStream getEncodedStream(StreamSupplier producer, String... path) { + StringBuilder sb = new StringBuilder(); + sb.append(cacheDir.getAbsolutePath()); + for (String p : path) { + sb.append(File.separatorChar); + sb.append(p); + } + sb.append(".gz"); + + String filePath = sb.toString(); + + try { + File encodedFile = new File(filePath); + if (!encodedFile.getCanonicalPath().startsWith(cacheDir.getCanonicalPath())) { + return null; + } + + if (!encodedFile.exists()) { + InputStream is = producer.getInputStream(); + if (is != null) { + File parent = encodedFile.getParentFile(); + if (!parent.isDirectory()) { + parent.mkdirs(); + } + FileOutputStream fos = new FileOutputStream(encodedFile); + GZIPOutputStream gos = new GZIPOutputStream(fos); + IOUtils.copy(is, gos); + gos.close(); + is.close(); + } else { + encodedFile = null; + } + } + + return encodedFile != null ? new FileInputStream(encodedFile) : null; + } catch (Exception e) { + logger.warn("Failed to encode resource", e); + return null; + } + } + + public String getEncoding() { + return "gzip"; + } + +} diff --git a/services/src/main/java/org/keycloak/encoding/GzipResourceEncodingProviderFactory.java b/services/src/main/java/org/keycloak/encoding/GzipResourceEncodingProviderFactory.java new file mode 100644 index 0000000000..ecdb1650a0 --- /dev/null +++ b/services/src/main/java/org/keycloak/encoding/GzipResourceEncodingProviderFactory.java @@ -0,0 +1,76 @@ +package org.keycloak.encoding; + +import org.apache.commons.io.FileUtils; +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.common.Version; +import org.keycloak.models.KeycloakSession; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +public class GzipResourceEncodingProviderFactory implements ResourceEncodingProviderFactory { + + private static final Logger logger = Logger.getLogger(GzipResourceEncodingProviderFactory.class); + + private Set excludedContentTypes = new HashSet<>(); + + private File cacheDir; + + @Override + public ResourceEncodingProvider create(KeycloakSession session) { + if (cacheDir == null) { + cacheDir = initCacheDir(); + } + + return new GzipResourceEncodingProvider(session, cacheDir); + } + + @Override + public void init(Config.Scope config) { + String e = config.get("excludedContentTypes", "image/png image/jpeg"); + for (String s : e.split(" ")) { + excludedContentTypes.add(s); + } + } + + @Override + public boolean encodeContentType(String contentType) { + return !excludedContentTypes.contains(contentType); + } + + @Override + public String getId() { + return "gzip"; + } + + private synchronized File initCacheDir() { + if (cacheDir != null) { + return cacheDir; + } + + File cacheRoot = new File(System.getProperty("java.io.tmpdir"), "kc-gzip-cache"); + File cacheDir = new File(cacheRoot, Version.RESOURCES_VERSION); + + if (cacheRoot.isDirectory()) { + for (File f : cacheRoot.listFiles()) { + if (!f.getName().equals(Version.RESOURCES_VERSION)) { + try { + FileUtils.deleteDirectory(f); + } catch (IOException e) { + logger.warn("Failed to delete old gzip cache directory", e); + } + } + } + } + + if (!cacheDir.isDirectory() && !cacheDir.mkdirs()) { + logger.warn("Failed to create gzip cache directory"); + return null; + } + + return cacheDir; + } +} diff --git a/services/src/main/java/org/keycloak/encoding/ResourceEncodingHelper.java b/services/src/main/java/org/keycloak/encoding/ResourceEncodingHelper.java new file mode 100644 index 0000000000..787d11e71f --- /dev/null +++ b/services/src/main/java/org/keycloak/encoding/ResourceEncodingHelper.java @@ -0,0 +1,26 @@ +package org.keycloak.encoding; + +import org.keycloak.models.KeycloakSession; + +public class ResourceEncodingHelper { + + public static ResourceEncodingProvider getResourceEncodingProvider(KeycloakSession session, String contentType) { + String acceptEncoding = session.getContext().getRequestHeaders().getHeaderString("Accept-Encoding"); + if (acceptEncoding != null) { + for (String e : acceptEncoding.split(",")) { + e = e.trim(); + ResourceEncodingProviderFactory f = (ResourceEncodingProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(ResourceEncodingProvider.class, e); + if (f != null && f.encodeContentType(contentType)) { + ResourceEncodingProvider provider = session.getProvider(ResourceEncodingProvider.class, e.trim()); + if (provider != null) { + return provider; + } + } else { + return null; + } + } + } + return null; + } + +} diff --git a/services/src/main/java/org/keycloak/encoding/ResourceEncodingProvider.java b/services/src/main/java/org/keycloak/encoding/ResourceEncodingProvider.java new file mode 100644 index 0000000000..fdf224782c --- /dev/null +++ b/services/src/main/java/org/keycloak/encoding/ResourceEncodingProvider.java @@ -0,0 +1,24 @@ +package org.keycloak.encoding; + +import org.keycloak.provider.Provider; + +import java.io.IOException; +import java.io.InputStream; + +public interface ResourceEncodingProvider extends Provider { + + InputStream getEncodedStream(StreamSupplier producer, String... path); + + String getEncoding(); + + @Override + default void close() { + } + + interface StreamSupplier { + + InputStream getInputStream() throws IOException; + + } + +} diff --git a/services/src/main/java/org/keycloak/encoding/ResourceEncodingProviderFactory.java b/services/src/main/java/org/keycloak/encoding/ResourceEncodingProviderFactory.java new file mode 100644 index 0000000000..29c22fec89 --- /dev/null +++ b/services/src/main/java/org/keycloak/encoding/ResourceEncodingProviderFactory.java @@ -0,0 +1,23 @@ +package org.keycloak.encoding; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderFactory; + +public interface ResourceEncodingProviderFactory extends ProviderFactory { + + boolean encodeContentType(String contentType); + + @Override + default void init(Config.Scope config) { + } + + @Override + default void postInit(KeycloakSessionFactory factory) { + } + + @Override + default void close() { + } + +} diff --git a/services/src/main/java/org/keycloak/encoding/ResourceEncodingSpi.java b/services/src/main/java/org/keycloak/encoding/ResourceEncodingSpi.java new file mode 100644 index 0000000000..ad3b0ff511 --- /dev/null +++ b/services/src/main/java/org/keycloak/encoding/ResourceEncodingSpi.java @@ -0,0 +1,29 @@ +package org.keycloak.encoding; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +public class ResourceEncodingSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "resource-encoding"; + } + + @Override + public Class getProviderClass() { + return ResourceEncodingProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return ResourceEncodingProviderFactory.class; + } + +} diff --git a/services/src/main/java/org/keycloak/services/resources/JsResource.java b/services/src/main/java/org/keycloak/services/resources/JsResource.java index bec3d20d06..14876f713c 100755 --- a/services/src/main/java/org/keycloak/services/resources/JsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/JsResource.java @@ -19,6 +19,9 @@ package org.keycloak.services.resources; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.common.Version; +import org.keycloak.encoding.ResourceEncodingHelper; +import org.keycloak.encoding.ResourceEncodingProvider; +import org.keycloak.models.KeycloakSession; import org.keycloak.services.util.CacheControlUtil; import org.keycloak.utils.MediaType; @@ -40,6 +43,9 @@ import java.io.InputStream; @Path("/js") public class JsResource { + @Context + private KeycloakSession session; + @Context private HttpRequest request; @@ -120,11 +126,24 @@ public class JsResource { cacheControl = CacheControlUtil.noCache(); } + String contentType = "text/javascript"; Cors cors = Cors.add(request).allowAllOrigins(); - InputStream inputStream = getClass().getClassLoader().getResourceAsStream(name); + ResourceEncodingProvider encodingProvider = ResourceEncodingHelper.getResourceEncodingProvider(session, contentType); + + InputStream inputStream; + if (encodingProvider != null) { + inputStream = encodingProvider.getEncodedStream(() -> getClass().getClassLoader().getResourceAsStream(name), "js", name); + } else { + inputStream = getClass().getClassLoader().getResourceAsStream(name); + } + if (inputStream != null) { - return cors.builder(Response.ok(inputStream).type("text/javascript").cacheControl(cacheControl)).build(); + Response.ResponseBuilder rb = Response.ok(inputStream).type(contentType).cacheControl(cacheControl); + if (encodingProvider != null) { + rb.encoding(encodingProvider.getEncoding()); + } + return cors.builder(rb).build(); } else { return cors.builder(Response.status(Response.Status.NOT_FOUND)).build(); } diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index a0a6ef1401..5202bae71d 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -18,6 +18,7 @@ package org.keycloak.services.resources; import com.fasterxml.jackson.core.type.TypeReference; import org.jboss.logging.Logger; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.Config; import org.keycloak.common.util.Resteasy; import org.keycloak.config.ConfigProviderFactory; @@ -160,7 +161,6 @@ public class KeycloakApplication extends Application { sessionFactory.publish(new PostMigrationEvent()); setupScheduledTasks(sessionFactory); - } protected void shutdown() { diff --git a/services/src/main/java/org/keycloak/services/resources/ThemeResource.java b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java index a959a04c44..e72c8b4cd7 100644 --- a/services/src/main/java/org/keycloak/services/resources/ThemeResource.java +++ b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java @@ -19,6 +19,8 @@ package org.keycloak.services.resources; import org.jboss.logging.Logger; import org.keycloak.common.Version; import org.keycloak.common.util.MimeTypeUtil; +import org.keycloak.encoding.ResourceEncodingHelper; +import org.keycloak.encoding.ResourceEncodingProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.services.ServicesLogger; import org.keycloak.services.util.CacheControlUtil; @@ -29,6 +31,7 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; +import java.io.File; import java.io.InputStream; /** @@ -39,8 +42,6 @@ import java.io.InputStream; @Path("/resources") public class ThemeResource { - protected static final Logger logger = Logger.getLogger(ThemeResource.class); - @Context private KeycloakSession session; @@ -60,10 +61,23 @@ public class ThemeResource { } try { + String contentType = MimeTypeUtil.getContentType(path); Theme theme = session.theme().getTheme(themeName, Theme.Type.valueOf(themType.toUpperCase())); - InputStream resource = theme.getResourceAsStream(path); + ResourceEncodingProvider encodingProvider = ResourceEncodingHelper.getResourceEncodingProvider(session, contentType); + + InputStream resource; + if (encodingProvider != null) { + resource = encodingProvider.getEncodedStream(() -> theme.getResourceAsStream(path), themType, themeName, path.replace('/', File.separatorChar)); + } else { + resource = theme.getResourceAsStream(path); + } + if (resource != null) { - return Response.ok(resource).type(MimeTypeUtil.getContentType(path)).cacheControl(CacheControlUtil.getDefaultCacheControl()).build(); + Response.ResponseBuilder rb = Response.ok(resource).type(contentType).cacheControl(CacheControlUtil.getDefaultCacheControl()); + if (encodingProvider != null) { + rb.encoding(encodingProvider.getEncoding()); + } + return rb.build(); } else { return Response.status(Response.Status.NOT_FOUND).build(); } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.encoding.ResourceEncodingProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.encoding.ResourceEncodingProviderFactory new file mode 100644 index 0000000000..f82732e859 --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.encoding.ResourceEncodingProviderFactory @@ -0,0 +1 @@ +org.keycloak.encoding.GzipResourceEncodingProviderFactory \ No newline at end of file diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index efc42fa49c..b43a16d810 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -22,4 +22,5 @@ org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi org.keycloak.services.x509.X509ClientCertificateLookupSpi org.keycloak.protocol.oidc.ext.OIDCExtSPI -org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessorSpi \ No newline at end of file +org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessorSpi +org.keycloak.encoding.ResourceEncodingSpi \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java index 0ae2d7b313..1432509ce2 100644 --- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java +++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java @@ -39,6 +39,7 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; import org.jboss.resteasy.spi.ResteasyDeployment; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.descriptor.api.Descriptor; @@ -83,6 +84,9 @@ public class KeycloakOnUndertow implements DeployableContainer Version.RESOURCES_VERSION, String.class); + + assertEncoded(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/resources/" + resourcesVersion + "/welcome/keycloak/css/welcome.css", "body {"); + assertEncoded(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/js/keycloak.js", "function(root, factory)"); + + testingClient.server().run(session -> { + assertTrue(Paths.get(System.getProperty("java.io.tmpdir"), "kc-gzip-cache", resourcesVersion, "welcome", "keycloak", "css", "welcome.css.gz").toFile().isFile()); + assertTrue(Paths.get(System.getProperty("java.io.tmpdir"), "kc-gzip-cache", resourcesVersion, "js", "keycloak.js.gz").toFile().isFile()); + }); + } + + private void assertEncoded(String url, String expectedContent) throws IOException { + try (CloseableHttpClient httpClient = HttpClientBuilder.create().disableContentCompression().build()) { + HttpGet get = new HttpGet(url); + CloseableHttpResponse response = httpClient.execute(get); + + InputStream is = response.getEntity().getContent(); + assertNull(response.getFirstHeader("Content-Encoding")); + + String plain = IOUtils.toString(is, StandardCharsets.UTF_8); + + response.close(); + + get = new HttpGet(url); + get.addHeader("Accept-Encoding", "gzip"); + response = httpClient.execute(get); + + + is = response.getEntity().getContent(); + assertEquals("gzip", response.getFirstHeader("Content-Encoding").getValue()); + + String gzip = IOUtils.toString(new GZIPInputStream(is), StandardCharsets.UTF_8); + + response.close(); + + assertEquals(plain, gzip); + assertTrue(plain.contains(expectedContent)); + } + } + /** * See KEYCLOAK-12926 */ @@ -64,25 +128,25 @@ public class ThemeResourceProviderTest extends AbstractTestRealmKeycloakTest { testingClient.server().run(session -> { try { Theme theme = session.theme().getTheme("base", Theme.Type.LOGIN); - Assert.assertEquals("Test en_US_variant", theme.getMessages("messages", new Locale("en", "US", "variant")).get("test.keycloak-12926")); - Assert.assertEquals("Test en_US", theme.getMessages("messages", new Locale("en", "US")).get("test.keycloak-12926")); - Assert.assertEquals("Test en", theme.getMessages("messages", Locale.ENGLISH).get("test.keycloak-12926")); - Assert.assertEquals("Test en_US", theme.getMessages("messages", new Locale("en", "US")).get("test.keycloak-12926")); - Assert.assertEquals("Test en", theme.getMessages("messages", Locale.ENGLISH).get("test.keycloak-12926")); + assertEquals("Test en_US_variant", theme.getMessages("messages", new Locale("en", "US", "variant")).get("test.keycloak-12926")); + assertEquals("Test en_US", theme.getMessages("messages", new Locale("en", "US")).get("test.keycloak-12926")); + assertEquals("Test en", theme.getMessages("messages", Locale.ENGLISH).get("test.keycloak-12926")); + assertEquals("Test en_US", theme.getMessages("messages", new Locale("en", "US")).get("test.keycloak-12926")); + assertEquals("Test en", theme.getMessages("messages", Locale.ENGLISH).get("test.keycloak-12926")); - Assert.assertEquals("only de_AT_variant", theme.getMessages("messages", new Locale("de", "AT", "variant")).get("test.keycloak-12926-resolving1")); - Assert.assertNull(theme.getMessages("messages", new Locale("de", "AT")).get("test.keycloak-12926-resolving1")); + assertEquals("only de_AT_variant", theme.getMessages("messages", new Locale("de", "AT", "variant")).get("test.keycloak-12926-resolving1")); + assertNull(theme.getMessages("messages", new Locale("de", "AT")).get("test.keycloak-12926-resolving1")); - Assert.assertEquals("only de_AT", theme.getMessages("messages", new Locale("de", "AT", "variant")).get("test.keycloak-12926-resolving2")); - Assert.assertNull(theme.getMessages("messages", new Locale("de")).get("test.keycloak-12926-resolving2")); + assertEquals("only de_AT", theme.getMessages("messages", new Locale("de", "AT", "variant")).get("test.keycloak-12926-resolving2")); + assertNull(theme.getMessages("messages", new Locale("de")).get("test.keycloak-12926-resolving2")); - Assert.assertEquals("only de", theme.getMessages("messages", new Locale("de", "AT", "variant")).get("test.keycloak-12926-only_de")); - Assert.assertNull(theme.getMessages("messages", Locale.ENGLISH).get("test.keycloak-12926-only_de")); + assertEquals("only de", theme.getMessages("messages", new Locale("de", "AT", "variant")).get("test.keycloak-12926-only_de")); + assertNull(theme.getMessages("messages", Locale.ENGLISH).get("test.keycloak-12926-only_de")); - Assert.assertEquals("fallback en", theme.getMessages("messages", new Locale("de", "AT", "variant")).get("test.keycloak-12926-resolving3")); - Assert.assertEquals("fallback en", theme.getMessages("messages", new Locale("de", "AT")).get("test.keycloak-12926-resolving3")); - Assert.assertEquals("fallback en", theme.getMessages("messages", new Locale("de")).get("test.keycloak-12926-resolving3")); - Assert.assertNull(theme.getMessages("messages", Locale.ENGLISH).get("fallback en")); + assertEquals("fallback en", theme.getMessages("messages", new Locale("de", "AT", "variant")).get("test.keycloak-12926-resolving3")); + assertEquals("fallback en", theme.getMessages("messages", new Locale("de", "AT")).get("test.keycloak-12926-resolving3")); + assertEquals("fallback en", theme.getMessages("messages", new Locale("de")).get("test.keycloak-12926-resolving3")); + assertNull(theme.getMessages("messages", Locale.ENGLISH).get("fallback en")); } catch (IOException e) { Assert.fail(e.getMessage()); diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakServer.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakServer.java index ddd3481e7b..87988c0662 100755 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakServer.java +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakServer.java @@ -374,6 +374,7 @@ public class KeycloakServer { long start = System.currentTimeMillis(); ResteasyDeployment deployment = new ResteasyDeployment(); + deployment.setApplicationClass(KeycloakApplication.class.getName()); Builder builder = Undertow.builder()