You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by th...@apache.org on 2022/05/19 20:53:23 UTC

[nifi] branch main updated: NIFI-3869 Added HTTP/2 support to ListenHTTP and HandleHttpRequest

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

thenatog 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 e0976f42d3 NIFI-3869 Added HTTP/2 support to ListenHTTP and HandleHttpRequest
e0976f42d3 is described below

commit e0976f42d33d151035d7bd8207342afb53d12745
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Fri May 13 13:27:05 2022 -0500

    NIFI-3869 Added HTTP/2 support to ListenHTTP and HandleHttpRequest
    
    Signed-off-by: Nathan Gough <th...@gmail.com>
    
    This closes #6048.
---
 nifi-commons/nifi-jetty-configuration/pom.xml      |  39 +++++
 .../connector/ApplicationLayerProtocol.java        |  36 ++++
 .../connector/ServerConnectorFactory.java          |  31 ++++
 .../connector/StandardServerConnectorFactory.java  | 193 +++++++++++++++++++++
 .../alpn/ALPNServerConnectionFactory.java          |  64 +++++++
 .../connector/alpn/StandardALPNProcessor.java      | 123 +++++++++++++
 .../StandardServerConnectorFactoryTest.java        | 172 ++++++++++++++++++
 nifi-commons/pom.xml                               |   1 +
 nifi-nar-bundles/nifi-jetty-bundle/pom.xml         |  10 ++
 .../nifi-standard-processors/pom.xml               |  13 ++
 .../processors/standard/HandleHttpRequest.java     |  99 +++--------
 .../processors/standard/HandleHttpResponse.java    |   1 -
 .../nifi/processors/standard/ListenHTTP.java       |  86 ++++-----
 .../standard/http/HttpProtocolStrategy.java        |  68 ++++++++
 .../nifi/processors/standard/TestListenHTTP.java   |  10 +-
 pom.xml                                            |  13 ++
 16 files changed, 835 insertions(+), 124 deletions(-)

