Content compression

Tags
  • encoding
  • webpack
  • spring
  • http
  • gzip
  • brotli
Publish date
Read time 3 min read

️️This week, I worked a bit on web content compression. Traditionally, I have always been involved with Java Spring Backends. So, it was a bit challenging for me to figure out how to implement compression properly, especially in our case where we don’t use the - so popular - Spring Boot. It’s a simple Spring MVC application.

Navigate to "Background" Background

Browsers are able to indicate their preferences or capabilities for content through a process called Content negotiation. Within this negotiation, the browser communicates the types of compression it can handle using the Accept-Encoding header. In response, the server can either comply with the request and serve a compressed asset or choose to provide an uncompressed asset. In the former case, it must indicate this choice using the Content-Encoding header. It’s also worth to mention that this task is done usually by a Proxy between the client and the server. This was not possible in our case. So let’s dive in.

Navigate to "Frontend" Frontend

The frontend part is almost too easy to discuss. Our application is bundled via Webpack, which has a cool plugin for this purpose. It implicitly supports gzip and brotli algorithms.

const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
    mode: 'production',
    optimization: {
		...
    },
	plugins: [
		new CompressionPlugin({
			filename: '[path][base].gz',
			algorithm: 'gzip',
		}),
		new CompressionPlugin({
			filename: '[path][base].br',
			algorithm: 'brotliCompress',
		}),
	],
});

This configuration will generate compressed files alongside the original uncompressed assets. For instance, every JavaScript, Font, CSS, and Image file will have brand new .gz and .br extensions. Half of the work is done; let’s move on to the backend.

Navigate to "Backend" Backend

As I introduced, I’m about to use a Spring MVC backend. Spring exposes every static asset from the /resources directory by default. However, the path resolution for the compressed assets must be done through a unique servlet. In this servlet, we have to define the lookup strategy.

@Configuration  
@EnableWebMvc  
@EnableTransactionManagement  
public class DispatcherServletConfiguration implements WebMvcConfigurer {  
  
    @Override  
    public void addResourceHandlers(ResourceHandlerRegistry registry) { 
        // Find all "/js/..." inside the "/js" package... 
        registry.addResourceHandler("/js/**")  
                .addResourceLocations("/js/")  
                .resourceChain(true)  
                // ... serve ".gz" or ".br" if requested  
                .addResolver(new EncodedResourceResolver())  
                // ... otherwise fallback to the original asset
                .addResolver(new PathResourceResolver());  
    }
}

The EncodedResourceResolver will cleverly look up the asset based on the Accept-Encoding header. For example, if Accept-Encoding: gzip, deflate, br is set by the browser, then it will serve the .gz extension with the Content-Encoding: gzip set. Finally, we have to make sure that the servlet mapping is correct because the resource handlers will be matched relative to the servlet mapping. In the next snippet, we configure it to catch all requests.

public class WebInit implements WebApplicationInitializer {  
  
    @Override  
    public void onStartup(ServletContext container) {
		ServletRegistration.Dynamic dispatcher = container.addServlet("mvc-dispatcher", new DispatcherServlet(dispatcherContext));  
		dispatcher.setLoadOnStartup(1);  
		dispatcher.addMapping("/*");
}

Navigate to "Conclusion" Conclusion

It’s a huge game-changer. Our assets are substantial, ranging from 100KiB to even 1MiB. Being able to serve only a fraction of the asset is a lifesaver when considering bandwidth costs. This applies to both the client and the hosted server. For example, one of our JavaScript chunks weighs 133KiB. With the default compression settings, we can serve a 42KiB .gz or a 37KiB .br. Pure win! 🥳

Navigate to "Reference" Reference

https://www.baeldung.com/spring-mvc-static-resources