KEYCLOAK-14548 Add support for cached gzip encoding of resources
This commit is contained in:
parent
e34ff6cd9c
commit
76f7fbb984
15 changed files with 376 additions and 23 deletions
|
@ -68,6 +68,7 @@
|
||||||
<module name="javax.api"/>
|
<module name="javax.api"/>
|
||||||
<module name="javax.activation.api"/>
|
<module name="javax.activation.api"/>
|
||||||
<module name="javax.json.api"/>
|
<module name="javax.json.api"/>
|
||||||
|
<module name="org.apache.commons.io"/>
|
||||||
<module name="org.apache.httpcomponents"/>
|
<module name="org.apache.httpcomponents"/>
|
||||||
<module name="org.twitter4j"/>
|
<module name="org.twitter4j"/>
|
||||||
<module name="javax.transaction.api"/>
|
<module name="javax.transaction.api"/>
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<ResourceEncodingProvider> {
|
||||||
|
|
||||||
|
boolean encodeContentType(String contentType);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<? extends Provider> getProviderClass() {
|
||||||
|
return ResourceEncodingProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return ResourceEncodingProviderFactory.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,6 +19,9 @@ package org.keycloak.services.resources;
|
||||||
|
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.common.Version;
|
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.services.util.CacheControlUtil;
|
||||||
import org.keycloak.utils.MediaType;
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
|
@ -40,6 +43,9 @@ import java.io.InputStream;
|
||||||
@Path("/js")
|
@Path("/js")
|
||||||
public class JsResource {
|
public class JsResource {
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
private HttpRequest request;
|
private HttpRequest request;
|
||||||
|
|
||||||
|
@ -120,11 +126,24 @@ public class JsResource {
|
||||||
cacheControl = CacheControlUtil.noCache();
|
cacheControl = CacheControlUtil.noCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String contentType = "text/javascript";
|
||||||
Cors cors = Cors.add(request).allowAllOrigins();
|
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) {
|
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 {
|
} else {
|
||||||
return cors.builder(Response.status(Response.Status.NOT_FOUND)).build();
|
return cors.builder(Response.status(Response.Status.NOT_FOUND)).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.services.resources;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.common.util.Resteasy;
|
import org.keycloak.common.util.Resteasy;
|
||||||
import org.keycloak.config.ConfigProviderFactory;
|
import org.keycloak.config.ConfigProviderFactory;
|
||||||
|
@ -160,7 +161,6 @@ public class KeycloakApplication extends Application {
|
||||||
sessionFactory.publish(new PostMigrationEvent());
|
sessionFactory.publish(new PostMigrationEvent());
|
||||||
|
|
||||||
setupScheduledTasks(sessionFactory);
|
setupScheduledTasks(sessionFactory);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void shutdown() {
|
protected void shutdown() {
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.keycloak.services.resources;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.Version;
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.common.util.MimeTypeUtil;
|
import org.keycloak.common.util.MimeTypeUtil;
|
||||||
|
import org.keycloak.encoding.ResourceEncodingHelper;
|
||||||
|
import org.keycloak.encoding.ResourceEncodingProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
|
@ -29,6 +31,7 @@ import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,8 +42,6 @@ import java.io.InputStream;
|
||||||
@Path("/resources")
|
@Path("/resources")
|
||||||
public class ThemeResource {
|
public class ThemeResource {
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(ThemeResource.class);
|
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
private KeycloakSession session;
|
private KeycloakSession session;
|
||||||
|
|
||||||
|
@ -60,10 +61,23 @@ public class ThemeResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
String contentType = MimeTypeUtil.getContentType(path);
|
||||||
Theme theme = session.theme().getTheme(themeName, Theme.Type.valueOf(themType.toUpperCase()));
|
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) {
|
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 {
|
} else {
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.encoding.GzipResourceEncodingProviderFactory
|
|
@ -22,4 +22,5 @@ org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi
|
||||||
org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi
|
org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi
|
||||||
org.keycloak.services.x509.X509ClientCertificateLookupSpi
|
org.keycloak.services.x509.X509ClientCertificateLookupSpi
|
||||||
org.keycloak.protocol.oidc.ext.OIDCExtSPI
|
org.keycloak.protocol.oidc.ext.OIDCExtSPI
|
||||||
org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessorSpi
|
org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessorSpi
|
||||||
|
org.keycloak.encoding.ResourceEncodingSpi
|
|
@ -39,6 +39,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
||||||
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
|
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
|
||||||
import org.jboss.resteasy.spi.ResteasyDeployment;
|
import org.jboss.resteasy.spi.ResteasyDeployment;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.jboss.shrinkwrap.api.Archive;
|
import org.jboss.shrinkwrap.api.Archive;
|
||||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||||
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
|
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
|
||||||
|
@ -83,6 +84,9 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
|
||||||
// RESTEASY-2034
|
// RESTEASY-2034
|
||||||
deployment.setProperty(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, true);
|
deployment.setProperty(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, true);
|
||||||
|
|
||||||
|
// Prevent double gzip encoding of resources
|
||||||
|
deployment.getDisabledProviderClasses().add("org.jboss.resteasy.plugins.interceptors.encoding.GZIPEncodingInterceptor");
|
||||||
|
|
||||||
DeploymentInfo di = undertow.undertowDeployment(deployment);
|
DeploymentInfo di = undertow.undertowDeployment(deployment);
|
||||||
di.setClassLoader(getClass().getClassLoader());
|
di.setClassLoader(getClass().getClassLoader());
|
||||||
di.setContextPath("/auth");
|
di.setContextPath("/auth");
|
||||||
|
|
|
@ -1,15 +1,37 @@
|
||||||
package org.keycloak.testsuite.theme;
|
package org.keycloak.testsuite.theme;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||||
|
import org.keycloak.testsuite.utils.io.IOUtil;
|
||||||
import org.keycloak.theme.Theme;
|
import org.keycloak.theme.Theme;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||||
public class ThemeResourceProviderTest extends AbstractTestRealmKeycloakTest {
|
public class ThemeResourceProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
@ -56,6 +78,48 @@ public class ThemeResourceProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void gzipEncoding() throws IOException {
|
||||||
|
final String resourcesVersion = testingClient.server().fetch(session -> 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
|
* See KEYCLOAK-12926
|
||||||
*/
|
*/
|
||||||
|
@ -64,25 +128,25 @@ public class ThemeResourceProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
testingClient.server().run(session -> {
|
testingClient.server().run(session -> {
|
||||||
try {
|
try {
|
||||||
Theme theme = session.theme().getTheme("base", Theme.Type.LOGIN);
|
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"));
|
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"));
|
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", 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"));
|
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", 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"));
|
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"));
|
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"));
|
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"));
|
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"));
|
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"));
|
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"));
|
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"));
|
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"));
|
assertEquals("fallback en", theme.getMessages("messages", new Locale("de")).get("test.keycloak-12926-resolving3"));
|
||||||
Assert.assertNull(theme.getMessages("messages", Locale.ENGLISH).get("fallback en"));
|
assertNull(theme.getMessages("messages", Locale.ENGLISH).get("fallback en"));
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Assert.fail(e.getMessage());
|
Assert.fail(e.getMessage());
|
||||||
|
|
|
@ -374,6 +374,7 @@ public class KeycloakServer {
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
ResteasyDeployment deployment = new ResteasyDeployment();
|
ResteasyDeployment deployment = new ResteasyDeployment();
|
||||||
|
|
||||||
deployment.setApplicationClass(KeycloakApplication.class.getName());
|
deployment.setApplicationClass(KeycloakApplication.class.getName());
|
||||||
|
|
||||||
Builder builder = Undertow.builder()
|
Builder builder = Undertow.builder()
|
||||||
|
|
Loading…
Reference in a new issue