diff --git a/nifi-commons/nifi-jetty-configuration/pom.xml b/nifi-commons/nifi-jetty-configuration/pom.xml
new file mode 100644
index 0000000000..8d8e2e7390
--- /dev/null
+++ b/nifi-commons/nifi-jetty-configuration/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-commons</artifactId>
+        <version>1.17.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-jetty-configuration</artifactId>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+        </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>
+    </dependencies>
+</project>
diff --git a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/ApplicationLayerProtocol.java b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/ApplicationLayerProtocol.java
new file mode 100644
index 0000000000..9561d75471
--- /dev/null
+++ b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/ApplicationLayerProtocol.java
@@ -0,0 +1,36 @@
+/*
+ * 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.jetty.configuration.connector;
+
+/**
+ * Application Layer Protocols supported for Server Connectors
+ */
+public enum ApplicationLayerProtocol {
+    HTTP_1_1("http/1.1"),
+
+    H2("h2");
+
+    private String protocol;
+
+    ApplicationLayerProtocol(final String protocol) {
+        this.protocol = protocol;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+}
diff --git a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/ServerConnectorFactory.java b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/ServerConnectorFactory.java
new file mode 100644
index 0000000000..20440d2055
--- /dev/null
+++ b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/ServerConnectorFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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.jetty.configuration.connector;
+
+import org.eclipse.jetty.server.ServerConnector;
+
+/**
+ * Jetty Server Connector Factory
+ */
+public interface ServerConnectorFactory {
+    /**
+     * Get Server Connector
+     *
+     * @return Configured Server Connector
+     */
+    ServerConnector getServerConnector();
+}
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
new file mode 100644
index 0000000000..e670ac71ea
--- /dev/null
+++ b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactory.java
@@ -0,0 +1,193 @@
+/*
+ * 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.jetty.configuration.connector;
+
+import org.apache.nifi.jetty.configuration.connector.alpn.ALPNServerConnectionFactory;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http2.HTTP2Cipher;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+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.util.ssl.SslContextFactory;
+
+import javax.net.ssl.SSLContext;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Standard implementation of Server Connector Factory supporting HTTP/2 and HTTP/1.1 with TLS or simple HTTP/1.1
+ */
+public class StandardServerConnectorFactory implements ServerConnectorFactory {
+    private static final boolean SEND_SERVER_VERSION = false;
+
+    private static final String[] INCLUDE_ALL_SECURITY_PROTOCOLS = new String[0];
+
+    private static final Set<ApplicationLayerProtocol> DEFAULT_APPLICATION_LAYER_PROTOCOLS = Collections.singleton(ApplicationLayerProtocol.HTTP_1_1);
+
+    private final Server server;
+
+    private final int port;
+
+    private Set<ApplicationLayerProtocol> applicationLayerProtocols = DEFAULT_APPLICATION_LAYER_PROTOCOLS;
+
+    private SSLContext sslContext;
+
+    private boolean needClientAuth;
+
+    private boolean wantClientAuth;
+
+    private String[] includeSecurityProtocols = INCLUDE_ALL_SECURITY_PROTOCOLS;
+
+    /**
+     * Standard Server Connector Factory Constructor with required properties
+     *
+     * @param server Jetty Server
+     * @param port Secure Port Number
+     */
+    public StandardServerConnectorFactory(
+            final Server server,
+            final int port
+    ) {
+        this.server = Objects.requireNonNull(server, "Server required");
+        this.port = port;
+    }
+
+    /**
+     * Get Server Connector configured with HTTP/2 and ALPN as well as fallback to HTTP/1.1 with TLS
+     *
+     * @return Secure Server Connector
+     */
+    @Override
+    public ServerConnector getServerConnector() {
+        final HttpConfiguration httpConfiguration = getHttpConfiguration();
+        final HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfiguration);
+
+        final ServerConnector serverConnector;
+        if (sslContext == null) {
+            serverConnector = new ServerConnector(server, httpConnectionFactory);
+        } else {
+            final List<ConnectionFactory> connectionFactories = new ArrayList<>();
+            if (applicationLayerProtocols.contains(ApplicationLayerProtocol.H2)) {
+                final ALPNServerConnectionFactory alpnServerConnectionFactory = new ALPNServerConnectionFactory();
+                final HTTP2ServerConnectionFactory http2ServerConnectionFactory = new HTTP2ServerConnectionFactory(httpConfiguration);
+
+                connectionFactories.add(alpnServerConnectionFactory);
+                connectionFactories.add(http2ServerConnectionFactory);
+            }
+            // Add HTTP/1.1 Connection Factory after HTTP/2
+            if (applicationLayerProtocols.contains(ApplicationLayerProtocol.HTTP_1_1)) {
+                connectionFactories.add(httpConnectionFactory);
+            }
+
+            // SslConnectionFactory must be first and must indicate the next protocol
+            final String nextProtocol = connectionFactories.get(0).getProtocol();
+            final SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(getSslContextFactory(), nextProtocol);
+            connectionFactories.add(0, sslConnectionFactory);
+
+            final ConnectionFactory[] factories = connectionFactories.toArray(new ConnectionFactory[0]);
+            serverConnector = new ServerConnector(server, factories);
+        }
+
+        serverConnector.setPort(port);
+        return serverConnector;
+    }
+
+    /**
+     * Set SSL Context enables TLS communication
+     *
+     * @param sslContext SSL Context
+     */
+    public void setSslContext(final SSLContext sslContext) {
+        this.sslContext = sslContext;
+    }
+
+    /**
+     * Set Need Client Authentication requires clients to provide certificates for mutual TLS
+     *
+     * @param needClientAuth Need Client Authentication status
+     */
+    public void setNeedClientAuth(final boolean needClientAuth) {
+        this.needClientAuth = needClientAuth;
+    }
+
+    /**
+     * Set Want Client Authentication requests clients to provide certificates for mutual TLS but does not require certificates
+     *
+     * @param wantClientAuth Want Client Authentication status
+     */
+    public void setWantClientAuth(final boolean wantClientAuth) {
+        this.wantClientAuth = wantClientAuth;
+    }
+
+    /**
+     * Set Include Security Protocols limits enabled TLS Protocols to the values provided
+     *
+     * @param includeSecurityProtocols Security Protocols with null or empty enabling all standard TLS protocol versions
+     */
+    public void setIncludeSecurityProtocols(final String[] includeSecurityProtocols) {
+        this.includeSecurityProtocols = includeSecurityProtocols == null ? INCLUDE_ALL_SECURITY_PROTOCOLS : includeSecurityProtocols;
+    }
+
+    /**
+     * Set Application Layer Protocols applicable when TLS is enabled
+     *
+     * @param applicationLayerProtocols Protocols requires at one Application Layer Protocol
+     */
+    public void setApplicationLayerProtocols(final Set<ApplicationLayerProtocol> applicationLayerProtocols) {
+        if (Objects.requireNonNull(applicationLayerProtocols, "Application Layer Protocols required").isEmpty()) {
+            throw new IllegalArgumentException("Application Layer Protocols not specified");
+        }
+        this.applicationLayerProtocols = applicationLayerProtocols;
+    }
+
+    private HttpConfiguration getHttpConfiguration() {
+        final HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+        if (sslContext != null) {
+            httpConfiguration.setSecurePort(port);
+            httpConfiguration.setSecureScheme(HttpScheme.HTTPS.asString());
+            httpConfiguration.setSendServerVersion(SEND_SERVER_VERSION);
+
+            final SecureRequestCustomizer secureRequestCustomizer = new SecureRequestCustomizer();
+            httpConfiguration.addCustomizer(secureRequestCustomizer);
+        }
+
+        return httpConfiguration;
+    }
+
+    private SslContextFactory.Server getSslContextFactory() {
+        final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+        sslContextFactory.setSslContext(sslContext);
+        sslContextFactory.setNeedClientAuth(needClientAuth);
+        sslContextFactory.setWantClientAuth(wantClientAuth);
+        sslContextFactory.setIncludeProtocols(includeSecurityProtocols);
+
+        if (applicationLayerProtocols.contains(ApplicationLayerProtocol.H2)) {
+            sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
+        }
+
+        return sslContextFactory;
+    }
+}
diff --git a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/ALPNServerConnectionFactory.java b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/ALPNServerConnectionFactory.java
new file mode 100644
index 0000000000..c5da7a5091
--- /dev/null
+++ b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/ALPNServerConnectionFactory.java
@@ -0,0 +1,64 @@
+/*
+ * 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.jetty.configuration.connector.alpn;
+
+import org.eclipse.jetty.alpn.server.ALPNServerConnection;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.ALPNProcessor;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
+
+import javax.net.ssl.SSLEngine;
+import java.util.List;
+
+/**
+ * ALPN Server Connection Factory with standard ALPN Processor implementation
+ */
+public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory {
+    private static final String ALPN_PROTOCOL = "alpn";
+
+    private final ALPNProcessor.Server processor;
+
+    public ALPNServerConnectionFactory() {
+        super(ALPN_PROTOCOL);
+        processor = new StandardALPNProcessor();
+    }
+
+    /**
+     * Create new Server Connection and configure the connection using ALPN Processor
+     *
+     * @param connector Connector for the Connection
+     * @param endPoint End Point for the Connection
+     * @param sslEngine SSL Engine for the Connection
+     * @param protocols Application Protocols
+     * @param defaultProtocol Default Application Protocol
+     * @return ALPN Server Connection
+     */
+    @Override
+    protected AbstractConnection newServerConnection(
+            final Connector connector,
+            final EndPoint endPoint,
+            final SSLEngine sslEngine,
+            final List<String> protocols,
+            final String defaultProtocol
+    ) {
+        final ALPNServerConnection connection = new ALPNServerConnection(connector, endPoint, sslEngine, protocols, defaultProtocol);
+        processor.configure(sslEngine, connection);
+        return connection;
+    }
+}
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
new file mode 100644
index 0000000000..0c8825226d
--- /dev/null
+++ b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/StandardALPNProcessor.java
@@ -0,0 +1,123 @@
+/*
+ * 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.jetty.configuration.connector.alpn;
+
+import org.eclipse.jetty.alpn.server.ALPNServerConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.ssl.ALPNProcessor;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.io.ssl.SslHandshakeListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/**
+ * Standard ALPN Processor supporting JDK 1.8.0-251 and higher based on Jetty JDK9ServerALPNProcessor
+ */
+public class StandardALPNProcessor implements ALPNProcessor.Server, SslHandshakeListener {
+    private static final Logger logger = LoggerFactory.getLogger(StandardALPNProcessor.class);
+
+    /**
+     * Applies to SSL Engine instances regardless of implementation
+     *
+     * @param sslEngine SSL Engine to be evaluated
+     * @return Applicable Status
+     */
+    @Override
+    public boolean appliesTo(final SSLEngine sslEngine) {
+        return true;
+    }
+
+    /**
+     * Configure ALPN negotiation for Connection
+     *
+     * @param sslEngine SSL Engine to be configured
+     * @param connection Connection to be configured
+     */
+    @Override
+    public void configure(final SSLEngine sslEngine, final Connection connection) {
+        logger.debug("Configuring Connection Remote Address [{}]", connection.getEndPoint().getRemoteAddress());
+        final ALPNServerConnection serverConnection = (ALPNServerConnection) connection;
+        final ProtocolSelector protocolSelector = new ProtocolSelector(serverConnection);
+        sslEngine.setHandshakeApplicationProtocolSelector(protocolSelector);
+
+        final SslConnection.DecryptedEndPoint endPoint = (SslConnection.DecryptedEndPoint) serverConnection.getEndPoint();
+        endPoint.getSslConnection().addHandshakeListener(protocolSelector);
+    }
+
+    private static final class ProtocolSelector implements BiFunction<SSLEngine, List<String>, String>, SslHandshakeListener {
+        private final ALPNServerConnection serverConnection;
+
+        private ProtocolSelector(final ALPNServerConnection connection) {
+            serverConnection = connection;
+        }
+
+        /**
+         * Select supported Application Layer Protocol based on requested protocols
+         *
+         * @param sslEngine SSL Engine
+         * @param protocols Protocols requested
+         * @return Protocol selected or null when no supported protocol found
+         */
+        @Override
+        public String apply(final SSLEngine sslEngine, final List<String> protocols) {
+            String protocol = null;
+            try {
+                serverConnection.select(protocols);
+                protocol = serverConnection.getProtocol();
+                logger.debug("Connection Remote Address [{}] Application Layer Protocol [{}] selected", serverConnection.getEndPoint().getRemoteAddress(), protocol);
+            } catch (final Throwable e) {
+                logger.debug("Connection Remote Address [{}] Application Layer Protocols {} not supported", serverConnection.getEndPoint().getRemoteAddress(), protocols);
+            }
+            return protocol;
+        }
+
+        /**
+         * Handler for successful handshake checks for selected Application Layer Protocol
+         *
+         * @param event Event is not used
+         */
+        @Override
+        public void handshakeSucceeded(final Event event) {
+            final InetSocketAddress remoteAddress = serverConnection.getEndPoint().getRemoteAddress();
+            final SSLSession session = event.getSSLEngine().getSession();
+            logger.debug("Connection Remote Address [{}] Handshake Succeeded [{}] Cipher Suite [{}]", remoteAddress, session.getProtocol(), session.getCipherSuite());
+
+            final String protocol = serverConnection.getProtocol();
+            if (protocol == null) {
+                logger.debug("Connection Remote Address [{}] Application Layer Protocol not supported", remoteAddress);
+                serverConnection.unsupported();
+            }
+        }
+
+        /**
+         * Handle for failed handshake logs status
+         *
+         * @param event Event is not used
+         * @param failure Failure cause to be logged
+         */
+        @Override
+        public void handshakeFailed(final Event event, final Throwable failure) {
+            logger.warn("Connection Remote Address [{}] Handshake Failed", serverConnection.getEndPoint().getRemoteAddress(), failure);
+        }
+    }
+}
diff --git a/nifi-commons/nifi-jetty-configuration/src/test/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactoryTest.java b/nifi-commons/nifi-jetty-configuration/src/test/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactoryTest.java
new file mode 100644
index 0000000000..d465504976
--- /dev/null
+++ b/nifi-commons/nifi-jetty-configuration/src/test/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactoryTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.jetty.configuration.connector;
+
+import org.apache.nifi.jetty.configuration.connector.alpn.ALPNServerConnectionFactory;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+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.util.ssl.SslContextFactory;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ssl.SSLContext;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+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.assertTrue;
+
+class StandardServerConnectorFactoryTest {
+    private static final int HTTP_PORT = 8080;
+
+    private static final int HTTPS_PORT = 8443;
+
+    private static final String[] INCLUDE_PROTOCOLS = new String[]{ "TLSv1.2" };
+
+    @Test
+    void testGetServerConnector() {
+        final Server server = new Server();
+        final StandardServerConnectorFactory factory = new StandardServerConnectorFactory(server, HTTP_PORT);
+
+        final ServerConnector serverConnector = factory.getServerConnector();
+
+        assertHttpConnectionFactoryFound(serverConnector);
+    }
+
+    @Test
+    void testGetServerConnectorSecured() throws NoSuchAlgorithmException {
+        final StandardServerConnectorFactory factory = createSecuredStandardServerConnectorFactory();
+
+        final ServerConnector serverConnector = factory.getServerConnector();
+
+        assertHttpConnectionFactoryFound(serverConnector);
+        final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
+
+        final HttpConnectionFactory httpConnectionFactory = assertHttpConnectionFactoryFound(serverConnector);
+        assertHttpConnectionFactorySecured(httpConnectionFactory);
+
+        final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
+        assertFalse(sslContextFactory.getNeedClientAuth());
+        assertFalse(sslContextFactory.getWantClientAuth());
+        assertNotNull(sslContextFactory.getIncludeProtocols());
+
+        final HTTP2ServerConnectionFactory http2ConnectionFactory = serverConnector.getConnectionFactory(HTTP2ServerConnectionFactory.class);
+        assertNull(http2ConnectionFactory);
+    }
+
+    @Test
+    void testGetServerConnectorSecuredNeedClientAuthentication() throws NoSuchAlgorithmException {
+        final StandardServerConnectorFactory factory = createSecuredStandardServerConnectorFactory();
+        factory.setNeedClientAuth(true);
+        factory.setIncludeSecurityProtocols(INCLUDE_PROTOCOLS);
+
+        final ServerConnector serverConnector = factory.getServerConnector();
+
+        assertHttpConnectionFactoryFound(serverConnector);
+        final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
+
+        final HttpConnectionFactory httpConnectionFactory = assertHttpConnectionFactoryFound(serverConnector);
+        assertHttpConnectionFactorySecured(httpConnectionFactory);
+
+        final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
+        assertTrue(sslContextFactory.getNeedClientAuth());
+        assertArrayEquals(INCLUDE_PROTOCOLS, sslContextFactory.getIncludeProtocols());
+    }
+
+    @Test
+    void testGetServerConnectorSecuredHttp2AndHttp1() throws NoSuchAlgorithmException {
+        final StandardServerConnectorFactory factory = createSecuredStandardServerConnectorFactory();
+        factory.setApplicationLayerProtocols(new LinkedHashSet<>(Arrays.asList(ApplicationLayerProtocol.H2, ApplicationLayerProtocol.HTTP_1_1)));
+
+        final ServerConnector serverConnector = factory.getServerConnector();
+
+        final HttpConnectionFactory httpConnectionFactory = assertHttpConnectionFactoryFound(serverConnector);
+        assertHttpConnectionFactorySecured(httpConnectionFactory);
+
+        final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
+        final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
+        assertFalse(sslContextFactory.getNeedClientAuth());
+
+        assertHttp2ConnectionFactoriesFound(serverConnector);
+    }
+
+    @Test
+    void testGetServerConnectorSecuredHttp2() throws NoSuchAlgorithmException {
+        final StandardServerConnectorFactory factory = createSecuredStandardServerConnectorFactory();
+        factory.setApplicationLayerProtocols(Collections.singleton(ApplicationLayerProtocol.H2));
+
+        final ServerConnector serverConnector = factory.getServerConnector();
+
+        final HttpConnectionFactory connectionFactory = serverConnector.getConnectionFactory(HttpConnectionFactory.class);
+        assertNull(connectionFactory);
+
+        final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
+        final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
+        assertFalse(sslContextFactory.getNeedClientAuth());
+
+        assertHttp2ConnectionFactoriesFound(serverConnector);
+    }
+
+    private StandardServerConnectorFactory createSecuredStandardServerConnectorFactory() throws NoSuchAlgorithmException {
+        final Server server = new Server();
+        final StandardServerConnectorFactory factory = new StandardServerConnectorFactory(server, HTTPS_PORT);
+        final SSLContext sslContext = SSLContext.getDefault();
+        factory.setSslContext(sslContext);
+        return factory;
+    }
+
+    private HttpConnectionFactory assertHttpConnectionFactoryFound(final ServerConnector serverConnector) {
+        assertNotNull(serverConnector);
+        final HttpConnectionFactory connectionFactory = serverConnector.getConnectionFactory(HttpConnectionFactory.class);
+        assertNotNull(connectionFactory);
+        return connectionFactory;
+    }
+
+    private void assertHttp2ConnectionFactoriesFound(final ServerConnector serverConnector) {
+        final HTTP2ServerConnectionFactory http2ConnectionFactory = serverConnector.getConnectionFactory(HTTP2ServerConnectionFactory.class);
+        assertNotNull(http2ConnectionFactory);
+
+        final ALPNServerConnectionFactory alpnServerConnectionFactory = serverConnector.getConnectionFactory(ALPNServerConnectionFactory.class);
+        assertNotNull(alpnServerConnectionFactory);
+    }
+
+    private SslConnectionFactory assertSslConnectionFactoryFound(final ServerConnector serverConnector) {
+        final SslConnectionFactory sslConnectionFactory = serverConnector.getConnectionFactory(SslConnectionFactory.class);
+        assertNotNull(sslConnectionFactory);
+        return sslConnectionFactory;
+    }
+
+    private void assertHttpConnectionFactorySecured(final HttpConnectionFactory httpConnectionFactory) {
+        final HttpConfiguration configuration = httpConnectionFactory.getHttpConfiguration();
+        assertEquals(HTTPS_PORT, configuration.getSecurePort());
+        assertEquals(HttpScheme.HTTPS.asString(), configuration.getSecureScheme());
+        final SecureRequestCustomizer secureRequestCustomizer = configuration.getCustomizer(SecureRequestCustomizer.class);
+        assertNotNull(secureRequestCustomizer);
+    }
+}
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index 0c435cd2fc..ee3963ba72 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -32,6 +32,7 @@
         <module>nifi-flow-encryptor</module>
         <module>nifi-hl7-query-language</module>
         <module>nifi-json-utils</module>
