Ralph Schaer opened SPR-16381 and commented
With the release of Safari 11 in September 2017 all major browsers support the Brotli content encoding.
Spring supports the gzip encoding with the org.springframework.web.servlet.resource.GzipResourceResolver but currently there is no equivalent for Brotli.
Would be nice if Spring could also bring support for Brotli. Similar to the GzipResourceResolver class but instead of gzip it needs to check for
br
``` in the Accept-Encoding header and look for files with the suffix
```java
.br
Referenced from: commits https://github.com/spring-projects/spring-boot/commit/56ab0da2872de71fddf55ef035aedd5b04b99960
Comment From: spring-projects-issues
Rossen Stoyanchev commented
Ralph Schaer, any recommendations on Brotli support for Java. I see there is one from Google but it's version 0.2 and hasn't seen any changes in 4 months.
Comment From: spring-projects-issues
Ralph Schaer commented
There might be a misunderstanding. This new resource resolvers does not have to do anything with brotli compression. All it does is serving pre-compressed files with the ending .br when the client is sending an Accept-Encoding with the value br. Like the GzipResourceResolver that does this for gzip pre-compressed files.
After thinking about this, it is maybe not a good idea to split this into a gzip and brotli resolver. This could lead to misconfiguration.
Modern browser send accept-encoding:gzip, deflate, br and if someone configures the gzip resolver before the brotli resolver,
the server would never serve the brotli compressed files.
I think there should be just one resolver that first checks for files ending with .br, then .gz and then for the plain file if the browser accepts br and gzip. Older browser just accept gzip and in that case the resolver should just look for the gz file.
Comment From: spring-projects-issues
Rossen Stoyanchev commented
Indeed, my bad. That makes perfect sense of course. As for accept-encoding, the GzipResourceResolver checks for existence based on the filename + ".gz", so shouldn't it all work fine, i.e. based on what the resolver finds, either filename + ".gz" or + ".br"?
Comment From: spring-projects-issues
Ralph Schaer commented
It makes sense to pre compress the files with gzip (for older browsers) and brotli (for newer browsers) so both files (.gz and .br) will exist.
Now when there are two resolvers a developer could configure them the following wrong way and the server always sends back the gzip file even when the browser sends accept-encoding: gzip, br
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/")
.setCacheControl(
CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic())
.resourceChain(false)
.addResolver(new GzipResourceResolver())
.addResolver(new BrotliResourceResolver());
}
Therefore I think it is better to only have one Resolver
Something like this:
@Override
protected Resource resolveResourceInternal(@Nullable HttpServletRequest request, String requestPath,
List<? extends Resource> locations, ResourceResolverChain chain) {
Resource resource = chain.resolveResource(request, requestPath, locations);
if (resource == null || (request != null && !isGzipAccepted(request) && !isBrotliAccepted(request))) {
return resource;
}
try {
if (isBrotliAccepted(request)) {
Resource brotli = new BrotliResource(resource);
if (brotli.exists()) {
return brotli;
}
}
if (isGzipAccepted(request)) {
Resource gzipped = new GzippedResource(resource);
if (gzipped.exists()) {
return gzipped;
}
}
}
catch (IOException ex) {
logger.trace("No gzipped resource for [" + resource.getFilename() + "]", ex);
}
return resource;
}
private boolean isGzipAccepted(HttpServletRequest request) {
String value = request.getHeader("Accept-Encoding");
return (value != null && value.toLowerCase().contains("gzip"));
}
private boolean isBrotliAccepted(HttpServletRequest request) {
String value = request.getHeader("Accept-Encoding");
return (value != null && value.toLowerCase().contains("br"));
}
Comment From: spring-projects-issues
Rossen Stoyanchev commented
Okay I agree. The logic is pretty much identical, except for a different extension and header directive, and dealing with the "Accept-Encoding" header in one place makes the most sense. Thanks for the advice.
Comment From: spring-projects-issues
Rossen Stoyanchev commented
Related PR https://github.com/spring-projects/spring-framework/pull/1699.
Comment From: spring-projects-issues
Florian Burka commented
As soon as different content encodings are served a Vary: Accept-Encoding response header must be send to avoid caching of br encoded content for users that only speak gzip. (It was the same for gzip/uncompressed, but as everybody supports gzip it's not that painful to ignore ;-) )
Comment From: spring-projects-issues
Rossen Stoyanchev commented
Ralph Schaer, this is now ready via #520767 and also #b472d1. The new EncodedResourceResolver is configured by default for brotli and gzip, in that order, so it should behave as expected out of the box.
Florian Burka I've also updated the cache key and added a Vary header, where the cache key is a bit more generalized which goes along with the new EncodedResourceResolver that can be configured to support other content codings, and in that case caching should just work without the need for further updates.
Have a look, and if you can, please give these changes a try with 5.1 snapshots.
Comment From: spring-projects-issues
Ralph Schaer commented
Thanks. This looks good. I created an example project.
https://github.com/ralscha/precompressed
It fetches four resources (uncompressed, gzip and brotli compressed and one file compressed with gzip and brotli). Works great. Vary header is set and the correct resource is served to the browser.
I tested it with Chrome and Edge on Windows.
Comment From: spring-projects-issues
Florian Burka commented
@rstoya05-aop Thanks a lot!
Sadly I am not able to test it right now as I am on vacation (without a laptop) till end of July.
It looks like the use of unused Accept-Encoding values might lead to a little cache bloat, as br+gz+deflate and br+gz would be cached separately and if you only have gz files for deflate+br+gz and br+gz and gz the same entry will be added to the cache.
I don't remember what exactly is being cached and if that might be an issue.
Comment From: spring-projects-issues
Rossen Stoyanchev commented
Florian Burka, indeed we could probably do better by caching the same instance, even if there are multiple encoding-based variations of the same canonical key.
Ralph Schaer, thanks for reviewing and testing!
Comment From: spring-projects-issues
Rossen Stoyanchev commented
I've switched CachingResourceResolver back to building a key only for specific encodings, and added an extra property to make that configurable.
This does mean CachingResourceResolver and EncodingResourceResolver must both be configured with the same list but as they're both set to ["br", "gzip"] and that list is unlikely to be customized often, this is probably good enough for now.
Comment From: hyperxpro
Is there any update on this?