Languages
[Edit]
EN

Spring Boot 2 - example APR native library configuration with Tomcat 9 (Http11AprProtocol + upgrade to HTTP2)

6 points
Created by:
Kara
541

In this article, we would like to show how in simple way configure APR native library with Spring Boot 2 (Tomcat 9).

Spring Boot 2 with Tomcat 9 APR native libary.
Spring Boot 2 with Tomcat 9 APR native libary.

Motivation to use APR native libraries is better connection management performance.

 

Using the below configuration you can:

  • use APR native library with Tomcat 9 (under Spring Boot 2 application),
  • enable HTTPS (or HTTP),
  • enable HTTP2 support,
  • enable HTTP/HTTPS conpression (gzip or brotli),
  • use only *.pem certificate and private key when APR is enabled.

Repository: GutHub

 

Used software

The application was run using:

 

APR installation

Under Debian (or: Ubuntu)

Run the command:

apt-get install libtcnative-1   # or:  apt-get install libapr1-dev libssl-dev
                                # or:  apt-get install libapr1.0-dev libssl-dev

Uder Windows

  1. download native libaries (download link 1, download link 2 or direct link)
  2. extract tomcat-native-2.0.3-openssl-3.0.8-win32-bin.zip archive,
  3. paste extracted directory into C:\Program Files directory (or: C:\Program Files (x86)).

 

Project configuration

It is necessary to add additional configurations to the application.properties file and paste TomcatAprConfig.java file into your web application configurations.

application.properties file:

# ...


server.tomcat.apr.enabled=true

server.ssl.enabled=true
server.http2.enabled=true


#   Required for Http11AprProtocol with *.pem files (only if server.tomcat.apr.enabled=true)
#
#   Use the OpenSSL command to generate own key store:
#       openssl.exe req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_certificate.pem -days 365 -nodes -subj '/CN=localhost'
#
#
server.ssl.public-certificate=classpath:certificates/public_certificate.pem
server.ssl.private-key=classpath:certificates/private_key.pem


#   Required for Http11NioProtocol with JKS (only if server.tomcat.apr.enabled=false)
#
#   Use the Java CLI command to generate own key store:
#       keytool -genkey -keyalg RSA -alias my-application -keystore keystore.jks -storepass P@$$word -validity 3650 -keysize 2048
#
#
# server.ssl.key-store=classpath:certificates/keystore.jks
# server.ssl.key-store-password=P@$$word


#   Required for Http11NioProtocol with P12 (only if server.tomcat.apr.enabled=false)
#
#   Use the OpenSSL command to convert *.pem files to *.p12 file:
#       openssl pkcs12 -export -in public_certificate.pem -inkey private_key.pem -out keystore.p12 -name my-application
#
#
# server.ssl.key-store-type=PKCS12
# server.ssl.key-store=classpath:certificates/keystore.p12
# server.ssl.key-store-password=P@$$word
# server.ssl.key-alias=my-application


server.compression.enabled=true
server.compression.min-response-size=2048
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml


# ...

 

TomcatAprConfig.java file:

package com.example.config;

import org.apache.catalina.LifecycleListener;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.coyote.http11.Http11AprProtocol;
import org.apache.coyote.http2.Http2Protocol;
import org.apache.tomcat.util.buf.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.Ssl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Configuration
@ConditionalOnProperty(name = "server.tomcat.apr.enabled", havingValue = "true")
public class TomcatAprConfig {

    // properties

    @Value("${server.ssl.enabled:#{true}}")
    private Boolean sslEnabled;

    @Value("${server.http2.enabled:#{false}}")
    private Boolean http2Enabled;

    @Value("${server.ssl.key-store-type:#{null}}")
    private String keyStoreType;

    @Value("${server.ssl.key-store:#{null}}")
    private String keyStorePath;

    @Value("${server.ssl.key-store-password:#{null}}")
    private String keyStorePassword;

    @Value("${server.ssl.key-alias:#{null}}")
    private String keyAlias;

    @Value("${server.ssl.public-certificate:#{null}}")
    private String publicCertificatePath;

    @Value("${server.ssl.private-key:#{null}}")
    private String privateKeyPath;

    @Value("${server.compression.enabled:#{false}}")
    private Boolean compressionEnabled;

    @Value("${server.compression.min-response-size:#{2048}}")
    private Integer compressionMinSize;

    @Value("${server.compression.mime-types:text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml}")
    private String compressionMimeTypes;

    // public methods

    @Bean(name = "tomcatServletWebServerFactory")
    public TomcatServletWebServerFactory createServerFactory(ServerProperties serverProperties, ResourceLoader resourceLoader) {
        // This configuration is used to improve client connection performance by using native libraries.
        if (this.keyStoreType != null || this.keyStorePath != null || this.keyStorePassword != null || this.keyAlias != null) {
            throw new RuntimeException("Tomcat APR configuration can not be uses with server.ssl.key-store-type, server.ssl.key-store, server.ssl.key-store-password and server.ssl.key-alias properties (Use server.ssl.public-certificate and server.ssl.private-key properties with PEM format)");
        }
        TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory() {
            @Override
            public Ssl getSsl() {
                return null;  // null returned to stop the default SSL customizer
            }
        };
        serverFactory.setProtocol("org.apache.coyote.http11.Http11AprProtocol");  // the protocol that will enable APR
        serverFactory.setContextLifecycleListeners(this.createLifecycleListeners());
        serverFactory.setTomcatConnectorCustomizers(this.createConnectorCustomizers(serverProperties, resourceLoader));
        return serverFactory;
    }