+        <module>nifi-jetty-configuration</module>
         <module>nifi-logging-utils</module>
         <module>nifi-metrics</module>
         <module>nifi-parameter</module>
diff --git a/nifi-nar-bundles/nifi-jetty-bundle/pom.xml b/nifi-nar-bundles/nifi-jetty-bundle/pom.xml
index f5e96121c2..0c3325b27d 100644
--- a/nifi-nar-bundles/nifi-jetty-bundle/pom.xml
+++ b/nifi-nar-bundles/nifi-jetty-bundle/pom.xml
@@ -78,5 +78,15 @@
             <artifactId>apache-jstl</artifactId>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-server</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-server</artifactId>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
index 44c1bef625..07acc867d0 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
@@ -92,6 +92,11 @@
             <artifactId>nifi-flowfile-packager</artifactId>
             <version>1.17.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-jetty-configuration</artifactId>
+            <version>1.17.0-SNAPSHOT</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-distributed-cache-client-service-api</artifactId>
@@ -180,6 +185,14 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-servlet</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-server</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java
index 96af6c2c7b..7598e39845 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java
@@ -33,6 +33,7 @@ import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.expression.ExpressionLanguageScope;
 import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.http.HttpContextMap;
+import org.apache.nifi.jetty.configuration.connector.StandardServerConnectorFactory;
 import org.apache.nifi.processor.AbstractProcessor;
 import org.apache.nifi.processor.DataUnit;
 import org.apache.nifi.processor.ProcessContext;
