You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by jg...@apache.org on 2022/06/03 15:54:34 UTC

[nifi] branch main updated: NIFI-9804 Added HTTP/2 support to Application Server

This is an automated email from the ASF dual-hosted git repository.

jgresock pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 4b655ecb68 NIFI-9804 Added HTTP/2 support to Application Server
4b655ecb68 is described below

commit 4b655ecb688685016f67fba7a9386683f8429df6
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Wed Jun 1 20:46:40 2022 -0500

    NIFI-9804 Added HTTP/2 support to Application Server
    
    - Added nifi.web.https.application.protocols property
    - Set default protocol to HTTP/1.1 and provided documentation for enabling HTTP/2
    - Changed StandardALPNProcessor handshakeFailed log to debug
    
    Signed-off-by: Joe Gresock <jg...@gmail.com>
    This closes #6093.
---
 .../connector/StandardServerConnectorFactory.java  |   8 +-
 .../connector/alpn/StandardALPNProcessor.java      |   2 +-
 .../java/org/apache/nifi/util/NiFiProperties.java  |  12 +
 .../src/main/asciidoc/administration-guide.adoc    |   7 +
 .../nifi-framework/nifi-resources/pom.xml          |   1 +
 .../src/main/resources/conf/nifi.properties        |   1 +
 .../nifi-framework/nifi-web/nifi-jetty/pom.xml     |  13 +
 .../org/apache/nifi/web/server/JettyServer.java    | 362 ++++-----------------
 .../connector/FrameworkServerConnectorFactory.java | 199 +++++++++++
 .../apache/nifi/web/server/JettyServerTest.java    | 181 -----------
 .../FrameworkServerConnectorFactoryTest.java       | 195 +++++++++++
 11 files changed, 490 insertions(+), 491 deletions(-)

diff --git a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactory.java b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactory.java
index e670ac71ea..22679ee81a 100644
--- a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactory.java
+++ b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactory.java
@@ -162,7 +162,11 @@ public class StandardServerConnectorFactory implements ServerConnectorFactory {
         this.applicationLayerProtocols = applicationLayerProtocols;
     }
 