    // private methods

    private List<AprLifecycleListener> createLifecycleListeners() {
        AprLifecycleListener lifecycleListener = new AprLifecycleListener();
        return Collections.singletonList(lifecycleListener);
    }

    private List<TomcatConnectorCustomizer> createConnectorCustomizers(ServerProperties serverProperties, ResourceLoader resourceLoader) {
        TomcatConnectorCustomizer connectorCustomizer = tomcatConnector -> {
            Http11AprProtocol aprProtocol = (Http11AprProtocol) tomcatConnector.getProtocolHandler();
            // we are able to force to disable even server.ssl.* properties are defined
            if (this.sslEnabled) {
                if (this.publicCertificatePath == null) {
                    throw new RuntimeException("Public certificate path is not configured.");
                }
                if (this.privateKeyPath == null) {
                    throw new RuntimeException("Private key  path is not configured.");
                }
                Ssl connectionSsl = serverProperties.getSsl();
                String[] connectionCiphers = connectionSsl.getCiphers();
                String[] connectionProtocols = connectionSsl.getEnabledProtocols();
                tomcatConnector.setSecure(true);
                tomcatConnector.setScheme("https");
                aprProtocol.setSSLEnabled(true);
                if (connectionProtocols != null && connectionProtocols.length > 0) {
                    aprProtocol.setSslEnabledProtocols(this.joinStrings(connectionProtocols));
                }
                if (connectionCiphers != null && connectionCiphers.length > 0) {
                    aprProtocol.setCiphers(this.joinStrings(connectionCiphers));
                }
                try {
                    aprProtocol.setSSLCertificateFile(this.resolvePath(resourceLoader, this.publicCertificatePath));
                    aprProtocol.setSSLCertificateKeyFile(this.resolvePath(resourceLoader, this.privateKeyPath));
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
                if (this.compressionEnabled) {
                    if (this.http2Enabled) {
                        Http2Protocol http2Protocol = new Http2Protocol();
                        http2Protocol.setCompression("on");
                        http2Protocol.setCompressibleMimeType(this.compressionMimeTypes);
                        http2Protocol.setCompressionMinSize(this.compressionMinSize);
                        tomcatConnector.addUpgradeProtocol(http2Protocol);
                    } else {
                        aprProtocol.setCompression("on");
                        aprProtocol.setCompressibleMimeType(this.compressionMimeTypes);
                        aprProtocol.setCompressionMinSize(this.compressionMinSize);
                    }
                } else {
                    if (this.http2Enabled) {
                        tomcatConnector.addUpgradeProtocol(new Http2Protocol());
                    }
                }
            } else {
                tomcatConnector.setSecure(false);
                tomcatConnector.setScheme("http");
                aprProtocol.setSSLEnabled(false);
                if (this.compressionEnabled) {
                    aprProtocol.setCompression("on");
                    aprProtocol.setCompressibleMimeType(this.compressionMimeTypes);
                    aprProtocol.setCompressionMinSize(this.compressionMinSize);
                }
            }
        };
        return Collections.singletonList(connectorCustomizer);
    }

    private String joinStrings(String[] strings) {
        List<String> list = Arrays.asList(strings);
        return StringUtils.join(list, ',');
    }

    private String resolvePath(ResourceLoader loader, String path) throws IOException {
        Resource resource = loader.getResource(path);
        try {
            File file = resource.getFile();
            return file.getAbsolutePath();
        } catch (Exception ex) {
            throw new IOException("Absolute '" + path + "' path resolving error.", ex);
        }
    }
}

 

Application building

Run the command:

mvn clean package

Note: consider to use absolute paths to certificates and do not store them inside *.jar file.

 

Application running

Depending on operating system we need to indicate where APR libaries are installed.

Under Debian (or: Ubuntu)

Run the command:

java -Djava.library.path="/usr/lib/x86_64-linux-gnu" -jar my-application.jar

Note: be sure the APR is installed in /usr/lib/x86_64-linux-gnu directory.

Uder Windows

Run the command:

java -Djava.library.path="C:\\Program Files\\tomcat-native-2.0.3-openssl-3.0.8-win32-bin\\bin\\x64" -jar my-application.jar

Note: use C:\\Program Files\\tomcat-native-2.0.3-openssl-3.0.8-win32-bin\\bin path under x86.

 

References

  1. Apache Portable Runtime (APR) based Native library for Tomcat
  2. http://tomcat.apache.org/native-doc
Donate to Dirask
Our content is created by volunteers - like Wikipedia. If you think, the things we do are good, donate us. Thanks!
Join to our subscribers to be up to date with content, news and offers.
Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

❤️💻 🙂

Join