@@ -40,21 +41,17 @@ import org.apache.nifi.processor.ProcessSession;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.exception.ProcessException;
 import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.standard.http.HttpProtocolStrategy;
 import org.apache.nifi.processors.standard.util.HTTPUtils;
 import org.apache.nifi.scheduling.ExecutionNode;
 import org.apache.nifi.ssl.RestrictedSSLContextService;
 import org.apache.nifi.ssl.SSLContextService;
 import org.apache.nifi.stream.io.StreamUtils;
 import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.Request;
-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.AbstractHandler;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
 
 import javax.net.ssl.SSLContext;
 import javax.servlet.AsyncContext;
@@ -187,6 +184,14 @@ public class HandleHttpRequest extends AbstractProcessor {
             .required(false)
             .identifiesControllerService(RestrictedSSLContextService.class)
             .build();
+    public static final PropertyDescriptor HTTP_PROTOCOL_STRATEGY = new PropertyDescriptor.Builder()
+            .name("HTTP Protocols")
+            .description("HTTP Protocols supported for Application Layer Protocol Negotiation with TLS")
+            .required(true)
+            .allowableValues(HttpProtocolStrategy.class)
+            .defaultValue(HttpProtocolStrategy.HTTP_1_1.getValue())
+            .dependsOn(SSL_CONTEXT)
+            .build();
     public static final PropertyDescriptor URL_CHARACTER_SET = new PropertyDescriptor.Builder()
             .name("Default URL Character Set")
             .description("The character set to use for decoding URL parameters if the HTTP Request does not supply one")
@@ -303,6 +308,7 @@ public class HandleHttpRequest extends AbstractProcessor {
         descriptors.add(PORT);
         descriptors.add(HOSTNAME);
         descriptors.add(SSL_CONTEXT);
+        descriptors.add(HTTP_PROTOCOL_STRATEGY);
         descriptors.add(HTTP_CONTEXT_MAP);
         descriptors.add(PATH_REGEX);
         descriptors.add(URL_CHARACTER_SET);
@@ -356,61 +362,24 @@ public class HandleHttpRequest extends AbstractProcessor {
         final long requestTimeout = httpContextMap.getRequestTimeout(TimeUnit.MILLISECONDS);
 
         final String clientAuthValue = context.getProperty(CLIENT_AUTH).getValue();
-        final boolean need;
-        final boolean want;
-        if (CLIENT_NEED.getValue().equals(clientAuthValue)) {
-            need = true;
-            want = false;
-        } else if (CLIENT_WANT.getValue().equals(clientAuthValue)) {
-            need = false;
-            want = true;
-        } else {
-            need = false;
-            want = false;
-        }
-
-        final SslContextFactory sslFactory = (sslService == null) ? null : createSslFactory(sslService, need, want);
-        final Server server = new Server(port);
-
-        // create the http configuration
-        final HttpConfiguration httpConfiguration = new HttpConfiguration();
-        if (sslFactory == null) {
-            // create the connector
-            final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
-
-            // set host and port
-            if (StringUtils.isNotBlank(host)) {
-                http.setHost(host);
-            }
-            http.setPort(port);
-
-            // If request timeout is longer than default Idle Timeout, then increase Idle Timeout as well.
-            http.setIdleTimeout(Math.max(http.getIdleTimeout(), requestTimeout));
-
-            // add this connector
-            server.setConnectors(new Connector[]{http});
-        } else {
-            // add some secure config
-            final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
-            httpsConfiguration.setSecureScheme("https");
-            httpsConfiguration.setSecurePort(port);
-            httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
-
-            // build the connector
-            final ServerConnector https = new ServerConnector(server, new SslConnectionFactory(sslFactory, "http/1.1"), new HttpConnectionFactory(httpsConfiguration));
-
-            // set host and port
-            if (StringUtils.isNotBlank(host)) {
-                https.setHost(host);
-            }
-            https.setPort(port);
-
-            // If request timeout is longer than default Idle Timeout, then increase Idle Timeout as well.
-            https.setIdleTimeout(Math.max(https.getIdleTimeout(), requestTimeout));
-
-            // add this connector
-            server.setConnectors(new Connector[]{https});
+        final Server server = new Server();
+
+        final StandardServerConnectorFactory serverConnectorFactory = new StandardServerConnectorFactory(server, port);
+        final boolean needClientAuth = CLIENT_NEED.getValue().equals(clientAuthValue);
+        serverConnectorFactory.setNeedClientAuth(needClientAuth);
+        final boolean wantClientAuth = CLIENT_WANT.getValue().equals(clientAuthValue);
+        serverConnectorFactory.setNeedClientAuth(wantClientAuth);
+        final SSLContext sslContext = sslService == null ? null : sslService.createContext();
+        serverConnectorFactory.setSslContext(sslContext);
+        final HttpProtocolStrategy httpProtocolStrategy = HttpProtocolStrategy.valueOf(context.getProperty(HTTP_PROTOCOL_STRATEGY).getValue());
+        serverConnectorFactory.setApplicationLayerProtocols(httpProtocolStrategy.getApplicationLayerProtocols());
+
+        final ServerConnector serverConnector = serverConnectorFactory.getServerConnector();
+        serverConnector.setIdleTimeout(Math.max(serverConnector.getIdleTimeout(), requestTimeout));
+        if (StringUtils.isNotBlank(host)) {
+            serverConnector.setHost(host);
         }
+        server.addConnector(serverConnector);
 
         final Set<String> allowedMethods = new HashSet<>();
         if (context.getProperty(ALLOW_GET).asBoolean()) {
@@ -522,18 +491,6 @@ public class HandleHttpRequest extends AbstractProcessor {
         return containerQueue.size();
     }
 
-    private SslContextFactory createSslFactory(final SSLContextService sslContextService, final boolean needClientAuth, final boolean wantClientAuth) {
-        final SslContextFactory.Server sslFactory = new SslContextFactory.Server();
-
-        sslFactory.setNeedClientAuth(needClientAuth);
-        sslFactory.setWantClientAuth(wantClientAuth);
-
-        final SSLContext sslContext = sslContextService.createContext();
-        sslFactory.setSslContext(sslContext);
-
-        return sslFactory;
-    }
-
     @OnUnscheduled
     public void shutdown() throws Exception {
         ready = false;
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpResponse.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpResponse.java
index 96aa93a803..eff8e4fbf0 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpResponse.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpResponse.java
@@ -188,7 +188,6 @@ public class HandleHttpResponse extends AbstractProcessor {
 
         try {
             session.exportTo(flowFile, response.getOutputStream());
-            response.flushBuffer();
         } catch (final ProcessException e) {
             getLogger().error("Failed to respond to HTTP request for {} due to {}", new Object[]{flowFile, e});
             try {
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenHTTP.java
index e5f98daeb0..a35b9edc73 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenHTTP.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenHTTP.java
@@ -31,6 +31,7 @@ import org.apache.nifi.components.ValidationContext;
 import org.apache.nifi.components.ValidationResult;
 import org.apache.nifi.expression.ExpressionLanguageScope;
 import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.jetty.configuration.connector.StandardServerConnectorFactory;
 import org.apache.nifi.processor.AbstractSessionFactoryProcessor;
 import org.apache.nifi.processor.DataUnit;
 import org.apache.nifi.processor.ProcessContext;
@@ -39,26 +40,21 @@ import org.apache.nifi.processor.ProcessSessionFactory;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.exception.ProcessException;
 import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.standard.http.HttpProtocolStrategy;
 import org.apache.nifi.processors.standard.servlets.ContentAcknowledgmentServlet;
 import org.apache.nifi.processors.standard.servlets.HealthCheckServlet;
 import org.apache.nifi.processors.standard.servlets.ListenHTTPServlet;
 import org.apache.nifi.scheduling.ExecutionNode;
 import org.apache.nifi.security.util.ClientAuth;
-import org.apache.nifi.security.util.TlsConfiguration;
 import org.apache.nifi.serialization.RecordReaderFactory;
 import org.apache.nifi.serialization.RecordSetWriterFactory;
 import org.apache.nifi.ssl.RestrictedSSLContextService;
 import org.apache.nifi.ssl.SSLContextService;
 import org.apache.nifi.stream.io.LeakyBucketStreamThrottler;
 import org.apache.nifi.stream.io.StreamThrottler;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.server.HttpConnectionFactory;
-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.servlet.ServletContextHandler;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 
 import javax.net.ssl.SSLContext;
@@ -191,10 +187,18 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
         .build();
     public static final PropertyDescriptor SSL_CONTEXT_SERVICE = new PropertyDescriptor.Builder()
         .name("SSL Context Service")
-        .description("The Controller Service to use in order to obtain an SSL Context")
+        .description("SSL Context Service enables support for HTTPS")
         .required(false)
         .identifiesControllerService(RestrictedSSLContextService.class)
         .build();
+    public static final PropertyDescriptor HTTP_PROTOCOL_STRATEGY = new PropertyDescriptor.Builder()
+        .name("HTTP Protocols")
+        .description("HTTP Protocols supported for Application Layer Protocol Negotiation with TLS")
+        .required(true)
+        .allowableValues(HttpProtocolStrategy.class)
+        .defaultValue(HttpProtocolStrategy.HTTP_1_1.getValue())
+        .dependsOn(SSL_CONTEXT_SERVICE)
+        .build();
     public static final PropertyDescriptor HEADERS_AS_ATTRIBUTES_REGEX = new PropertyDescriptor.Builder()
         .name("HTTP Headers to receive as Attributes (Regex)")
         .description("Specifies the Regular Expression that determines the names of HTTP Headers that should be passed along as FlowFile attributes")
@@ -276,6 +280,7 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
             HEALTH_CHECK_PORT,
             MAX_DATA_RATE,
             SSL_CONTEXT_SERVICE,
+            HTTP_PROTOCOL_STRATEGY,
             CLIENT_AUTHENTICATION,
             AUTHORIZED_DN_PATTERN,
             AUTHORIZED_ISSUER_DN_PATTERN,
@@ -396,7 +401,6 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
         int maxThreadPoolSize = context.getProperty(MAX_THREAD_POOL_SIZE).asInteger();
         throttlerRef.set(streamThrottler);
 
-        final boolean sslRequired = sslContextService != null;
         final PropertyValue clientAuthenticationProperty = context.getProperty(CLIENT_AUTHENTICATION);
         final ClientAuthentication clientAuthentication = getClientAuthentication(sslContextService, clientAuthenticationProperty);
 
@@ -409,12 +413,13 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
 
         // get the configured port
         final int port = context.getProperty(PORT).evaluateAttributeExpressions().asInteger();
-
+        final HttpProtocolStrategy httpProtocolStrategy = HttpProtocolStrategy.valueOf(context.getProperty(HTTP_PROTOCOL_STRATEGY).getValue());
         final ServerConnector connector = createServerConnector(server,
                 port,
                 sslContextService,
-                sslRequired,
-                clientAuthentication);
+                clientAuthentication,
+                httpProtocolStrategy
+        );
         server.addConnector(connector);
 
         // Add a separate connector for the health check port (if specified)
@@ -423,12 +428,14 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
             final ServerConnector healthCheckConnector = createServerConnector(server,
                     healthCheckPort,
                     sslContextService,
-                    sslRequired,
-                    ClientAuthentication.NONE);
+                    ClientAuthentication.NONE,
+                    httpProtocolStrategy
+            );
             server.addConnector(healthCheckConnector);
         }
 
-        final ServletContextHandler contextHandler = new ServletContextHandler(server, "/", true, sslRequired);
+        final boolean securityEnabled = sslContextService != null;
+        final ServletContextHandler contextHandler = new ServletContextHandler(server, "/", true, securityEnabled);
         for (final Class<? extends Servlet> cls : getServerClasses()) {
             final Path path = cls.getAnnotation(Path.class);
             // Note: servlets must have a path annotation - this will NPE otherwise
@@ -488,41 +495,24 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
     private ServerConnector createServerConnector(final Server server,
                                                   final int port,
                                                   final SSLContextService sslContextService,
-                                                  final boolean sslRequired,
-                                                  final ClientAuthentication clientAuthentication) {
-        final ServerConnector connector;
-        final HttpConfiguration httpConfiguration = new HttpConfiguration();
-        if (sslRequired) {
-            httpConfiguration.setSecureScheme("https");
-            httpConfiguration.setSecurePort(port);
-            httpConfiguration.addCustomizer(new SecureRequestCustomizer());
-
-            final SslContextFactory contextFactory = createSslContextFactory(sslContextService, clientAuthentication);
-
-            connector = new ServerConnector(server, new SslConnectionFactory(contextFactory, "http/1.1"), new HttpConnectionFactory(httpConfiguration));
-        } else {
-            connector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
-        }
-
-        connector.setPort(port);
-        return connector;
-    }
-
-    private SslContextFactory createSslContextFactory(final SSLContextService sslContextService, final ClientAuthentication clientAuthentication) {
-        final SslContextFactory.Server contextFactory = new SslContextFactory.Server();
-        final SSLContext sslContext = sslContextService.createContext();
-        contextFactory.setSslContext(sslContext);
-
-        final TlsConfiguration tlsConfiguration = sslContextService.createTlsConfiguration();
-        contextFactory.setIncludeProtocols(tlsConfiguration.getEnabledProtocols());
-
-        if (ClientAuthentication.REQUIRED.equals(clientAuthentication)) {
-            contextFactory.setNeedClientAuth(true);
-        } else if (ClientAuthentication.WANT.equals(clientAuthentication)) {
-            contextFactory.setWantClientAuth(true);
+                                                  final ClientAuthentication clientAuthentication,
+                                                  final HttpProtocolStrategy httpProtocolStrategy
+    ) {
+        final StandardServerConnectorFactory serverConnectorFactory = new StandardServerConnectorFactory(server, port);
+        final SSLContext sslContext = sslContextService == null ? null : sslContextService.createContext();
+        serverConnectorFactory.setSslContext(sslContext);
+
+        final String[] enabledProtocols = sslContextService == null ? new String[0] : sslContextService.createTlsConfiguration().getEnabledProtocols();
+        serverConnectorFactory.setIncludeSecurityProtocols(enabledProtocols);
+
+        if (ClientAuthentication.REQUIRED == clientAuthentication) {
+            serverConnectorFactory.setNeedClientAuth(true);
+        } else if (ClientAuthentication.WANT == clientAuthentication) {
+            serverConnectorFactory.setWantClientAuth(true);
         }
 
-        return contextFactory;
+        serverConnectorFactory.setApplicationLayerProtocols(httpProtocolStrategy.getApplicationLayerProtocols());
+        return serverConnectorFactory.getServerConnector();
     }
 
     @OnScheduled
@@ -572,7 +562,7 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
         for (final String id : findOldFlowFileIds(context)) {
             final FlowFileEntryTimeWrapper wrapper = flowFileMap.remove(id);
             if (wrapper != null) {
-                getLogger().warn("failed to received acknowledgment for HOLD with ID {} sent by {}; rolling back session", new Object[] {id, wrapper.getClientIP()});
+                getLogger().warn("failed to received acknowledgment for HOLD with ID {} sent by {}; rolling back session", id, wrapper.getClientIP());
                 wrapper.session.rollback();
             }
         }
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/http/HttpProtocolStrategy.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/http/HttpProtocolStrategy.java
new file mode 100644
index 0000000000..d99d4182ef
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/http/HttpProtocolStrategy.java
@@ -0,0 +1,68 @@
+/*
+ * 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.processors.standard.http;
+
+import org.apache.nifi.components.DescribedValue;
+import org.apache.nifi.jetty.configuration.connector.ApplicationLayerProtocol;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singleton;
+
+/**
+ * HTTP protocol configuration strategy
+ */
+public enum HttpProtocolStrategy implements DescribedValue {
+    HTTP_1_1("http/1.1", "HTTP/1.1", singleton(ApplicationLayerProtocol.HTTP_1_1)),
+
+    H2_HTTP_1_1("h2 http/1.1", "HTTP/2 and HTTP/1.1 negotiated based on requested protocols", new LinkedHashSet<>(asList(ApplicationLayerProtocol.HTTP_1_1, ApplicationLayerProtocol.H2))),
+
+    H2("h2", "HTTP/2", singleton(ApplicationLayerProtocol.H2));
+
+    private final String displayName;
+
+    private final String description;
+
+    private final Set<ApplicationLayerProtocol> applicationLayerProtocols;
+
+    HttpProtocolStrategy(final String displayName, final String description, final Set<ApplicationLayerProtocol> applicationLayerProtocols) {
+        this.displayName = displayName;
+        this.description = description;
+        this.applicationLayerProtocols = applicationLayerProtocols;
+    }
+
+    @Override
+    public String getValue() {
+        return name();
+    }
+
+    @Override
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    public Set<ApplicationLayerProtocol> getApplicationLayerProtocols() {
+        return applicationLayerProtocols;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java
index cc006c333d..489a4d3382 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java
@@ -47,6 +47,7 @@ import okhttp3.RequestBody;
 import okhttp3.Response;
 import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSessionFactory;
+import org.apache.nifi.processors.standard.http.HttpProtocolStrategy;
 import org.apache.nifi.remote.io.socket.NetworkUtils;
 import org.apache.nifi.reporting.InitializationException;
 import org.apache.nifi.security.util.SslContextFactory;
@@ -99,7 +100,6 @@ public class TestListenHTTP {
     private static final Duration CLIENT_CALL_TIMEOUT = Duration.ofSeconds(10);
     public static final String LOCALHOST_DN = "CN=localhost";
 
-    private static TlsConfiguration tlsConfiguration;
     private static TlsConfiguration serverConfiguration;
     private static TlsConfiguration serverTls_1_3_Configuration;
     private static TlsConfiguration serverNoTruststoreConfiguration;
@@ -117,7 +117,7 @@ public class TestListenHTTP {
     @BeforeClass
     public static void setUpSuite() throws GeneralSecurityException {
         // generate new keystore and truststore
-        tlsConfiguration = new TemporaryKeyStoreBuilder().build();
+        final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build();
 
         serverConfiguration = new StandardTlsConfiguration(
                 tlsConfiguration.getKeystorePath(),
@@ -223,23 +223,25 @@ public class TestListenHTTP {
     }
 
     @Test
-    public void testSecurePOSTRequestsReceivedWithoutEL() throws Exception {
+    public void testSecurePOSTRequestsReceivedWithoutELHttp2AndHttp1() throws Exception {
         configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration);
 
         runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
         runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH);
+        runner.setProperty(ListenHTTP.HTTP_PROTOCOL_STRATEGY, HttpProtocolStrategy.H2_HTTP_1_1.getValue());
         runner.assertValid();
 
         testPOSTRequestsReceived(HttpServletResponse.SC_OK, true, false);
     }
 
     @Test
-    public void testSecurePOSTRequestsReturnCodeReceivedWithoutEL() throws Exception {
+    public void testSecurePOSTRequestsReturnCodeReceivedWithoutELHttp2() throws Exception {
         configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration);
 
         runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
         runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH);
         runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT));
+        runner.setProperty(ListenHTTP.HTTP_PROTOCOL_STRATEGY, HttpProtocolStrategy.H2.getValue());
         runner.assertValid();
 
         testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT, true, false);
diff --git a/pom.xml b/pom.xml
index ed6a0a35f8..6a12a4d1ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -420,6 +420,19 @@
                 <version>${jetty.version}</version>
                 <scope>provided</scope>
             </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-alpn-server</artifactId>
+                <version>${jetty.version}</version>
+                <scope>provided</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty.http2</groupId>
+                <artifactId>http2-server</artifactId>
+                <version>${jetty.version}</version>
+                <scope>provided</scope>
+            </dependency>
+
             <dependency>
                 <groupId>org.eclipse.jetty</groupId>
                 <artifactId>jetty-alpn-client</artifactId>