-    private HttpConfiguration getHttpConfiguration() {
+    protected Server getServer() {
+        return server;
+    }
+
+    protected HttpConfiguration getHttpConfiguration() {
         final HttpConfiguration httpConfiguration = new HttpConfiguration();
 
         if (sslContext != null) {
@@ -177,7 +181,7 @@ public class StandardServerConnectorFactory implements ServerConnectorFactory {
         return httpConfiguration;
     }
 
-    private SslContextFactory.Server getSslContextFactory() {
+    protected SslContextFactory.Server getSslContextFactory() {
         final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
         sslContextFactory.setSslContext(sslContext);
         sslContextFactory.setNeedClientAuth(needClientAuth);
diff --git a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/StandardALPNProcessor.java b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/StandardALPNProcessor.java
index 0c8825226d..2e813f98ff 100644
--- a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/StandardALPNProcessor.java
+++ b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/StandardALPNProcessor.java
@@ -117,7 +117,7 @@ public class StandardALPNProcessor implements ALPNProcessor.Server, SslHandshake
          */
         @Override
         public void handshakeFailed(final Event event, final Throwable failure) {
-            logger.warn("Connection Remote Address [{}] Handshake Failed", serverConnection.getEndPoint().getRemoteAddress(), failure);
+            logger.debug("Connection Remote Address [{}] Handshake Failed", serverConnection.getEndPoint().getRemoteAddress(), failure);
         }
     }
 }
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index 78b9ae5af9..995c720271 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -225,6 +225,7 @@ public class NiFiProperties extends ApplicationProperties {
     public static final String WEB_HTTPS_PORT = "nifi.web.https.port";
     public static final String WEB_HTTPS_PORT_FORWARDING = "nifi.web.https.port.forwarding";
     public static final String WEB_HTTPS_HOST = "nifi.web.https.host";
+    public static final String WEB_HTTPS_APPLICATION_PROTOCOLS = "nifi.web.https.application.protocols";
     public static final String WEB_HTTPS_CIPHERSUITES_INCLUDE = "nifi.web.https.ciphersuites.include";
     public static final String WEB_HTTPS_CIPHERSUITES_EXCLUDE = "nifi.web.https.ciphersuites.exclude";
     public static final String WEB_HTTPS_NETWORK_INTERFACE_PREFIX = "nifi.web.https.network.interface.";
@@ -334,6 +335,7 @@ public class NiFiProperties extends ApplicationProperties {
     public static final String DEFAULT_LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE = "conf/login-identity-providers.xml";
     public static final Integer DEFAULT_REMOTE_INPUT_PORT = null;
     public static final Path DEFAULT_TEMPLATE_DIRECTORY = Paths.get("conf", "templates");
+    private static final String DEFAULT_WEB_HTTPS_APPLICATION_PROTOCOLS = "http/1.1";
     public static final int DEFAULT_WEB_THREADS = 200;
     public static final String DEFAULT_WEB_MAX_HEADER_SIZE = "16 KB";
     public static final String DEFAULT_WEB_WORKING_DIR = "./work/jetty";
@@ -705,6 +707,16 @@ public class NiFiProperties extends ApplicationProperties {
         }
     }
 
+    /**
+     * Get Web HTTPS Application Protocols defaults to HTTP/1.1
+     *
+     * @return Set of configured HTTPS Application Protocols
+     */
+    public Set<String> getWebHttpsApplicationProtocols() {
+        final String protocols = getProperty(WEB_HTTPS_APPLICATION_PROTOCOLS, DEFAULT_WEB_HTTPS_APPLICATION_PROTOCOLS);
+        return Arrays.stream(protocols.split("\\s+")).collect(Collectors.toSet());
+    }
+
     public String getWebMaxHeaderSize() {
         return getProperty(WEB_MAX_HEADER_SIZE, DEFAULT_WEB_MAX_HEADER_SIZE);
     }
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index d61b2134ef..11df488013 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -3884,6 +3884,13 @@ For example, to provide two additional network interfaces, a user could also spe
 `nifi.web.https.network.interface.eth1=eth1` +
 +
 Providing three total network interfaces, including  `nifi.web.https.network.interface.default`.
+|`nifi.web.https.application.protocols`|The space-separated list of application protocols supported when running with HTTPS enabled.
+
+The default value is `http/1.1`.
+
+The value can be set to `h2 http/1.1` to support Application Layer Protocol Negotiation (ALPN) for HTTP/2 or HTTP/1.1 based on client capabilities.
+
+The value can be set to `h2` to require HTTP/2 and disable HTTP/1.1.
 |`nifi.web.jetty.working.directory`|The location of the Jetty working directory. The default value is `./work/jetty`.
 |`nifi.web.jetty.threads`|The number of Jetty threads. The default value is `200`.
 |`nifi.web.max.header.size`|The maximum size allowed for request and response headers. The default value is `16 KB`.
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
index 0827a19fdb..8bd65c08c4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
@@ -126,6 +126,7 @@
         <nifi.web.https.host>127.0.0.1</nifi.web.https.host>
         <nifi.web.https.port>8443</nifi.web.https.port>
         <nifi.web.https.network.interface.default />
+        <nifi.web.https.application.protocols>http/1.1</nifi.web.https.application.protocols>
         <nifi.jetty.work.dir>./work/jetty</nifi.jetty.work.dir>
         <nifi.web.jetty.threads>200</nifi.web.jetty.threads>
         <nifi.web.max.header.size>16 KB</nifi.web.max.header.size>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index 1426b27280..c0a593d23a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -151,6 +151,7 @@ nifi.web.http.network.interface.default=${nifi.web.http.network.interface.defaul
 nifi.web.https.host=${nifi.web.https.host}
 nifi.web.https.port=${nifi.web.https.port}
 nifi.web.https.network.interface.default=${nifi.web.https.network.interface.default}
+nifi.web.https.application.protocols=${nifi.web.https.application.protocols}
 nifi.web.jetty.working.directory=${nifi.jetty.work.dir}
 nifi.web.jetty.threads=${nifi.web.jetty.threads}
 nifi.web.max.header.size=${nifi.web.max.header.size}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
index 07b052e923..5483945edd 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
@@ -152,6 +152,19 @@
             <artifactId>nifi-ui-extension</artifactId>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-jetty-configuration</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-server</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-web-security</artifactId>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
index f4d794ec8a..199b10e890 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
@@ -51,37 +51,29 @@ import org.apache.nifi.nar.NarProvider;
 import org.apache.nifi.nar.NarThreadContextClassLoader;
 import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
 import org.apache.nifi.nar.StandardNarLoader;
-import org.apache.nifi.processor.DataUnit;
-import org.apache.nifi.security.util.KeyStoreUtils;
-import org.apache.nifi.security.util.TlsConfiguration;
 import org.apache.nifi.security.util.TlsException;
 import org.apache.nifi.services.FlowService;
 import org.apache.nifi.ui.extension.UiExtension;
 import org.apache.nifi.ui.extension.UiExtensionMapping;
-import org.apache.nifi.util.FormatUtils;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.ContentAccess;
 import org.apache.nifi.web.NiFiWebConfigurationContext;
 import org.apache.nifi.web.UiExtensionType;
+import org.apache.nifi.web.server.connector.FrameworkServerConnectorFactory;
 import org.apache.nifi.web.server.filter.FilterParameter;
 import org.apache.nifi.web.server.filter.RequestFilterProvider;
 import org.apache.nifi.web.server.filter.RestApiRequestFilterProvider;
 import org.apache.nifi.web.server.filter.StandardRequestFilterProvider;
 import org.apache.nifi.web.server.log.RequestLogProvider;
 import org.apache.nifi.web.server.log.StandardRequestLogProvider;
-import org.apache.nifi.web.server.util.TrustStoreScanner;
 import org.eclipse.jetty.annotations.AnnotationConfiguration;
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.RequestLog;
-import org.eclipse.jetty.server.SecureRequestCustomizer;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.SslConnectionFactory;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.server.handler.HandlerCollection;
 import org.eclipse.jetty.server.handler.HandlerList;
@@ -89,8 +81,6 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
 import org.eclipse.jetty.servlet.DefaultServlet;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.ssl.KeyStoreScanner;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.webapp.Configuration;
 import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
@@ -111,8 +101,8 @@ import java.io.File;
 import java.io.FileFilter;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.io.UncheckedIOException;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
@@ -130,7 +120,6 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
@@ -164,17 +153,11 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
     private static final String DEFAULT_NAR_PROVIDER_POLL_INTERVAL = "5 min";
     private static final String DEFAULT_NAR_PROVIDER_CONFLICT_RESOLUTION = "IGNORE";
 
-    private static final int DOS_FILTER_REJECT_REQUEST = -1;
-
     private static final FileFilter WAR_FILTER = pathname -> {
         final String nameToTest = pathname.getName().toLowerCase();
         return nameToTest.endsWith(".war") && pathname.isFile();
     };
 
-    // property parsing util
-    private static final String REGEX_SPLIT_PROPERTY = ",\\s*";
-    protected static final String JOIN_ARRAY = ", ";
-
     private Server server;
     private NiFiProperties props;
 
@@ -184,7 +167,6 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
     private NarAutoLoader narAutoLoader;
     private ExternalResourceProviderService narProviderService;
     private DiagnosticsFactory diagnosticsFactory;
-    private SslContextFactory.Server sslContextFactory;
     private DecommissionTask decommissionTask;
     private StatusHistoryDumpFactory statusHistoryDumpFactory;
 
@@ -342,7 +324,6 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
         return gzip(webAppContextHandlers);
     }
 
-
     @Override
     public void loadExtensionUis(final Set<Bundle> bundles) {
         // Find and load any WARs contained within the set of bundles...
@@ -661,7 +642,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
     private void addDocsServlets(WebAppContext docsContext) {
         try {
             // Load the nifi/docs directory
-            final File docsDir = getDocsDir("docs");
+            final File docsDir = getDocsDir();
 
             // load the component documentation working directory
             final File componentDocsDirPath = props.getComponentDocumentationWorkingDirectory();
@@ -705,10 +686,10 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
      * is that the documentation links under the 'General' portion of the help
      * page will not be accessible, but at least the process will be running.
      *
-     * @param docsDirectory Name of documentation directory in installation directory.
      * @return A File object to the documentation directory; else startUpFailure called.
      */
-    private File getDocsDir(final String docsDirectory) {
+    private File getDocsDir() {
+        final String docsDirectory = "docs";
         File docsDir;
         try {
             docsDir = Paths.get(docsDirectory).toRealPath().toFile();
@@ -748,268 +729,43 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
         return webApiDocsDir;
     }
 
-    private void configureConnectors(final Server server) throws ServerConfigurationException {
-        // create the http configuration
-        final HttpConfiguration httpConfiguration = new HttpConfiguration();
-        final int headerSize = DataUnit.parseDataSize(props.getWebMaxHeaderSize(), DataUnit.B).intValue();
-        httpConfiguration.setRequestHeaderSize(headerSize);
-        httpConfiguration.setResponseHeaderSize(headerSize);
-        httpConfiguration.setSendServerVersion(props.shouldSendServerVersion());
-
-        // Check if both HTTP and HTTPS connectors are configured and fail if both are configured
-        if (bothHttpAndHttpsConnectorsConfigured(props)) {
-            logger.error("NiFi only supports one mode of HTTP or HTTPS operation, not both simultaneously. " +
-                    "Check the nifi.properties file and ensure that either the HTTP hostname and port or the HTTPS hostname and port are empty");
-            startUpFailure(new IllegalStateException("Only one of the HTTP and HTTPS connectors can be configured at one time"));
-        }
-
-        if (props.getSslPort() != null) {
-            configureHttpsConnector(server, httpConfiguration);
-        } else if (props.getPort() != null) {
-            configureHttpConnector(server, httpConfiguration);
-        } else {
-            logger.error("Neither the HTTP nor HTTPS connector was configured in nifi.properties");
-            startUpFailure(new IllegalStateException("Must configure HTTP or HTTPS connector"));
-        }
-    }
-
-    /**
-     * Configures an HTTPS connector and adds it to the server.
-     *
-     * @param server            the Jetty server instance
-     * @param httpConfiguration the configuration object for the HTTPS protocol settings
-     */
-    private void configureHttpsConnector(Server server, HttpConfiguration httpConfiguration) {
-        String hostname = props.getProperty(NiFiProperties.WEB_HTTPS_HOST);
-        final Integer port = props.getSslPort();
-        String connectorLabel = "HTTPS";
-        final Map<String, String> httpsNetworkInterfaces = props.getHttpsNetworkInterfaces();
-        ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> scc = (s, c) -> createUnconfiguredSslServerConnector(s, c, port);
-
-        configureGenericConnector(server, httpConfiguration, hostname, port, connectorLabel, httpsNetworkInterfaces, scc);
-
-        if (props.isSecurityAutoReloadEnabled()) {
-            configureSslContextFactoryReloading(server);
-        }
-    }
-
-    /**
-     * Configures a KeyStoreScanner and TrustStoreScanner at the configured reload intervals.  This will
-     * reload the SSLContextFactory if any changes are detected to the keystore or truststore.
-     *
-     * @param server The Jetty server
-     */
-    private void configureSslContextFactoryReloading(Server server) {
-        final int scanIntervalSeconds = Double.valueOf(FormatUtils.getPreciseTimeDuration(
-                props.getSecurityAutoReloadInterval(), TimeUnit.SECONDS))
-                .intValue();
-
-        final KeyStoreScanner keyStoreScanner = new KeyStoreScanner(sslContextFactory);
-        keyStoreScanner.setScanInterval(scanIntervalSeconds);
-        server.addBean(keyStoreScanner);
-
-        final TrustStoreScanner trustStoreScanner = new TrustStoreScanner(sslContextFactory);
-        trustStoreScanner.setScanInterval(scanIntervalSeconds);
-        server.addBean(trustStoreScanner);
-    }
-
-    /**
-     * Configures an HTTP connector and adds it to the server.
-     *
-     * @param server            the Jetty server instance
-     * @param httpConfiguration the configuration object for the HTTP protocol settings
-     */
-    private void configureHttpConnector(Server server, HttpConfiguration httpConfiguration) {
-        String hostname = props.getProperty(NiFiProperties.WEB_HTTP_HOST);
-        final Integer port = props.getPort();
-        String connectorLabel = "HTTP";
-        final Map<String, String> httpNetworkInterfaces = props.getHttpNetworkInterfaces();
-        ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> scc = (s, c) -> new ServerConnector(s, new HttpConnectionFactory(c));
-
-        configureGenericConnector(server, httpConfiguration, hostname, port, connectorLabel, httpNetworkInterfaces, scc);
-    }
-
-    /**
-     * Configures an HTTP(S) connector for the server given the provided parameters. The functionality between HTTP and HTTPS connectors is largely similar.
-     * Here the common behavior has been extracted into a shared method and the respective calling methods obtain the right values and a lambda function for the differing behavior.
-     *
-     * @param server                 the Jetty server instance
-     * @param configuration          the HTTP/HTTPS configuration instance
-     * @param hostname               the hostname from the nifi.properties file
-     * @param port                   the port to expose
-     * @param connectorLabel         used for log output (e.g. "HTTP" or "HTTPS")
-     * @param networkInterfaces      the map of network interfaces from nifi.properties
-     * @param serverConnectorCreator a function which accepts a {@code Server} and {@code HttpConnection} instance and returns a {@code ServerConnector}
-     */
-    private void configureGenericConnector(Server server, HttpConfiguration configuration, String hostname, Integer port, String connectorLabel, Map<String, String> networkInterfaces,
-                                           ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> serverConnectorCreator) {
-        if (port < 0 || (int) Math.pow(2, 16) <= port) {
-            throw new ServerConfigurationException("Invalid " + connectorLabel + " port: " + port);
-        }
-
-        logger.info("Configuring Jetty for " + connectorLabel + " on port: " + port);
-
-        final List<Connector> serverConnectors = new ArrayList<>();
-
-        // Calculate Idle Timeout as twice the auto-refresh interval. This ensures that even with some variance in timing,
-        // we are able to avoid closing connections from users' browsers most of the time. This can make a significant difference
-        // in HTTPS connections, as each HTTPS connection that is established must perform the SSL handshake.
-        final String autoRefreshInterval = props.getAutoRefreshInterval();
-        final long autoRefreshMillis = autoRefreshInterval == null ? 30000L : FormatUtils.getTimeDuration(autoRefreshInterval, TimeUnit.MILLISECONDS);
-        final long idleTimeout = autoRefreshMillis * 2;
-
-        // If the interfaces collection is empty or each element is empty
-        if (networkInterfaces.isEmpty() || networkInterfaces.values().stream().filter(value -> StringUtils.isNotBlank(value)).collect(Collectors.toList()).isEmpty()) {
-            final ServerConnector serverConnector = serverConnectorCreator.create(server, configuration);
-
-            // Set host and port
-            if (StringUtils.isNotBlank(hostname)) {
-                serverConnector.setHost(hostname);
-            }
-            serverConnector.setPort(port);
-            serverConnector.setIdleTimeout(idleTimeout);
-            serverConnectors.add(serverConnector);
-        } else {
-            // Add connectors for all IPs from network interfaces
-            serverConnectors.addAll(new ArrayList<>(networkInterfaces.values().stream().map(ifaceName -> {
-                NetworkInterface iface = null;
-                try {
-                    iface = NetworkInterface.getByName(ifaceName);
-                } catch (SocketException e) {
-                    logger.error("Unable to get network interface by name {}", ifaceName, e);
-                }
-                if (iface == null) {
-                    logger.warn("Unable to find network interface named {}", ifaceName);
+    private void configureConnectors(final Server server) {
+        try {
+            final FrameworkServerConnectorFactory serverConnectorFactory = new FrameworkServerConnectorFactory(server, props);
+            final Map<String, String> interfaces = props.isHTTPSConfigured() ? props.getHttpsNetworkInterfaces() : props.getHttpNetworkInterfaces();
+            final Set<String> interfaceNames = interfaces.values().stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
+            // Add Server Connectors based on configured Network Interface Names
+            if (interfaceNames.isEmpty()) {
+                final ServerConnector serverConnector = serverConnectorFactory.getServerConnector();
+                final String host = props.isHTTPSConfigured() ? props.getProperty(NiFiProperties.WEB_HTTPS_HOST) : props.getProperty(NiFiProperties.WEB_HTTP_HOST);
+                if (StringUtils.isNotBlank(host)) {
+                    serverConnector.setHost(host);
                 }
-                return iface;
-            }).filter(Objects::nonNull).flatMap(iface -> Collections.list(iface.getInetAddresses()).stream())
-                    .map(inetAddress -> {
-                        final ServerConnector serverConnector = serverConnectorCreator.create(server, configuration);
-
-                        // Set host and port
-                        serverConnector.setHost(inetAddress.getHostAddress());
-                        serverConnector.setPort(port);
-                        serverConnector.setIdleTimeout(idleTimeout);
-
-                        return serverConnector;
-                    }).collect(Collectors.toList())));
-        }
-        // Add all connectors
-        serverConnectors.forEach(server::addConnector);
-    }
-
-    /**
-     * Returns true if there are configured properties for both HTTP and HTTPS connectors (specifically port because the hostname can be left blank in the HTTP connector).
-     * Prints a warning log message with the relevant properties.
-     *
-     * @param props the NiFiProperties
-     * @return true if both ports are present
-     */
-    static boolean bothHttpAndHttpsConnectorsConfigured(NiFiProperties props) {
-        Integer httpPort = props.getPort();
-        String httpHostname = props.getProperty(NiFiProperties.WEB_HTTP_HOST);
-
-        Integer httpsPort = props.getSslPort();
-        String httpsHostname = props.getProperty(NiFiProperties.WEB_HTTPS_HOST);
-
-        if (httpPort != null && httpsPort != null) {
-            logger.warn("Both the HTTP and HTTPS connectors are configured in nifi.properties. Only one of these connectors should be configured. See the NiFi Admin Guide for more details");
-            logger.warn("HTTP connector:   http://" + httpHostname + ":" + httpPort);
-            logger.warn("HTTPS connector: https://" + httpsHostname + ":" + httpsPort);
-            return true;
-        }
-
-        return false;
-    }
-
-    private ServerConnector createUnconfiguredSslServerConnector(Server server, HttpConfiguration httpConfiguration, int port) {
-        // add some secure config
-        final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
-        httpsConfiguration.setSecureScheme("https");
-        httpsConfiguration.setSecurePort(port);
-        httpsConfiguration.setSendServerVersion(props.shouldSendServerVersion());
-        httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
-
-        // build the connector
-        return new ServerConnector(server,
-                new SslConnectionFactory(createSslContextFactory(), "http/1.1"),
-                new HttpConnectionFactory(httpsConfiguration));
-    }
-
-    private SslContextFactory createSslContextFactory() {
-        final SslContextFactory.Server serverContextFactory = new SslContextFactory.Server();
-        configureSslContextFactory(serverContextFactory, props);
-        this.sslContextFactory = serverContextFactory;
-        return serverContextFactory;
-    }
-
-    protected static void configureSslContextFactory(SslContextFactory.Server contextFactory, NiFiProperties props) {
-        // Explicitly exclude legacy TLS protocol versions
-        contextFactory.setIncludeProtocols(TlsConfiguration.getCurrentSupportedTlsProtocolVersions());
-        contextFactory.setExcludeProtocols("TLS", "TLSv1", "TLSv1.1", "SSL", "SSLv2", "SSLv2Hello", "SSLv3");
-
-        // on configuration, replace default application cipher suites with those configured
-        final String includeCipherSuitesProps = props.getProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE);
-        if (StringUtils.isNotEmpty(includeCipherSuitesProps)) {
-            final String[] includeCipherSuites = includeCipherSuitesProps.split(REGEX_SPLIT_PROPERTY);
-            logger.info("Setting include cipher suites from configuration; parsed property = [{}].",
-                    StringUtils.join(includeCipherSuites, JOIN_ARRAY));
-            contextFactory.setIncludeCipherSuites(includeCipherSuites);
-        }
-        final String excludeCipherSuitesProps = props.getProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE);
-        if (StringUtils.isNotEmpty(excludeCipherSuitesProps)) {
-            final String[] excludeCipherSuites = excludeCipherSuitesProps.split(REGEX_SPLIT_PROPERTY);
-            logger.info("Setting exclude cipher suites from configuration; parsed property = [{}].",
-                    StringUtils.join(excludeCipherSuites, JOIN_ARRAY));
-            contextFactory.setExcludeCipherSuites(excludeCipherSuites);
-        }
-
-        // require client auth when not supporting login, Kerberos service, or anonymous access
-        if (props.isClientAuthRequiredForRestApi()) {
-            contextFactory.setNeedClientAuth(true);
-        } else {
-            contextFactory.setWantClientAuth(true);
-        }
-
-        /* below code sets JSSE system properties when values are provided */
-        // keystore properties
-        if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_KEYSTORE))) {
-            contextFactory.setKeyStorePath(props.getProperty(NiFiProperties.SECURITY_KEYSTORE));
-        }
-        String keyStoreType = props.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE);
-        if (StringUtils.isNotBlank(keyStoreType)) {
-            contextFactory.setKeyStoreType(keyStoreType);
-            String keyStoreProvider = KeyStoreUtils.getKeyStoreProvider(keyStoreType);
-            if (StringUtils.isNoneEmpty(keyStoreProvider)) {
-                contextFactory.setKeyStoreProvider(keyStoreProvider);
-            }
-        }
-        final String keystorePassword = props.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD);
-        final String keyPassword = props.getProperty(NiFiProperties.SECURITY_KEY_PASSWD);
-        if (StringUtils.isNotBlank(keystorePassword)) {
-            // if no key password was provided, then assume the keystore password is the same as the key password.
-            final String defaultKeyPassword = (StringUtils.isBlank(keyPassword)) ? keystorePassword : keyPassword;
-            contextFactory.setKeyStorePassword(keystorePassword);
-            contextFactory.setKeyManagerPassword(defaultKeyPassword);
-        } else if (StringUtils.isNotBlank(keyPassword)) {
-            // since no keystore password was provided, there will be no keystore integrity check
-            contextFactory.setKeyManagerPassword(keyPassword);
-        }
-
-        // truststore properties
-        if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE))) {
-            contextFactory.setTrustStorePath(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE));
-        }
-        String trustStoreType = props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE);
-        if (StringUtils.isNotBlank(trustStoreType)) {
-            contextFactory.setTrustStoreType(trustStoreType);
-            String trustStoreProvider = KeyStoreUtils.getKeyStoreProvider(trustStoreType);
-            if (StringUtils.isNoneEmpty(trustStoreProvider)) {
-                contextFactory.setTrustStoreProvider(trustStoreProvider);
+                server.addConnector(serverConnector);
+            } else {
+                interfaceNames.stream()
+                        // Map interface name properties to Network Interfaces
+                        .map(interfaceName -> {
+                            try {
+                                return NetworkInterface.getByName(interfaceName);
+                            } catch (final SocketException e) {
+                                throw new UncheckedIOException(String.format("Network Interface [%s] not found", interfaceName), e);
+                            }
+                        })
+                        // Map Network Interfaces to host addresses
+                        .filter(Objects::nonNull)
+                        .flatMap(networkInterface -> Collections.list(networkInterface.getInetAddresses()).stream())
+                        .map(InetAddress::getHostAddress)
+                        // Map host addresses to Server Connectors
+                        .map(host -> {
+                            final ServerConnector serverConnector = serverConnectorFactory.getServerConnector();
+                            serverConnector.setHost(host);
+                            return serverConnector;
+                        })
+                        .forEach(server::addConnector);
             }
-        }
-        if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD))) {
-            contextFactory.setTrustStorePassword(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD));
+        } catch (final Throwable e) {
+            startUpFailure(e);
         }
     }
 
