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 <[email protected]>
AuthorDate: Fri May 13 13:27:05 2022 -0500
NIFI-3869 Added HTTP/2 support to ListenHTTP and HandleHttpRequest
Signed-off-by: Nathan Gough <[email protected]>
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>