@@ -1123,7 +879,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
                     logger.info("Loading Flow...");
 
                     ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(webApiContext.getServletContext());
-                    flowService = ctx.getBean("flowService", FlowService.class);
+                    flowService = Objects.requireNonNull(ctx).getBean("flowService", FlowService.class);
 
                     // start and load the flow
                     flowService.start();
@@ -1157,7 +913,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
         final Set<String> externalSourceNames = props.getDirectSubsequentTokens(providerPropertyPrefix);
 
         for(final String externalSourceName : externalSourceNames) {
-            logger.info("External resource provider \'{}\' found in configuration", externalSourceName);
+            logger.info("External resource provider '{}' found in configuration", externalSourceName);
 
             final String providerClass = props.getProperty(providerPropertyPrefix + externalSourceName + "." + NAR_PROVIDER_IMPLEMENTATION_PROPERTY);
             final String providerId = UUID.randomUUID().toString();
@@ -1174,6 +930,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
      * In case the provider class is not an implementation of {@code ExternalResourceProvider} the method tries to instantiate it as a {@code NarProvider}. {@code NarProvider} instances
      * are wrapped into an adapter in order to envelope the support.
      */
+    @SuppressWarnings("deprecation")
     private ExternalResourceProvider createProviderInstance(
             final ExtensionManager extensionManager,
             final String providerClass,
@@ -1185,7 +942,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
         try {
             provider = NarThreadContextClassLoader.createInstance(extensionManager, providerClass, ExternalResourceProvider.class, props, providerId);
         } catch (final ClassCastException e) {
-            logger.warn("Class {} does not implement \"ExternalResourceProvider\" falling back to \"NarProvider\"");
+            logger.warn("Class {} does not implement ExternalResourceProvider falling back to NarProvider", providerClass);
             provider = new NarProviderAdapter(NarThreadContextClassLoader.createInstance(extensionManager, providerClass, NarProvider.class, props, providerId));
         }
 
@@ -1259,11 +1016,9 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
                     hosts.add(serverConnector.getHost());
                 } else {
                     Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
-                    if (networkInterfaces != null) {
-                        for (NetworkInterface networkInterface : Collections.list(networkInterfaces)) {
-                            for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) {
-                                hosts.add(inetAddress.getHostAddress());
-                            }
+                    for (NetworkInterface networkInterface : Collections.list(networkInterfaces)) {
+                        for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) {
+                            hosts.add(inetAddress.getHostAddress());
                         }
                     }
                 }
@@ -1384,24 +1139,17 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
     private static class ThreadDumpDiagnosticsFactory implements DiagnosticsFactory {
         @Override
         public DiagnosticsDump create(final boolean verbose) {
-            return new DiagnosticsDump() {
-                @Override
-                public void writeTo(final OutputStream out) throws IOException {
-                    final DiagnosticsDumpElement threadDumpElement = new ThreadDumpTask().captureDump(verbose);
-                    final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
-                    for (final String detail : threadDumpElement.getDetails()) {
-                        writer.write(detail);
-                        writer.write("\n");
-                    }
-
-                    writer.flush();
+            return out -> {
+                final DiagnosticsDumpElement threadDumpElement = new ThreadDumpTask().captureDump(verbose);
+                final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
+                for (final String detail : threadDumpElement.getDetails()) {
+                    writer.write(detail);
+                    writer.write("\n");
                 }
+
+                writer.flush();
             };
         }
     }
 }
 
-@FunctionalInterface
-interface ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> {
-    ServerConnector create(Server server, HttpConfiguration httpConfiguration);
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java
new file mode 100644
index 0000000000..3fe9153824
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.server.connector;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.nifi.jetty.configuration.connector.ApplicationLayerProtocol;
+import org.apache.nifi.jetty.configuration.connector.StandardServerConnectorFactory;
+import org.apache.nifi.processor.DataUnit;
+import org.apache.nifi.security.util.StandardTlsConfiguration;
+import org.apache.nifi.security.util.TlsConfiguration;
+import org.apache.nifi.security.util.TlsException;
+import org.apache.nifi.security.util.TlsPlatform;
+import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.server.util.TrustStoreScanner;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ssl.KeyStoreScanner;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import javax.net.ssl.SSLContext;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static org.apache.nifi.security.util.SslContextFactory.createSslContext;
+
+/**
+ * Framework extension of Server Connector Factory configures additional settings based on application properties
+ */
+public class FrameworkServerConnectorFactory extends StandardServerConnectorFactory {
+    private static final String DEFAULT_AUTO_REFRESH_INTERVAL = "30 s";
+
+    private static final int IDLE_TIMEOUT_MULTIPLIER = 2;
+
+    private static final String CIPHER_SUITE_SEPARATOR_PATTERN = ",\\s*";
+
+    private final int headerSize;
+
+    private final int idleTimeout;
+
+    private final Integer storeScanInterval;
+
+    private final String includeCipherSuites;
+
+    private final String excludeCipherSuites;
+
+    private TlsConfiguration tlsConfiguration;
+
+    private SslContextFactory.Server sslContextFactory;
+
+    /**
+     * Framework Server Connector Factory Constructor with required properties
+     *
+     * @param server Jetty Server
+     * @param properties NiFi Properties
+     */
+    public FrameworkServerConnectorFactory(final Server server, final NiFiProperties properties) {
+        super(server, getPort(properties));
+
+        includeCipherSuites = properties.getProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE);
+        excludeCipherSuites = properties.getProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE);
+        headerSize = DataUnit.parseDataSize(properties.getWebMaxHeaderSize(), DataUnit.B).intValue();
+        idleTimeout = getIdleTimeout(properties);
+
+        if (properties.isHTTPSConfigured()) {
+            tlsConfiguration = StandardTlsConfiguration.fromNiFiProperties(properties);
+            try {
+                final SSLContext sslContext = createSslContext(tlsConfiguration);
+                setSslContext(sslContext);
+            } catch (final TlsException e) {
+                throw new IllegalStateException("Invalid nifi.web.https configuration in nifi.properties", e);
+            }
+
+            if (properties.isClientAuthRequiredForRestApi()) {
+                setNeedClientAuth(true);
+            } else {
+                setWantClientAuth(true);
+            }
+
+            if (properties.isSecurityAutoReloadEnabled()) {
+                final String securityAutoReloadInterval = properties.getSecurityAutoReloadInterval();
+                final double reloadIntervalSeconds = FormatUtils.getPreciseTimeDuration(securityAutoReloadInterval, TimeUnit.SECONDS);
+                storeScanInterval = (int) reloadIntervalSeconds;
+            } else {
+                storeScanInterval = null;
+            }
+
+            setApplicationLayerProtocols(properties);
+
+            // Set Transport Layer Security Protocols based on platform configuration
+            setIncludeSecurityProtocols(TlsPlatform.getPreferredProtocols().toArray(new String[0]));
+        } else {
+            storeScanInterval = null;
+        }
+    }
+
+    /**
+     * Get HTTP Configuration with additional settings based on application properties
+     *
+     * @return HTTP Configuration
+     */
+    @Override
+    protected HttpConfiguration getHttpConfiguration() {
+        final HttpConfiguration httpConfiguration = super.getHttpConfiguration();
+
+        httpConfiguration.setRequestHeaderSize(headerSize);
+        httpConfiguration.setResponseHeaderSize(headerSize);
+        httpConfiguration.setIdleTimeout(idleTimeout);
+
+        return httpConfiguration;
+    }
+
+    /**
+     * Get Jetty Server SSL Context Factory and reuse the same instance for multiple invocations
+     *
+     * @return Jetty Server SSL Context Factory
+     */
+    @Override
+    protected SslContextFactory.Server getSslContextFactory() {
+        if (sslContextFactory == null) {
+            sslContextFactory = super.getSslContextFactory();
+
+            if (StringUtils.isNotBlank(includeCipherSuites)) {
+                final String[] cipherSuites = getCipherSuites(includeCipherSuites);
+                sslContextFactory.setIncludeCipherSuites(cipherSuites);
+            }
+            if (StringUtils.isNotBlank(excludeCipherSuites)) {
+                final String[] cipherSuites = getCipherSuites(excludeCipherSuites);
+                sslContextFactory.setExcludeCipherSuites(cipherSuites);
+            }
+
+            if (storeScanInterval != null) {
+                sslContextFactory.setKeyStorePath(tlsConfiguration.getKeystorePath());
+                final KeyStoreScanner keyStoreScanner = new KeyStoreScanner(sslContextFactory);
+                keyStoreScanner.setScanInterval(storeScanInterval);
+                getServer().addBean(keyStoreScanner);
+
+                sslContextFactory.setTrustStorePath(tlsConfiguration.getTruststorePath());
+                final TrustStoreScanner trustStoreScanner = new TrustStoreScanner(sslContextFactory);
+                trustStoreScanner.setScanInterval(storeScanInterval);
+                getServer().addBean(trustStoreScanner);
+            }
+        }
+
+        return sslContextFactory;
+    }
+
+    private void setApplicationLayerProtocols(final NiFiProperties properties) {
+        final Set<String> protocols = properties.getWebHttpsApplicationProtocols();
+
+        final Set<ApplicationLayerProtocol> applicationLayerProtocols = Arrays.stream(ApplicationLayerProtocol.values())
+                .filter(
+                        applicationLayerProtocol -> protocols.contains(applicationLayerProtocol.getProtocol())
+                )
+                .collect(Collectors.toSet());
+        setApplicationLayerProtocols(applicationLayerProtocols);
+    }
+
+    private int getIdleTimeout(final NiFiProperties properties) {
+        final String autoRefreshInterval = StringUtils.defaultIfBlank(properties.getAutoRefreshInterval(), DEFAULT_AUTO_REFRESH_INTERVAL);
+        final double autoRefreshMilliseconds = FormatUtils.getPreciseTimeDuration(autoRefreshInterval, TimeUnit.MILLISECONDS);
+        return Math.multiplyExact((int) autoRefreshMilliseconds, IDLE_TIMEOUT_MULTIPLIER);
+    }
+
+    private String[] getCipherSuites(final String cipherSuitesProperty) {
+        return cipherSuitesProperty.split(CIPHER_SUITE_SEPARATOR_PATTERN);
+    }
+
+    private static int getPort(final NiFiProperties properties) {
+        final Integer httpsPort = properties.getSslPort();
+        final Integer httpPort = properties.getPort();
+
+        if (ObjectUtils.allNull(httpsPort, httpPort)) {
+            throw new IllegalStateException("Invalid port configuration in nifi.properties: Neither nifi.web.https.port nor nifi.web.http.port specified");
+        } else if (ObjectUtils.allNotNull(httpsPort, httpPort)) {
+            throw new IllegalStateException("Invalid port configuration in nifi.properties: Both nifi.web.https.port and nifi.web.http.port specified");
+        }
+
+        return ObjectUtils.defaultIfNull(httpsPort, httpPort);
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/JettyServerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/JettyServerTest.java
deleted file mode 100644
index 842826d908..0000000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/JettyServerTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.nifi.web.server;
-
-import static org.apache.nifi.security.util.KeyStoreUtils.SUN_PROVIDER_NAME;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.security.util.KeystoreType;
-import org.apache.nifi.util.NiFiProperties;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.Test;
-
-public class JettyServerTest {
-    @Test
-    public void testConfigureSslContextFactoryWithKeystorePasswordAndKeyPassword() {
-        // Expect that if we set both passwords, KeyStore password is used for KeyStore, Key password is used for Key Manager
-        String testKeystorePassword = "testKeystorePassword";
-        String testKeyPassword = "testKeyPassword";
-
-        final Map<String, String> addProps = new HashMap<>();
-        addProps.put(NiFiProperties.SECURITY_KEYSTORE_PASSWD, testKeystorePassword);
-        addProps.put(NiFiProperties.SECURITY_KEY_PASSWD, testKeyPassword);
-        NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
-        SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
-        JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
-        verify(mockSCF).setKeyStorePassword(testKeystorePassword);
-        verify(mockSCF).setKeyManagerPassword(testKeyPassword);
-    }
-
-    @Test
-    public void testConfigureSslContextFactoryWithKeyPassword() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
-        // Expect that with no KeyStore password, we will only need to set Key Manager Password
-        String testKeyPassword = "testKeyPassword";
-
-        final Map<String, String> addProps = new HashMap<>();
-        addProps.put(NiFiProperties.SECURITY_KEY_PASSWD, testKeyPassword);
-        NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
-        SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
-        JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
-        verify(mockSCF).setKeyManagerPassword(testKeyPassword);
-        verify(mockSCF, never()).setKeyStorePassword(anyString());
-    }
-
-    @Test
-    public void testConfigureSslContextFactoryWithKeystorePassword() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
-        // Expect that with no KeyPassword, we use the same one from the KeyStore
-        String testKeystorePassword = "testKeystorePassword";
-
-        final Map<String, String> addProps = new HashMap<>();
-        addProps.put(NiFiProperties.SECURITY_KEYSTORE_PASSWD, testKeystorePassword);
-        NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
-        SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
-        JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
-        verify(mockSCF).setKeyStorePassword(testKeystorePassword);
-        verify(mockSCF).setKeyManagerPassword(testKeystorePassword);
-    }
-
-    @Test
-    public void testConfigureSslContextFactoryWithJksKeyStore() {
-        // Expect that we will not set provider for jks keystore
-        final Map<String, String> addProps = new HashMap<>();
-        String keyStoreType = KeystoreType.JKS.toString();
-        addProps.put(NiFiProperties.SECURITY_KEYSTORE_TYPE, keyStoreType);
-        NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
-        SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
-        JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
-        verify(mockSCF).setKeyStoreType(keyStoreType);
-        verify(mockSCF).setKeyStoreProvider(SUN_PROVIDER_NAME);
-    }
-
-    @Test
-    public void testConfigureSslContextFactoryWithPkcsKeyStore() {
-        // Expect that we will set Bouncy Castle provider for pkcs12 keystore
-        final Map<String, String> addProps = new HashMap<>();
-        String keyStoreType = KeystoreType.PKCS12.toString();
-        addProps.put(NiFiProperties.SECURITY_KEYSTORE_TYPE, keyStoreType);
-        NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
-        SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
-        JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
-        verify(mockSCF).setKeyStoreType(keyStoreType);
-        verify(mockSCF).setKeyStoreProvider(BouncyCastleProvider.PROVIDER_NAME);
-    }
-
-    @Test
-    public void testConfigureSslContextFactoryWithJksTrustStore() {
-        // Expect that we will not set provider for jks truststore
-        final Map<String, String> addProps = new HashMap<>();
-        String trustStoreType = KeystoreType.JKS.toString();
-        addProps.put(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, trustStoreType);
-        NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
-        SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
-        JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
-        verify(mockSCF).setTrustStoreType(trustStoreType);
-        verify(mockSCF).setTrustStoreProvider(SUN_PROVIDER_NAME);
-    }
-
-    @Test
-    public void testConfigureSslContextFactoryWithPkcsTrustStore() {
-        // Expect that we will set Bouncy Castle provider for pkcs12 truststore
-        final Map<String, String> addProps = new HashMap<>();
-        String trustStoreType = KeystoreType.PKCS12.toString();
-        addProps.put(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, trustStoreType);
-        NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
-        SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
-        JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
-        verify(mockSCF).setTrustStoreType(trustStoreType);
-        verify(mockSCF).setTrustStoreProvider(BouncyCastleProvider.PROVIDER_NAME);
-    }
-
-    /**
-     * Verify correct processing of cipher suites with multiple elements.  Verify call to override runtime ciphers.
-     */
-    @Test
-    public void testConfigureSslIncludeExcludeCiphers() {
-        final String[] includeCipherSuites = {"TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"};
-        final String includeCipherSuitesProp = StringUtils.join(includeCipherSuites, JettyServer.JOIN_ARRAY);
-        final String[] excludeCipherSuites = {".*DHE.*", ".*ECDH.*"};
-        final String excludeCipherSuitesProp = StringUtils.join(excludeCipherSuites, JettyServer.JOIN_ARRAY);
-        final Map<String, String> addProps = new HashMap<>();
-        addProps.put(NiFiProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE, includeCipherSuitesProp);
-        addProps.put(NiFiProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE, excludeCipherSuitesProp);
-        final NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
-
-        final SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-        JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-        verify(mockSCF, times(1)).setIncludeCipherSuites(includeCipherSuites);
-        verify(mockSCF, times(1)).setExcludeCipherSuites(excludeCipherSuites);
-    }
-
-    /**
-     * Verify skip cipher configuration when NiFiProperties are not specified.
-     */
-    @Test
-    public void testDoNotConfigureSslIncludeExcludeCiphers() {
-        final NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null);
-        final SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-        JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-        verify(mockSCF, times(0)).setIncludeCipherSuites(any());
-        verify(mockSCF, times(0)).setExcludeCipherSuites(any());
-    }
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java
new file mode 100644
index 0000000000..e997f25ac6
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.server.connector;
+
+import org.apache.nifi.jetty.configuration.connector.alpn.ALPNServerConnectionFactory;
+import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
+import org.apache.nifi.security.util.TlsConfiguration;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.server.util.TrustStoreScanner;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.KeyStoreScanner;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class FrameworkServerConnectorFactoryTest {
+    private static final String PROPERTIES_FILE_PATH = null;
+
+    private static final int HTTP_PORT = 8080;
+
+    private static final int HTTPS_PORT = 8443;
+
+    private static final String H2_HTTP_1_1_PROTOCOLS = "h2 http/1.1";
+
+    private static final String EXCLUDED_CIPHER_SUITE = "TLS_PSK_WITH_NULL_SHA";
+
+    private static final String INCLUDED_CIPHER_SUITE_PATTERN = ".*AES_256_GCM.*";
+
+    private static TlsConfiguration tlsConfiguration;
+
+    @BeforeAll
+    static void setTlsConfiguration() {
+        final TemporaryKeyStoreBuilder builder = new TemporaryKeyStoreBuilder();
+        tlsConfiguration = builder.build();
+    }
+
+    @Test
+    void testHttpPortAndHttpsPortNotConfiguredException() {
+        final Properties serverProperties = new Properties();
+        final NiFiProperties properties = getProperties(serverProperties);
+
+        final Server server = new Server();
+        final IllegalStateException e = assertThrows(IllegalStateException.class, () -> new FrameworkServerConnectorFactory(server, properties));
+        assertTrue(e.getMessage().contains(NiFiProperties.WEB_HTTP_PORT));
+    }
+
+    @Test
+    void testHttpPortAndHttpsPortException() {
+        final Properties serverProperties = new Properties();
+        serverProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, Integer.toString(HTTP_PORT));
+        serverProperties.setProperty(NiFiProperties.WEB_HTTPS_PORT, Integer.toString(HTTPS_PORT));
+        final NiFiProperties properties = getProperties(serverProperties);
+
+        final Server server = new Server();
+        final IllegalStateException e = assertThrows(IllegalStateException.class, () -> new FrameworkServerConnectorFactory(server, properties));
+        assertTrue(e.getMessage().contains(NiFiProperties.WEB_HTTP_PORT));
+    }
+
+    @Test
+    void testGetServerConnector() {
+        final Properties serverProperties = new Properties();
+        serverProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, Integer.toString(HTTP_PORT));
+        final NiFiProperties properties = getProperties(serverProperties);
+
+        final Server server = new Server();
+        final FrameworkServerConnectorFactory factory = new FrameworkServerConnectorFactory(server, properties);
+
+        final ServerConnector serverConnector = factory.getServerConnector();
+
+        assertHttpConnectionFactoryFound(serverConnector);
+    }
+
+    @Test
+    void testGetServerConnectorHttps() {
+        final Properties serverProperties = getHttpsProperties();
+        serverProperties.setProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE, EXCLUDED_CIPHER_SUITE);
+        serverProperties.setProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE, INCLUDED_CIPHER_SUITE_PATTERN);
+        serverProperties.setProperty(NiFiProperties.SECURITY_AUTO_RELOAD_ENABLED, Boolean.TRUE.toString());
+        final FrameworkServerConnectorFactory factory = getHttpsConnectorFactory(serverProperties);
+
+        final ServerConnector serverConnector = factory.getServerConnector();
+
+        assertHttpConnectionFactoryFound(serverConnector);
+        final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
+
+        final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
+        assertTrue(sslContextFactory.getNeedClientAuth());
+        assertFalse(sslContextFactory.getWantClientAuth());
+
+        assertCipherSuitesConfigured(sslContextFactory);
+        assertAutoReloadEnabled(serverConnector);
+
+        final HTTP2ServerConnectionFactory http2ServerConnectionFactory = serverConnector.getConnectionFactory(HTTP2ServerConnectionFactory.class);
+        assertNull(http2ServerConnectionFactory);
+    }
+
+    @Test
+    void testGetServerConnectorHttpsHttp2AndHttp11() {
+        final Properties serverProperties = getHttpsProperties();
+        serverProperties.setProperty(NiFiProperties.WEB_HTTPS_APPLICATION_PROTOCOLS, H2_HTTP_1_1_PROTOCOLS);
+        final FrameworkServerConnectorFactory factory = getHttpsConnectorFactory(serverProperties);
+
+        final ServerConnector serverConnector = factory.getServerConnector();
+
+        assertHttpConnectionFactoryFound(serverConnector);
+        assertSslConnectionFactoryFound(serverConnector);
+
+        final HTTP2ServerConnectionFactory http2ServerConnectionFactory = serverConnector.getConnectionFactory(HTTP2ServerConnectionFactory.class);
+        assertNotNull(http2ServerConnectionFactory);
+
+        final ALPNServerConnectionFactory alpnServerConnectionFactory = serverConnector.getConnectionFactory(ALPNServerConnectionFactory.class);
+        assertNotNull(alpnServerConnectionFactory);
+    }
+
+    private Properties getHttpsProperties() {
+        final Properties serverProperties = new Properties();
+        serverProperties.setProperty(NiFiProperties.WEB_HTTPS_PORT, Integer.toString(HTTPS_PORT));
+        serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath());
+        serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType());
+        serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword());
+        serverProperties.setProperty(NiFiProperties.SECURITY_KEY_PASSWD, tlsConfiguration.getKeyPassword());
+        serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath());
+        serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType());
+        serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword());
+        return serverProperties;
+    }
+
+    private FrameworkServerConnectorFactory getHttpsConnectorFactory(final Properties serverProperties) {
+        final NiFiProperties properties = getProperties(serverProperties);
+        final Server server = new Server();
+        return new FrameworkServerConnectorFactory(server, properties);
+    }
+
+    private SslConnectionFactory assertSslConnectionFactoryFound(final ServerConnector serverConnector) {
+        final SslConnectionFactory sslConnectionFactory = serverConnector.getConnectionFactory(SslConnectionFactory.class);
+        assertNotNull(sslConnectionFactory);
+        return sslConnectionFactory;
+    }
+
+    private void assertHttpConnectionFactoryFound(final ServerConnector serverConnector) {
+        assertNotNull(serverConnector);
+        final HttpConnectionFactory connectionFactory = serverConnector.getConnectionFactory(HttpConnectionFactory.class);
+        assertNotNull(connectionFactory);
+    }
+
+    private void assertCipherSuitesConfigured(final SslContextFactory sslContextFactory) {
+        final String[] excludedCipherSuites = sslContextFactory.getExcludeCipherSuites();
+        assertEquals(1, excludedCipherSuites.length);
+        assertEquals(EXCLUDED_CIPHER_SUITE, excludedCipherSuites[0]);
+
+        final String[] includedCipherSuites = sslContextFactory.getIncludeCipherSuites();
+        assertEquals(1, includedCipherSuites.length);
+        assertEquals(INCLUDED_CIPHER_SUITE_PATTERN, includedCipherSuites[0]);
+    }
+
+    private void assertAutoReloadEnabled(final ServerConnector serverConnector) {
+        final Server server = serverConnector.getServer();
+        final KeyStoreScanner keyStoreScanner = server.getBean(KeyStoreScanner.class);
+        assertNotNull(keyStoreScanner);
+
+        final TrustStoreScanner trustStoreScanner = server.getBean(TrustStoreScanner.class);
+        assertNotNull(trustStoreScanner);
+    }
+
+    private NiFiProperties getProperties(final Properties serverProperties) {
+        return NiFiProperties.createBasicNiFiProperties(PROPERTIES_FILE_PATH, serverProperties);
+    }
+}